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

StatusVisible by URLAppears in ListingsUse Case
draftDev mode onlyDev mode onlyWork in progress
reviewDev mode onlyDev mode onlyReady for editorial review
publishedYesYesLive content (default)
archivedYesNoOld 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.

ValueVisible fromArchived 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:

  1. published: false always maps to draft (backward compatibility)
  2. publish_date in the future maps to draft (not yet visible)
  3. unpublish_date in the past maps to archived (expired)
  4. The explicit status field 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

ModeBehavior
accent serveReturns HTTP 301 or 302 with Location header
accent buildWrites 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

RedirectRewrite
Browser URLChanges to targetStays the same
HTTP response301 or 302200
Use caseMoved pages, vanity URLsAliases, landing pages
Frontmatterredirect: /targetrewrite: /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

URLBehavior
/docs/v1.1/getting-startedServes v1.1 content directly
/docs/v1.1/api-referenceFalls back to v1.0 content (transparent, 200 response)
/docsRedirects to /docs/v1.1 (the default version)
/docs/v0.9/getting-startedRedirects 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: default makes every page in another version name the default version’s copy as canonical, so search results land on the version /docs redirects to. /docs/v1.0/getting-started names /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 has noindex: true.
  • noindex lists version states to keep out of search engines. Pages in those versions get a noindex robots 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
---