You're reading docs for Nextly Alpha. APIs may change between releases.

Content Releases

Content Releases

Group documents into a release and publish or unpublish them together, at a time you choose.

A release is a set of documents that go live at the same instant. A product launch touching a landing page, three blog posts and a pricing single becomes one thing you schedule, rather than five things someone has to publish in the right order at 9am.

Each member of a release is set to either publish or unpublish, so the same release can put new pages live and retire old ones in the same moment.

Availability

Built inNo plugin required
AdminA Releases section with a calendar view, and an "Add to release" action on documents
PermissionScheduling requires publish access. A key scoped only to publishing releases can schedule without holding a general read grant

What it is not

This is scheduled publishing, not an approval workflow. There are no review stages, transitions or assignees. If you need "editor submits, manager approves, then it goes live", that is not what a release does.

The shape of a release

StepWhat happens
CreateAn empty release with a name
Add membersEach member names a document and an action, publish or unpublish
ScheduleThe release is given a time. Blocking problems are checked first
ApplyOn a background-jobs pass after that time, every member's action is applied
CancelAn unapplied release can be cancelled

Scheduling stores a time; a job runner applies it

Scheduling records when a release is due. The work is done by a releases:drain sweep that runs as part of the background jobs pass, triggered by calling the jobs route from a cron job, a platform scheduler, or an operator.

The scaffolded apps mount Nextly's handlers at src/app/admin/api/[[...params]]/route.ts, so the URL is:

POST /admin/api/jobs/run

There is no general /api handler in a scaffolded app, so a scheduler pointed at /api/jobs/run gets a 404 and your releases never publish. If you mounted the handlers somewhere else, use that path instead.

With no scheduler configured, a release stays scheduled forever. Its due time passes and nothing happens, because nothing ran the sweep. Set up an authenticated periodic call to /api/jobs/run before you rely on scheduling.

A pass is bounded: it claims at most 10 queued jobs, and it stops starting new ones after 20 seconds. That 20 seconds is a budget for starting work, not a hard ceiling on the request — the check happens before each claim, so a handler already running is awaited to completion and the call can overrun. Size any serverless timeout for the slowest handler, not for 20 seconds.

The sweep can also defer work past its deadline while still reporting success. So a release is applied on the first pass that actually reaches and completes it, which is not always the first pass after its due time. With a backlog, or several expensive releases due together, expect it a pass or two later. Treat the scheduled time as "not before", not as "at".

Blocking reasons

Before a release can be scheduled, it is checked for problems that would make it fail half-way — a member pointing at a document that no longer exists, for example. Those are reported as blocking reasons so they can be fixed while there is still time, rather than surfacing as a partial publish at 9am.

An empty release has no blockers; there is nothing to go wrong.

One release covers every language

A member is the whole document. Adding a post to a release schedules that post in every language it has.

Per-locale scheduling is not supported: adding a member that names a single locale is refused, and the admin only offers the action from a document's default locale. Each language does have its own published state — see Localization — but that is publishing, not scheduling.