| name | ds-tokens |
| description | Tokens are how Dashboard Studio passes user-facing values into searches and visualization options. Read when wiring inputs to data sources, when a panel is not updating, or when adding token filters (|h, |u, |s). Covers $token$ syntax, $token.subfield$ for timerange tokens, $row.<field>.value$ inside drilldowns, $click.value$, token filters, and the most common 'tokens don't resolve' debugging steps. Triggers on 'tokens', 'token filter', '$tok$', 'inputs not affecting search', 'why is my panel not updating'. |
| version | 1 |
| verified_against | Splunk Enterprise 10.2.1 |
| test_dashboards | ["splunk-knowledge-testing/ds_interactivity_core_dark (§1, §2)","splunk-knowledge-testing/ds_interactivity_core_light (§1, §2)"] |
ds-tokens — token reference
Verified live against ds_interactivity_core_dark / _light. Open the
dashboard, change any input at the top, and watch the §1 Token echo
panel string update in real time. That panel is just a splunk.singlevalue
bound to a makeresults | eval msg=... search that interpolates every
token — it's the simplest possible proof that token resolution works.
What a token is
A token is a named value that lives on the dashboard. Inputs write tokens
when the user picks a value. Searches and visualization options read
tokens via $token_name$ syntax. When a token's value changes, every
search and option that references it re-evaluates.
Tokens have no declaration block. They come into existence the moment
an input declares one in options.token, or a drilldown writes one via
drilldown.setToken. There is no tokens: { ... } top-level key.
The 4 places tokens come from
-
Inputs — inputs.<id>.options.token: "name" declares a writeable
token. The user controls it via the rendered widget. Every input flavour
(timerange, dropdown, multiselect, text, checkbox, radio, number) emits
exactly one token.
-
Drilldown setToken — eventHandlers[].type: "drilldown.setToken"
captures click context (row.<field>.value, click.value,
click.value2) into a named token. See ds-drilldowns.
-
URL parameters — When you navigate to a dashboard from another
dashboard via drilldown.linkToDashboard with tokens: { "form.host": "..." }, those form.<name> URL params populate matching input tokens
on load.
-
Defaults (special) — The defaults block at the dashboard root
uses tokens like $global_time.earliest$ to wire a global value into
every ds.search. See ds-defaults.
Reading a token
In SPL (ds.search.options.query)
| stats count by host | search status=$status$ index=$selected_index$
In a visualization option
"options": {
"majorValue": "$selected_count$"
}
In a DOS expression
Inside DOS strings (the > primary | ... syntax), bare $ characters get
interpreted as token boundaries. Escape with \$ when you need a literal
dollar sign:
"options": {
"majorValue": "> primary | seriesByName(\"count\") | lastPoint() | prefix(\"\\$\")"
}
Subfield accessors (timerange only)
input.timerange is the one input type whose token is not a single
string. It exposes two read-only sub-tokens:
| Token | Value |
|---|
$global_time.earliest$ | The relative or absolute earliest expression (e.g. -24h@h) |
$global_time.latest$ | The latest expression (e.g. now) |
The bare token $global_time$ is not useful — read either subfield
instead. This is wired into searches via the defaults block (see
ds-defaults).
Drilldown context tokens
Inside eventHandlers only, additional contextual tokens exist:
| Token | Meaning |
|---|
$row.<field>.value$ | The value of <field> in the clicked row (table/event panels) |
$click.value$ | The category / x-axis value the user clicked (charts) |
$click.value2$ | The series / second axis value (where applicable, e.g. y in scatter) |
$click.name$ | Field name (where applicable) |
$click.name2$ | Second field name |
These do not persist after the click — they're scoped to the event
handler. To make them persist, use drilldown.setToken to copy them into
a named token. See ds-drilldowns.
Token filters
Append |<filter> to a token name to transform the value at substitution time:
| Syntax | Filter | Use case |
|---|
| `$token | s$` | Quote each value (string-quote filter) |
| `$token | h$` | HTML escape |
| `$token | u$` | URL encode |
Default (no filter) emits the raw value: a string for single-value
tokens, a comma-separated joined list for multiselect arrays (no
quoting around individual values).
Example from the live test bench:
"url": "https://example.com/investigate?host=$row.host.value|u$&action=$row.action.value|u$"
|u is mandatory inside customUrl — without it a row value containing
& or ? will silently corrupt the URL.
Multiselect tokens
input.multiselect produces a token whose runtime expansion depends on
context. The token value is an array.
- Default interpolation joins the array with commas, no quoting
around values:
["200", "404"] → 200,404. This is unsafe for SPL
fields whose values contain spaces or hyphens — web-01,web-02
parses literally as web then chokes on the dash.
|s filter quotes each value: ["web-01", "web-02"] →
"web-01","web-02". This is the documented form for SPL IN()
clauses.
The canonical multiselect search pattern (verified in the Splunk doc
"Network Traffic" example):
| where Username IN ($username|s$) OR ("$username$" = "*")
The IN(...) clause uses |s so the array becomes a properly-quoted
list; the trailing OR ("$tok$" = "*") handles the "All" sentinel
case where the user picks *.
If you forget |s, multiselect with N>1 always produces invalid SPL
the moment any value contains a space, hyphen, colon, or quote. The
test bench's §1 Token echo demonstrates the right form — it renders
the SPL status IN ("200","404") (quoted) instead of status IN (200, 404) (unquoted).
"My panel is not updating" — debug ladder
In order from most-likely to least-likely:
- Token name typo.
$selected_index$ vs $select_index$. Cmd-F the
token name across the JSON; it must appear exactly the same wherever
it's read or written.
- Input never wrote the token. Verify
inputs.<id>.options.token
matches what searches read. The widget can render fine and still write
to a different token name.
- Input is not on the layout. An input declared but not listed in
layout.globalInputs (or in a tabs.layoutDefinitions[].inputs) does
not render → can't be changed → token stays at defaultValue.
- Search does not actually reference the token. Maybe the search has
a hardcoded value that overrides where you think the token goes.
enableSmartSources is off. When using DOS-typed tokens for input
items (the dynamic dropdown pattern), the parent ds.search must
have options.enableSmartSources: true to re-fire when the upstream
input changes. Without it, the dropdown items render once and freeze.
- Scope problem with timerange. Always use
$token.earliest$ /
$token.latest$, never bare $token$, for input.timerange.
The §1 Token echo panel is purpose-built for step 4 — if the value shows
up there, the token is resolving. If it doesn't, the wiring is broken
upstream.
Reserved namespaces
form.<name> — populated from URL query params on dashboard load. Used
by drilldown.linkToDashboard to pass values across dashboards.
row.<field>.value / row.<field>.name — drilldown event scope only.
click.* — drilldown event scope only.
<token>.earliest / <token>.latest — automatic for input.timerange.
Avoid using these prefixes for your own input tokens.
Common gotchas
- Quotes in DOS context. Inside DOS strings, fields are referenced
with escaped double quotes:
seriesByName(\"count\"). The token
expansion happens before DOS parses, so a token containing a " will
break DOS — sanitize with allow-listed values.
- Token filters apply at substitution time. They run on the value,
not on the search. So
$search_text|s$ keeps the value raw — it does
not escape SPL metacharacters. There is no SPL-injection-safe filter;
use input allow-lists instead of free text where possible.
defaultValue is a string for most inputs, an array for multiselect,
and a comma-separated string for timerange. Schema validation rejects
the object form for timerange (see ds-inputs).
$row.<field>.value$ is case-sensitive. Field name must match what
Splunk produces. If your SPL renames a field, the renamed name is what
the drilldown sees.
- You cannot read a token before any input has written to it. On
first load, before defaults populate, inputs may briefly show empty
values. Set
defaultValue on every input you depend on.
Quick recipes
Echo every token to a single panel
Best debug pattern. The §1 Token echo panel in the live test bench:
"ds_token_demo": {
"type": "ds.search",
"options": {
"query": "| makeresults | eval msg=\"index=$selected_index$ status=$status$ filter=$search_text$ host=$host_filter$ time=$global_time.earliest$..$global_time.latest$\" | table msg"
}
},
"viz_token_echo": {
"type": "splunk.singlevalue",
"dataSources": {"primary": "ds_token_demo"},
"options": {
"majorValue": "> primary | seriesByName(\"msg\") | lastPoint()"
}
}
Use a multiselect inside a search
| stats count by host | where status IN ($status|s$) OR ("$status$" = "*")
status is declared as input.multiselect with defaultValue: ["200","201"]. The |s filter quotes each array element so IN()
parses cleanly even when values contain hyphens / spaces; the
OR ("$status$" = "*") clause handles the "All" sentinel.
Pass tokens to another dashboard
{
"type": "drilldown.linkToDashboard",
"options": {
"app": "search",
"dashboard": "host_detail_view",
"tokens": {
"form.host": "$row.host.value$",
"form.earliest": "$global_time.earliest$"
}
}
}
The receiving dashboard must declare an input with token: "host" for
form.host to land somewhere readable.
See also
ds-inputs — for declaring what writes tokens.
ds-defaults — for wiring tokens (specifically global_time) into every
ds.search.
ds-drilldowns — for the click-time tokens (row.*, click.*) and
setToken action.
ds-visibility — for using token state to show/hide panels.
reference/ds-syntax — the legacy monolith. Same content, less focus.