From omni-analytics
Create, update, and manage Omni Analytics documents and dashboards programmatically — document lifecycle, tiles, visualizations, filters, and layouts — using the Omni CLI. Use this skill whenever someone wants to build a dashboard, create a workbook, add tiles or charts, configure dashboard filters, update an existing dashboard's model, set up a KPI view, create visualizations, lay out a dashboard, create a document, rename a workbook, delete a dashboard, move a document to a folder, duplicate a dashboard, or any variant of "build a dashboard for", "create a report showing", "add a chart to", "make a dashboard", "update the dashboard layout", "rename this document", "move to folder", or "delete this dashboard". Also use when modifying dashboard-level model customizations like workbook-specific joins or fields.
npx claudepluginhub exploreomni/omni-agent-skills --plugin omni-integrationsThis skill uses the workspace's default tool permissions.
Create, update, and manage Omni documents and dashboards programmatically via the Omni CLI — document lifecycle, workbook models, filters, and dashboard content.
Programmatically create, update, and manage Omni Analytics documents and dashboards via REST API, including lifecycle, tiles, visualizations, filters, layouts, and workbook models.
Automates Looker dashboards by adding elements, tiles, and filters via Node.js scripts. Useful for data discovery and business intelligence workflows.
Generates self-contained interactive HTML dashboards with KPI cards, charts, filters, and tables from queries, CSVs, or samples for reports and monitoring.
Share bugs, ideas, or general feedback.
Create, update, and manage Omni documents and dashboards programmatically via the Omni CLI — document lifecycle, workbook models, filters, and dashboard content.
Tip: Use
omni-model-explorerto understand available fields andomni-content-explorerto find existing dashboards to modify or learn from.
omni query run, check viz spec consistency, and verify the dashboard after creation by reading it back and executing its queries.config, visType, or prefersChart are misconfigured. Default to chartType: "table" for reliable rendering, and configure chart visualizations in the Omni UI.identifier not id for all document API calls — .id is null for workbook-type documents and will silently fail.pivots array is present (reported Omni bug). If boolean filters aren't applying, remove the pivot and test again.PUT /api/v1/documents/{documentId} replaces the entire document state. Always read the existing document first and modify from there, or you'll lose tiles you didn't include.# Verify the Omni CLI is installed — if not, ask the user to install it
# See: https://github.com/exploreomni/cli#readme
command -v omni >/dev/null || echo "ERROR: Omni CLI is not installed."
# Show available profiles and select the appropriate one
omni config show
# If multiple profiles exist, ask the user which to use, then switch:
omni config use <profile-name>
omni documents --help # Document operations
omni dashboards --help # Dashboard operations
omni models yaml-create --help # Writing model YAML
Tip: Use
-o jsonto force structured output for programmatic parsing, or-o humanfor readable tables. The default isauto(human in a TTY, JSON when piped).
Omni dashboards are built from documents (workbooks). Each has:
Documents can be created with full query and visualization configurations via queryPresentations. Fine-tuning tile layout is best done in the Omni UI.
omni documents create --body '{
"modelId": "your-model-id",
"name": "Q1 Revenue Report"
}'
Returns the new document's identifier, workbookId, and dashboardId.
Use queryPresentations to create a document pre-populated with query tabs and visualization configurations.
Doc gap: The create-document API docs mention queryPresentations but don't show the complete structure. This section documents the full format.
omni documents create --body '{
"modelId": "your-model-id",
"name": "Q1 Revenue Report",
"queryPresentations": [
{
"name": "Monthly Revenue Trend",
"topicName": "order_items",
"prefersChart": true,
"visType": "basic",
"fields": ["order_items.created_at[month]", "order_items.total_revenue"],
"query": {
"table": "order_items",
"fields": ["order_items.created_at[month]", "order_items.total_revenue"],
"sorts": [{ "column_name": "order_items.created_at[month]", "sort_descending": false }],
"filters": { "order_items.created_at": "this quarter" },
"limit": 100,
"join_paths_from_topic_name": "order_items",
"visConfig": { "chartType": "lineColor" }
},
"config": {}
}
]
}'
Tip: Default to
"config": {}for reliable rendering — Omni will auto-generate chart config. For precise chart styling, build a reference dashboard in the UI and read it back viaGET /api/v1/documents/{documentId}. See references/queryPresentations.md for complete config examples by chart type (KPI, line, bar, area, pie, scatter, etc.).
See references/queryPresentations.md for the complete reference — parameter tables for queryPresentation and query objects, chart type examples, and caveats when reusing presentations from existing dashboards. See references/visConfig.md for the full visConfig and config object reference — all accepted chartType values, config structure for every chart family (cartesian, KPI, pie, funnel, sankey, heatmap, map), and worked examples.
Key points:
prefersChart must be true to render a chart (otherwise always shows table)visType: "omni-kpi" for KPI tiles, "basic" for all other chartsvisConfig goes inside the query object (silently dropped if placed as sibling)fields must be duplicated at both the queryPresentation and query levelsmodelId is inherited from the document — not needed inside query"config": {} for reliable rendering — Omni auto-generates chart configvisConfig and config schema details, see references/visConfig.mdTo learn the exact structure for a chart type, build a reference dashboard in the Omni UI and read it back:
omni documents get <documentId>
When reusing queryPresentations from existing documents, always strip model_extension_id from query objects (causes "Chart unavailable" errors) and filter to only the tiles you want.
omni documents update <documentId> --name "Q1 Revenue Report (Updated)" --clear-existing-draft true
Pass --clear-existing-draft true if the document has an existing draft, otherwise the API returns 409 Conflict.
omni documents delete <documentId>
Soft-deletes the document (moves to Trash).
omni documents move <documentId> "/Marketing/Reports" --scope organization
Use "null" as the folder path to move to root. --scope is optional — auto-computed from the destination folder.
omni documents duplicate <documentId> "Copy of Q1 Revenue Report" --folder-path "/Marketing/Reports"
Only published documents can be duplicated. Draft documents return 404.
Update the tiles, queries, filters, and visualizations on an existing dashboard using PUT /api/v1/documents/{documentId}. This is a full replacement — you must pass the complete desired state of the document, not just the fields you want to change.
Warning: This endpoint only works with dashboard documents. Workbook-only documents are not supported and return 400.
Note: This endpoint is documented at docs.omni.co but may not appear in the OpenAPI spec at
/openapi.jsonyet.
Step 1 — Read the existing document to get its current state:
omni documents get <documentId>
This returns the full document including queryPresentations, filterConfig, filterOrder, modelId, name, and other fields. Use this as your starting point.
Step 2 — Modify the response as needed:
queryPresentations arrayqueryPresentations arrayquery, config, fields, etc.filterConfig and filterOrderStep 3 — PUT the updated document:
# Note: Full document replacement via PUT is not yet available in the CLI.
# Use direct HTTP for now, or use omni documents update for partial updates (PATCH).
# Derive OMNI_BASE_URL and OMNI_API_TOKEN from the active profile for this call.
curl -L -X PUT "$OMNI_BASE_URL/api/v1/documents/{documentId}" \
-H "Authorization: Bearer $OMNI_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"modelId": "your-model-id",
"name": "Q1 Revenue Report",
"facetFilters": false,
"refreshInterval": null,
"filterConfig": {},
"filterOrder": [],
"clearExistingDraft": true,
"queryPresentations": [ ... ]
}'
The queryPresentations array uses the same structure as document creation — see above.
| Parameter | Type | Description |
|---|---|---|
modelId | string | Model ID for query transformation |
name | string | Document name (1–254 characters) |
facetFilters | boolean | Enable facet filters on the dashboard |
refreshInterval | integer or null | Auto-refresh interval in seconds (min 60), or null to disable |
filterConfig | object | Dashboard filter configuration — pass {} for no filters |
filterOrder | array | Ordered filter IDs — pass [] for no filters |
queryPresentations | array | At least one query presentation required (same structure as document creation) |
| Parameter | Type | Description |
|---|---|---|
clearExistingDraft | boolean | Discard existing draft before updating. Required when the published document has a draft — otherwise returns 409 Conflict. |
documentMetadata | object | Presentation settings including filter collapsibility |
queryPresentation you include becomes a tile. Any tile you omit from the array is removed. Always start from the existing document's queryPresentations and modify from there.clearExistingDraft: true is set.model_extension_id).Push custom dimensions and measures to a specific dashboard by writing to its workbook model. Each workbook has its own model that extends the shared model — so the ID you write YAML to is a model ID, not a separate "workbook ID". This is a two-step flow:
Step 1 — get the document to find its workbook model ID:
omni documents get <documentId>
# → use the top-level "modelId" field from the response — that IS the workbook model ID
Note: The response does not contain a field called
workbook_id. The top-levelmodelIdis the workbook's own model (which extends the shared model) and is what you pass toomni models yaml-create.
Step 2 — POST YAML to the workbook model with mode: "extension":
omni models yaml-create <workbookModelId> --body '{
"fileName": "order_items.view",
"yaml": "dimensions:\n is_high_value:\n sql: \"${sale_price} > 100\"\n label: High Value Order\nmeasures:\n high_value_count:\n sql: \"${order_items.id}\"\n aggregate_type: count_distinct\n label: High Value Orders",
"mode": "extension"
}'
Critical: Always pass
"mode": "extension"when editing an existing view in a workbook model. The default is"combined", which treats your YAML body as the complete view definition and marks every field you didn't include asignored: true— silently breaking queries that depend on fields from the shared base view. Extension mode layers your new dimensions and measures on top of the inherited view.
fileName must be "model", "relationships", or end with .view or .topic. The yaml value is a YAML string (not a JSON object). Writing to a workbook model skips git sync entirely — authorization is still checked against the underlying shared model's permissions.
After writing, confirm the base view's fields are still available by querying one:
omni query run --body '{
"query": {
"modelId": "<workbookModelId>",
"table": "order_items",
"fields": ["order_items.id", "order_items.high_value_count"],
"limit": 1,
"join_paths_from_topic_name": "order_items"
}
}'
If the response errors on a field that exists in the shared model (e.g. order_items.id), your write likely used combined mode and ignored the inherited fields. Re-run Step 2 with "mode": "extension".
omni dashboards get-filters <dashboardId>
Filters can be updated via two approaches:
PUT /api/v1/documents/{documentId} (recommended) — update filters as part of a full document update. Include filterConfig and filterOrder alongside queryPresentations and other required fields. See the Update Existing Dashboard section.omni dashboards update-filters <dashboardId> — partial filter update. Has been reported to return 405 or 500 in some configurations.For new dashboards, the most reliable way is to include filterConfig and filterOrder in the initial omni documents create call. See references/filterConfig.md for complete examples of each filter type.
omni documents create --body '{
"modelId": "your-model-id",
"name": "Filtered Dashboard",
"filterConfig": {
"date_filter": {
"type": "date",
"label": "Date Range",
"fieldName": "order_items.created_at",
"kind": "TIME_FOR_INTERVAL_DURATION",
"ui_type": "PAST",
"left_side": "6 months ago",
"right_side": "6 months"
},
"state_filter": {
"type": "string",
"label": "State",
"kind": "EQUALS",
"fieldName": "users.state",
"values": []
}
},
"filterOrder": ["date_filter", "state_filter"],
"queryPresentations": [...]
}'
The keys in filterConfig (e.g., "date_filter") are arbitrary IDs — they must match the entries in filterOrder. To learn the exact filter structure, read filters from an existing dashboard with omni dashboards get-filters <dashboardId>.
Date Range (relative) — type: "date", kind: "TIME_FOR_INTERVAL_DURATION", requires fieldName
Date Range (absolute) — type: "date", kind: "WITHIN_RANGE", requires fieldName
String Dropdown — type: "string", kind: "EQUALS", requires fieldName, values: []
Boolean Toggle — type: "boolean", requires fieldName
Hidden Filter — any filter with "hidden": true (applied but not visible)
Critical: Every filter MUST include
fieldNamewith the fully qualified field name (e.g.,"order_items.created_at"). Without it, the filter won't bind to any column. For date filters, do NOT include a timeframe bracket infieldName.
Controls change what fields or granularity tiles display. They go in a controls array (NOT in filterConfig), but their IDs are included in filterOrder.
Time Frame Switcher — type: "FIELD_SELECTION", kind: "TIMEFRAME" with options array
Field Switcher — type: "FIELD_SELECTION", kind: "FIELD" with options array
See references/filterConfig.md for complete filter and control examples.
After creating or finding content, always provide the user a direct link:
Dashboard: {OMNI_BASE_URL}/dashboards/{identifier}
Workbook: {OMNI_BASE_URL}/w/{identifier}
The identifier comes from the document's identifier field in API responses (not id, which is null for workbooks).
Every dashboard build or update must include validation before and after creation. Broken tiles, bad field references, and misconfigured viz specs are silent failures — the dashboard renders but tiles show "Chart unavailable" or "No data" with no API-level error.
Before building any queries, confirm the underlying model is healthy:
omni models validate <modelId>
Check the response for errors (not just warnings). If is_warning is false on any issue, the field or join may be broken and queries referencing it will fail silently on the dashboard.
Run each planned query through omni query run before including it in a dashboard. This is the single most important validation step.
omni query run --body '{
"query": {
"modelId": "your-model-id",
"table": "order_items",
"fields": ["order_items.created_at[month]", "order_items.total_revenue"],
"filters": { "order_items.created_at": "last 90 days" },
"limit": 10,
"join_paths_from_topic_name": "order_items"
}
}'
What to check in the response:
error key, the query is broken. Fix before proceeding.summary.row_count > 0 — a query that returns zero rows will render as an empty tile. This may be correct (no data for the filter range) but is worth flagging.filterConfig as query-level filters here. This catches bad filter expressions (e.g., wrong field name, unsupported syntax) before they become dashboard-level problems.remaining_job_ids, poll with omni query wait --jobids <ids> until complete, then check the final result for errors.Do this for every query you plan to include as a tile. A dashboard with 5 tiles needs 5 validated queries.
Before assembling queryPresentations, check each tile's viz configuration against these rules. Mismatches cause "No chart available" or silent fallback to table rendering.
Required consistency checks:
| Rule | What to check |
|---|---|
prefersChart must be true for charts | If false or omitted, Omni renders a table regardless of other viz settings |
visType must match chart category | "omni-kpi" for KPI tiles, "basic" for all other chart types (line, bar, area, scatter, pie, table) |
visConfig.chartType must be valid | Must be one of: table, kpi, lineColor, barColor, areaColor, stackedBarColor, pie, scatter, heatmap, map |
config fields must match chart type | Cartesian charts (line, bar, area, scatter) require mark, series, tooltip, version, behaviors, configType: "cartesian", _dependentAxis |
_dependentAxis must match orientation | "y" for vertical charts (line, vertical bar, area, scatter), "x" for horizontal bar charts |
mark.type must match visConfig.chartType | lineColor → "line", barColor/stackedBarColor → "bar", areaColor → "area", scatter → "point" |
series[].yAxis or series[].xAxis | Must use yAxis: "y" for vertical charts, xAxis: "x" for horizontal bars |
KPI tiles need markdownConfig | config.markdownConfig array with at least one entry referencing a field from the query |
fields must be duplicated | The fields array must appear at both the queryPresentation level AND inside the query object |
| Every query must have a measure | Queries with only dimensions produce empty/broken tiles |
Tip: When unsure about a viz config, default to
"prefersChart": falsewith"config": {}to render as a table. Tables always work. Configure charts in the Omni UI afterward.
After creating or updating a dashboard, always read it back and verify the tiles work:
4a. Read back the document:
omni documents get <documentIdentifier>
Check that:
queryPresentations (count matches what you sent)queryPresentations entries have null or missing query objectsidentifier is present (you'll need it for the share link)4b. Execute the dashboard's queries to verify they run:
# Extract the queries powering the dashboard tiles
omni documents get-queries <documentIdentifier>
This returns the query objects for each tile. Run each one to confirm they execute without errors:
# For each query returned, execute it
omni query run --body '{
"query": <query-object-from-get-queries>,
"resultType": "csv"
}'
Using "resultType": "csv" makes it easy to spot-check that the data looks reasonable (correct columns, non-empty rows, expected value ranges).
What to check:
summary.row_count > 0 for tiles that should show dataremaining_job_ids (which might indicate query timeout issues)4c. If any query fails: The dashboard has a broken tile. Either update the document to fix the query (via PUT /api/v1/documents/{documentId}) or flag the issue to the user before sharing the link.
| Phase | Check | Tool |
|---|---|---|
| Pre-build | Model has no errors | omni models validate <modelId> |
| Pre-build | Each query executes successfully | omni query run per query |
| Pre-build | Each query returns rows | Check summary.row_count |
| Pre-build | Filters parse correctly | Include filters in omni query run |
| Pre-build | Viz specs are internally consistent | Manual check against rules above |
| Post-build | Document has all expected tiles | omni documents get and count queryPresentations |
| Post-build | All tile queries execute on the dashboard | omni documents get-queries + omni query run each |
| Post-build | Data looks correct | Spot-check CSV output for reasonableness |
omni-model-explorer to find topic + fieldsomni models validate <modelId> and check for errorsomni query run (using omni-query) before building the dashboard. Include the same filters you plan to use in filterConfig as query-level filters to confirm they parse correctly. This catches field name typos, missing join paths, bad filter expressions, and permission errors before they become broken tiles.visType/chartType/config/prefersChart against the consistency rules before assembling the payloadomni documents create with queryPresentations + filterConfig + filterOrder all in one callomni documents get, confirm all tiles are present, then run each tile's query via omni documents get-queries + omni query run to verify no broken tiles{OMNI_BASE_URL}/dashboards/{identifier} to the user (only after verification passes)omni-content-explorer or omni documents list to locate itomni documents get <documentId> to get the full document including queryPresentations, filterConfig, etc.queryPresentations array; update filterConfig/filterOrder as neededomni query run to confirm they work. Check modified viz specs against the consistency rules.PUT /api/v1/documents/{documentId} with the complete modified document and clearExistingDraft: trueomni documents get and confirm the expected tiles are present. Run omni documents get-queries + omni query run on modified tiles to verify they execute without error.{OMNI_BASE_URL}/dashboards/{identifier} to the user (only after verification passes)omni-model-builder for shared fields, or update-model for dashboard-specific fields# Start async download
omni dashboards download <dashboardId> --body '{ "format": "pdf" }'
# Poll job
omni dashboards download-status <dashboardId> <jobId>