Defining a product tour in JSON
createWorkflowFromConfig turns a plain JSON configuration into the same WorkflowDefinition produced by the JavaScript builder. Tours can therefore be authored in a CMS, stored in a database, or served by an API without changing how GlowTour renders or runs them.
The config API ships as a separate entry point, so applications using only the builder do not pay for it.
Quick start
Section titled “Quick start”Import the config entry point for your framework, load a JSON object, then run the generated workflow on a tour that is rendered by the adapter. This React example uses useGlowTour(); other adapters follow the same pattern with their own component setup:
import "@glowhop/styles-tour/default.css";import { GlowTourDefault, useGlowTour } from "@glowhop/react-tour";import { createWorkflowFromConfig } from "@glowhop/react-tour/config";
export function OnboardingTour() { const { tour, start } = useGlowTour();
async function startTour() { const config = await fetch("/tours/onboarding.json").then((response) => response.json()); await start(createWorkflowFromConfig(config)); }
return ( <> <button onClick={() => void startTour()}>Start tour</button> <GlowTourDefault tour={tour} /> </> );}createWorkflowFromConfig accepts unknown, validates the complete configuration, and throws a ConfigValidationError before building when anything is invalid.
Configuration format
Section titled “Configuration format”{ "name": "onboarding", "version": "1.1", "overlay": { "opacity": 0.55 }, "popover": { "gap": 16 }, "steps": [ { "id": "invite-button", "target": "#invite-button", "title": "Invite your team", "content": "Send an invite to get started.", "data": { "trackingId": "invite-step" }, "actions": [ { "type": "waitUntilElement", "selector": "#invite-button", "timeout": 5000 }, { "type": "clickTarget" } ], "targetEvents": [ { "event": "click", "action": { "type": "focusTarget" } } ] }, { "id": "done-banner", "target": "#done-banner", "title": "All set!", "content": "You're ready to go.", "actions": [{ "type": "wait", "ms": 400 }] } ]}version,name, andstepsare required.versionis the version of the config format, currently"1.1".- Every step requires
id,target, andcontent;titleis optional. Step ids must be unique within the workflow; they are whatstart(workflow, { startAt })uses to resume a tour. targetis a CSS selector. Function andHTMLElementtargets remain builder-only.titleandcontentare strings for JSON loaded from a CMS or API.overlay,popover,indicator,behavior, andclassNamesuse the same options as the builder, globally or per step. A step’sclassNamesentry overrides the global one for the same component, as described in Class name options.dataacceptsstring,number,boolean, andnullvalues.
Unknown keys, invalid nested options, and unsupported values are rejected rather than silently ignored.
Authoring JSON with TypeScript
Section titled “Authoring JSON with TypeScript”Use WorkflowConfig while authoring a JSON payload to get autocomplete and catch structural errors in the editor:
import type { WorkflowConfig } from "@glowhop/react-tour/config";
const config: WorkflowConfig = { name: "onboarding", version: "1.1", steps: [ { id: "invite-button", target: "#invite-button", title: "Invite your team", content: "Send an invite to get started.", actions: [ { type: "waitUntilElement", selector: "#invite-button" }, { type: "clickTarget" }, ], }, ],};
const json = JSON.stringify(config, null, 2);Keep this object declarative when it must be transported as JSON. WorkflowConfig also accepts same-runtime JavaScript callbacks, but functions are not preserved by JSON.stringify.
Built-in actions
Section titled “Built-in actions”actions[] and targetEvents[].action accept declarative action objects:
type |
Fields | Builder equivalent |
|---|---|---|
"wait" |
ms: number |
.wait(ms) |
"waitUntilElement" |
selector: string, interval?: number, timeout?: number |
.waitUntilElement(...) |
"clickTarget" |
- | .clickTarget() |
"focusTarget" |
- | .focusTarget() |
.waitUntil(predicate) has no JSON equivalent because an arbitrary predicate cannot be serialized. Use waitUntilElement or the JavaScript builder instead.
Validation errors
Section titled “Validation errors”All problems are reported together with their path:
import { ConfigValidationError } from "@glowhop/react-tour/config";
try { createWorkflowFromConfig(config);} catch (error) { if (error instanceof ConfigValidationError) { for (const issue of error.issues) { console.error(`${issue.path}: ${issue.message}`); } }}This makes CMS payloads and generated configuration easier to diagnose in one pass.
Adding JavaScript behavior
Section titled “Adding JavaScript behavior”Plain JSON covers targets, presentation options, data, and built-in actions. Same-runtime configuration objects can additionally contain functions, but they can no longer be transported as JSON.
actions[] and targetEvents[].action accept an inline function:
import type { WorkflowConfig } from "@glowhop/react-tour/config";
const config: WorkflowConfig = { name: "onboarding", version: "1.1", steps: [ { id: "invite-button", target: "#invite-button", title: "Invite your team", content: "Send an invite to get started.", actions: [ { type: "waitUntilElement", selector: "#invite-button" }, async (context) => { await analytics.track("invite_step_shown", { target: context.target.id }); }, ], }, ],};
const workflow = createWorkflowFromConfig(config);beforeEnter and beforeLeave (the config form of .beforeEnter() and .beforeLeave()), plus onStart, onCancel, and onFinish, accept only functions. Built-in actions cannot run in those contexts.
For CMS-driven behavior, keep an identifier in data and attach the implementation in application code:
import { type StepConfig, validateWorkflowConfig, type WorkflowConfig } from "@glowhop/react-tour/config";
const parsed = validateWorkflowConfig(await fetch("/tours/onboarding.json").then((response) => response.json()));const config: WorkflowConfig = { ...parsed, steps: parsed.steps.map((step): StepConfig => ({ ...step, beforeLeave: ({ direction, props }) => { if (direction !== "advance") return; analytics.track(String(props.get().data?.trackingId ?? "unknown-step")); }, })),};Entry points
Section titled “Entry points”Framework adapters expose pre-bound config entry points:
@glowhop/react-tour/config@glowhop/vue-tour/config@glowhop/solid-tour/config@glowhop/angular-tour/config@glowhop/vanilla-tour/config
@glowhop/core-tour/config is the unbound entry point for custom integrations that render the tour themselves. The core has no UI of its own: a workflow run on a bare createGlowTour() from @glowhop/core-tour shows nothing unless your integration renders it. With an adapter, use the adapter’s entry point above.
Each entry point also exports the config types, ConfigValidationError, and validateWorkflowConfig.
Rich framework content
Section titled “Rich framework content”The default validation requires string values because that is the safe format for JSON from a CMS or API. For a configuration object assembled inside the application, framework content such as a React node, Vue VNode, Angular TemplateRef, or DOM Node can be accepted with the optional validateContent callback:
const workflow = createWorkflowFromConfig( { name: "onboarding", version: "1.1", steps: [ { id: "invite-button", target: "#invite-button", title: <b>Invite your team</b>, content: "Send an invite to get started.", }, ], }, { validateContent: (value) => (value === undefined ? "must not be undefined" : null), },);validateContent(value, path) returns an error message or null. Keep the default string validation for untrusted JSON.
Security
Section titled “Security”The config module never evaluates strings as code: it uses no eval, new Function, or id-to-function registry. A payload produced by JSON.parse() cannot introduce executable functions.
Limitations
Section titled “Limitations”- CSS selector targets only.
- No declarative conditions such as
showIf. - No config exporter yet; the generated definition retains its validated source in
.source. - No built-in i18n integration. Translation keys and interpolation values can be stored in
contentanddata.
For the JavaScript builder equivalent, see the Builder reference and Programmatic control guide.
