A block is a component your site already has
A block is not an Avocado widget you have to adopt. On an existing site it is one of your own components, registered with a schema that declares which of its props are content.name, price and blurb have field metadata, so they appear in the property panel, the AI can edit them, and the preview can mark them for inline editing. badgeVariant is in the schema but has no field entry — it stays a developer concern, and no amount of asking will move it.
Three properties of that declaration matter more than they look:
- The schema decides what is legal. Every write is checked against it before it lands. An AI that tries to set
priceto a number is rejected, not coerced into something surprising. - Undeclared props survive. A block carrying its own bookkeeping — a CMS source snapshot, the locale it was read in, an origin slot a migration recorded — keeps it through an edit. Validation decides what is legal; it does not decide what is kept.
- Adding a field is one line. When marketing needs something the panel does not expose, you widen the boundary deliberately, in your own repo, through your own review.
Pages, blocks and props
Content is structured data, never raw HTML.PageDoc— one page. It carriesid,slug,title,updatedAt, an orderedblocksarray and optionalmeta(title,description,ogImage,path).BlockInstance— one section on that page: anid, atype, and apropsobject.- Props — the fields that make up a block’s content, governed by the Zod schema the block was registered with.
The slug is an identity, not necessarily a URL. A slug is a path beginning with
/; the home page is /, never "". When the URL a visitor uses differs from that identity — a locale prefix, a basePath, a CMS that owns its own routing — the URL goes in meta.path and the slug stays the identity. A field-level-i18n CMS keeps one editable page per document per language, so /de/events can be a page identity on a site that serves German at the root. See multilingual sites.Operations are the safety boundary
An operation is a structured, schema-validated edit action. When someone says “change the hero heading to Welcome”, nothing writes HTML and nothing writes a file. The system produces this:packages/shared/src/schemas.ts, and every surface writes through it: the AI chat editor, the visual editor, the MCP server, the Jira integration and any direct POST /ops caller all submit operations against the same union.
Read that table for what is missing. There is no
write_file, no run_command, no install_dependency, no edit_component, no change_route. A model driving Avocado at full confidence with every guardrail switched off cannot express any of them, because the vocabulary has no verb for them. This is the difference between safety that is structural and safety that is procedural: a pull request, a reviewer and an audit log are human steps that can be rushed or skipped, and when they are, the blast radius is your production deploy. A type has no bad day.
Inside the boundary
Block props, list items, block order, page metadata, pages, navigation, theme tokens — validated, previewable, undoable.
Outside the boundary
Components, routes, dependencies, middleware, build config, CI. Not gated — unexpressible.
How an operation is validated
Every operation is checked before it changes anything.- Shape. The operation must parse against
operationSchema. A malformed op is rejected with a category, not silently dropped. - Target. The page must exist, the block must exist, the anchor must exist, a list index must be in range.
- Props. When the site supplied a block manifest, the resulting props are checked against that manifest’s
propsSchema— which is how your custom blocks are enforced. With no manifest entry, the props are checked against the registered Zod schema instead. - Atomicity. A batch either applies in full or leaves the page as it was.
schema_violation, not_found, ambiguity, no_effective_change, malformed_output, planner_refusal, incomplete_output, internal_error. Only schema_violation is worth re-prompting a model about, so only that category triggers a repair pass — the rest either need a person or need nothing.
The block manifest
The editor and the orchestrator both need to know what blocks a particular site has, not what happens to be registered in their own process. Your site answers that atGET /api/editor/blocks.
listFields, a description, and chrome: true. A chrome block — a site header or footer — is structurally pinned: always present, never added, moved or removed. An agent reading the manifest needs to be told that, or it plans an add_block the engine will refuse.
The manifest is the AI’s instruction manual as much as it is the validator’s rulebook. A block with a thin schema is a block the AI edits badly, so completeness here pays for itself. Coverage checks grade how much of it your pages actually expose.
Plans and approval
A chat turn produces an edit plan: an intent, a summary, a change log and a list of operations.- it removes a page, always, whatever that page contains;
- its operations touch more than one page in a single turn;
- it contains three or more
remove_blockoperations; - it would remove more than half the blocks on any one page.
Draft mode
Draft mode is how your own site renders unpublished changes.- Published content — what visitors see, stored in your CMS, database or files.
- Draft content — what the editor shows, held in the orchestrator’s session state, invisible to visitors.
- Publishing — promoting draft content to published, on your explicit action.
GET /draft/pages, /draft/slugs and /draft/site-config, scoped by session and siteId. Your renderers are unchanged — they receive the same PageDoc either way.
Two things authorise a draft request in production: Next.js draft mode already being enabled through your secret-gated /api/draft handler, or a valid secret on the request itself. The second exists because the editor renders your site in a cross-origin iframe where the draft cookie is frequently blocked outright. In development the __editor=1 routing hint alone is enough; in production it authorises nothing.
Undo, redo and the version log
Every applied change snapshots the page it touched before writing.- Undo and redo are per page, per session, capped at 50 entries in each direction.
- A chat turn is one entry. A plan with six operations undoes as one action, because a sentence is what the person meant, not the six ops it decomposed into. Direct edits from the visual editor push their own entries.
- The version log keeps up to 100 entries per session with restorable snapshots, so you can go back to a named point rather than stepping.
The three services
The two editing surfaces are not two products. The AI chat editor is the default: describe the change, watch the plan stream into the live preview, approve or undo. The visual editor — Puck-based, enabled per site — is click-and-drag on the same preview, with the AI chat alongside it. Both emit operations from the same nineteen. See visual editor mode.
The site talks back over a
postMessage protocol (site-editor/v1): clicking a block tells the editor which block is selected, and the editor pushes liveDraft frames during streaming and a draftUpdated frame when a change lands.
Bring your own model
The orchestrator calls your Anthropic, OpenAI or Google API keys. There is no token resale and no per-seat markup — you pay your own provider for your own usage. Each provider is exposed as four tiers, chosen in the editor per request, and each tier’s model name is overridable by environment variable:
Not every request reaches a model at all. Simple, unambiguous edits are answered by a deterministic planner with no model call; a fast intent router handles the next tier up; the full planner runs only when it is needed. See AI providers and how it works.
Where to go next
How it works
A sentence, followed all the way to an applied, reviewable, publishable change.
Architecture
Services, packages and the data flow between them.
Custom blocks
Register your own components and declare which props are content.
Next.js integration
Routes, markers, draft mode, preview and publishing.