Content Collections

Purpose and Scope

Content collections are Astro’s primary workflow for managing related, structured content such as blog posts, product records, recipes, author profiles, or other entries that share a common shape. The official guide frames a collection as a set of related entries that can come from local files, a single data file, a remote CMS, an API, or another data source. In the repository, that concept is represented by configuration helpers, loader contracts, runtime content plugins, and tests that verify collection-driven pages remain reactive during development.

The important distinction for developers is that a content collection is not only a folder convention. It is a typed data pipeline. A collection definition can declare a schema, attach a loader, and expose entries through Astro’s content APIs with generated TypeScript support. The source types separate classic content and data collections from the newer content layer loader path, and they also define live collections for request-time access. This lets projects choose whether content is gathered at build time, kept in a local store, or loaded through a live source when a request needs it.

Sources: packages/astro/src/content/config.ts, packages/astro/src/content/loaders/types.ts

Relevant Source Files

  • packages/astro/src/content/config.ts — Defines collection configuration shapes, schema context, DataEntry, DataStore, MetaStore, defineLiveCollection(), and type-level constraints for build-time and live collection setup.
  • packages/astro/src/content/loaders/types.ts — Defines the public loader contracts: LoaderContext, Loader, LiveLoader, LoadEntryContext, LoadCollectionContext, parsing, rendering, digest generation, watcher support, and metadata storage.
  • packages/astro/src/content/index.ts — Re-exports internal content-layer entry points used by Astro’s server listeners, type generation, content path utilities, Vite content asset handling, import handling, and virtual modules.
  • packages/astro/src/content/loaders/index.ts — Exposes the built-in file and glob loaders and re-exports loader types for the astro/loaders authoring surface.
  • packages/astro/e2e/content-collections.test.ts — Provides an end-to-end development signal for content collections by starting a fixture dev server and asserting HMR behavior after editing an Astro component used by the collection fixture.
  • packages/astro/src/assets/fonts/infra/fs-font-file-content-resolver.ts — Shows a nearby filesystem content-resolution pattern that combines file identity and file contents for cache-sensitive asset behavior, with Astro error wrapping on filesystem failures.

Core Primitives

A collection definition starts with a configuration object. In packages/astro/src/content/config.ts, Astro models build-time collection configuration as a union of content collections, data collections, and content-layer collections. Content collections default to type: 'content' and do not accept a loader. Data collections explicitly use type: 'data'. Content-layer collections use a loader and may provide a schema or a schema function that receives a SchemaContext. This context includes an image helper whose shape mirrors image metadata fields such as src, width, height, and format.

Typical build-time collection setup, matching the documented public API:

import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'astro:content';
 
const blog = defineCollection({
  loader: glob({ pattern: '**/*.md', base: './src/content/blog' }),
  schema: z.object({
    title: z.string(),
    published: z.date(),
  }),
});
 
export const collections = { blog };

The loader layer is the bridge between collection definitions and actual content sources. LoaderContext gives loader authors the collection name, a mutable data store, a metadata store, Astro config, a logger, and helpers for validation, Markdown rendering, digest generation, and dev-time filesystem watching. A build-time Loader has a unique name and an async load(context) method. It may define a static schema, or provide createSchema() to generate both a schema and extra type text. This is why custom loaders can behave like first-class content providers instead of ad hoc data fetching utilities.

Sources: packages/astro/src/content/config.ts, packages/astro/src/content/loaders/types.ts, packages/astro/src/content/loaders/index.ts

System-to-Code Mapping

The public content collection experience maps to several layers in the Astro package. The user-facing astro:content module is backed by configuration and type-generation machinery, while astro/loaders is backed by the loader exports. packages/astro/src/content/loaders/index.ts is intentionally small: it exports file, glob, and the shared loader types. This mirrors the official docs, where glob() handles directory-oriented local content and file() handles content stored in individual local files or aggregate data files.

packages/astro/src/content/index.ts shows the internal systems that make collection definitions useful at runtime and during development. It exports server listener attachment, content type generation, content path utilities, Vite plugins for content assets and content imports, and a virtual module plugin. Those names describe the lifecycle: read configuration, discover paths, load or import entries, generate types, and expose virtual modules that page code can query. The collection feature is therefore both a data model and a build-tool integration, not just a helper function.

Live collections are represented separately from build-time loaders. defineLiveCollection() accepts a LiveCollectionConfig with a LiveLoader, an optional schema, and a default live type. The implementation checks the importer filename and raises an Astro error if live collections are not defined from src/live.config.ts. That restriction matters because live loaders use a different runtime contract: loadEntry() retrieves one entry by filter, while loadCollection() retrieves a set of entries, also optionally filtered. Use live collections when entries need to be read at request time rather than baked into build output.

Sources: packages/astro/src/content/config.ts, packages/astro/src/content/index.ts, packages/astro/src/content/loaders/types.ts

Build-Time and Live Loader Contracts

Build-time loaders update a data store. The DataStore contract includes get, entries, set, values, keys, delete, clear, and has. Each stored DataEntry has an id, a data object, and optional filePath and body fields. Loader authors should treat id as unique per collection and should call parseData() when they need schema validation and normalized data. They can use renderMarkdown() to turn Markdown content into rendered output and metadata, and generateDigest() to detect whether a source record has changed since the last load.

The metadata store is intentionally smaller. MetaStore is a string key-value store intended for loader state such as sync tokens, timestamps, or remote cursors. Keeping entry data and loader metadata separate is important for repeatable builds and efficient incremental refreshes. When Astro runs in development, LoaderContext.watcher can be used to watch source files and trigger updates. refreshContextData provides a hook for integrations that trigger a loader refresh with additional contextual data, which gives integrations a structured path into the content layer without changing the loader’s public shape.

Live loaders use a read contract rather than a store mutation contract. A LiveLoader has a unique name, loadEntry(context), and loadCollection(context). loadEntry() returns a live data entry, undefined, or an error object. loadCollection() returns a live data collection or an error object. The generic parameters allow a loader to type its returned data, entry filter, collection filter, and error type. This is the source-level reason live collections can support filtered request-time reads while still preserving TypeScript information for callers.

Sources: packages/astro/src/content/loaders/types.ts, packages/astro/src/content/config.ts

Runtime and Development Behavior

During local development, content collections participate in the same feedback loop as pages and components. The e2e test in packages/astro/e2e/content-collections.test.ts starts a dev server for the content-collections fixture, opens the root page, edits ./src/components/MyComponent.astro, and asserts that the heading style updates from red to green through HMR. The test does not document every collection API, but it provides an important signal: content-collection fixtures must continue to work with Astro’s dev server and hot module replacement path.

The source exports in packages/astro/src/content/index.ts explain why development behavior needs multiple cooperating pieces. Content imports, content assets, virtual modules, and type generation all have to remain consistent while the project is running. When a loader updates the store, when an Astro file changes, or when a generated type surface changes, the dev server must keep pages queryable and renderable. For authors, the practical result is that collection-backed pages can be developed like ordinary Astro pages while still benefiting from validation and generated types.

The font file resolver included in the source set is not a collection API, but it illustrates an Astro infrastructure pattern that also matters to content-adjacent systems: filesystem content can affect cache identity. FsFontFileContentResolver returns remote-like URLs unchanged when a path is not absolute, but for absolute paths it combines the URL with the file contents so a renamed or swapped local font does not incorrectly reuse a stale identity. When filesystem reads fail, the resolver wraps the failure in an Astro error. Loader authors should apply the same discipline: derive stable identities from source data and surface failures through Astro’s error model where appropriate.

Sources: packages/astro/e2e/content-collections.test.ts, packages/astro/src/content/index.ts, packages/astro/src/assets/fonts/infra/fs-font-file-content-resolver.ts

Compact Reference

NameKindSource-backed contract
DataEntryInterfaceEntry stored in a collection store with id, data, optional filePath, and optional body.
DataStoreInterfaceMutable collection database with read, write, delete, clear, key, value, and existence operations.
MetaStoreInterfaceString key-value store for loader metadata such as sync state.
SchemaContextTypeProvides collection schema helpers, including image.
CollectionConfigTypeUnion of content, data, and content-layer collection configuration shapes.
LiveCollectionConfigTypeLive collection configuration with a live loader and optional schema.
defineLiveCollection(config)FunctionValidates live collection placement, defaults the collection type to live, and returns the live collection configuration.
LoaderContextInterfaceContext passed to build-time loaders, including stores, logger, Astro config, schema parsing, Markdown rendering, digest generation, watcher, and refresh data.
LoaderTypeBuild-time loader object with name, async load(context), and optional schema or dynamic schema creation.
LiveLoaderInterfaceRequest-time loader with loadEntry(context) and loadCollection(context).
fileExportBuilt-in local file loader exposed from astro/loaders.
globExportBuilt-in local glob loader exposed from astro/loaders.

Next Steps

Use content collections when entries share structure and should be queried through Astro’s content APIs with validation and editor support. Start with glob() for folders of Markdown, MDX, Markdoc, JSON, YAML, or TOML files; use file() when entries live in a single local data file; and reach for a custom build-time loader when content comes from a remote source but can be resolved before build output is produced. Use live collections for request-time reads that need loadEntry() or loadCollection() behavior from src/live.config.ts.

For the next layer down, read the Content Loaders page to design custom loaders against LoaderContext, DataStore, and MetaStore. For authoring Markdown-driven entries, continue to Markdown and MDX Content. For the public import surfaces, use the Astro Modules Reference and API Reference pages to connect astro:content and astro/loaders to application code.