DASH:Gen

Docs

Handlebars chart templates — how to edit the library

How the template library works, how to add or change a template, and the rules a template must obey.

There are two ways in, and the difference is who owns the result:

Libraries → Chart templates (§2)Repository files (§3–§9)
Who can add oneany signed-in userwhoever can merge to the repo
Who can edit itits author, and nobody elsewhoever can merge
Who can use iteveryoneeveryone
Shown asby <author>built-in
Survives a redeployyes, untouched — the seeder never writes an authored rowyes, re-seeded from the file

Use the tab for your own work. Use the repository for the templates the platform should ship with out of the box.

The technical ground truth for both — the exact params Apache Superset accepts, the render context, the helper list, the live-import proof against Apache Superset 6.1.0 — is superset-handlebars-findings.


1. What a template is

A template is a rendering override for Apache Superset and the HTML dashboard, not a chart type. Every chart keeps its standard ChartType (TABLE, KPI, BAR, …); pinning a template makes the Apache Superset exporter emit a chart of viz_type: handlebars whose HTML comes from the template's body instead of the type's own builder, and makes an HTML dashboard draw that chart with the template in the browser, from the rows embedded in the file (HTML dashboard §4, Template charts). One template serves both: the same body, the same { data: rows }, the same two helpers. Switching back to "Standard" simply clears the reference.

On an HTML dashboard a template is pinned from the chart's title or the Charts panel under the result, and becomes a new, free version of the dashboard. There, a template is drawn only on a chart of its own baseType; anywhere else the chart keeps its standard look.

Each template declares which standard type it belongs to (baseType) — that is how the preview's picker groups the library ("I chose Table → show me the Table templates") — and which result shape it iterates (queryMode):

queryModeThe rows the template receives
AGGREGATEOne row per group: the dimension column(s) plus one key per metric (sum__revenue, count, …)
RAWOne row per record, keyed by the chart's projected column names

A design may name one per chart type (visuals.chart, HTML dashboard §5a): a chart nobody pinned then takes it, when it fits — on HTML always, on Apache Superset where the Handlebars tab is switched on, since that is the deployment saying its Superset renders them. A pin by a person or the agent always wins, and a design can name only a template its author may use.

1a. The Handlebars tab in an Apache Superset dashboard

Every dashboard generated for Apache Superset ends with one extra tab, Handlebars, holding copies of up to seven of its charts, each drawn by one of the built-in templates: a row of cards, a trend card, a ranked list, category tiles, two measures by period, a grouped table and a raw-row table. The charts on the other tabs are unchanged — the standard Apache Superset visualisations they have always been. The point is to let you judge the look on your own data, after the import, instead of before it.

The charts on it draw nothing until your Apache Superset allows two things — see What your Apache Superset has to allow below, which the tab also names in its own closing note, so you do not have to come back here to find out why it looks empty. A deployment that does not want the offer at all switches it off with SUPERSET_HANDLEBARS_TAB=false.

It opens with a block of text, Why these charts: one sentence on what the dashboard is for and one per chart on what that chart answers — the same explanation the HTML dashboard shows on its Notes tab. It closes with a note saying what the tab is and naming the two Apache Superset settings below, so a reader who imported the bundle without them can see why the charts are empty without leaving the dashboard.

What you can do with it once the bundle is imported:

You can also pin a template to a single chart instead, from the dashboard preview after a run — that needs the same two Apache Superset settings, and nothing else.

What your Apache Superset has to allow

Measured on a stock Apache Superset 6.1.0, a Handlebars chart needs two settings changed before it shows anything. Neither can be set from here — the bundle is a file you import into your own Superset — so check them before judging the tab:

Symptom in the chartSetting
"Evaluating a string as JavaScript violates the following Content Security Policy directive…"Apache Superset's default policy forbids unsafe-eval, and Handlebars compiles a template by evaluating a string. Add 'unsafe-eval' to script-src in TALISMAN_CONFIG, or set TALISMAN_ENABLED=False.
Unstyled rows, with the template's CSS printed as text underneathThe default HTML sanitiser strips every attribute — class and style — and drops the <style> block. Extend HTML_SANITIZATION_SCHEMA_EXTENSIONS to allow them, for example {"tagNames": ["style"], "attributes": {"*": ["className", "class", "style"]}}.

Both loosen a security setting your Apache Superset chose on purpose. Weigh that: if you would rather not, delete the tab — or use the HTML dashboard, which draws the very same templates with no such constraint.

One more CSS quirk, once the sanitiser lets your stylesheet through: Apache Superset drops a whole rule that contains a property it does not recognise, not just that property. Keep an unusual declaration in a rule of its own, or a neighbour like display: flex goes down with it.

Two further limits, whatever the settings. A Handlebars chart is rendered HTML, so it has no tooltips, no zoom, and it does not emit cross-filters the way a native visualisation does — "two measures by period" is a table of periods, not a two-axis chart. And the plugin has no time-grain control, so a chart grouped by a raw timestamp column shows one row per timestamp; a period column (a month, a date) is what reads.


2. Adding templates from the UI

Open Libraries, then Chart templates (labelled Apache Superset and HTML dashboards). It lists the whole shared library — yours first, then everyone else's — with a search box (name, description, "when to use", keywords, author) and a chart-type filter.

Create one: New template opens an editor with the form on the left and a live preview on the right, rendered on sample data through the same pipeline the real dashboard preview uses. What you see is what someone picking your template will see, including the empty-rows case (there is a toggle for it — that is where most templates break).

Fill in:

Edit or delete — only on your own rows. Deleting is safe: any chart still pinned to the template falls back to its standard chart type on the next export.

Copy — available on every template, including the built-ins. This is how you start from one that nearly works: it pre-fills the editor with the bodies and fields, clears the identity, and saves as a new template of yours. It is also the answer to "I want to tweak a built-in", which the API refuses outright (409 template-not-editable).

From a chart: the chart picker shows by &lt;author&gt; under each template, and — on your own — an Edit this template shortcut that jumps straight into this editor.

Private or Public — every template you create carries a visibility flag, and it starts Private: creating is not publishing. A private template appears in your library only, and only you can pin it to a chart. Switch it to Public and everyone sees and uses it — but it stays yours to change or delete, and a colleague still gets 404 on a PUT.

Publishing is safe in one direction only: once somebody's dashboard pins your template, taking it private again does not break their export. Visibility decides who may choose a template, never what is delivered — the same rule that lets a disabled Power BI theme keep exporting.

There is a limit of 10 templates per user, per library: ten Handlebars charts, ten Apache Superset themes, ten Power BI Dashboard-Templates, ten Power BI Visual-Templates. The library header shows your count ("3 of 10 yours") and the New button is disabled at ten. The eleventh is refused with the library and the number in the message; delete one you no longer use.

Everything here is free: authoring a template runs no AI and consumes no coins.


3. The repository path (built-in templates)

The seven templates that ship with the platform live as files and are managed by the seeder. Nothing in the UI can change them.

4. Where the built-in templates live

generator/src/main/resources/chart-templates/<name>.json

The file name (minus .json) is the template's stable identity. ChartTemplateSeeder upserts every file into the chart_template table on each boot of the Superset engine:

Deleting a file does not delete the row; the template just stops being updated. Remove the row by hand if it should disappear entirely.

The seeder also never touches a row that has an author — a user's template can never be overwritten by a redeploy, and the two paths cannot collide (authored names are generated with a u- prefix).

5. The file format

{
  "displayName": "Clean table",
  "description": "Grouped rows as a plain table; headers come from the query, numbers are right-aligned.",
  "baseType": "TABLE",
  "queryMode": "AGGREGATE",
  "handlebars": "<table class=\"hb-table\">\n  ...\n</table>\n",
  "css": ".hb-table { width: 100%; }\n"
}
FieldRequiredRules
displayNameyes≤ 96 chars; what the picker shows
descriptionno≤ 255 chars; one line, what it looks like
whenToUseyes for shipped files≤ 1000 chars; when to pick it and what it expects
keywordsyes for shipped files≤ 255 chars; the words people will search for
baseTypeyesone of KPI, KPI_TREND, LINE, BAR, PIE, TABLE, SCATTER, FUNNEL, AREA. Not PIVOT: a matrix renders its own crosstab, so a template would replace it rather than restyle it, and both the API and the preview refuse the pin.
queryModeyesAGGREGATE or RAW
handlebarsyesthe template body — see §6
cssnoplain CSS, self-contained

6. Rules every template body must obey

The seeder (and the write API, using the same shared rules) rejects (skips, with an error log naming the file) any body containing:

Beyond the hard rejections:

7. The two guardrails (run in CI)

Adding a broken file fails the build, not a user's preview:

  1. generator — ChartTemplateAssetsTest: JSON parses, required fields present, enums valid, no forbidden content, loop rooted at data. Run: ./mvnw -pl generator -am test -Dtest=ChartTemplateAssetsTest -Dsurefire.failIfNoSpecifiedTests=false -DskipFrontend=true
  2. studio frontend — handlebarsLibrary.test: actually compiles and renders every body against sample data in the same runtime the preview uses, including the empty-rows case. Run: cd frontend && npm test -- handlebarsLibrary

8. Step by step: adding a built-in template

  1. Copy the closest existing file in chart-templates — the seven shipped ones cover cards, tables (both query modes), a ranked list and category tiles.
  2. Edit displayName, description, baseType, queryMode, the body and the CSS. Keep class names prefixed (hb-…) so they cannot collide with Apache Superset's own styles.
  3. Run both guardrail tests (§5).
  4. Restart the generator engine (or docker compose up --build generator) — the seeder logs Chart template '<name>' seeded.
  5. Check it in Studio: generate an Apache Superset dashboard, open the preview, click a chart of the template's baseType — the template appears in the Library group with a live mini-preview on the chart's own data.
  6. For a change that touches query params rather than just markup, do the real check once: import an exported bundle into a local Apache Superset (docker run -p 8088:8088 -e SUPERSET_SECRET_KEY=… apache/superset, then superset db upgrade / fab create-admin / superset init inside the container) and confirm the chart renders. Record surprises in superset-handlebars-findings.

9. Editing and removing a built-in