Skip to main content
When a user clicks Publish in the Content Studio, the orchestrator takes the in-memory draft and hands it to a publish target — a plugin that writes the content to wherever you actually serve from. The product ships three built-in targets and a registry for plugging in your own. The same POST /publish endpoint dispatches to whichever target wins selection.

The three built-in targets

Selection order

Every publish call runs through the registry in this order:
  1. If PUBLISH_TARGET=<name> env var is set and that target is registered → use it verbatim.
  2. Otherwise iterate registered targets in registration order; pick the first whose canHandle(ctx) returns true.
  3. Fall back to the legacy PUBLISH_MODE env: git (default) or deploy_hookdeploy-hook.
Targets self-select based on what’s in the request context — e.g. the site-contract target claims any request that came from a registered site with a valid origin; the git target claims local-only deployments.

Site contract (the default)

Most production deployments use this. Avocado posts the draft to your site’s /api/publish route, your site validates the bearer token, and your adapter writes to the CMS. Your site’s /api/publish handler is wired by the Site SDK. It validates the shared secret, then calls your CMS adapter’s write path. The orchestrator never talks to your CMS directly — your site is always in the middle, which means your existing CMS auth, hooks, and validation all still run. Env vars:

Git target

Writes the draft pages as a JSON snapshot under your site’s content directory. Optionally git commit && git push the change so Vercel / Netlify pick it up. Good for:
  • Local development without a CMS
  • “Git as your CMS” setups where content is checked into the repo
  • Static sites where publishing means triggering a rebuild
Env vars:
Each publish writes the snapshot, and (if PUBLISH_GIT_AUTO_COMMIT=1 is set) commits with a generated message and pushes to origin. If the push fails (auth, conflicts, etc.) the publish call returns 502 and the tracker records the error.

Deploy hook target

Calls a Vercel or Netlify deploy hook URL. Useful when content is already in place — committed to the repo, or written to a CMS by some other process — and “publish” just means “redeploy.” Env vars:
The orchestrator POSTs to the URL, captures the deployment id, and the Content Studio polls /publish/status to surface “building / ready / failed” in real time.

Building a custom target

PublishTarget is a two-method interface:
Register before the server boots:
The orchestrator’s route handler does nothing target-specific — it builds the PublishContext (session, scoped session, draft pages, slugs, site config, image dir, logger), calls target.publish(ctx), saves the returned tracker, and replies with outcome.response at outcome.httpStatus. Your target controls every byte of the wire response.

Publish status

GET /publish/status?session=<id>&siteId=<slug> returns the latest tracker:
The Content Studio polls this endpoint after a publish to show build progress inline. Targets that don’t have async deployments (git, S3, etc.) return status: "ready" immediately.

Snapshots and rollback

Every publish writes a snapshot to the version log. From the MCP server (or the editor’s history panel) you can:
  • avocado-list-snapshots — list published snapshots with commit shas + timestamps
  • avocado-restore-snapshot — rewind the draft to a specific snapshot (doesn’t trigger a republish; you publish again to push the rolled-back version live)
  • avocado-compute-publish-diff — diff the current draft against the last published snapshot before publishing
The number of snapshots kept is bounded by VERSION_LOG_CAP (default 100). Older snapshots are evicted FIFO.

See also