Draft and publish
With drafts, editing a record never changes what visitors see. Edits go to a working copy, and a separate live copy changes only when you publish.
Enabling drafts
Add draftable: true to the model:
const post = makeModel("post", {
name: "Post",
searchFields: ["title"],
draftable: true,
schema: z.object({
title: text({ label: "Title" }),
body: richtext({ label: "Body" }),
}),
});Every record then carries two extra fields, so do not declare fields named status or publishedAt on a draftable model:
| Field | Value |
|---|---|
status |
"draft", "published" or "changed" |
publishedAt |
When the record was published, or null |
If you turn draftable on for a model that already has records, those records predate drafts, so they are treated as live content: when the server starts and nothing has been published yet, njin publishes the records that have no status.
Working copy vs. live copy
| Status | Meaning |
|---|---|
draft |
Never published, or unpublished since |
published |
The working copy is the same as the live copy |
changed |
Published, but the working copy has edits that are not live yet |
A new record starts as draft. Editing a published record makes it changed. The live copy is untouched until you publish again.
Publishing
await post.publish(id); // copy the working copy to the live copy
await post.unpublish(id); // take the live copy offline; the working copy stays as a draft
await post.discard(id); // throw away unpublished edits: the working copy goes back to the live copyOver HTTP: POST /api/post/:id/publish, and likewise /unpublish and /discard.
discardanswers409when the record was never published, because there is nothing to go back to.- Deleting a record removes both its working copy and its live copy.
uniqueis enforced on the working copy.
Reading published content
read() and show() return working copies, which is what the admin panel edits. Nothing filters drafts out of them for you. In public pages and routes, use published:
@let(result = await post.published.read({ sort: 'publishedAt', order: 'desc' }))
@each(item in result.data)
<h2>{{ item.title }}</h2>
@endpost.published.read() and post.published.show(id) accept the same options as read() and show().
To list only drafts in the working copies:
await post.read({ filters: { status: "draft" } });Hooks
Run code after a publish or unpublish with afterPublish(model, fn) and afterUnpublish(model, fn). See Hooks.
With languages
When a draftable model also has i18n: true, each language is drafted and published on its own. See Languages.