Tour Studio bundle import / export
Describe pages, actions and guided flows in one versioned JSON file. Import and export it from `/admin/tours`, like an n8n workflow.
≈ 11 min read
Overview
Tour Studio uses a self-contained JSON bundle for an entire guided studio: pages, actions and flows, all linked by `key` (never by database id). The usual workflow is the admin console at `/admin/tours`: select the target app in the workspace picker, then use **Export JSON** or **Import JSON**. The `@guidia/skills` npm package helps agents generate and validate bundles offline before you upload the file in Tour Studio.
Import and export in the console
Open `/admin/tours` with the correct tenant and application selected. **Export JSON** downloads `guidia-tour-studio-*.json` with the full studio. **Import JSON** opens a dialog: choose the file, pick `merge` (upsert by key, keep what is not in the file) or `replace` (wipe the studio first, destructive). The UI calls `POST /api/tour-studio/apps/{app_id}/bundle/import-file` (multipart upload).
Bundle schema
A bundle is a minimal JSON object with the shape below. The three arrays (`guided_pages`, `guided_actions`, `guided_flows`) reference each other by `key`.
{
"bundle_version": 1,
"kind": "guidia.tour_studio.bundle",
"meta": { "generated_by": "my-agent" },
"guided_pages": [
{ "key": "dashboard", "title": "Dashboard", "page_type": "dashboard", "url": "/dashboard" }
],
"guided_actions": [
{
"key": "open-billing",
"label": "Open billing",
"page_key": "dashboard",
"action_type": "click",
"target_mode": "selector",
"target_selector": "[data-test=open-billing]"
}
],
"guided_flows": [
{
"key": "upgrade-tour",
"title": "Upgrade tour",
"entry_page_keys": ["dashboard"],
"steps": [
{ "title": "Open billing", "action_key": "open-billing", "page_key": "dashboard", "ui_mode": "highlight" }
]
}
]
}Required fields
- `bundle_version`: integer ≥ 1. Current version = 1.
- `kind`: constant `"guidia.tour_studio.bundle"` discriminator.
- `guided_pages[].key` + `title`: key matches `^[a-z0-9][a-z0-9_-]*$`, max 80 chars.
- `guided_actions[].key` + `label`: reference an existing page through `page_key`.
- `guided_flows[].key` + `title` + `steps[]`: steps reference actions and pages by key.
REST endpoints (reference)
- `GET /api/tour-studio/apps/{app_id}/bundle/export`: same payload as **Export JSON**.
- `POST /api/tour-studio/apps/{app_id}/bundle/import-file`: used by **Import JSON** (multipart file).
- `POST /api/tour-studio/apps/{app_id}/bundle/import`: JSON body `{ bundle, strategy }` (integrations, scripts).
- `DELETE /api/tour-studio/apps/{app_id}/imports/{import_id}`: rollback a tagged import.
Import strategies
- `merge` (default): upsert by key. Pages, actions and flows not in the bundle are kept. Safe to re-run.
- `replace`: wipe the whole studio first, then write. Destructive: confirm in the dialog.
`@guidia/skills` (generate and validate offline)
Optional npm package for AI agents (Cursor, Claude, Gemini, Copilot…) or local editing. Install agent configs once, generate bundles from your app routes, validate, then import on `/admin/tours`. No API credentials required for the standard path.
npm i -D @guidia/skills
# Once per project (Cursor, Claude Code, Gemini, Copilot, AGENTS.md):
npx skills setup --all
npx skills generate
npx skills validate guidia/workflow.bundle.json
npx skills summary guidia/workflow.bundle.json --format md
# Import in the console:
# /admin/tours → Import JSON → guidia/workflow.bundle.json → merge | replaceProgrammatic builder
Agents can build a bundle in TypeScript, validate it, then hand off the JSON file for import on `/admin/tours`.
import { workflow, validateBundle } from "@guidia/skills";
const wb = workflow({ meta: { generated_by: "my-agent" } });
wb.addPage({ title: "Dashboard" }).type("dashboard").url("/dashboard");
wb.addAction({ label: "Open billing", on: "dashboard" })
.kind("click")
.selector("[data-test=open-billing]");
wb.addFlow({ title: "Upgrade tour", goal: "Upgrade plan" })
.entryAt("dashboard")
.doAction({ action_key: "open-billing", title: "Open billing" });
const bundle = wb.build();
const check = validateBundle(bundle);
if (!check.ok) throw new Error("Invalid bundle");
// Download bundle.toJSON() or write to disk, then:
// /admin/tours → Import JSONJSON Schema for editors
The official JSON Schema ships inside the package at `node_modules/@guidia/skills/schemas/workflow.schema.json`. IDEs with JSON Schema support (VS Code, JetBrains) autocomplete when you set `$schema` or `json.schemas`.
{
"$schema": "./node_modules/@guidia/skills/schemas/workflow.schema.json",
"bundle_version": 1,
"kind": "guidia.tour_studio.bundle",
...
}Route coverage (`meta.coverage`)
Bundles can embed a `meta.coverage` object that explains how complete the studio is. Each Guidia application has its own routes (`base_url`, customer SPA, evolving screens). They are not inferred from the Guidia marketing repo (`frontend/src/app/`) unless you explicitly use `repo` mode.
Three inventory modes
- `bundle` (default for customer apps): reference routes are the `url` / `url_pattern` fields inside `guided_pages`. Reports integrity (pages without actions, orphan refs, empty flows, duplicate URLs).
- `repo`: compares against a static inventory of guidia.dev routes (marketing site + admin console). For Guidia reference bundles only.
- `custom` + `--inventory routes.json`: compares the bundle to a route list you provide (crawl, OpenAPI, previous export, product map).
# 1. Export from /admin/tours → Exporter JSON (target app selected)
# 2. Optional local check with @guidia/skills:
npm i -g @guidia/skills
guidia coverage my-export.json --mode bundle
# Compare to your own route list:
guidia coverage my-export.json --mode custom --inventory ./discovered-routes.jsonCustom inventory file format
{
"routes": [
{ "path": "/dashboard", "label": "Dashboard" },
{ "path": "/settings/billing", "label": "Billing" }
]
}`meta.coverage` includes `inventory_mode` (`bundle-self` | `guidia-repo` | `custom-inventory`), a summary, covered/missing routes when an external inventory is used, and `bundle_self` with integrity details in bundle mode.
Common errors
- `ERROR duplicate.key`: two entities share the same key in one array. Use distinct suffixes.
- `warn ref.unknown_action`: a step points to a missing action. Add the action or remove the step.
- Import rejected in the UI: the server schema is stricter than the JS validator. Re-check required fields.
- Wrong app selected: export/import always applies to the app in the workspace picker on `/admin/tours`.