| name | surveycto |
| description | Design, edit, debug, and convert SurveyCTO forms (XLSForm .xlsx with survey/choices/settings sheets), server datasets (.xml with a dataset root element), Data Explorer workbook definitions, and field plug-ins (.fieldplugin.zip bundles attached via `custom-name` appearances). Covers form logic, expressions, choice lists, repeat groups, skip patterns, constraints, calculations, dataset publishing, and case management. Also covers converting forms from KoboToolbox/ODK XLSForms, CommCare XForms XML, Qualtrics .qsf JSON, and other sources into SurveyCTO XLSForms. Use when the user mentions SurveyCTO, XLSForm, ODK-based forms, survey forms, or data collection forms; when working with relevant .xlsx, .xml, or .fieldplugin.zip files; or when converting a form from another platform. New XLSForms always start from the bundled template at assets/xlsform-template.xlsx — never built from scratch.
|
| license | Apache-2.0 |
| metadata | {"author":"Dobility, Inc. (SurveyCTO)","version":"1.0.0-beta.8"} |
SurveyCTO Form, Plug-in, and Dataset Authoring
Skill version: 1.0.0-beta.8. SurveyCTO MCP tool responses publish the current skill-version policy: start_xlsform_session returns a skill_advisory, and get_surveycto_mcp_capabilities returns intended_skill.versions. When you see one, compare it against this skill's version above (semantic-version ordering, pre-release aware) and act: if this version is below the response's deprecated_below_version, warn the user that their installed SurveyCTO skill is deprecated and should be updated now from the response's download_url; if it is below recommended_min_version, mention a newer version is available and offer to help update. Say nothing about versions when this skill is current, and raise it at most once per conversation. Skills do not auto-update, so this is the only update signal users get. See references/mcp.md for the exact comparison rules.
SurveyCTO is a mobile data collection platform built on the XLSForm and ODK standards, with platform-specific extensions and divergences. This skill provides SurveyCTO domain expertise for the four definition file types you may encounter:
- XLSForm form definitions (
.xlsx) — survey instruments
- Dataset definitions (
.xml) — server dataset structure and publishing rules
- Data Explorer workbook definitions (
.xlsx) — monitoring dashboards
- Field plug-in bundles (
.fieldplugin.zip) — custom HTML/CSS/JS that takes over rendering of a single field
For a high-level orientation, read references/overview.md first.
Identifying SurveyCTO files
| File type | Format | How to recognize |
|---|
| XLSForm | .xlsx | Worksheets named survey, choices, settings |
| Dataset definition | .xml | Root element <dataset> with <definition> child |
| Data Explorer workbook | .xlsx | Worksheets named summaries, settings, global_filters, global_exclusions |
| Field plug-in bundle | .fieldplugin.zip | Zip with manifest.json, template.html, style.css, script.js at the root; referenced from a field's appearance as custom-<name> |
XLSForm starting point
Before choosing tools, before writing code, before reading further:
- New form → Step 1 is ALWAYS a literal file copy from
assets/xlsform-template.xlsx to the output path (for example, cp assets/xlsform-template.xlsx <output_path> in a Unix shell). The template is bundled with this skill at assets/xlsform-template.xlsx (resolve relative to the skill's root directory). Then proceed with the tooling rules below, uploading or opening the copied file.
- Existing form → load the user's workbook as-is; do not regenerate it.
- NEVER create an
.xlsx from scratch. This rule applies regardless of which tools are available — it is not an MCP-only requirement and there is no fallback exemption. See Anti-pattern: building the workbook from scratch for the specific tools and idioms to avoid.
This rule is the most commonly-skipped step in this skill. If you are about to call openpyxl.Workbook(), pandas.ExcelWriter against a new path, libreoffice --headless to create a new file, or any code that writes XLSX bytes from scratch, stop and copy the template instead.
Tools you may have available
This skill is fully usable with no special tools — you have enough SurveyCTO knowledge embedded here and at the live documentation links to advise the user, describe edits in prose, and reason about forms. If additional tools are present, use them in the priority order below.
Tool-selection rule for XLSForm work: if the SurveyCTO MCP server tools are connected, you MUST use them for nontrivial XLSForm inspection, editing, validation, and export. Do not choose generic bash, python, openpyxl, LibreOffice, or spreadsheet tooling as the primary authoring path when SurveyCTO MCP tools are available. Generic tooling is only a fallback if MCP tools are unavailable, disconnected, or return an unrecoverable error.
SurveyCTO MCP server (preferred when available)
The SurveyCTO MCP server is a public, no-auth MCP server with capabilities built for this domain (XLSForm session inspect/edit/export and SurveyCTO knowledge-base search). Endpoint: https://assistant-be.surveycto.net/mcp (Streamable HTTP, stateless, no auth). If the server isn't connected when the user is editing XLSForms or asking factual SurveyCTO questions, mention it exists and offer to help install it; don't insist.
If the MCP tools are connected but XLSForm uploads or downloads are failing with network errors, see references/install.md for details to guide the user. Claude Cowork in particular requires explicit network-egress configuration, and the symptom is usually a sandbox that's already cached the "blocked" state and needs a fresh chat after the setting is changed.
Before using any SurveyCTO MCP tool, read references/mcp.md. It is mandatory for MCP usage; do not guess tool signatures, patch semantics, concurrency rules, error handling, or limits from this abbreviated overview. Read its "Preflight" section before your first XLSForm session in a chat; if the preflight fails, fix egress before proceeding.
Recommended workflow
- Before any MCP tool call: read
references/mcp.md, then call get_surveycto_mcp_capabilities when unsure to learn the current tool list and primer topics. If the work involves XLSForms, you must also have read references/xlsform.md first — see Prerequisite: read the XLSForm reference.
- Factual SurveyCTO questions:
kb_search → quote the returned URLs in your answer.
- Create a new XLSForm: follow the Workflow: create a new form below — it is the canonical sequence and enforces the template-first rule.
- Add, update, or verify form-label translations: see Workflow: translate form labels below, which points to
references/translation.md.
- Inspect or edit an existing XLSForm:
- Load the user's workbook (do not regenerate).
start_xlsform_session for a fresh upload, or get_xlsform_summary if resuming an existing session_id.
- Take a starting inventory from
form_summary before patching or paging rows. Note existing column names, choice lists (especially reusable ones like yesno), settings values, and any warnings. Do not assume spellings or values from memory.
xls_get_rows / xls_get_row to inspect rows you intend to touch. Parallel calls are fine.
xls_apply_patches — batch all related edits into one call. Use validate_only=true on the full batch for risky changes. Update settings via change_setting, not edit_row. For large batches set return_form_summary=false and include_details=false, and trust a successful response — verify once at the end of the build, not after each batch (see references/mcp.md → Trust patch success; verify once at the end).
export_xlsform as soon as a form is in a deliverable state — especially when more forms remain in the same chat, so the user has each finished file in hand before you move on. Hand the file, download_url, or resource link to the user. Avoid format="base64" for real workbooks. If the form references any custom-<name> appearances, remind the user to attach the matching .fieldplugin.zip files in the SurveyCTO console at upload time — this skill and the MCP server only edit local files.
- Usually leave the session open until TTL expiry; only call
end_xlsform_session for explicit cleanup.
Generic spreadsheet/xlsx tooling
Use generic xlsx, Excel, Python/openpyxl, LibreOffice, or spreadsheet tools only when SurveyCTO MCP tools are not available or have failed in a way you cannot recover from. These tools can read and edit .xlsx files cell by cell, but they do not understand SurveyCTO form semantics, MCP session concurrency, or server-side formula recalculation. If you must use generic tooling, explicitly tell the user you are falling back because the MCP tools are unavailable or failed, then follow the editing rules in the Working with XLSForm .xlsx files section below.
Web fetch
If you can fetch URLs, the most authoritative live sources are:
Prefer these over the bundled primers when verifying current product behavior.
No file tooling at all
You can still help: describe edits in prose ("add a row to the survey worksheet with type=integer, name=age, label=Age in years, constraint=. >= 0 and . <= 120"), explain expressions, debug logic, and validate the user's draft against the rules below. The user can apply changes themselves.
Conserving context with sub-agents (when your host supports them)
If the host supports delegation (Cowork's Task tool, Claude Code's Task tool, etc.), use sub-agents to absorb read-heavy or context-bloating work when the parent agent does not need the raw tokens (only the conclusion). On long, multi-form builds in particular, context management is key.
Candidates for delegation: MCP egress preflight (the upload check), kb_search lookups, end-of-build verification (try not to re-verify after every batch; that anxiety loop is the proximate cause of context exhaustion on long builds). Do not delegate the actual xls_apply_patches writes. Patches must be serialized per session_id; running them inside a sub-agent the parent can't synchronize against invites session_conflict errors. The parent owns writes, sub-agents can own reads and summarization.
Authority hierarchy for knowledge questions
When the user asks about SurveyCTO product behavior, conventions, or documentation, prefer sources in this order:
- MCP
kb_search / get_surveycto_primer if available — current, indexed, authoritative.
- Live web fetch of
docs.surveycto.com, support.surveycto.com, www.surveycto.com if you can.
- Bundled primers in
references/ — stable summaries, may lag the live docs.
- General XLSForm/ODK knowledge — only with an explicit caveat that SurveyCTO has diverged in places (
= vs ==, index() vs position(), choice-label() vs jr:choice-name(), relevance column name, selected() for select_multiple, etc.).
Cite which level you're answering from when it matters.
XLSForm overview
An XLSForm is an XLSX workbook with three worksheets:
- survey — every question, group, repeat, and calculation is a row
- choices — choice lists for
select_one and select_multiple fields
- settings — form-wide settings (title, ID, version, language, encryption)
Key columns (survey worksheet)
| Column | Purpose |
|---|
type | Field type: text, integer, decimal, select_one [list], select_multiple [list], note, calculate, date, geopoint, image, begin group, end group, begin repeat, end repeat, etc. |
name | Unique variable name (no spaces/punctuation); becomes the export column |
label | Prompt text (supports label:Language for translations) |
relevance | Skip logic expression controlling when the row appears |
constraint | Validation expression (. = current value) |
calculation | Expression for calculate fields |
choice_filter | Expression filtering choices for select fields |
appearance | Display options (minimal, quick, likert, field-list, etc.) |
repeat_count | Number of repetitions for repeat groups |
Key columns (choices worksheet)
| Column | Purpose |
|---|
list_name | Choice list name (referenced by select_one [list] / select_multiple [list]) |
value | Stored code for the choice |
label | Display text (supports label:Language) |
Core mechanics
${fieldname} references another field's value in expressions.
. refers to the current field's proposed value (used in constraints).
- Groups (
begin group / end group) organize fields; a relevance on the group controls all contents.
- Repeats (
begin repeat / end repeat) repeat a block; repeat_count sets the number.
- Field names must be unique across the entire form.
Full reference: references/xlsform.md.
SurveyCTO expression conventions
SurveyCTO uses XPath-like expressions with several platform-specific conventions. Always use these instead of standard ODK equivalents:
| Use this (SurveyCTO) | Not this (ODK/XPath) | Context |
|---|
= | == | Equality comparison |
index() | position() | Current repeat instance number |
choice-label() | jr:choice-name() | Get choice label text |
relevance column | relevant column | Skip logic column name |
div | / | Division operator in expressions |
Select field conventions
- For
select_one: both ${field} = 'value' and selected(${field}, 'value') work.
- For
select_multiple: always use selected(). The = operator does not work for multi-select.
String literals
Use single quotes for string literals: ${field} = 'yes'
Exception: when the string contains single quotes, use double quotes:
if(${yesno} = 1, "a string with 'quotes' in it", "no quotes")
Common expression patterns
Skip logic (relevance): ${consent} = 'yes'
Constraint with range: . >= 0 and . <= 120
Calculated age from date of birth: int((today() - ${dob}) div 365.25)
Conditional value: if(${gender} = 'female', 'F', 'M')
Pull data from attached dataset: pulldata('households', 'head_name', 'hhid', ${household_id})
Random assignment (calculate once): once(if(random() < 0.5, 'treatment', 'control'))
Full expression reference: references/expressions.md. Live documentation: Using expressions in your forms.
Working with XLSForm .xlsx files
This section applies regardless of which file-tooling path you're using (SurveyCTO MCP server, generic xlsx tool, or describing edits in prose for the user to apply). The rules are the same; only the mechanism differs. When MCP tools are connected, use this section to plan and validate edits, but apply the edits through xls_apply_patches and export through export_xlsform.
Prerequisite: read the XLSForm reference
Before any XLSForm work — creating, editing, debugging, or answering questions about an XLSForm — read references/xlsform.md. This is mandatory regardless of which tooling path you use (MCP, generic xlsx tooling, or prose-only) and regardless of how simple the task seems. It documents SurveyCTO-specific column conventions, multi-language structure, group/repeat rules, and expression syntax that diverge from general ODK knowledge — do not rely on general ODK memory, even for "obvious" things. If anything remains unclear after reading the reference, use kb_search (or live docs at docs.surveycto.com) before proceeding.
One known-recurring gotcha worth flagging up front, because general ODK memory consistently gets it wrong: in multi-language forms, the default language goes in the unsuffixed column (label, hint, constraint message, …) and additional languages go in suffixed columns (e.g., label:Spanish). Putting the default language in a suffixed column and leaving the base empty silently breaks the form. Other divergences are documented in references/xlsform.md; read it.
For translation work specifically, see Workflow: translate form labels and references/translation.md.
CRITICAL: Always start from the template
Every new XLSForm must start from the bundled template at assets/xlsform-template.xlsx.
How to use the template:
- Make a literal file copy from
assets/xlsform-template.xlsx to the output path (for example, cp assets/xlsform-template.xlsx <output_path> in a Unix shell; resolve assets/ relative to the skill's root directory). Do not regenerate the file.
- If MCP tools are available, pass the copied file to
start_xlsform_session via the upload URL flow and apply edits with xls_apply_patches.
- If MCP tools are unavailable, open the copied file and edit it in place — add your content into the existing worksheets.
Never do any of the following:
- Do NOT create a new
.xlsx file from scratch.
- Do NOT create new worksheets and build the structure yourself.
- Do NOT delete and recreate worksheets from the template.
- Do NOT "rebuild" the form "properly" — the template IS the proper starting point.
Anti-pattern: building the workbook from scratch
Never run any of these for a new form:
from openpyxl import Workbook; wb = Workbook() (or any other code that constructs a fresh workbook object and adds survey/choices/settings sheets to it)
pandas.ExcelWriter(<new_path>) or pandas.DataFrame.to_excel(<new_path>) against a path that doesn't already contain the template's contents
libreoffice --headless --convert-to xlsx starting from a non-template source
- Writing XLSX bytes directly (zipfile +
xl/worksheets/*.xml)
These produce a technically valid but unusable form: no conditional formatting, no help-survey/help-choices/help-settings sheets, no caseid row, no hidden audit calculations, no reusable yesno choice list, no auto-updating version formula, and no color-coded row styling. The user has to redo the work in Excel by hand. Copy assets/xlsform-template.xlsx instead — this is true even when MCP tools are unavailable and you fall back to openpyxl for editing. The fallback is for editing, not for creation from scratch.
Workflow: create a new form
Follow these steps in order:
- Make a literal file copy from
assets/xlsform-template.xlsx to the output path. Do not create a new workbook object, generate worksheets, or convert from another source.
- Read
references/xlsform.md before designing any form content. This is mandatory — not a lookup to do if confused later. Column conventions, multi-language structure, group/repeat rules, and expression syntax all have SurveyCTO-specific details that differ from general ODK knowledge. See Prerequisite: read the XLSForm reference.
- If MCP tools are available, read
references/mcp.md, then upload the copied file via start_xlsform_session. If MCP is unavailable, open the copied workbook directly with the best available spreadsheet/XLSX tooling.
- Read the starting structure fully before editing. With MCP, use the returned
form_summary; without MCP, inspect the workbook sheets directly. Note next append location, exact column names already present, existing reusable choice lists, and current settings values.
- Apply related edits in one batch. With MCP, use a single
xls_apply_patches call and change_setting for form_id, form_title, default_language, and/or instance_name (the only patch-addressable settings keys); without MCP, write directly into the copied workbook's existing worksheets.
- Export or save the result. With MCP, use
export_xlsform and hand the download_url to the user. Remind the user to attach any required .fieldplugin.zip files in the SurveyCTO console at upload time.
Why the template is mandatory: It contains conditional formatting rules, help worksheets, column headers, starter metadata fields, formula-based versioning, and pre-formatted rows that cannot be reliably recreated programmatically. Skipping the template produces files that are technically valid but painful for humans to edit in Excel.
The template provides:
- survey worksheet with correct column headers and starter metadata fields, including hidden audit calculations and a
caseid row
- choices worksheet with headers and a
yesno choice list
- settings worksheet with headers and an auto-updating
version formula
- help-survey, help-choices, help-settings worksheets with reference documentation
- Conditional formatting rules that color-code rows by field type
Template starter content is a helpful starting point, not a fixed requirement. The starter rows (metadata fields, caseid, audit calculations) and the yesno choice list are included as conveniences for common cases. Keep standard metadata and audit rows by default because they are generally useful and not user-facing clutter; remove or replace them only when the source form or user request gives you a reason. Treat caseid as context-dependent: keep it for case-management work, but delete it when it would confuse a non-case draft. The yesno choice list is just an example choice list — drop or replace it freely when the form uses a different yes/no convention or no row references it. What's worth preserving is the tooling: the auto-updating version formula, the conditional formatting, the help worksheets, and the column-header structure. When you change starter content (e.g., rewrite a choice's value), be consistent — also update everything that references it.
Workflow: translate form labels
Adding a language, updating translations after the source changes, or verifying existing translations is its own workflow. Read references/translation.md before starting — don't improvise from the column-convention sketch in references/xlsform.md.
Workflow: convert a form from another platform
Converting a form definition exported from another data collection platform — KoboToolbox or ODK (XLSForm .xlsx), CommCare (XForms .xml), Qualtrics (.qsf JSON), or anything else — into a SurveyCTO XLSForm is its own workflow. Note that the conversion is best-effort and agent-driven, and the SurveyCTO MCP XLSForm tools work only on SurveyCTO-shaped XLSForms (do not point them at the source file). Read references/form-conversion.md before starting, then load the matching platform-specific reference (form-conversion-qualtrics.md, form-conversion-kobo.md, form-conversion-odk.md, form-conversion-commcare.md) if one exists for the source platform; for platforms without a dedicated reference, the base workflow still applies.
Editing rules (apply to every path)
- Preserve all existing worksheets — including
help-survey, help-choices, help-settings and any other sheets present in the template or existing form.
- Preserve existing formatting — do not clear or overwrite conditional formatting, column widths, or cell styles.
- Only write to rows that contain or will contain data. Don't touch unused rows below your content; don't write empty strings or
None to clear already-empty cells.
- Append new rows after existing data. Do not assume a fixed starter-row count — the template ships with several pre-populated metadata and calculation rows (including hidden audit calculations and a
caseid row), and an existing form may have any number of rows. With MCP, add_row appends automatically; you do not need to compute a row index, but you can derive one from form_summary (counts.n_rows, or the largest excel_row in survey_preview_rows) or by paging xls_get_rows if you need it. Without MCP, scan the workbook for the first row in survey (and choices) where type/list_name and the surrounding columns are all empty, and append there.
- Don't overwrite the
version cell in settings — it contains an auto-updating formula. (The MCP server's export_xlsform recalculates this for you.)
- Use plain labels by default. SurveyCTO does not render Markdown in labels, hints, notes, or messages. Prefer plain text; use simple inline HTML only when it materially helps. See Label, Hint, And Message Formatting.
Editing workflow
- Start from the right base — copy the template (new forms) or load the existing workbook.
- Read
references/xlsform.md before writing any patches. This applies to edits of existing forms just as much as new ones. Do not rely on general ODK memory for column names, expression syntax, or multi-language conventions. See Prerequisite: read the XLSForm reference.
- Read the existing structure so you know what's there.
- With MCP available: upload first with
start_xlsform_session, then use the returned form_summary as the starting overview. Call xls_get_rows only when you need more detail than the summary provides.
- Without MCP: open and read all three sheets.
- Plan the edits — identify the rows to add/modify and the cell values.
- Apply — write cell values into the appropriate rows and columns.
- With MCP available:
xls_apply_patches.
- Without MCP: write cells directly.
- Validate against the checklist below.
- Save / export — with MCP available,
export_xlsform runs formula recalculation and returns the file; without MCP, save the workbook and use the best available recalculation path.
Common operations
- Add a field: write
type, name, and label (at minimum) into the next empty row in survey. Place it in the correct position relative to groups and repeats.
- Add a choice list: write
list_name, value, label rows in choices. Ensure list_name matches what's referenced in the select_one/select_multiple type.
- Add a group:
begin group row and matching end group row with the same name. Place fields between them.
- Add a repeat:
begin repeat / end repeat rows with matching name. Set repeat_count if needed.
- Add skip logic: write the expression into the
relevance column on the target row.
- Add a constraint: write the expression into
constraint (use . for current value); write a user-facing message into constraint message.
- Add a calculation: row with
type=calculate, unique name, expression in calculation.
- Update settings: with MCP, use
change_setting for form_title, form_id, default_language, and/or instance_name (the only patch-addressable settings keys; other columns such as public_key and submission_url must be written by hand to a downloaded workbook). Without MCP, write those values into row 2 of settings. Leave version alone.
Validation checklist
After editing, verify:
Dataset XML definitions
Dataset definitions are XML files with a <dataset> root element. They define column structure, form attachments, and publishing rules.
Before reading, writing, or editing any dataset definition XML, read references/datasets-xml.md first. This is mandatory regardless of how simple the task seems. It documents the standard column sets per dataset type, the schema-enforced order of <definition> children, the mandatory children of the option blocks, and the idFormatOptions and table-view value rules that the schema does not enforce and that otherwise cause silent upload rejections. Do not author dataset XML from the summary below or from general knowledge alone.
Key elements
| Element | Purpose |
|---|
<id> | Dataset ID used in pulldata() and search() calls |
<title> | Display name |
<datasetType> | Always SERVER (legacy CLIENT/REPORT are never authored; see full reference) |
<fieldNames> | Comma-separated column names/order |
<formLinks> | Forms that attach this dataset for pre-loading |
<dataLinks> | Publishing rules (incoming from forms, outgoing to files) |
<caseManagementOptions> | Case management display and workflow config |
<uniqueRecordField> | Column used as unique key for upserts |
Critical notes
- Element names are case-sensitive (e.g.,
otherUserCode, not otherUsercode).
- Forms in
<formLinks> and <dataLinks> must be deployed before uploading the definition.
<showColumnsWhenTable> contains multiple <columnNames> child elements, not a comma-separated string.
- After creating or editing any dataset XML, validate and self-correct before delivering. Run the bundled validator and fix what it reports. Read
references/dataset-validation.md for how to run it and how to act on its findings.
Full reference (read before any dataset XML work): references/datasets-xml.md.
Data Explorer workbook definitions
Data Explorer workbooks are XLSX files with four worksheets: summaries, settings, global_filters, global_exclusions.
The summaries worksheet defines visualizations. Each row is a summary with a type (text, categorical, numeric, temporal, scatterplot, crosstab, …) and a field reference. Groups use begin group / end group with matching label values.
Full reference: references/data-explorer.md.
Field plug-ins
A field plug-in is a .fieldplugin.zip bundle (HTML/CSS/JS at the zip root) that takes over the rendering of a single form field. Plug-ins are supported only on text, integer, decimal, select_one, and select_multiple fields, and they run inside SurveyCTO Collect (Android), SurveyCTO Collect for iOS, and web forms.
When to use a field plug-in
Default to native field types and appearances. Plug-ins add real cost — attachment management, version bumps, aggressive caching, and cross-platform (Android/iOS/web) testing — so reach for them only when a native field can't deliver the needed behavior or UX. Decide in this order:
- Native fields and appearances first. Most needs are met by built-in
type/appearance combinations.
- Use as-is from the field plug-in catalog. If a maintained catalog plug-in already does what's needed, attach it without authoring anything.
- Customize an existing plug-in. Download the closest catalog plug-in or SurveyCTO
baseline-* repo as a ZIP from GitHub (Code → Download ZIP), or clone/fork it if the user is comfortable with Git, then edit the four files at the bundle root.
- Start from the bundled template.
assets/field-plugin-template/ is an intentionally minimal text-only skeleton — useful for an offline starting point or the smallest possible reading surface, but it omits several behaviors that baseline-text ships (see What the bundled template omits in references/field-plugins.md). Add those back manually if you need them.
Agent behavior rule: when a form could benefit from a plug-in, present the trade-offs (added complexity vs. functionality/UX gained) and confirm with the user before adding any plug-in dependency. Unless the user has explicitly directed plug-in use, do not add custom-<name> appearances or attach .fieldplugin.zip files unilaterally.
Use an existing plug-in in a form
- Attach the
.fieldplugin.zip to the form (Form Designer → Attachments, or as a regular form attachment when uploading the XLSForm).
- On the field, set
appearance to custom-<filenamestem> — the part of the zip filename before .fieldplugin.zip, e.g. myplugin.fieldplugin.zip → custom-myplugin — with optional (key=value, …) parameters.
Authoring (when the user wants to build a plug-in)
Follow the decision order above. Concretely:
- Preferred starting point: download the closest SurveyCTO baseline plug-in for the field type (
baseline-text, baseline-integer, baseline-decimal, baseline-select_one, baseline-select_multiple) — or a closely related catalog plug-in — as a ZIP from GitHub's Code → Download ZIP button, then customize it. If the user prefers a Git workflow, cloning or forking works equally well; otherwise no Git tooling is required.
- Minimal/offline alternative: copy
assets/field-plugin-template/. It is original code, not a copy of baseline-text, and intentionally leaves out several baseline behaviors (media rendering, HTML-entity unescaping in label/hint, standard numbers/numbers_decimal/numbers_phone appearance handling, soft-keyboard invocation, default placeholder fallback, read-only display of empty values). See references/field-plugins.md for the full list.
- Edit the four files at the bundle root:
manifest.json, template.html, style.css, script.js. Always use triple-brace Mustache for {{{LABEL}}} and {{{HINT}}} (they may contain HTML).
- Re-package as
<name>.fieldplugin.zip with all files at the zip root (subdirectories get flattened on upload). Bump manifest.version on every re-upload.
Testing
- Preview by default while authoring. When you create or edit a plug-in inside this skill, render
assets/field-plugin-test-harness/preview.html immediately so the user can see the result, and re-render after every revision. In agent UIs that support inline HTML/JS artifacts, serve it as an artifact pre-populated with the four core files; otherwise point the user at the file on disk. Also run validate.mjs (static checks on the bundle — manifest schema, file presence, naming rules) after every change.
- Final validation in the in-product field plug-in console (required before deployment). In the SurveyCTO form designer, click Test (top-right toggle, or the Test button next to the form on the Design tab) to enter test view. Navigate to the field that uses the plug-in; an icon button appears on the left edge of the form area — click it to expand the console. It shows the plug-in's details (name, version, author, supported field types, filename), the current values of any dynamic parameters and metadata, and three editable boxes (HTML/CSS/JS) with a Reload button for previewing live code edits. Console edits are session-scoped and not saved to the
.zip; copy fixes back to your source files and re-upload. The local harness's stubs are not a substitute for the real Android/iOS/web runtimes. See Testing field plug-ins.
- Real-device pass before going live: SurveyCTO Collect on a representative Android device, Collect for iOS, and at least one desktop browser for web forms.
Agent behavior rule: after creating or revising plug-in source files, proactively (a) re-render the harness so the user can see the current state, and (b) recommend the in-product field plug-in console as the required next step (with the one-line "Form Designer → Test → navigate to the field → click the plug-in console icon" reminder). Do not present the work as done before the user has had a chance to validate there.
For everything else — full manifest schema, form API (field properties, runtime CSS classes, provided/called JS functions), parameters and metadata model, Android-only intent and phone APIs, and common pitfalls — see references/field-plugins.md.
Common patterns
Cascading selects
Filter choices in one field based on the selection in another:
- In
choices, add a filter column (e.g., region) to the child list.
- In
survey, set choice_filter on the child field: region = ${region}.
Pre-loaded data lookup with pulldata()
- Create a
SERVER dataset with the lookup data.
- Attach it to the form via
<formLinks> in the dataset definition (this is what pre-loads it onto devices).
- Use
pulldata('dataset_id', 'column_to_return', 'lookup_column', ${key_field}) in a calculation.
Dynamic select list from pre-loaded data
- Create a
SERVER dataset and attach it to the form via <formLinks>.
- In
survey, set type to select_one listname and appearance to search('dataset_id') (or a more specific search expression).
- In
choices, add one row for listname where value and label contain column names from the dataset (not literal values).
See references/xlsform.md for search() syntax patterns.
Random sampling from a repeat group
- Inside the repeat:
calculate field with calculation = once(random()).
- Outside the repeat:
rank-index(1, ${random_calc}) returns the index of the randomly-selected instance.
Use a custom field plug-in
Before adding a plug-in to a form, confirm with the user that the added complexity is worth the gained functionality, and remind them they will need to attach the .fieldplugin.zip themselves when uploading the form.
Attach the .fieldplugin.zip to the form, then set appearance on the field to custom-<filenamestem> with any parameters the plug-in expects. The <filenamestem> is the part of the zip filename before .fieldplugin.zip (for example, phonenumber.fieldplugin.zip → custom-phonenumber). The name field inside manifest.json is a human-readable display name and does not drive the appearance.
| type | name | appearance |
|---|
text | phone | custom-phonenumber(country='US', placeholder=${default_format}) |
Parameter values are evaluated by the form before the plug-in loads, so ${field} references and full SurveyCTO expressions work. In the plug-in, read parameters with getPluginParameter('country') rather than indexing fieldProperties.PARAMETERS. Full reference: references/field-plugins.md.
Publishing form data to a dataset
In the dataset XML, add a <dataLink> with:
<dataLinkClass>FORM</dataLinkClass>
<dataLinkType>INCOMING</dataLinkType>
<linkObjectId>form_id</linkObjectId>
<fieldMap> with JSON mapping form fields to dataset columns
Debugging common issues
| Symptom | Likely cause | Fix |
|---|
| Form won't upload | Duplicate name values | Search for duplicates in the name column |
| Form won't upload | Unbalanced groups/repeats | Check every begin/end pair |
| Field never appears | Incorrect relevance expression | Verify syntax; verify referenced fields exist |
| Constraint never passes | Wrong use of . vs ${fieldname} | In constraints, . is the current field; ${fieldname} is another field |
| Choices missing | list_name mismatch | select_one [list] must match a list_name in choices |
select_multiple logic fails | Using = instead of selected() | Always use selected(${field}, 'value') |
| Date comparison fails | Comparing string to date | Wrap with date(): ${field} > date('2024-01-01') |
| Dataset won't upload | Referenced form not deployed | Deploy all forms in <formLinks> and <dataLinks> first |
| Dataset element ignored | Case-sensitivity error | Check exact casing (e.g., otherUserCode) |
Plug-in field doesn't appear / custom-<name> ignored | Plug-in .fieldplugin.zip not attached, name mismatch, or unsupported field type | Re-attach the zip; confirm the custom-<name> token matches the .fieldplugin.zip filename stem (e.g. myplugin.fieldplugin.zip → custom-myplugin), not manifest.name; plug-ins only work on text/integer/decimal/select_one/select_multiple |
| Plug-in answer not saved | setAnswer not called, or select_multiple value joined with commas | Wire input events to setAnswer(value); for select_multiple, pass a space-separated list |
| Plug-in attachments 404 at runtime | Files placed in subdirectories inside the zip | Flatten the zip — every file at the root, no duplicate basenames |
References
| Primer | When to read |
|---|
references/overview.md | First — orientation, file types, how they fit together |
references/xlsform.md | Mandatory before any XLSForm work — column conventions, expressions, groups/repeats, multi-language |
references/translation.md | Adding, updating, or verifying form-label translations — workflow, preserve-verbatim rules, glossary handling, self-review, back-translation spot check, verification checklist |
references/form-conversion.md | Mandatory before any form conversion — platform-agnostic workflow for converting form definitions from other data collection platforms into SurveyCTO XLSForms, including the universal mapping concerns, the conversion-report contract, and pointers to platform-specific references |
references/form-conversion-qualtrics.md | Converting a Qualtrics .qsf JSON export — .qsf anatomy, question-type / selector mapping, display-logic translation, flow/branch handling, gotchas |
references/form-conversion-kobo.md | Converting a KoboToolbox XLSForm .xlsx — expression rewrites, Kobo-specific extensions (kobo--matrix, kobo--score, kobo--rank), settings differences |
references/form-conversion-odk.md | Converting an ODK XLSForm .xlsx — expression rewrites, column-name normalization (relevant→relevance), field-type and settings differences |
references/form-conversion-commcare.md | Converting a CommCare XForms .xml export — XForms anatomy, parsing helper, itext → label:Lang, case-management caveats |
references/expressions.md | Any expression work (relevance, constraint, calculation, choice_filter) |
references/datasets-xml.md | Server dataset XML definitions |
references/dataset-validation.md | Run after authoring/editing any dataset XML — validate the definition against the server's create/edit rules with the bundled assets/dataset-validation/validate_dataset.py, then self-correct |
references/data-explorer.md | Data Explorer dashboards |
references/field-plugins.md | Field plug-in authoring, packaging, form API, and testing |
Key documentation links