Skip to main content
This page is for the self-hoster who wants a public playground on their own key. DEMO_MODE=1 turns a standalone orchestrator deployment into a public “try before you sign up” playground. You front the LLM bill on a shared API key, and the orchestrator stops the playground from being abused:
  • Only allow-listed operations run (default: update_props on Hero blocks)
  • Each visitor gets an isolated ephemeral session keyed by sha1(ip) — no persistence
  • Per-IP rate limit (default 20 requests/hour)
  • Image generation short-circuited so AI generation / Unsplash can’t be triggered
  • Agent, Jira and session-listing routes return 403
It’s the right answer when you want a marketing landing page where prospects can chat-edit a sample site without authenticating, signing up, or installing anything.
The gate is server-enforced: the operation allow-list runs in the operations engine, and the route gate, rate limit and session rewrite run in the standalone server’s request hooks. Client-trusted flags can’t bypass it. Library mode has no route gate, so run a demo on the standalone orchestrator. That said, this is a playground gate, not a security boundary — anyone hitting your demo orchestrator is using your LLM budget. Always pair DEMO_MODE=1 with a rate limit and provider-level spend caps.

Public demo: every edit, bounded spend

DEMO_MODE restricts what a visitor can do. If you want visitors to try the full editor on your key — any edit, on their own session — use PUBLIC_DEMO=1 instead. It is the orchestrator half of the editor’s VITE_PUBLIC_DEMO=1, which already gives each browser its own session and hides publishing: It also answers the image-source question up front: with both Unsplash and AI images configured, an ordinary deployment asks “Where should this image come from?” on the first image request, and the public demo uses Unsplash instead. Set CHAT_IMAGE_SOURCE_DEFAULT to unsplash, ai or ask to choose for any deployment. The two flags are independent; PUBLIC_DEMO gates no operations. Like the DEMO_MODE route gate, the metering lives in the standalone server. The same warning applies: pair it with provider-level spend caps.

What demo mode does

Quick setup

Three services, each on its own subdomain: the orchestrator on a host that runs a long-lived container, and the editor and the site on any static or Next.js host.

Orchestrator

With DEMO_MODE=1, an unset or empty ORCHESTRATOR_DB_FILE already means :memory:; the explicit value is documentation. An explicit file path would override it. No snapshots are taken of an in-memory store.

Editor (separate from your production editor)

VITE_LOCK_SITE_ID=1 hides the site picker; visitors can only edit the one demo site.

Site (separate from your production site)

The middleware rewrites body.session and body.siteId on every demo request, so clients don’t need to know their demo session key — they just send session=dev (or anything) and the orchestrator swaps it for demo-<hash(ip)> + siteId=avocado-stories.

Tuning the allow-list

The defaults — update_props on Hero — are deliberately narrow. They demonstrate the AI editing UX (rewrite a headline, change a tagline, swap CTA copy) without letting visitors restructure pages or evict sample content. Widen carefully. Each new allowed op type is a new vector for visitors to use your LLM budget.
What you probably don’t want to allow in a public playground:
  • add_block / remove_block — visitors will spam blocks
  • move_block — easy to grief the page layout
  • Anything on RichText — opens the door to long-form content abuse
If you want a wider playground for a specific audience (design partners, prospects under NDA), gate access at your reverse proxy (basic auth, signed URLs) instead of widening the demo allow-list.

What demo mode is not

  • Not a multi-tenant SaaS. Each demo session is keyed by IP, not by user. Two visitors behind the same NAT share a session.
  • Not a security boundary. It stops casual abuse, not a determined attacker. Use upstream rate limits (Cloudflare, your reverse proxy) and provider spend caps for actual cost control.
  • Not a free trial. State doesn’t persist; visitors can’t save their work or come back to it. For a “free trial that converts,” look at hosting the full editor behind auth instead.

Testing locally

Tests in apps/orchestrator/src/demo-mode.test.ts cover the split-allow logic (allow update_props but only on Hero, etc.) — useful reference if you’re widening the allow-list.

Reverting

Remove DEMO_MODE=1 from the orchestrator env and redeploy. There’s no migration — demo state was never persisted.

See also