Content Workflow
Accent CMS provides a publishing workflow that lets you control when content is visible on your site. Pages can be drafts, under review, published, or archived, and you can schedule dates for automatic transitions.
Page Status
Every page has a status field in its frontmatter. The default status is published, so existing content continues to work without changes.
Available Statuses
| Status | Visible by URL | Appears in Listings | Use Case |
|---|---|---|---|
draft | Dev mode only | Dev mode only | Work in progress |
review | Dev mode only | Dev mode only | Ready for editorial review |
published | Yes | Yes | Live content (default) |
archived | Yes | No | Old content, accessible but hidden from navigation |
Setting the Status
Add a status field to your page’s frontmatter:
--- title: My New Article status: draft --- This content is only visible in development mode.
Valid values are draft, review, published, and archived. If omitted, the page defaults to published.
A draft or in-review page is not published, so a link to it from a published page is a 404 for readers. accent validate reports such a link as an error, and accent build fails on it, naming the draft. Publish the page, remove the link, or pass --lax to either command to report it as a warning while you work.
Scheduled Publishing
You can set a future date when a page should become visible using publish_date:
--- title: Product Launch Announcement status: published publish_date: "2026-06-01" --- We are excited to announce...
Before the publish_date, the page is treated as a draft (hidden from public view). On and after the date, the page becomes visible according to its status.
Dates and timestamps
publish_date and unpublish_date take a date, such as 2026-06-01, or an ISO 8601 timestamp, such as 2026-06-01T09:00:00Z or 2026-06-01T09:00:00+02:00. A timestamp with no zone is UTC.
The schedule is kept by the day, in UTC, for accent serve and accent build alike, so a timestamp is rounded to keep its promise: the page is never shown before its publish_date or after its unpublish_date.
| Value | Visible from | Archived from |
|---|---|---|
publish_date: "2026-06-01" | June 1 | – |
publish_date: "2026-06-01T00:00:00Z" | June 1 | – |
publish_date: "2026-06-01T09:00:00Z" | June 2 | – |
unpublish_date: "2026-09-01" | – | September 2 |
unpublish_date: "2026-09-01T18:00:00Z" | – | September 1 |
A static site shows a scheduled page from the first build on or after its day, so rebuild daily to keep a schedule.
A publish_date that is neither a date nor a timestamp keeps the page a draft, and accent validate reports it as an error, so a typo cannot publish an embargoed page early. An unreadable unpublish_date is reported the same way and ignored.
Because the schedule is kept by the day, a publish and unpublish window within one day never shows the page; accent validate reports a schedule whose first shown day is not before its archive day as an error. A schedule written in a slash or dot spelling, such as 01/03/2026, reads month-first, which not every locale writes, so accent validate warns and names the day it read; write the day as YYYY-MM-DD or a timestamp.
Automatic Archiving
Set unpublish_date to automatically archive content after a specific date:
--- title: Summer Sale publish_date: "2026-06-01" unpublish_date: "2026-09-01" --- Limited time offer...
After the unpublish_date, the page is treated as archived: still accessible by direct URL but removed from navigation, listings, and collections.
Date Window Example
Combine both dates to create a visibility window:
--- title: Conference Registration publish_date: "2026-03-01" unpublish_date: "2026-05-15" ---
This page is:
- Hidden before March 1, 2026
- Visible from March 1 through May 15, 2026
- Archived (hidden from listings) after May 15, 2026
Previewing Drafts
By default, draft and review pages are hidden. To preview them during development, enable show_drafts in your config.yaml:
dev: show_drafts: true
With this setting enabled, all draft and review pages are visible when running accent serve. This is useful for content authors who want to preview their work before publishing.
In production mode (--production flag) or when building with accent build, show_drafts is always forced to false, ensuring that unpublished content never appears on your live site.
Template Integration
The current page’s effective status is available in templates as page.status:
{% if page.status == "draft" %}
<div class="draft-notice">
This page is a draft and not yet published.
</div>
{% endif %}
{% if page.status == "archived" %}
<div class="archive-notice">
This content has been archived and may be outdated.
</div>
{% endif %}
Filtering in Collections
Pages excluded by status are automatically removed from template listings (pages, all_pages, and page.collection). You do not need to filter by status in your templates.
Backward Compatibility
The legacy published: false frontmatter field continues to work and is treated as status: draft. New content should use the status field instead:
# Legacy (still works) published: false # Recommended status: draft
When both published: false and an explicit status are present, published: false takes priority and the page is treated as a draft.
Priority Rules
The effective status is resolved in the following order:
published: falsealways maps todraft(backward compatibility)publish_datein the future maps todraft(not yet visible)unpublish_datein the past maps toarchived(expired)- The explicit
statusfield value
A publish_date that cannot be read keeps the page a draft; an unpublish_date that cannot be read is ignored. accent validate reports both as errors.
Redirects
Use the redirect frontmatter field to send visitors from one URL to another. This is useful for moved pages, vanity URLs, or making the root URL forward to a landing page.
Simple Redirect (302)
A string value creates a temporary redirect (HTTP 302):
--- title: Accent CMS redirect: /home ---
When a visitor opens this page, the browser immediately navigates to /home. In serve mode this returns an HTTP 302 response with a Location header and Cache-Control: no-cache, so browsers re-resolve the target on every visit – you can freely change or remove the redirect later. In static builds it generates a meta-refresh HTML file.
Permanent Redirect (301)
Use the detailed form with code: 301 for a deliberate, permanent URL migration:
--- title: Moved Page redirect: url: /new-location code: 301 ---
Browsers cache 301 responses indefinitely, so once a visitor has seen one, they will keep jumping straight to the target even if you later change or remove the redirect. Reserve 301 for URL changes that are truly final; the default 302 is the right choice everywhere else.
External Redirects
Redirect targets can be external URLs:
--- title: GitHub redirect: https://github.com/your-org/your-repo ---
How Redirects Work
| Mode | Behavior |
|---|---|
accent serve | Returns HTTP 301 or 302 with Location header |
accent build | Writes a meta-refresh HTML file |
A redirect page serves no content at its URL, so no index lists it: not content collections (page.collection), not tag or other term pages, not sitemap.xml, not the feeds, not the search index, and not llms.txt or llms-full.txt. A translation that redirects is not one of its siblings’ page.translations either, so no hreflang link or language switcher names it. You don’t need noindex: true or a search.exclude entry for a stub. Redirect pages remain visible in menus and page hierarchy, so you can use them as navigation stubs that point elsewhere.
The redirect target URL is available in templates as page.redirect.
Rewrites
Use the rewrite frontmatter field to serve another page’s content at the current URL. The browser URL does not change, but the visitor sees the target page’s rendered content.
--- title: Getting Started rewrite: /docs/getting-started ---
This is useful for:
- Aliases: Serve the same content at multiple URLs without duplication
- Landing pages: Use a simple URL that renders a more deeply nested page
Rewrite Behavior
- The target page is loaded and rendered using its own template and frontmatter
- The browser URL remains the original page’s URL
- Only one level of rewrite is followed (no chaining)
- If the target does not exist, the original page is served and a warning is logged
The rewrite target URL is available in templates as page.rewrite.
Redirect vs Rewrite
| Redirect | Rewrite | |
|---|---|---|
| Browser URL | Changes to target | Stays the same |
| HTTP response | 301 or 302 | 200 |
| Use case | Moved pages, vanity URLs | Aliases, landing pages |
| Frontmatter | redirect: /target | rewrite: /target |
If both redirect and rewrite are set on the same page, redirect takes precedence. The content validator warns about this conflict.
Processing Control
The process frontmatter field controls which rendering stages apply to a page. This is separate from the publishing workflow but is another per-page control that affects how content is handled.
Disabling Markdown Rendering
Set process.markdown to false to skip the markdown parser entirely:
--- title: Custom HTML Page process: markdown: false --- <div class="custom-layout"> <h1>Raw HTML</h1> <p>This content is passed to the template without markdown processing.</p> </div>
The page remains routable, indexed, and visible according to its status. Only the rendering step changes: the content body goes directly to the template as-is instead of being parsed as markdown.
When omitted, process.markdown defaults to true (standard markdown rendering).
See Processing Control for full details on what is affected.
Content Versioning
Accent CMS supports versioned content trees for maintaining multiple versions of a documentation section (or any content area) side by side. Versions use a sparse folder structure: only pages that differ between versions need their own files. Missing pages automatically fall back to an older version.
Setting Up Versions
Create version folders inside your content section:
site/content/main/
04.docs/
v1.0/
01.getting-started/default.md
02.api-reference/default.md
v1.1/
01.getting-started/default.md # updated for v1.1
03.migration/default.md # new in v1.1
In this example, /docs/v1.1/api-reference automatically serves the v1.0 content because v1.1 has no 02.api-reference/ folder. Only changed or new pages need to exist in the newer version.
Versioning Configuration
Register each versioning root in config.yaml:
versioning: roots: /docs: versions: v1.0: label: "1.0 (LTS)" badge: lts v1.1: label: "1.1 (Latest)" badge: latest fallback: v1.0 default: v1.1 redirects: v0.9: v1.0
See the Configuration Reference for all fields.
How Versioning Works
| URL | Behavior |
|---|---|
/docs/v1.1/getting-started | Serves v1.1 content directly |
/docs/v1.1/api-reference | Falls back to v1.0 content (transparent, 200 response) |
/docs | Redirects to /docs/v1.1 (the default version) |
/docs/v0.9/getting-started | Redirects to /docs/v1.0/getting-started (deprecated version) |
Fallback is transparent: the URL stays the same and the response is 200. This applies in both accent serve and accent build (which writes fallback content to the target version’s output path).
Fallback Chains
Versions can chain fallbacks: v1.2 -> v1.1 -> v1.0. The resolver walks the chain up to 5 levels deep. Circular chains are detected at startup and rejected with a clear error message.
Search engines
A versioned site publishes one copy of each page per version, and by default every copy names itself as canonical. Search engines then choose which of the near-identical copies to show, and often pick an old one. Two settings on a versioning root change that:
versioning: roots: /docs: versions: v1.0: label: "1.0" state: released v1.1: label: "1.1" state: released v1.2-dev: label: "1.2 (Dev)" state: dev fallback: v1.1 default: v1.1 canonical: default noindex: [dev]
canonical: defaultmakes every page in another version name the default version’s copy as canonical, so search results land on the version/docsredirects to./docs/v1.0/getting-startednames/docs/v1.1/getting-started. A page keeps naming itself when the default version doesn’t serve it, directly or through its fallback chain, or when that copy is a draft or hasnoindex: true.noindexlists version states to keep out of search engines. Pages in those versions get anoindexrobots tag and name themselves as canonical. Use[dev]so docs for unreleased features don’t show up in search results.
The sitemap follows the same rules. It lists a page only when the page names itself as canonical and isn’t noindex, so it never contradicts a page’s own head. It also leaves out the versioning root itself, such as /docs, which redirects to the default version. Both settings key on the default version and on version states, so they keep working when you release a new version without further edits.
Pages in older versions stop appearing in search results. Readers on an older release reach the current docs from a search engine and switch back with the version dropdown. If your readers stay on older releases for a long time, such as an LTS line, leave canonical at its default, self.
accent build and accent serve produce the same tags and sitemap. Themes don’t need any changes: the canonical link and robots tag come from page.canonical_url and page.noindex, which the default theme’s partials/head_meta.html.jinja already renders.
Template Integration
Templates receive a version context variable on versioned pages (see the Page Context guide for all properties). Include the version dropdown partial in any template:
{% include "partials/version-dropdown.html.jinja" %}
Show a notice when content comes from a fallback version:
{% if version.is_fallback %}
<div class="notice">
This page has not been updated for {{ version.label }}.
You are viewing the {{ version.source }} version.
</div>
{% endif %}
Quick Reference
# Simple draft --- title: Work in Progress status: draft --- # Scheduled for future publication --- title: Upcoming Feature publish_date: "2026-06-01" --- # Time-limited content --- title: Holiday Special publish_date: "2026-12-20" unpublish_date: "2026-12-26" --- # Archived content (accessible by URL, hidden from listings) --- title: Old Documentation status: archived --- # Under editorial review --- title: Pending Approval status: review --- # Raw HTML page (no markdown processing) --- title: Design System process: markdown: false --- # Temporary redirect (302, the default) --- title: Seasonal redirect: /winter-sale --- # Permanent redirect (301, deliberate URL migration) --- title: Old Page redirect: url: /new-page code: 301 --- # Rewrite (serve another page's content) --- title: Alias rewrite: /docs/getting-started ---