Go library for building, validating, and rendering dynamic surveys with conditional logic, grouped questions, and multi-format output (CSV, HTML, JSON, TipTap).
- Table of Contents
- Key Features
- Installation
- Quick Start
- Question Types
- Conditional Logic (DependsOn)
- Render Package
- AnswerExpr
- API Overview
- Testing
- Documentation
- AI Agent Skill
- Tech Stack
- License
| Feature | Description |
|---|---|
| 20+ Question Types | Choice, text, asset, toggle, external, datetime |
| Conditional Logic | DependsOn (OR-of-ANDs) + option-triggered groups |
| Answer Validation | Type-specific reviewers with detailed error reporting |
| Multi-Format Render | CSV, HTML, JSON (SurveyCard), TipTap in single pass |
| Custom Expressions | expr-lang/expr for answer transformation (AnswerExpr) |
| Survey Visualization | Interactive tree (go-echarts) + JSON group hierarchy |
| Runtime Modification | Add/remove/update questions and groups dynamically |
| Grouped Answers | Repeatable groups with cartesian CSV expansion |
| BSON Support | MongoDB-ready with BSON tags on all structs |
| Agent Skill | Built-inAI coding agent guidance |
go get github.com/rendis/surveygo/v2import surveygo "github.com/rendis/surveygo/v2"
import "github.com/rendis/surveygo/v2/render"Requirements: Go 1.25+
// Parse survey from JSON
survey, err := surveygo.ParseFromBytes(jsonData)
// Provide answers
answers := surveygo.Answers{
"event_rating": {"good"},
"favorite_game": {"zombie_apocalypse"},
"name": {"John Doe"},
"email": {"john@example.com"},
}
// Validate answers
resume, err := survey.ReviewAnswers(answers)
fmt.Printf("Answered: %d/%d\n", resume.TotalQuestionsAnswered, resume.TotalQuestions)
fmt.Printf("Errors: %v\n", resume.InvalidAnswers)import "github.com/rendis/surveygo/v2/render"
// CSV
csv, err := render.AnswersToCSV(survey, answers)
// HTML (independent CSS)
html, err := render.AnswersToHTML(survey, answers)
// html.HTML, html.CSS
custom := html.WithCSSPath("/assets/survey.css") // replace CSS href
// TipTap document
tiptap, err := render.AnswersToTipTap(survey, answers)
// Multiple formats in one pass
result, err := render.AnswersTo(survey, answers, render.OutputOptions{
CSV: true,
JSON: true,
HTML: true,
})
// result.CSV, result.JSON, result.HTML, result.TipTapdesc := "Annual feedback"
survey, err := surveygo.NewSurvey("Event Survey", "1.0", &desc)
// Add question from JSON
err = survey.AddQuestionJson(`{
"nameId": "rating",
"visible": true,
"type": "radio",
"label": "How would you rate the event?",
"required": true,
"value": {
"options": [
{"nameId": "great", "label": "Great"},
{"nameId": "good", "label": "Good"},
{"nameId": "meh", "label": "Meh"}
]
}
}`)
// Add to group
err = survey.AddQuestionToGroup("rating", "grp-general", -1)See the example/ directory for complete working examples.
| Category | Types | Description |
|---|---|---|
| Choice | single_select, multi_select, radio, checkbox |
Options with labels; can trigger groups via groupsIds |
| Toggle | toggle |
On/off switch with custom labels |
| Text | input_text, text_area, email, telephone, information, identification_number |
Text input with type-specific validation |
| DateTime | date_time |
Date/time with configurable format |
| Asset | image, video, audio, document |
File upload with size/type constraints |
| External | external_question |
Integration with external survey systems |
For complete field definitions and JSON structure, see Survey Structure Reference.
Questions and groups can have a dependsOn field that controls visibility based on selections in other questions. Structure is [][]DependsOn (OR of ANDs):
- Outer array: OR conditions (any group matches = visible)
- Inner array: AND conditions (all must match)
"dependsOn": [
[{ "questionNameId": "rating", "optionNameId": "terrible" }],
[
{ "questionNameId": "rating", "optionNameId": "meh" },
{ "questionNameId": "attendance", "optionNameId": "would_not_attend" }
]
]This shows the element if the user selected "terrible" OR (selected "meh" AND would not attend).
During ReviewAnswers(), questions/groups with unsatisfied dependsOn are excluded from totals -- required questions with unmet conditions are not expected to be answered.
dependsOncan only reference choice-type questions (single_select,multi_select,radio,checkbox,toggle).
The render package generates survey outputs from definitions and answers.
| Function | Returns | Description |
|---|---|---|
AnswersToCSV(survey, answers, checkMark...) |
[]byte, error |
CSV with cartesian expansion for repeat groups |
AnswersToJSON(survey, answers) |
*SurveyCard, error |
Structured survey card |
AnswersToHTML(survey, answers) |
*HTMLResult, error |
HTML + CSS (independent) |
HTMLResult.WithCSSPath(path) |
*HTMLResult |
Replace CSS href in HTML |
AnswersToTipTap(survey, answers) |
*TipTapNode, error |
TipTap-compatible document |
AnswersTo(survey, answers, opts) |
*AnswersResult, error |
Multiple formats, single pass |
| Function | Returns | Description |
|---|---|---|
DefinitionTreeJSON(survey) |
*GroupTree, error |
Group hierarchy with cycle detection |
DefinitionTreeHTML(survey) |
[]byte, error |
Interactive tree visualization (go-echarts) |
DefinitionTree(survey) |
*TreeResult, error |
Both HTML + JSON |
Customize selected/not-selected marks for multi-select, checkbox, and toggle CSV columns:
csv, err := render.AnswersToCSV(survey, answers, &render.CheckMark{
Selected: "x",
NotSelected: "",
})
// Defaults to "true"/"false" when nilWhen a question has answerExpr set, the render package evaluates it using expr-lang/expr and uses the result instead of default type-based extraction. Falls back silently on error.
Environment variables:
ans--[]anyraw answer data for the questionoptions--map[string]string(nameId to label), only for choice-type questions
Examples:
ans[1] // extract phone number only (skip country code)
ans[0] + " " + ans[1] // concatenate country code + number
ans[0] ? "Yes" : "No" // toggle to text
options[ans[0]] // resolve selected option to its label
| Function | Description |
|---|---|
NewSurvey(title, version, desc) |
Create new survey |
ParseFromBytes(b) |
Parse from JSON bytes |
ParseFromJsonStr(s) |
Parse from JSON string |
survey.ToJson() |
Serialize to JSON string |
survey.ToMap() |
Serialize to map |
| Method | Description |
|---|---|
ValidateSurvey() |
Validate structure + cross-reference consistency |
ReviewAnswers(ans) |
Validate answers, return *SurveyResume |
TranslateAnswers(ans, ignoreUnknown) |
Convert raw answers to human-readable labels |
GroupAnswersByType(ans) |
Group answers by question type |
| Method | Description |
|---|---|
AddQuestion(q) |
Add question (also AddQuestionJson, AddQuestionMap, AddQuestionBytes) |
UpdateQuestion(q) |
Update question (also Json/Map/Bytes variants) |
AddOrUpdateQuestion(q) |
Upsert question (also Json/Map/Bytes variants) |
RemoveQuestion(nameId) |
Remove question + clean up DependsOn refs |
GetQuestionsAssignments() |
Map of questionNameId to groupNameId |
GetAssetQuestions() |
List asset-type questions |
| Method | Description |
|---|---|
AddGroup(g) |
Add group (also AddGroupJson, AddGroupMap, AddGroupBytes) |
UpdateGroup(g) |
Update group (also Json/Map/Bytes variants) |
RemoveGroup(nameId) |
Remove group |
AddQuestionToGroup(qId, gId, pos) |
Assign question to group at position (-1 = end) |
RemoveQuestionFromGroup(qId, gId) |
Unassign question from group |
UpdateGroupQuestions(gId, qIds) |
Replace group's question list |
UpdateGroupsOrder(order) |
Reorder top-level groups |
EnableGroup(nameId) / DisableGroup(nameId) |
Toggle group visibility |
| Method | Description |
|---|---|
GetDisabledQuestions() |
Questions in disabled groups |
GetEnabledQuestions() |
Questions in enabled groups |
GetRequiredQuestions() |
Required + enabled questions |
GetOptionalQuestions() |
Optional + enabled questions |
go test ./...Run the example application:
go run example/main.goThe example/ directory includes two survey JSON files (survey.json, ecommerce_survey.json) demonstrating question types, grouped answers, and validation.
| Document | Description |
|---|---|
| Survey Structure Reference | Complete JSON schema for all question types |
| Example Application | Working code with survey JSON samples |
SurveyGo includes an Agent Skill that provides AI coding agents (Claude Code, Cursor, etc.) with structured guidance for consuming this library.
Install via skills.sh
npx skills add https://github.com/rendis/surveygo --skill surveygoln -s /path/to/surveygo/skills/surveygo ~/.claude/skills/surveygoOr use the distributable package: skills/surveygo.skill
| Component | Technology |
|---|---|
| Language | Go 1.25+ |
| Validation | go-playground/validator/v10 |
| Expressions | expr-lang/expr |
| Visualization | go-echarts/go-echarts/v2 |
| BSON | go.mongodb.org/mongo-driver |
MIT -- Copyright (c) 2025 rendis


