The @lightdash/query-sdk in this workspace declares its capabilities in a
registry: node_modules/@lightdash/query-sdk/dist/features.js (SDK_FEATURES,
each { key, label, description, wiring? }). That registry is the source of
truth — never invent capabilities from export lists or type definitions. This
skill adds what the registry can't: how each feature maps to the Lightdash
host UI and the exact wiring recipe.
Vocabulary: users speak host-UI, the SDK speaks keys
Users describe features by what they see in the Lightdash editor. Translate:
| User says / host UI | Registry key | Wiring |
|---|---|---|
| "Inspect data" (Queries panel button) | lineage |
required — see below |
| "select an element", editor element picker | inspect |
none (automatic) |
| thumbnails / screenshots / scheduled deliveries | screenshot |
required — see below |
| drill down, click into a chart | drill-down |
app code opt-in |
| drill into a viz data point (reusable visualization) | viz-drill-down |
app code opt-in |
| "share this view", URL that restores state | url-state |
app code opt-in |
| Google Sheets export | gsheet-export |
app code opt-in |
| "delivery has all tabs", full data in scheduled deliveries | delivery-render |
app code opt-in |
| external API data | external-fetch |
app code opt-in |
| runs inside a dashboard tile | viz-context |
required — see below |
| reusable chart/table with pivoted results | viz-pivoted-results |
required — see below |
| "view underlying data", raw rows behind a point (viz only) | viz-underlying-data |
app code opt-in |
| light/dark mode, "matches my Lightdash theme" | follow-host-theme |
CSS tokens — see below |
Automatic (zero wiring — active on any current-SDK bundle)
createClient() mounts these itself: the capability manifest, the editor
element inspector (inspect), and the lineage runtime. Core querying
(query, saved-chart via useLightdash/savedChart) is just normal SDK
usage. If the app calls createClient(), these need nothing from you.
Wiring recipes
lineage — the host's "Inspect data" button
The lineage runtime only announces itself once the DOM contains
data-ld-query stamps; until then the host's Inspect data button stays
disabled. Spread the lineage props returned by useLightdash onto the root
element of every query-bound block:
const { data, lineage } = useLightdash(myQuery);
return <div {...lineage}>{/* chart rendered from data */}</div>;
Stamp every visualization, not just one — each stamp maps that block to its query in the host's Queries panel.
screenshot — thumbnails and scheduled deliveries
Provided by the template file src/screenshotHandler.js; main.jsx must
import and call it (initScreenshotHandler()). Apps migrated from older
templates may be missing the file or the call — copy the file from the
template and add the call rather than reimplementing.
viz-context — apps embedded as dashboard tiles
Wrap the app in VizContextProvider (see the template main.jsx) and read
the host-supplied query context with useVizContext. Only relevant for
visualization-style apps meant to run inside dashboards.
follow-host-theme — light/dark mode
The SDK puts the dark class on <html> as the app boots and again
whenever the viewer toggles their Lightdash theme, so an app that styles
everything through the theme tokens follows along with no code at all. What an
older app usually needs is the opposite of wiring — removing what pins it to
one mode:
- drop any
className="dark …"on the app shell and anydocument.documentElement.classList.add('dark'); - move dark values out of
:rootand into.dark, leaving a complete set of light values on:root; - keep both sets complete for every token the app defines.
For colours CSS can't reach (a chart library's theme object, a logo swap), read
the mode: const colorScheme = useColorScheme(); — 'light' | 'dark',
re-rendering on every host toggle.
viz-pivoted-results — reusable visualization pivots
Read pivotDetails from useVizContext(). When it is non-null, match
pivotDetails.valuesColumns to the mapped metric by referenceField, derive series
labels from pivotValues, and read each generated pivotColumnName with
getRaw(row, pivotColumnName) or getFormatted(row, pivotColumnName). Generated and
ordinary row keys both contain VizContextCell objects; never coerce row[fieldId]
directly. Use the remaining metadata for the visualization's actual shape: indexColumn and
originalColumns provide row-grain/type semantics, groupByColumns provides header
order, sortBy describes result ordering, totalColumnCount exposes truncation, and
passthroughDimensions identifies hidden fields retained on rows. Preserve the ordinary
fieldMapping path when pivotDetails is null. The full contract is in the
reusable-visualization skill.
App-code opt-ins (call the API where it fits the app)
drill-down:drillDown(...)derives a more detailed query from a clicked result row — wire it to click handlers on charts/rows.viz-drill-down: in a reusable visualization, the data-point action menu offers "Drill into …" whenuseVizContext().drillDown.enabled; selection callsdrillDown.open({ row, metric })and Lightdash opens its drill dialog. Distinct fromdrill-down, which is the full-app query helper.url-state:useUrlState(...)syncs a piece of app state into the page URL so views can be shared and restored.gsheet-export:exportToSheets(...)sends tabular results to a new Google Sheet — offer it wherever the app renders a table.external-fetch:client.externalFetch(alias, opts)calls an external connection linked to this app. The connection must already be linked by the host; you cannot add one from app code.delivery-render:useDeliveryRender()istrueduring scheduled-delivery and delivery-preview capture renders. Gate tab/slide DATA mounting on it so every tab's queries execute during capture while only the active tab stays visible — never mount all tabs unconditionally (interactive loads must stay lazy).viz-underlying-data(vizs only): keep the untransformedsourceRowon each interactive datum, gate a data-point action menu onuseVizContext().underlyingData.enabled, renderunderlyingData.get({ row, metric })in a themed dialog, and wire its Download button tounderlyingData.download. Full contract in thereusable-visualizationskill.
After a template upgrade
An upgrade rebuilds the app on the current SDK, which turns on the automatic
capabilities but does NOT add wiring — that is a deliberate rail. When you
offer newly available features, offer wiring-required ones too (their registry
entries carry a wiring note); implement only when the user asks.