Content collections
are the best way to manage and author content in any Astro project. Collections help to organize your documents, validate your frontmatter, and provide automatic TypeScript type-safety for all of your content.
A
content collection
is any top-level directory inside the reserved
src/content
project directory, such as
src/content/newsletter
and
src/content/authors
. Only content collections are allowed inside the
src/content
directory. This directory cannot be used for anything else.
A
collection entry
is any piece of content stored inside of your content collection directory. Entries can use content authoring formats including Markdown (
.md
) and MDX (
.mdx
using the
MDX integration
) or as one of two supported data formats: YAML (
.yaml
) and JSON (
.json
). We recommend using a consistent naming scheme (lower-case, dashes instead of spaces) for your files to make it easier to find and organize your content, but this is not required. You can also
exclude entries from being built
by prefixing the filename with an underscore (_).
Directory
src/content/
Directory
newsletter/
the “newsletter” collection
week-1.md
a collection entry
week-2.md
a collection entry
week-3.md
a collection entry
Once you have a collection, you can start
querying your content
using Astro’s built-in content APIs.
Astro stores important metadata for content collections in an
.astro
directory in your project. No action is needed on your part to maintain or update this directory. You are encouraged to ignore it entirely while working in your project.
The
.astro
directory will be updated for you automatically anytime you run the
astro dev
,
astro build
commands. You can run
astro sync
at any time to update the
.astro
directory manually.
If two files represent different kinds of content (e.g. a blog post and an author profile), they most likely belong in different collections. This is important because many features (frontmatter validation, automatic TypeScript type-safety) require that all entries in a collection share a similar structure.
If you find yourself working with different types of content, you should create multiple collections to represent each type. You can create as many different collections in your project as you’d like.
A content collection is always a top-level folder inside of the
src/content/
directory. You cannot nest one collection inside of another. However, you can use subdirectories to organize your content within a collection.
For example, you can use the following directory structure to organize i18n translations within a single
docs
collection. When you query this collection, you’ll be able to filter the result by language using the file path.
Directory
src/content/
Directory
docs/
this collection uses subdirectories to organize by language
To get the most out of your content collections, create a
src/content/config.ts
file in your project (
.js
and
.mjs
extensions are also supported.) This is a special file that Astro will automatically load and use to configure your content collections.
If you
do not
already extend Astro’s
strict
or
strictest
recommended TypeScript settings in your
tsconfig.json
file, you may need to update your
tsconfig.json
to enable
strictNullChecks
.
tsconfig.json
{
// Note: No change needed if you use "astro/tsconfigs/strict" or "astro/tsconfigs/strictest"
"extends": "astro/tsconfigs/base",
"compilerOptions": {
"strictNullChecks": true
}
}
If you use
.js
or
.mjs
files in an Astro project, you can enable IntelliSense and type checking in your editor by enabling
allowJs
in your
tsconfig.json
:
tsconfig.json
{
// Note: No change needed if you use "astro/tsconfigs/strict" or "astro/tsconfigs/strictest"
Schemas enforce consistent frontmatter or entry data within a collection. A schema
guarantees
that this data exists in a predictable form when you need to reference or query it. If any file violates its collection schema, Astro will provide a helpful error to let you know.
Schemas also power Astro’s automatic TypeScript typings for your content. When you define a schema for your collection, Astro will automatically generate and apply a TypeScript interface to it. The result is full TypeScript support when you query your collection, including property autocompletion and type-checking.
To define your first collection, create a
src/content/config.ts
file if one does not already exist (
.js
and
.mjs
extensions are also supported.) This file should:
Import the proper utilities
from
astro:content
.
Define each collection that you’d like to validate.
This includes a
type
(introduced in Astro v2.5.0) specifying whether the collection contains content authoring formats like Markdown (
type: 'content'
) or data formats like JSON or YAML (
type: 'data'
). It also includes a
schema
that defines the shape of your frontmatter or entry data.
Export a single
collections
object
to register your collections.
You can use
defineCollection()
as many times as you want to create multiple schemas. All collections must be exported from inside the single
collections
object.
src/content/config.ts
const blogCollection = defineCollection({
type: 'content',
schema: z.object({ /* ... */ })
});
const newsletter = defineCollection({
type: 'content',
schema: z.object({ /* ... */ })
});
const authors = defineCollection({
type: 'data',
schema: z.object({ /* ... */ })
});
export const collections = {
'blog': blogCollection,
'newsletter': newsletter,
'authors': authors,
};
As your project grows, you are also free to reorganize your codebase and move logic out of the
src/content/config.ts
file. Defining your schemas separately can be useful for reusing schemas across multiple collections and sharing schemas with other parts of your project.
You can import collection schemas from anywhere, including external npm packages. This can be useful when working with themes and libraries that provide their own collection schemas for you to use.
Astro uses
Zod
to power its content schemas. With Zod, Astro is able to validate every file’s frontmatter within a collection
and
provide automatic TypeScript types when you go to query content from inside your project.
To use Zod in Astro, import the
z
utility from
"astro:content"
. This is a re-export of the Zod library, and it supports all of the features of Zod. See
Zod’s README
for complete documentation on how Zod works and what features are available.
// Example: A cheatsheet of many common Zod datatypes
Collection entries can also “reference” other related entries.
With the
reference()
function from the Collections API, you can define a property in a collection schema as an entry from another collection. For example, you can require that every
space-shuttle
entry includes a
pilot
property which uses the
pilot
collection’s own schema for type checking, autocomplete, and validation.
A common example is a blog post that references reusable author profiles stored as JSON, or related post URLs stored in the same collection:
import { defineCollection, reference, z } from'astro:content';
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
// Reference a single author from the `authors` collection by `id`
author: reference('authors'),
// Reference an array of related posts from the `blog` collection by `slug`
relatedPosts: z.array(reference('blog')),
})
});
const authors = defineCollection({
type: 'data',
schema: z.object({
name: z.string(),
portfolio: z.string().url(),
})
});
export const collections = { blog, authors };
This example blog post specifies the
slug
s of related posts and the
id
of the post author:
When using
type: 'content'
, every content entry generates a URL-friendly
slug
property from its
file
id
. The slug is used to query the entry directly from your collection. It is also useful when creating new pages and URLs from your content.
You can override an entry’s generated slug by adding your own
slug
property to the file frontmatter. This is similar to the “permalink” feature of other web frameworks.
"slug"
is a special, reserved property name that is not allowed in your custom collection
schema
and will not appear in your entry’s
data
property.
Any
references defined in your schema
must be queried separately after first querying your collection entry. You can use the
getEntry()
function again, or
getEntries()
, to retrieve the referenced entry from the returned
data
object.
getCollection()
takes an optional “filter” callback that allows you to filter your query based on an entry’s
id
or
data
(frontmatter) properties. For collections of
type: 'content'
, you can also filter based on
slug
.
You can use this to filter by any content criteria you like. For example, you can filter by properties like
draft
to prevent any draft blog posts from publishing to your blog:
// Example: Filter out content entries with `draft: true`
import { getCollection } from'astro:content';
const publishedBlogEntries = await getCollection('blog', ({ data }) => {
return data.draft !== true;
});
You can also create draft pages that are available when running the dev server, but not built in production:
// Example: Filter out content entries with `draft: true` only when building for production
import { getCollection } from'astro:content';
const blogEntries = await getCollection('blog', ({ data }) => {
The filter argument also supports filtering by nested directories within a collection. Since the
id
includes the full nested path, you can filter by the start of each
id
to only return items from a specific nested directory:
// Example: Filter entries by sub-directory in the collection
import { getCollection } from'astro:content';
const englishDocsEntries = await getCollection('docs', ({ id }) => {
Once you have queried your collection entries, you can access each entry directly inside of your Astro component template. This lets you render HTML for things like links to your content (using the content
slug
) or information about your content (using the
data
property).
A component can also pass an entire content entry as a prop.
If you do this, you can use the
CollectionEntry
utility to correctly type your components props using TypeScript. This utility takes a string argument that matches the name of your collection schema, and will inherit all of the properties of that collection’s schema.
Once queried, you can render Markdown and MDX entries to HTML using the entry
render()
function property. Calling this function gives you access to rendered content and metadata, including both a
<Content />
component and a list of all rendered headings.
Content collections are stored outside of the
src/pages/
directory. This means that no routes are generated for your collection items by default. You will need to manually create a new
dynamic route
to generate HTML pages from your collection entries. Your dynamic route will map the incoming request param (ex:
Astro.params.slug
in
src/pages/blog/[...slug].astro
) to fetch the correct entry inside a collection.
The exact method for generating routes will depend on your build
output
mode: ‘static’ (the default) or ‘server’ (for SSR).
If you are building a static website (Astro’s default behavior), you would use the
getStaticPaths()
function to create multiple pages from a single
src/pages/
component during your build.
Call
getCollection()
inside of
getStaticPaths()
to
query your content or data collection
. Then, create your new URL paths using the
slug
property (content collections) or
id
property (data collections) of each content entry.
src/pages/posts/[...slug].astro
---
import { getCollection } from'astro:content';
// 1. Generate a new path for every collection entry
exportasyncfunctiongetStaticPaths() {
const blogEntries = await getCollection('blog');
return blogEntries.map(entry=> ({
params: { slug: entry.slug }, props: { entry },
}));
}
// 2. For your template, you can get the entry directly from the prop
const { entry } = Astro.props;
const { Content } = await entry.render();
---
<h1>{entry.data.title}</h1>
<Content />
This will generate a new page for every entry in the
blog
collection. For example, an entry at
src/content/blog/hello-world.md
will have a slug of
hello-world
, and therefore its final URL will be
/posts/hello-world/
.
If you are building a dynamic website (using Astro’s SSR support), you are not expected to generate any paths ahead of time during the build. Instead, your page should examine the request (using
Astro.request
or
Astro.params
) to find the
slug
on-demand, and then fetch it using
getEntry()
.
src/pages/posts/[...slug].astro
---
import { getEntry } from"astro:content";
// 1. Get the slug from the incoming server request
const { slug } = Astro.params;
if (slug ===undefined) {
thrownewError("Slug is required");
}
// 2. Query for the entry directly using the request slug
const entry = await getEntry("blog", slug);
// 3. Redirect if the entry does not exist
if (entry ===undefined) {
return Astro.redirect("/404");
}
// 4. (Optional) Render the entry to HTML in the template
If you have an existing Astro project, such as a blog, that uses Markdown or MDX files in subfolders inside
src/pages/
, consider migrating related content or data files to content collections.
If you are working with collections of type
data
, Astro will auto-generate JSON schema files for your editor to get IntelliSense and type-checking. A separate file will be created for each data collection in your project based on your collections defined in
src/content/config.ts
using a library called
zod-to-json-schema
.
This feature requires you to manually set your schema’s file path as the value for
$schema
in each data entry file of the collection:
Alternatively, you can set this value in your editor settings. For example, to set this value in
VSCode’s
json.schemas
setting
, provide the path of files to match and the location of your JSON schema:
If you are working with large collections, you may wish to enable cached builds with the
experimental.contentCollectionCache
flag. This experimental feature optimizes Astro’s build process, enabling unchanged collections to be stored and reused between builds.
In many cases, this can lead to significant build performance improvements.
While this feature stabilizes, you may run into issues with the stored cache. You can always reset your build cache by running the following command:
Astro supports remark or rehype plugins that
modify your frontmatter directly
. You can access this modified frontmatter inside of a content entry by using the
remarkPluginFrontmatter
property returned from
render()
:
The remark and rehype pipelines only run when your content is rendered, which explains why
remarkPluginFrontmatter
is only available after you call
render()
on your content entry. In contrast,
getCollection()
and
getEntry()
cannot return these values directly because they do not render your content.
Several date formats are possible in content collections, but your collection’s schema must match the format used in your Markdown or MDX YAML frontmatter.
YAML uses the
ISO-8601
standard to express dates. Use the format
yyyy-mm-dd
(e.g.
2021-07-28
) along with a schema type of
z.date()
:
src/pages/posts/example-post.md
---
title: My Blog Post
pubDate: 2021-07-08
---
The date format will be specified in UTC if a timezone is not provided. If you need to specify a timezone, you can use the
ISO 8601
format.
src/pages/posts/example-post.md
---
title: My Blog Post
pubDate: 2021-07-08T12:00:00-04:00
---
To render only the
YYYY-MM-DD
from the full UTC timestamp, use the JavaScript
slice
method to remove the timestamp:
To see an example of using
toLocaleDateString
to format the day, month, and year instead, see the
<FormattedDate />
component
in the official Astro blog template.