Working with content

Drafts and publishing

What each write does to a live entry, how working copies behave, previews, and scheduling.

On this page

entries_create always saves a draft, and entries_update never changes an entry’s published status. Going live is a separate call to entries_publish, gated on the collection’s publish permission. What an edit does to an entry that’s already live depends on whether the collection has revisions.

Try it#

Call the tools in any order and watch what visitors see. Turn revisions off and repeat the same calls. The result lines are the exact strings the server returns.

Visitors see
Nothing yet
entries_get and entries_preview show
Nothing yet
What the tool answered

Press entries_create to begin.

With revisions, and without#

With revisions enabled, an edit to a live entry becomes a working copy, the same one the Control Panel creates. Visitors keep seeing the live version until someone publishes it. entries_get returns the working copy by default, because that’s what entries_update builds on and what entries_publish would promote. Pass working_copy: false to read the live entry instead. The source field says which one you got, and has_working_copy says whether changes are staged.

Without revisions, the edit saves straight to the entry. If the entry is live, visitors see the change right away, the same as saving it in the Control Panel.

Moving an entry with parent is the exception to working copies. Working copies don’t store an entry’s place in the tree, so the move is live at once, also on revision-enabled collections. That’s the same as the Control Panel’s tree view, and it needs reorder {collection} entries.

Terms, globals, assets, and navigations have no draft state. Writes to them go live immediately.

Why publishing is its own tool#

A role without the publish permission can’t publish through MCP, whatever the agent sends. entries_create and entries_update refuse a published argument outright.

published is not accepted by entries_update — publish state changes only through entries_publish and entries_unpublish

Because publishing is a separate tool, a client that asks before tool calls asks you about it separately. You can let an agent call entries_update all session and still approve each publish yourself.

Preview before publishing#

entries_preview returns a Live Preview URL that renders the entry through your own templates, drafts included. The agent can fetch the page itself and check its work before anyone publishes.

{
  "id": "6e7bf292-b666-4e36-b0bf-054069ee8712",
  "url": "https://example.com/blog/first-ride?live-preview=…&token=…",
  "expires_at": "2026-11-04T10:43:07+00:00",
  "target": "Entry",
  "targets": ["Entry"]
}
  • It needs the edit permission, as Live Preview does in the Control Panel.
  • On revision-enabled entries it shows the staged working copy, not the live entry.
  • The page shows the entry as it was when the tool ran, so call it again after an edit.
  • target picks one of the collection’s preview targets, and the response lists them all.
  • An entry in a collection without a route has no page, so the call fails.
  • read_only keeps this tool, because it changes no content.

Anyone with the URL can open it until expires_at, an hour after the call. Treat it as you would the draft itself. The token opens only that one entry, and other drafts still return 404 with it.

Scheduling#

Scheduling is Statamic’s own. On a dated collection whose future date behavior is private, a published entry dated in the future is scheduled. It returns a 404 until its date, then goes live with no further call. So an agent schedules a post in two steps.

  1. Set the date with entries_create or entries_update.
  2. Call entries_publish.
{
  "id": "40b366a1-6040-460f-9d57-6df830069b94",
  "slug": "launch",
  "status": "scheduled",
  "url": "/news/launch",
  "result": "scheduled — published, but not live until its date",
  "cp_edit_url": "https://example.com/cp/collections/news/entries/40b366a1-6040-460f-9d57-6df830069b94",
  "date": "2031-06-01T07:00:00+00:00"
}

statamic_overview reports each dated collection’s date_behavior, with future and past set to public, unlisted, or private.

  • Where future is public, a future-dated entry is live as soon as it’s published, and the result says so.
  • A collection created in the Control Panel defaults to future: private. One defined in YAML without date_behavior defaults to public.
  • Where past is private, an entry whose date has passed is expired and hidden.

Dates and time zones#

Dates accept an offset, such as 2026-10-06T09:00:00+02:00. A time without one is read in server.timezone from statamic_overview, which is app.timezone. An entry’s date comes back in UTC with its offset.

blueprints_get reports time_enabled on the date field. Without it, the Control Panel shows only the day of a scheduled entry, not the time. A localization inherits its origin’s date unless the date field is localizable, so schedule it on the origin entry.

Does it need cron?#

The entry goes live on time without cron, because Statamic checks the date on every request. Statamic’s scheduler (schedule:run plus a queue worker) is what refreshes the static cache and search index at that minute, the same as for entries scheduled in the Control Panel.

What the result field says#

Every write response states the resulting liveness in result and includes cp_edit_url, a link to the Control Panel’s edit page. Delete responses omit cp_edit_url, because the page would 404.

Situationresult
Create or update a draftsaved as draft — not live
Publish a draftpublished
Publish a staged working copypublished — working copy is now live
Publish with nothing stagedno-op — already published, nothing staged
Edit a live entry, no revisionspublished
Edit a live entry, revisions onworking copy created — live entry unchanged
Edit it againworking copy amended — live entry unchanged
Unpublishunpublished — not live
Unpublish with a working copy stagedunpublished — working copy applied, not live
Unpublish a draftno-op — already a draft
Publish, dated in the future, future: privatescheduled — published, but not live until its date
Publish, date passed, past: privateexpired — published, but its date has passed, not live
An update that changes nothingno-op — merged result equals the current entry; nothing saved, no revision created
Create a termcreated — live
Update a term, global set, asset, or navigationupdated — live
Upload an assetuploaded — live