Skip to main content
Avocado Studio doesn’t ship its own content database. Pages live in your CMS (or a JSON file in git, or MDX, or an internal API); the orchestrator holds draft / undo / chat state in SQLite and reads published content from your store via a small adapter. That means you keep your existing CMS, your existing content model, your existing publishing workflow. Avocado adds an AI-native editing surface on top. Switching content stores is a swap of one adapter, not a migration.

The contract

Every adapter implements the same two-method interface from @ai-site-editor/orchestrator-core:
That’s the whole contract.
  • getPages() is called lazily on the first chat for a fresh session. Its result seeds SQLite so chat turns like “edit the homepage hero” resolve against your real content instead of a 404.
  • onPublish() is called when a client POSTs to /publish on the library-mode handler with { session, siteId }. The orchestrator reads the current draft from SQLite and hands the resulting PageDoc[] to the adapter. If you omit onPublish, /publish becomes a 200 no-op (with written: false in the response) and SQLite still holds the draft.
The adapter is the seed and the sink, never the live working copy. All in-flight edits live in SQLite; that’s what makes the same chat UX work against a static JSON file, a Sanity space, or anything in between.

Bundled adapters

Two adapter implementations ship in @ai-site-editor/orchestrator-core/cms:

jsonFileAdapter

Reads PageDoc[] from a JSON file on disk. Smallest possible adapter — useful when your content is already checked into git, or for prototypes before you pick a real CMS.
The file may be either a bare PageDoc[] or an object with a pages key. Each entry is parsed through the lenient PageDoc schema, so partial or extra fields are tolerated.

editorApiAdapter

Fetches PageDoc[] from a site’s /api/editor/pages endpoint. Use this when your site already exposes a page-listing API, or when the orchestrator runs out-of-process from the site.
Pages that fail the lenient PageDoc schema are dropped from the seed and a warning is logged with the candidate index, slug, and first Zod issue path — pass logger: yourLogger if you want those routed to your own log sink.

Wiring it up

Library-mode integration (orchestrator mounted as a Next.js catch-all route inside your site) is the recommended pattern. One file, fully drop-in:
That’s it — the first chat turn for a new session calls getPages(), seeds SQLite, and the planner can immediately reason about your pages.

Publishing back

Once adapter.onPublish is defined, your editor (or any client) can publish a session’s draft to the upstream store with one POST:
The orchestrator reads the current draft for the scoped session out of SQLite and hands the resulting PageDoc[] to adapter.onPublish(pages). If the adapter has no onPublish, the route still returns 200 but with written: false — useful for sites that publish via CI / git commit rather than a runtime writeback.
Session scoping. When adapter is set, sessions auto-scope to siteId (default "library") so they bypass the legacy demo-content seed path. If you see a chat returning demo blocks instead of your content, pass an explicit siteId to force the scope.

Writing a custom adapter

If your content lives somewhere jsonFileAdapter and editorApiAdapter don’t reach, write your own. The contract is small enough to inline:
Then pass the instance into createOrchestrator({ adapter: myCmsAdapter({ spaceId: "..." }) }). Failures inside getPages() are logged but non-fatal — the session simply starts empty. If you build a non-trivial adapter (Storyblok, Hygraph, Payload, Directus, etc.) we’d love to merge it into examples/.

Custom block schemas

If your site renders custom block shapes (e.g. a Hero with carouselImages instead of canonical imageUrl), register your schemas with the global block registry alongside the adapter:
Inside lib/my-blocks.ts, call registerBlock("Hero", { schema, meta }) for each type you want to override. See Custom blocks for the full schema shape.
The schema-override import must be the last import in the file. ESM resolves imports up front but fires side effects in source order — if your override runs before the orchestrator’s transitive deps, the canonical schemas will re-register on top of yours.

Example apps

Five working examples live under examples/ in the repo. Each one boots in under two minutes: Every CMS example (Contentful, Sanity, Strapi) ships with both wirings side-by-side: a library-mode /api/avocado/[[...path]]/route.ts that mounts createOrchestrator({ adapter: ... }) (recommended), plus the legacy split-mode /api/editor/* route for sites that want the orchestrator deployed separately.

Contentful

Free Community plan is enough. The setup script creates the full content model (20 block types + page + siteConfig).
Full walkthrough: examples/contentful-site/README.md.

Sanity

The example ships with an embedded Sanity Studio at /studio alongside the Avocado editor.
Add http://localhost:3004 as a CORS origin in Sanity project settings (with credentials allowed). Full walkthrough: examples/sanity-site/README.md.

Strapi

Self-hosted, open-source. The setup script generates Strapi v5 schema files from the Avocado block registry.
Generate an API token from the Strapi admin (Settings → API Tokens) with read+write for page and site-config. Full walkthrough: examples/strapi-site/README.md.

See also