analytics-dashboard-json
Use this skill when editing CRM Analytics dashboard JSON directly to implement advanced bindings, custom SAQL/SOQL step queries, layout changes, step parameters, or cross-widget interactions. Trigger keywords: dashboard JSON, SAQL step, binding syntax, mustache binding, dashboard REST API, widget layout, step limit, datasetId, datasetVersionId. NOT for standard dashboard builder UI configuration, chart type selection, or dataset design.
Best use case
analytics-dashboard-json is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Use this skill when editing CRM Analytics dashboard JSON directly to implement advanced bindings, custom SAQL/SOQL step queries, layout changes, step parameters, or cross-widget interactions. Trigger keywords: dashboard JSON, SAQL step, binding syntax, mustache binding, dashboard REST API, widget layout, step limit, datasetId, datasetVersionId. NOT for standard dashboard builder UI configuration, chart type selection, or dataset design.
Teams using analytics-dashboard-json should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/analytics-dashboard-json/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How analytics-dashboard-json Compares
| Feature / Agent | analytics-dashboard-json | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Use this skill when editing CRM Analytics dashboard JSON directly to implement advanced bindings, custom SAQL/SOQL step queries, layout changes, step parameters, or cross-widget interactions. Trigger keywords: dashboard JSON, SAQL step, binding syntax, mustache binding, dashboard REST API, widget layout, step limit, datasetId, datasetVersionId. NOT for standard dashboard builder UI configuration, chart type selection, or dataset design.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
Related Guides
SKILL.md Source
# CRM Analytics Dashboard JSON
This skill activates when a practitioner needs to edit a CRM Analytics dashboard's JSON body directly — covering SAQL/SOQL step construction, binding syntax, layout manipulation, step parameters, and the REST API workflow for GET/PUT operations. It does not cover the standard dashboard builder UI or dataset design.
---
## Before Starting
Gather this context before working on anything in this domain:
- Retrieve the current dashboard JSON via `GET /services/data/vXX.X/wave/dashboards/{dashboardId}` and inspect the `state`, `steps`, and `widgets` top-level keys before making any edits.
- Identify dataset references: practitioners commonly assume dataset name is sufficient — it is not. You need `datasetId` and `datasetVersionId` from `GET /services/data/vXX.X/wave/datasets` to construct portable SAQL steps.
- Know the row limit in play: SAQL steps default to 2,000 rows. If a downstream binding or widget depends on a full population, the default will silently truncate results without any error.
---
## Core Concepts
### Dashboard JSON Structure
Every CRM Analytics dashboard body is a JSON document with three top-level sections:
**`steps`** — A map of named query definitions. Each step runs a SAQL or SOQL query against a dataset and holds its results in memory for widget binding. Steps declare their `query` (SAQL string or SOQL string), `type` (`saql` or `soql`), `datasets` array, and optional `limit` property. The `limit` defaults to 2,000 rows and can be raised to a maximum of 10,000 per step.
**`widgets`** — A map of named visual components. Each widget specifies its `type` (chart, table, filter, text, etc.), its pixel-based `layout` (top, left, width, height in pixels), and its `parameters` object which maps widget inputs (e.g., `measures`, `dimensions`, `filters`) to step results via binding expressions.
**`state`** — A map of global selections, active filters, and interaction state. State carries the current user selection from one widget so that other widgets can read it via bindings. State is modified at runtime by user interaction and can be pre-seeded with default values.
### Binding Syntax
Bindings are mustache-delimited expressions embedded in widget `parameters` values. They read from either step results or selection state at render time.
The canonical cell-level binding form is:
```
{{cell(stepName.selection, 0, "fieldName")}}
```
- `stepName` — the key in the `steps` map
- `.selection` — reads the user's current selection in that step
- `0` — the row index (zero-based)
- `"fieldName"` — the field to extract
An empty binding (when the referenced cell has no value, such as when no selection has been made) returns an empty string, not an error or null. This causes silent suppression: a filter widget whose bound value is empty will produce no filter clause, which may return unintended full-population results rather than an empty result set.
Array bindings for multi-select filters use:
```
{{#arrayToObject}}...{{/arrayToObject}}
```
or the `columnMap` binding form for mapping multiple fields simultaneously.
### Dataset References in SAQL Steps
SAQL steps reference datasets inside the `datasets` array of the step definition. The `name` field in the datasets array is the display alias used in the SAQL query body. The **`id`** and **`label`** fields, however, must use the `datasetId` and `datasetVersionId` obtained from the dataset API — not the human-readable dataset name used in the UI.
Using only the dataset name instead of the `id`/`version` pair causes the step to resolve by name lookup at render time. This works in a single org but breaks silently when the dashboard is migrated, cloned into a sandbox, or deployed via Metadata API — because dataset IDs differ across orgs and the name may not match. The SAQL step returns no results rather than an error.
### REST API Versioning
CRM Analytics dashboards are versioned via the History API. Every `PUT /services/data/vXX.X/wave/dashboards/{dashboardId}` call that modifies the dashboard body automatically creates a new history snapshot. Previous versions are accessible at:
```
GET /services/data/vXX.X/wave/dashboards/{dashboardId}/histories
```
This means every PUT is non-destructive — the prior version is always recoverable. However, there is no built-in diff view; practitioners must compare full JSON bodies manually or via script.
---
## Common Patterns
### Cross-Widget Binding via Selection State
**When to use:** A filter widget (list selector, range slider) in one part of the dashboard should drive the data shown in charts or tables elsewhere, across datasets.
**How it works:**
1. Define a step for the filter source dataset. Set `selectionFields` to the field you want to expose (e.g., `["Account.Industry"]`).
2. In the target chart step's SAQL query, add a `where` clause that reads the binding:
```
| filter 'Industry' in ["{{#each cell(filterStep.selection, 0, "Industry")}}{{this}}{{/each}}"]
```
3. In the target widget's `parameters`, bind `filters` to the selection state of the source step.
4. Test with no selection (binding returns empty string) to confirm the default behavior is acceptable.
**Why not hardcode values:** Hardcoded filter values cannot be driven by user interaction. Faceting (automatic cross-filtering) only works within the same dataset; cross-dataset filtering requires explicit bindings in JSON.
### Raising SAQL Step Row Limits
**When to use:** A step backing a table or export widget needs to surface more than 2,000 records, or a step used as a binding source needs a full population to avoid silent truncation.
**How it works:**
Add the `limit` property to the step definition in the `steps` map:
```json
"myStep": {
"type": "saql",
"query": "q = load \"datasetId/datasetVersionId\"; q = limit q 10000; q;",
"datasets": [...],
"limit": 10000
}
```
Both the `limit` property on the step object and the `limit` clause in the SAQL query string must be set. The platform enforces a hard maximum of 10,000 rows per step regardless of the value set.
**Why not rely on default:** The 2,000-row default is silently applied. There is no UI warning when results are truncated — the widget renders with partial data and no error message.
---
## Decision Guidance
| Situation | Recommended Approach | Reason |
|---|---|---|
| Filtering across two datasets | Explicit step binding using mustache cell() expression | Faceting only works within a single dataset |
| Dashboard migration across orgs | Use datasetId + datasetVersionId in SAQL step datasets array | Name-based resolution fails silently in target org |
| Need more than 2,000 rows in a step | Set `limit: 10000` on step AND add `limit 10000` in SAQL query | Both must be set; SAQL alone or property alone is insufficient |
| Recovering a prior dashboard version | GET /wave/dashboards/{id}/histories and re-PUT the desired body | PUT auto-creates history; old versions are always accessible |
| Debugging an empty binding result | Check if the source selection step has an active user selection | Empty binding = empty string, not error; no selection = no value |
---
## Recommended Workflow
Step-by-step instructions for an AI agent or practitioner editing dashboard JSON:
1. **Retrieve the current dashboard body** via `GET /services/data/vXX.X/wave/dashboards/{dashboardId}`. Save the full JSON response as a working copy before making any changes — the History API preserves versions after PUT, but having a local baseline is essential for diffing.
2. **Resolve dataset identifiers** via `GET /services/data/vXX.X/wave/datasets` before constructing or modifying any SAQL step. Record the `id` (datasetId) and the `currentVersionId` (datasetVersionId) for every dataset the dashboard queries. Do not use display names.
3. **Edit steps first, then widgets, then state.** Steps are referenced by name from widgets; editing in this order avoids forward-reference confusion. When adding a new step, assign it a camelCase key in the `steps` map and verify it is unique.
4. **Construct bindings using the exact mustache syntax** `{{cell(stepName.selection, 0, "fieldName")}}`. Test the empty-binding case: when no selection exists, the binding returns an empty string. Decide explicitly whether an empty binding should produce no filter (full population) or a zero-result filter, and handle accordingly in the SAQL where clause.
5. **Set explicit row limits** on any step where truncation matters. Add both `"limit": 10000` to the step object and `limit 10000` to the SAQL query string. The platform maximum is 10,000 rows per step.
6. **PUT the modified body** via `PUT /services/data/vXX.X/wave/dashboards/{dashboardId}` with `Content-Type: application/json`. The platform automatically creates a history snapshot. Verify the response `id` and `lastModifiedDate` match expectations.
7. **Validate in the dashboard UI** by opening the dashboard, triggering every filter interaction, and confirming that cross-widget bindings resolve correctly and no widgets show empty or unexpected results.
---
## Review Checklist
Run through these before marking work in this area complete:
- [ ] All SAQL steps reference datasets by `datasetId` and `datasetVersionId`, not by display name
- [ ] All mustache binding expressions use the exact form `{{cell(stepName.selection, 0, "fieldName")}}`
- [ ] Empty-binding behavior (no active selection) is explicitly handled for all filter-driven steps
- [ ] Any step returning more than 2,000 rows has `"limit"` set on the step object and in the SAQL query string
- [ ] The dashboard JSON was retrieved via REST API before editing and the full body was PUT back (not a partial update)
- [ ] History API was confirmed to have a new snapshot after the PUT (verify via GET /histories)
- [ ] Cross-widget filter interactions were tested end-to-end in the dashboard UI
---
## Salesforce-Specific Gotchas
Non-obvious platform behaviors that cause real production problems:
1. **Dataset name resolution fails silently across orgs** — Using the dataset display name instead of `datasetId`/`datasetVersionId` in a SAQL step's datasets array works in the authoring org but resolves by name lookup at render time. When the dashboard is deployed to another org or sandbox, the name may not exist or may point to a different dataset. The step returns no results — no error is thrown, no warning is displayed.
2. **Empty bindings return empty string, not null or error** — When a binding's source step has no active user selection, `{{cell(stepName.selection, 0, "fieldName")}}` returns an empty string. A SAQL `where` clause built around this value may behave differently than intended: some constructs silently drop the filter (returning the full population) while others produce a literal empty-string comparison that returns zero rows.
3. **SAQL step row limit defaults to 2,000 with no UI warning** — Truncation is silent. A step returning exactly 2,000 rows is almost always truncated. Widgets render partial data with no indicator. The maximum is 10,000, and both the step-level `limit` property and the SAQL `limit` clause must be set explicitly.
---
## Output Artifacts
| Artifact | Description |
|---|---|
| Modified dashboard JSON body | Full dashboard JSON ready for PUT to /wave/dashboards/{id} |
| SAQL step definitions | Step objects with datasetId/datasetVersionId references, explicit limits, and query strings |
| Binding expressions | Mustache cell() expressions for cross-widget filter wiring |
---
## Related Skills
- admin/analytics-dashboard-design — Design-level decisions (chart type selection, faceting, layout structure) that precede JSON editingRelated Skills
einstein-analytics-data-model
Use this skill when working with CRM Analytics (Einstein Analytics) extended metadata (XMD) — the multi-layer metadata system that controls field display labels, aliases, number formatting, date formatting, measure/dimension classification, and color palettes on CRM Analytics datasets. Trigger keywords: XMD API, dataset field formatting CRM Analytics, wave dataset labels, main XMD update, dataset versioning Analytics. NOT for dataflow development, recipe node configuration, dataset ingestion setup, standard dashboard design, or SAQL query construction — those are covered by analytics-dataflow-development and analytics-recipe-design.
crm-analytics-security-predicates
Row-level security in CRM Analytics datasets via security predicates — SAQL filter expressions stored on the dataset that apply at query time per running user. Covers the syntax (`'DatasetColumn' operator value`), the `$User.*` context variables, multi-level predicates (role hierarchy + team + region), the performance cost of complex predicates, and the testing discipline (admins bypass predicates by default). NOT for Salesforce Core sharing rules (different runtime), NOT for App / Dashboard / Lens-level access (that's CRM Analytics App sharing, not predicates), NOT for field-level masking inside a dataset (use Encryption + dataset transformations).
community-analytics-data
Use when analyzing Experience Cloud site analytics including login metrics, member engagement, page view tracking, and content performance. Triggers: Experience Cloud site analytics, community member engagement data, portal login tracking, page view reports community, GA4 Experience Cloud integration. NOT for CRM Analytics or Tableau CRM. NOT for internal Salesforce reporting on standard CRM objects.
commerce-analytics-data
Use when analyzing B2C Commerce storefront metrics (conversion funnel, cart abandonment, product performance, revenue trends) via the Business Manager Reports and Dashboards app, or when deriving B2B Commerce analytics via SOQL on core platform objects or the CRM Analytics B2B Commerce template. NOT for CRM Analytics platform configuration, Einstein Analytics, Experience Cloud analytics, or general Salesforce report builder usage.
analytics-external-data
Use when bringing non-Salesforce data into CRM Analytics via the External Data API, Data Connectors, or Live Datasets. Trigger keywords: InsightsExternalData, External Data API, live dataset, remote connection, Snowflake connector, BigQuery connector, Tableau Bridge, external CSV upload, analytics connector. NOT for standard data import into Salesforce objects. NOT for Salesforce object sync via dataflow local connectors. NOT for standard ETL into Sales or Service Cloud.
analytics-dataset-optimization
Use this skill when tuning CRM Analytics dataset performance through field selection, date granularity choices, dataset splitting strategy, and run-budget optimization. Trigger keywords: dataset too many fields, SAQL timeseries slow, epoch vs date storage, dataset field count limit, dataset partition, split dataset by year, CRM Analytics performance tuning. NOT for SOQL optimization, Salesforce report tuning, Data Cloud segmentation performance, or choosing between analytics tools.
analytics-data-preparation
Use this skill when customizing CRM Analytics dataset field metadata via the XMD (Extended Metadata) REST API or augmenting CRM Analytics recipes with external non-Salesforce data: labeling fields, setting display formats, reclassifying measures vs dimensions, and applying org-level field annotations via main XMD PATCH. Trigger keywords: XMD field labels, CRM Analytics main XMD update, dataset field formatting wave, analytics external data augmentation, WaveXmd REST API. NOT for recipe node transformation logic, dataflow SOQL extraction, dataset row count management, or standard CRM data quality — those are covered by analytics-recipe-design, analytics-dataflow-development, and analytics-dataset-management.
embedded-analytics-architecture
Use this skill to architect CRM Analytics dashboard embedding in Lightning pages, Experience Cloud, or Visualforce — covering dashboard context strategy, filter/state management, cross-dashboard context propagation, performance optimization, and LWC vs Aura component selection. Trigger keywords: embed CRM Analytics dashboard, embedded analytics Lightning page, analytics dashboard filter, wave dashboard LWC, analytics state attribute. NOT for CRM Analytics dashboard design/building (use analytics skills), standard Lightning report embedding, or Data Cloud analytics.
data-cloud-vs-analytics-decision
Use when choosing or explaining how Salesforce Data Cloud (Data 360) and CRM Analytics (Tableau CRM / Einstein Analytics) fit together versus overlap — unified data platform vs analytics consumption, Direct Data on Data Model Objects (DMOs), identity and activation vs dashboards and recipes. Triggers: 'Data Cloud vs CRM Analytics', 'when to use Data Cloud versus Einstein Analytics', 'should we buy Data Cloud or CRM Analytics', 'CRM Analytics on Data Cloud DMOs', 'direct data connector Data Cloud', 'analytics on unified profile architecture'. NOT for step-by-step implementation of Data Cloud data streams, identity resolution rules, CRM Analytics recipes, dataflows, or dashboard build — use architect/data-cloud-architecture, admin/data-cloud-identity-resolution, or admin/einstein-analytics-basics for that.
crm-analytics-vs-tableau-decision
Use when deciding between CRM Analytics (formerly Einstein Analytics / Tableau CRM) and Tableau Desktop, Tableau Server, or Tableau Cloud for a Salesforce-centric analytics requirement. Triggers: 'CRM Analytics vs Tableau', 'which BI tool for Salesforce', 'Tableau for Salesforce data', 'Einstein Analytics vs Tableau', 'analytics platform decision', 'licensing comparison CRM Analytics Tableau', 'Tableau Next', 'Tableau+ for Salesforce'. NOT for implementation guidance on configuring CRM Analytics datasets, recipes, or Tableau workbooks — use admin/einstein-analytics-basics for that.
analytics-security-architecture
Use this skill when designing or auditing CRM Analytics (formerly Einstein Analytics / Tableau CRM) data access controls: row-level security predicates, dataset-level visibility, app-level sharing roles, sharing inheritance configuration, and cross-dataset security embedding strategies. Trigger keywords: CRM Analytics security, row-level security predicate, Analytics dataset access, sharing inheritance, security predicate design, cross-dataset security, analytics entitlement dataset. NOT for standard Salesforce sharing model design (OWD, role hierarchy, sharing rules on standard objects), standard reports/dashboards folder sharing, or Tableau Server/Tableau Cloud security.
analytics-embedded-components
Use this skill when embedding a CRM Analytics dashboard into a Lightning App Builder page, Experience Cloud page, or when embedding a custom LWC inside an Analytics dashboard. Trigger keywords: wave-wave-dashboard-lwc, wave-community-dashboard, analytics__Dashboard target, embed dashboard, record-id context filtering, dashboard state JSON, analytics component. NOT for standard LWC development unrelated to Analytics dashboards, NOT for designing dashboard content or datasets, NOT for CRM Analytics app creation or dataset management.