
TL;DR
- Astro has no built-in draft system, so you add one manually using a
draft: truefrontmatter property and a build-time filter. - Draft posts are hidden by default at build time; they only appear when you run the build with a
--buildDraftsargument. - You need to handle drafts in three places: the content schema, the collection query, and any page entry points.
Why Astro Needs a Manual Draft System
Astro does not ship with a draft feature. If you publish a content collection without filtering, every file in that folder gets built and served, including work-in-progress posts.
The workaround is a frontmatter property combined with a build-time filter. It takes about five minutes to set up and gives you the same behavior you would expect from a CMS draft system.
This guide is based on how the draft system is implemented in Astroplate, a free Astro starter template built with Astro, Tailwind CSS, and TypeScript. If you want a working reference, the full implementation is available there.
Step 1: Add a draft Property to Your Frontmatter
In any Markdown or MDX file you want to hide from the published build, add draft: true to the frontmatter:
---
title: "My Draft Post"
description: "This post is a draft."
draft: true
---
Leave the field out entirely on published posts. The field is optional, so Astro will not throw an error if it is missing.
Step 2: Add draft to the Content Schema
In content.config.ts, define draft as an optional boolean so Astro accepts it without a schema validation error:
draft: z.boolean().optional()
Without this, Astro may warn or error when it encounters the draft field in your frontmatter.
Step 3: Filter Drafts in Your Collection Query
The main filtering logic goes in contentParser.astro (or wherever you call getCollection). Check for a --buildDrafts argument in the build process and exclude drafts when it is absent:
const buildDrafts = process.argv.includes("--buildDrafts");
const allPages = await getCollection(collectionName, ({ data, id }) => {
if (!buildDrafts && (data as PageData)?.draft) {
return false;
}
return !id.startsWith("-");
});
This keeps draft posts out of every page that uses the collection - index pages, category pages, RSS feeds, and anything else that loops over your content.
Step 4: Block Draft Pages at the Route Level
Filtering the collection covers list views, but a user who knows the URL can still access a draft page directly if you do not also block it at the route level.
In index.astro and [slug].astro, add this check after you retrieve the page data:
const buildDrafts = process.argv.includes("--buildDrafts");
if (!buildDrafts && postIndex.data.draft) {
return Astro.redirect("/404");
}
This redirects anyone who hits the URL of a draft page to a 404 unless the build was explicitly run with draft support.
Step 5: Build with Drafts Included
To preview draft content during development or a staging build, pass the --buildDrafts argument:
npm run build -- --buildDrafts
This tells every filter in the project to include draft items. Remove it for production builds.
How the Pieces Work Together
This draft system has four parts, each covering a different surface:
| Part | What it covers |
|---|---|
draft: true in frontmatter | Marks the content file as a draft |
draft in content schema | Prevents Astro schema validation errors |
Filter in getCollection | Hides drafts from list pages and feeds |
Redirect in [slug].astro | Blocks direct URL access to draft pages |
All four need to be in place for drafts to be fully hidden in a production build.
You can see this entire pattern implemented in Astroplate - a free, open-source Astro starter template by Zeon Studio. It is a good starting point if you want draft support, search indexing, and content collections already wired up together.
Frequently Asked Questions
Does Astro have a built-in draft mode?
No. As of Astro 6, there is no native draft feature. You need to add one manually using a frontmatter property and a build-time filter, as described in this guide.
Can I preview draft posts in astro dev mode?
Yes, but only if you also update your dev server config to pass --buildDrafts, or you temporarily remove the draft filter during local development. The filter checks process.argv, so it applies in both dev and build modes.
Do I need to add the frontmatter field to every published post?
No. The draft field is optional in the schema. Posts without a draft field are treated as published by default.
Is there a working example of this draft system I can reference?
Yes. Astroplate is a free Astro starter template by Zeon Studio that has this draft system fully implemented. It covers the content schema, collection filter, route-level redirect, and JSON exclusion out of the box.
Can I use this pattern with MDX files?
Yes. The draft frontmatter property and the getCollection filter work the same way for both .md and .mdx files.



