skills/app-studio/variable-creation/SKILL.md
Create and manage Domo card variables (dropdowns, sliders, pills); function templates, control registration, card save flow, curl or CLI.
npx skillsauth add stahura/domo-ai-vibe-rules variable-creationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Create Domo card variables (interactive controls) and output curl commands or CLI commands to execute them.
Variables are user-adjustable values that feed into Beast Mode formulas, enabling dynamic what-if analysis. They appear as dropdown, pill, slider, textbox, or date picker controls on cards and dashboards.
Variable creation requires three sequential API calls. All three are mandatory — skipping the middle step causes a 400 error on the card save.
1. Check name uniqueness (GET /query/v1/functions/variables/uniqueName)
2. Create the function template WITH embedded control (POST /query/v1/functions/template)
3. Register the variable control (PUT /content/v1/variable/controls)
4. Read the current card definition (PUT /content/v3/cards/kpi/definition)
5. Save the card with the new control (PUT /content/v3/cards/kpi/{cardId})
| Parameter | Source | Required |
| -------------- | ------------------------------------------------- | ---------------------------- |
| instanceUrl | Domo instance (e.g., domo-gordon-pont.domo.com) | Always |
| devToken | CLI auth / developer token | Always |
| cardId | Card ID | Always |
| dataSourceId | Dataset UUID (from card definition or columns) | For validation and card save |
| Data Type | Expression Examples | Compatible Controls |
| --------- | -------------------------------- | ------------------------------- |
| string | 'TEST', 'Option A' | DROPDOWN, PILL, TEXTBOX |
| numeric | 100, 0.5 | DROPDOWN, PILL, SLIDER, TEXTBOX |
| date | CURRENT_DATE(), '2024-01-01' | DROPDOWN, DATE_PICKER |
| Type | Description | Best For |
| ------------- | --------------------------------------- | ------------------------------------------- |
| DROPDOWN | Select from a list of predefined values | Categorical selections, string/numeric/date |
| PILL | Inline toggle chips | Small number of options (2-5) |
| SLIDER | Numeric range slider | Numeric ranges with min/max |
| TEXTBOX | Free-text input | Open-ended string/numeric input |
| DATE_PICKER | Calendar date selector | Date variables |
| Data Type | exprType |
| --------- | --------------- |
| string | STRING_VALUE |
| numeric | NUMERIC_VALUE |
| date | DATE_VALUE |
For DROPDOWN and PILL controls, the values array defines the selectable options:
{"expression": {"value": "Option Text", "exprType": "STRING_VALUE"}}
For SLIDER controls, values define the min/max range:
[
{"expression": {"value": "0", "exprType": "NUMERIC_VALUE"}},
{"expression": {"value": "100", "exprType": "NUMERIC_VALUE"}}
]
Endpoint: GET /api/query/v1/functions/variables/uniqueName?name={name}
curl -X GET "https://{instanceUrl}/api/query/v1/functions/variables/uniqueName?name={variableName}" \
-H "X-DOMO-Developer-Token: {devToken}"
Returns [] if the name is available. Returns an array of function template IDs if the name is already in use. Variable names must be globally unique across the entire Domo instance.
Endpoint: POST /api/query/v1/functions/template
This creates the variable's function template. The request body must include an embedded control object — this is critical and differs from regular beast mode creation.
curl -X POST "https://{instanceUrl}/api/query/v1/functions/template" \
-H "X-DOMO-Developer-Token: {devToken}" \
-H "Content-Type: application/json" \
-d '{
"name": "{variableName}",
"formula": "{defaultExpression}",
"status": "VALID",
"dataType": "{dataType}",
"persistedOnDataSource": false,
"isAggregatable": true,
"bignumber": false,
"owner": null,
"nonAggregatedColumns": [],
"legacyId": "calculation_{uuid}",
"variable": true,
"isCalculation": true,
"initialPersistedOnDataSource": false,
"saved": true,
"query": "{defaultExpression}",
"control": {
"format": {"type": "default"},
"controlType": "VARIABLE",
"function": {
"name": "{variableName}",
"dataType": "{dataType}",
"expression": "{defaultExpression}",
"id": -201
},
"saveOverride": true,
"description": "",
"values": [
{"expression": {"value": "{value1}", "exprType": "{exprType}"}},
{"expression": {"value": "{value2}", "exprType": "{exprType}"}}
],
"type": "{controlType}",
"unsaved": true,
"name": "{variableName}",
"id": -101
},
"description": "",
"locked": false,
"cacheWindow": "non_dynamic",
"containsAggregation": false,
"containsAnalytic": false,
"invalidColumns": [],
"nonAggregatedExpressions": [],
"formulaTemplateDependencies": [],
"columnPositions": [],
"formulaId": "calculation_{another_uuid}",
"formulaDependencies": [],
"isControlled": false,
"global": true,
"unsaved": true,
"expression": "{defaultExpression}"
}'
Key fields:
variable: true — marks this as a variable function (not a regular beast mode)control — the embedded control definition with placeholder IDs (-201, -101)legacyId and formulaId — generate unique UUIDs in the format calculation_{uuid}global: true — variables are instance-wideowner: null — server assigns the ownerResponse — save the id (numeric function template ID):
{
"id": 1172,
"name": "{variableName}",
"legacyId": "calculation_{uuid}",
"variable": true,
...
}
Endpoint: PUT /api/content/v1/variable/controls
This step registers the control with the Domo control system. Without this step, the card save will return 400.
curl -X PUT "https://{instanceUrl}/api/content/v1/variable/controls" \
-H "X-DOMO-Developer-Token: {devToken}" \
-H "Content-Type: application/json" \
-d '[
{
"format": {"type": "default"},
"controlType": "VARIABLE",
"function": {
"name": "{variableName}",
"dataType": "{dataType}",
"expression": "{defaultExpression}",
"id": {functionTemplateId from Step 2}
},
"saveOverride": true,
"description": "",
"values": [
{"expression": {"value": "{value1}", "exprType": "{exprType}"}},
{"expression": {"value": "{value2}", "exprType": "{exprType}"}}
],
"type": "{controlType}",
"unsaved": false,
"name": "{variableName}"
}
]'
Key differences from Step 2's control:
function.id is now the real template ID from Step 2 (not -201)unsaved: false (not true)id: -101 at the control levelResponse — returns an array with the registered control, including server-assigned id:
[
{
"id": 145,
"function": {"id": 1172, ...},
"type": "DROPDOWN",
"values": [...],
"controlType": "VARIABLE",
...
}
]
curl -X PUT "https://{instanceUrl}/api/content/v3/cards/kpi/definition" \
-H "X-DOMO-Developer-Token: {devToken}" \
-H "Content-Type: application/json" \
-d '{
"dynamicText": true,
"variables": true,
"urn": "{cardId}"
}'
Save the full response. You need definition.subscriptions, definition.controls, definition.charts, definition.modified, etc.
Get the dataSourceId from columns[].sourceId in the response.
Endpoint: PUT /api/content/v3/cards/kpi/{cardId}
Build the update payload by merging the new control into the card definition.
curl -X PUT "https://{instanceUrl}/api/content/v3/cards/kpi/{cardId}" \
-H "X-DOMO-Developer-Token: {devToken}" \
-H "Content-Type: application/json" \
-d '{
"definition": {
"subscriptions": {existing subscriptions from card def — do NOT add dataSourceId here},
"formulas": {"dsUpdated": [], "dsDeleted": [], "card": []},
"annotations": {"new": [], "modified": [], "deleted": []},
"conditionalFormats": {"card": [], "datasource": []},
"controls": [{existing controls with saveOverride: true}, {new control}],
"segments": {"active": [], "create": [], "update": [], "delete": []},
"charts": {existing charts},
"dynamicTitle": {existing dynamicTitle},
"dynamicDescription": {"text": []},
"chartVersion": "{existing chartVersion}",
"allowTableDrill": true,
"inputTable": false,
"modified": {existing modified timestamp},
"title": "{card title}",
"description": ""
},
"dataProvider": {"dataSourceId": "{dataSourceId}"},
"variables": true,
"columns": false
}'
Critical payload notes:
variables: true and columns: false at the top leveldataSourceId goes in dataProvider only — NOT in subscriptions.mainformulas uses the {dsUpdated, dsDeleted, card} structure (not the array format from the read response)saveOverride: true addedfunction.id = the template ID from Step 2modified timestamp must match the card definition's valueEndpoint: POST /api/query/v1/functions/validateFormulas
Validation is optional but recommended before creating. The formula entry must have "variable": true.
curl -X POST "https://{instanceUrl}/api/query/v1/functions/validateFormulas" \
-H "X-DOMO-Developer-Token: {devToken}" \
-H "Content-Type: application/json" \
-d '{
"dataSourceId": "{dataSourceId}",
"columns": [{column schema array}],
"formulas": {
"{formulaId}": {
"id": "{formulaId}",
"name": "{variableName}",
"formula": "{defaultExpression}",
"status": "valid",
"dataType": "{dataType}",
"variable": true,
"templateId": null,
"legacyId": "{formulaId}",
"formulaDependencies": []
}
}
}'
A variable has no effect on a card unless it's referenced in a Beast Mode formula:
CASE
WHEN `My Variable` = 'Option A' THEN `column_a`
WHEN `My Variable` = 'Option B' THEN `column_b`
ELSE `column_a`
END
Variables are referenced by name in backticks, just like dataset columns.
Variables have three levels of override:
The community-domo-cli handles the full 3-step flow automatically:
# Check if a variable name is available
community-domo-cli variables check-name "My Variable"
# List variables on a card
community-domo-cli variables list {cardId}
# Create a variable (handles all 3 steps internally)
community-domo-cli variables create {cardId} --body-file variable.json
# Validate a variable formula
community-domo-cli variables validate --body-file validate-payload.json
# Read full card definition (includes controls)
community-domo-cli cards definition {cardId}
variable.json:
{
"name": "Region Selector",
"type": "DROPDOWN",
"function": {
"name": "Region Selector",
"dataType": "string",
"expression": "'North'"
},
"description": "Select a region to filter by",
"values": [
{"expression": {"value": "North", "exprType": "STRING_VALUE"}},
{"expression": {"value": "South", "exprType": "STRING_VALUE"}},
{"expression": {"value": "East", "exprType": "STRING_VALUE"}},
{"expression": {"value": "West", "exprType": "STRING_VALUE"}}
]
}
community-domo-cli variables create 1810280719 --body-file variable.json --yes
The CLI automatically:
PUT /content/v1/variable/controls| Problem | Cause | Fix |
| --------------------------------------- | ------------------------------------------------ | -------------------------------------------------------------------- |
| Variable not visible on card | Not referenced in any beast mode | Create a beast mode that uses the variable |
| Card save returns 400 | Missing PUT /content/v1/variable/controls step | Must register the control before saving the card |
| Card save returns 400 | dataSourceId in subscriptions.main | Remove it — only put dataSourceId in dataProvider |
| Card save returns 400 | Existing controls not in full server format | Pass existing controls as-is from card def with saveOverride: true |
| 500 Internal database error | Duplicate variable name | Check name with GET /query/v1/functions/variables/uniqueName first |
| Validation returns INVALID | Bad expression syntax | Check quotes for strings, function syntax for dates |
| Dashboard doesn't show variable control | Card-level override doesn't propagate | Set the control at dashboard level |
| Variable resets on dashboard refresh | Expected behavior | Dashboard controls reset to default on page load |
When generating commands for the user, provide:
tools
Step-by-step orchestrator for building Domo App Studio apps with native KPI cards via community-domo-cli. Sequences app creation, pages, theme, hero metrics, native charts, filter cards, layout assembly, and navigation. CLI-first — no raw API calls.
tools
Create, update, and execute Magic ETL dataflows programmatically via API and CLI. Covers DAG-based JSON dataflow definitions, input/transform/output node wiring, join operations, and execution lifecycle.
tools
Magic ETL dataflows via community-domo-cli — list, get-definition, create, update, run, execution status; JSON DAG actions, transforms, joins. Use when automating dataflows with the community Domo CLI end-to-end. For REST/Java-CLI–first flows or mixed API patterns, use magic-etl instead.
development
Clean, professional dashboard theme for Domo custom apps. CSS custom properties, layout patterns, typography, and design polish that feel native to the Domo platform. Includes OKLCH color palette, layered shadows, concentric border radius, tabular numbers, and micro-interaction patterns.