Content collections
本頁內容尚未翻譯。
新增於:
astro@2.0.0
Content collections are the best way to manage sets of content in any Astro project. Collections help to organize and query your documents, enable Intellisense and type checking in your editor, and provide automatic TypeScript type-safety for all of your content.
Astro provides performant, scalable APIs to load, query, and render content from anywhere: stored locally in your project, hosted remotely, or fetched live from frequently-updating sources.
What are Content Collections?
Section titled “What are Content Collections?”A content collection is a set of related, structurally identical data. This data can be stored in one or several files locally (e.g. a folder of individual Markdown files of blog posts, a single JSON file of product descriptions) or fetched from remote sources such as a database, CMS, or API endpoint. Each member of the collection is called an entry.
目錄src/
- …
目錄newsletter/ the “newsletter” collection
- week-1.md a collection entry
- week-2.md a collection entry
- week-3.md a collection entry
目錄authors/ the “author” collection
- authors.json a single file containing all collection entries
Collections are defined by the location and shape of its entries and provide a convenient way to query and render your content and associated metadata. You can create a collection any time you have a group of related data or content, stored in the same location, that shares a common structure.
Two types of content collections are available to allow you to work with data fetched either at build time or at request time. Both build-time collections and live updating collections use:
- A required
loaderto retrieve your content and metadata from wherever it is stored and make it available to your project through content-focused APIs. - An optional collection
schemathat allows you to define the expected shape of each entry for type safety, autocomplete, and validation in your editor.
Collections stored locally in your project or on your filesystem can use one of Astro’s provided build-time loaders to fetch data from Markdown, MDX, Markdoc, YAML, TOML, or JSON files. Point Astro to the location of your content, define your data shape, and you’re good to go with a blog or similarly content-heavy, mostly static site in no time!
With community-built loaders or by building a custom build-time collection loader or live loader yourself, you can fetch remote data from any external source, such as a CMS, database, or headless payment system, either at build time or live on demand.
When to create a collection
Section titled “When to create a collection”Define your data as a collection when:
- You have multiple files or data to organize that share the same overall structure (e.g. a directory of blog posts written in Markdown which all have the same frontmatter properties).
- You have existing content stored remotely, such as in a CMS, and want to take advantage of the collections helper functions instead of using
fetch()or SDKs. - You need to fetch (tens of) thousands of related pieces of data at build time, and need a querying and caching method that handles at scale.
Much of the benefit of using collections comes from:
- Defining a common data shape to validate that an individual entry is “correct” or “complete”, avoiding errors in production.
- Content-focused APIs designed to make querying intuitive (e.g.
getCollection()instead ofimport.meta.glob()) when importing and rendering content on your pages. - Access to both built-in loaders and access to the low-level Content Loader API for retrieving your content. There are additionally several third-party and community-built loaders available, and you can build your own custom loader to fetch data from anywhere.
- Performance and scalability. Build-time content collections data can be cached between builds and is suitable for tens of thousands of content entries.
When not to create a collection
Section titled “When not to create a collection”Collections provide excellent structure, safety, and organization when you have multiple pieces of content that must share the same properties.
Collections may not be your solution if:
- You have only one or a small number of different content pages. Consider making individual page components such as
src/pages/about.astrowith your content directly instead. - You are displaying files that are not processed by Astro, such as PDFs. Place these static assets in the
public/directory of your project instead. - Your data source has its own SDK/client library for imports that is incompatible with or does not offer a content loader, and you prefer to use it directly.
Types of collections
Section titled “Types of collections”Build-time content collections are updated at build time, and data is saved to a storage layer. This provides excellent performance for most content, but may not be suitable for frequently updating data sources requiring up-to-the-moment data freshness, such as live stock prices.
For the best performance and scalability, use build-time content collections when one or more of these is true:
- Performance is critical and you want to prerender data at build time.
- Your data is relatively static (e.g., blog posts, documentation, product descriptions).
- You want to benefit from build-time optimization and caching.
- You need to process MDX or perform image optimization.
- Your data can be fetched once and reused across multiple builds.
See the official Astro blog starter template to get up and running quickly with an example of using the built-in glob() loader and defining a schema for a collection of local Markdown or MDX blog posts.
Live content collections fetch their data at runtime rather than build time. This allows you to access frequently updated data from CMSs, APIs, databases, or other sources using a unified API, without needing to rebuild your site when the data changes. However, this can come at a performance cost since data is fetched at each request and returned directly with no data store persistence.
Live content collections are designed for data that changes frequently and needs to be up-to-date when a page is requested. Consider using them when one or more of these is true:
- You need real-time information (e.g. user-specific data, current stock levels).
- You want to avoid constant rebuilds for content that changes often.
- Your data updates frequently (e.g. up-to-the-minute product inventory, prices, availability).
- You need to pass dynamic filters to your data source based on user input or request parameters.
- You’re building preview functionality for a CMS where editors need to see draft content immediately.
Both kinds of collections can exist in the same project, so you can always choose the best type of collection for each individual data source. For example, a build-time collection can manage product descriptions, while a live collection can manage content inventory.
Both types of collections use similar APIs (e.g. getCollection() and getLiveCollection()), so that working with collections will feel familiar no matter which one you choose, while still ensuring that you always know which type of collection you are working with.
We suggest using build-time content collections whenever possible, and using live collections when your content needs updating in real time and the performance tradeoffs are acceptable. Additionally, live content collections have some limitations compared to build-time collections:
- No MDX support: MDX cannot be rendered at runtime
- No image optimization: Images cannot be processed at runtime
- Performance considerations: Data is fetched on each request (unless cached)
- No data store persistence: Data is not saved to the content layer data store
TypeScript configuration for collections
Section titled “TypeScript configuration for collections”Content collections rely on TypeScript to provide Zod validation, Intellisense, and type checking in your editor. If you are not extending one of Astro’s strict or strictest TypeScript settings, you will need to ensure the following compilerOptions are set in your tsconfig.json:
{ // Included with "astro/tsconfigs/strict" or "astro/tsconfigs/strictest" "extends": "astro/tsconfigs/base", "compilerOptions": { "strictNullChecks": true, // add if using `base` template "allowJs": true // required, and included with all Astro templates }}Build-time content collections
Section titled “Build-time content collections”All of your build-time content collections are defined in a special src/content.config.ts file (.js and .mjs extensions are also supported) using defineCollection(), and then a single collections object is exported for use in your project.
Each individual collection configures:
- a build-time
loaderfor a data source (required) - a build-time
schemafor type safety (optional, but highly recommended!)
// 1. Import utilities from `astro:content`import { defineCollection, z } from 'astro:content';
// 2. Import loader(s)import { glob } from 'astro/loaders';
// 3. Define a `loader` and `schema` for each collectionconst blog = defineCollection({ loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }), schema: z.object({ title: z.string(), description: z.string(), pubDate: z.coerce.date(), updatedDate: z.coerce.date().optional(), }),});
// 4. Export a single `collections` object to register your collection(s)export const collections = { blog };You can then use the dedicated getCollection() and getEntry() functions to query your content collections data and render your content.
You can choose to generate page routes from your build-time collection entries at build time for an entirely static, prerendered site. Or, you can render your build-time collections on demand, choosing to delay building your page until it is first requested. This is useful when you have a large number of pages (e.g. thousands or tens of thousands) and want to delay building a static page until it is needed.
Build-time collection loaders
Section titled “Build-time collection loaders”Astro provides two built-in loader functions (glob() and file()) for fetching your local content at build time, as well as access to the Content Loader API to construct your own loaders to fetch remote data.
The glob() loader
Section titled “The glob() loader”The glob() loader fetches entries from directories of Markdown, MDX, Markdoc, JSON, YAML, or TOML files from anywhere on the filesystem. If you store your content entries locally using one file per entry, such as a directory of blog posts, then the glob() loader is all you need to access your content.
This loader accepts a pattern of entry files to match using glob patterns supported by micromatch, and a base file path of where your files are located. Each entry’s id will be automatically generated from its file name.
import { defineCollection } from 'astro:content';import { glob } from 'astro/loaders';
const blog = defineCollection({ loader: glob({ pattern: "**/*.md", base: "./src/data/blog" }),});
export const collections = { blog };The file() loader
Section titled “The file() loader”The file() loader fetches multiple entries from a single local file. This is useful if your content entries are stored as multiple objects within a single JSON or TOML file. Each entry in the file must have a unique id key property.
It accepts a file path to your file and optionally a parser function for data files it cannot parse automatically. Use this loader when your data file can be parsed as an array of objects.
import { defineCollection } from 'astro:content';import { file } from 'astro/loaders';
const dogs = defineCollection({ loader: file("src/data/dogs.json"),});
export const collections = { dogs };parser function
Section titled “parser function”The file() loader accepts a second argument that defines a parser function. This allows you to specify a custom parser (e.g. csv-parse) to create a collection from a file’s contents.
The file() loader will automatically detect and parse (based on their file extension) a single array of objects from JSON and YAML files, and will treat each top-level table as an independent entry in TOML files. Support for these file types is built-in, and there is no need for a parser unless you have a nested JSON document. To use other files, such as .csv, you will need to create a parser function.
The following example shows importing a CSV parser, then loading a cats collection into your project by passing both a file path and parser function to the file() loader:
import { defineCollection } from "astro:content";import { file } from "astro/loaders";import { parse as parseCsv } from "csv-parse/sync";
const cats = defineCollection({ loader: file("src/data/cats.csv", { parser: (text) => parseCsv(text, { columns: true, skipEmptyLines: true }), }),});Nested .json documents
Section titled “Nested .json documents”The parser argument also allows you to load a single collection from a nested JSON document. For example, this JSON file contains multiple collections:
{"dogs": [{}], "cats": [{}]}You can separate these collections by passing a custom parser to the file() loader for each collection:
import { file } from "astro/loaders";import { defineCollection } from "astro:content";
const dogs = defineCollection({ loader: file("src/data/pets.json", { parser: (text) => JSON.parse(text).dogs })});const cats = defineCollection({ loader: file("src/data/pets.json", { parser: (text) => JSON.parse(text).cats })});Custom build-time loaders
Section titled “Custom build-time loaders”You can build a custom loader to fetch remote content from any data source, such as a CMS, a database, or an API endpoint.
Using a loader to fetch your data will automatically create a collection from your remote data. This gives you all the benefits of local collections, including collection-specific API helpers such as getCollection() and render() to query and display your data, as well as schema validation.
Find community-built and third-party loaders in the Astro integrations directory.
Inline build-time loaders
Section titled “Inline build-time loaders”You can define a loader inline, inside your collection, as an async function that returns an array of entries.
This is useful for loaders that don’t need to manually control how the data is loaded and stored. Whenever the loader is called, it will clear the store and reload all the entries.
import { defineCollection } from "astro:content";
const countries = defineCollection({ loader: async () => { const response = await fetch("https://restcountries.com/v3.1/all"); const data = await response.json(); // Must return an array of entries with an id property, or an object with IDs as keys and entries as values return data.map((country) => ({ id: country.cca3, ...country, })); },});The returned entries are stored in the collection and can be queried using the getCollection() and getEntry() functions.
Loader objects
Section titled “Loader objects”For more control over the loading process, you can use the Content Loader API to create a loader object. For example, with access to the load method directly, you can create a loader that allows entries to be updated incrementally or clears the store only when necessary.
Similar to creating an Astro integration or Vite plugin, you can distribute your loader as an npm package that others can use in their projects.
Defining the collection schema
Section titled “Defining the collection schema”Schemas enforce consistent frontmatter or entry data within a collection through Zod validation. 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.
In order for Astro to recognize a new or updated schema, you may need to restart the dev server or sync the content layer (s + enter) to define the astro:content module.
Providing a schema is optional, but highly recommended! If you choose to use a schema, then every frontmatter or data property of your collection entries must be defined using a Zod data type:
import { defineCollection, z } from 'astro:content';import { glob, file } from 'astro/loaders';
const blog = defineCollection({ loader: glob({ pattern: "**/*.md", base: "./src/data/blog" }), schema: z.object({ title: z.string(), description: z.string(), pubDate: z.coerce.date(), updatedDate: z.coerce.date().optional(), })});const dogs = defineCollection({ loader: file("src/data/dogs.json"), schema: z.object({ id: z.string(), breed: z.string(), temperament: z.array(z.string()), }),});
export const collections = { blog, dogs };Defining datatypes with Zod
Section titled “Defining datatypes with Zod”Astro uses Zod to power its content schemas. With Zod, Astro is able to validate every file’s data within a collection and provide automatic TypeScript types when you 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 3.
// Example: A cheatsheet of many common Zod datatypesimport { z, defineCollection } from 'astro:content';
defineCollection({ schema: z.object({ isDraft: z.boolean(), title: z.string(), sortOrder: z.number(), image: z.object({ src: z.string(), alt: z.string(), }), author: z.string().default('Anonymous'), language: z.enum(['en', 'es']), tags: z.array(z.string()), footnote: z.string().optional(),
// In YAML, dates written without quotes around them are interpreted as Date objects publishDate: z.date(), // e.g. 2024-09-17
// Transform a date string (e.g. "2022-07-08") to a Date object updatedDate: z.string().transform((str) => new Date(str)),
authorContact: z.string().email(), canonicalURL: z.string().url(), })})Zod schema methods
Section titled “Zod schema methods”All Zod schema methods (e.g. .parse(), .transform()) are available, with some limitations. Notably, performing custom validation checks on images using image().refine() is unsupported.
Defining collection references
Section titled “Defining collection references”Collection entries can also “reference” other related entries.
With the reference() function, 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';import { glob } from 'astro/loaders';
const blog = defineCollection({ loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }), 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 `id` relatedPosts: z.array(reference('blog')), })});
const authors = defineCollection({ loader: glob({ pattern: '**/*.json', base: "./src/data/authors" }), schema: z.object({ name: z.string(), portfolio: z.string().url(), })});
export const collections = { blog, authors };This example blog post specifies the ids of related posts and the id of the post author:
---title: "Welcome to my blog"author: ben-holmes # references `src/data/authors/ben-holmes.json`relatedPosts:- about-me # references `src/data/blog/about-me.md`- my-year-in-review # references `src/data/blog/my-year-in-review.md`---These references will be transformed into objects containing a collection key and an id key, allowing you to easily query them in your templates.
Defining custom IDs
Section titled “Defining custom IDs”When using the glob() loader with Markdown, MDX, Markdoc, JSON, or TOML files, every content entry id is automatically generated in an URL-friendly format based on the content filename. The id 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 id by adding your own slug property to the file frontmatter or data object for JSON files. This is similar to the “permalink” feature of other web frameworks.
---title: My Blog Postslug: my-custom-id/supports/slashes---Your blog post content here.{ "title": "My Category", "slug": "my-custom-id/supports/slashes", "description": "Your category description here."}Querying build-time collections
Section titled “Querying build-time collections”Astro provides helper functions to query a build-time collection and return one or more content entries.
getCollection()fetches an entire collection and returns an array of entries.getEntry()fetches a single entry from a collection.
These return entries with a unique id, a data object with all defined properties, and will also return a body containing the raw, uncompiled body of a Markdown, MDX, or Markdoc document.
---import { getCollection, getEntry } from 'astro:content';
// Get all entries from a collection.// Requires the name of the collection as an argument.const allBlogPosts = await getCollection('blog');
// Get a single entry from a collection.// Requires the name of the collection and `id`const poodleData = await getEntry('dogs', 'poodle');---The sort order of generated collections is non-deterministic and platform-dependent. This means that if you are calling getCollection() and need your entries returned in a specific order (e.g. blog posts sorted by date), you must sort the collection entries yourself:
---import { getCollection } from 'astro:content';
const posts = (await getCollection('blog')).sort( (a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf(),);---CollectionEntry type.
Using content in Astro templates
Section titled “Using content in Astro templates”After querying your collections, you can access each entry’s content and metadata directly inside of your Astro component template.
For example, you can create a list of links to your blog posts, displaying information from your entry’s frontmatter using the data property:
---import { getCollection } from 'astro:content';const posts = await getCollection('blog');---<h1>My posts</h1><ul> {posts.map(post => ( <li><a href={`/blog/${post.id}`}>{post.data.title}</a></li> ))}</ul>Rendering body content
Section titled “Rendering body content”Once queried, you can render Markdown and MDX entries to HTML using the render() function from astro:content. Calling this function gives you access to rendered HTML content, including both a <Content /> component and a list of all rendered headings.
---import { getEntry, render } from 'astro:content';
const entry = await getEntry('blog', 'post-1');if (!entry) { // Handle Error, for example: throw new Error('Could not find blog post 1');}const { Content, headings } = await render(entry);---<p>Published on: {entry.data.published.toDateString()}</p><Content /><Content /> to replace HTML elements with custom alternatives.
Passing content as props
Section titled “Passing content as props”A component can also pass an entire collection entry as a prop.
You can use the CollectionEntry utility to correctly type your component’s 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.
---import type { CollectionEntry } from 'astro:content';interface Props { post: CollectionEntry<'blog'>;}
// `post` will match your 'blog' collection schema typeconst { post } = Astro.props;---Filtering collection queries
Section titled “Filtering collection queries”getCollection() takes an optional “filter” callback that allows you to filter your query based on an entry’s id or data properties.
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 productionimport { getCollection } from 'astro:content';const blogEntries = await getCollection('blog', ({ data }) => { return import.meta.env.PROD ? data.draft !== true : true;});---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 collectionimport { getCollection } from 'astro:content';const englishDocsEntries = await getCollection('docs', ({ id }) => { return id.startsWith('en/');});---Accessing referenced data
Section titled “Accessing referenced data”Any references defined in your schema must be queried separately after first querying your collection entry. Since the reference() function transforms a reference to an object with collection and id as keys, you can use the getEntry() function to return a single referenced item, or getEntries() to retrieve multiple referenced entries from the returned data object.
---import { getEntry, getEntries } from 'astro:content';
const blogPost = await getEntry('blog', 'welcome');
// Resolve a singular reference (e.g. `{collection: "authors", id: "ben-holmes"}`)const author = await getEntry(blogPost.data.author);// Resolve an array of references// (e.g. `[{collection: "blog", id: "about-me"}, {collection: "blog", id: "my-year-in-review"}]`)const relatedPosts = await getEntries(blogPost.data.relatedPosts);---
<h1>{blogPost.data.title}</h1><p>Author: {author.data.name}</p>
<!-- ... -->
<h2>You might also like:</h2>{relatedPosts.map(post => ( <a href={post.id}>{post.data.title}</a>))}Live content collections
Section titled “Live content collections”Live collections use a different API than build-time content collections, although the configuration and helper functions are designed to feel familiar.
Key differences include:
- Execution time: Run at request time instead of build time
- Configuration file: Use
src/live.config.tsinstead ofsrc/content.config.ts - Collection definition: Use
defineLiveCollection()instead ofdefineCollection() - Loader API: Implement
loadCollectionandloadEntrymethods instead of theloadmethod - Data return: Return data directly instead of storing in the data store
- User-facing functions: Use
getLiveCollection()/getLiveEntry()instead ofgetCollection()/getEntry()
Additionally, you must have an adapter configured for on-demand rendering of live collection data.
Define your live collections in the special file src/live.config.ts (separate from your src/content.config.ts for build-time collections, if you have one).
Each individual collection configures:
- a live
loaderfor your data source, and optionally for type safety (required) - a live collection
schemafor type safety (optional)
Unlike for build-time collections, there are no built-in live loaders available. You will need to create a custom live loader for your specific data source or find a third-party loader to pass to your live collection’s loader property.
You can optionally include type safety in your live loaders. Therefore, defining a Zod schema for live collections is optional. However, if you provide one, it will take precedence over the live loader’s types.
// Define live collections for accessing real-time dataimport { defineLiveCollection } from 'astro:content';import { storeLoader } from '@mystore/astro-loader';
const products = defineLiveCollection({ loader: storeLoader({ apiKey: process.env.STORE_API_KEY, endpoint: 'https://api.mystore.com/v1', }),});
// Export a single `collections` object to register your collection(s)export const collections = { products };You can then use the dedicated getLiveCollection() and getLiveEntry() functions to access your live data and render your content.
You can generate page routes from your live collection entries on demand, fetching your data fresh at runtime upon each request without needing a rebuild of your site like build-time collections do. This is useful when accessing live, up-to-the-moment data is more important than having your content available in a performant data storage layer that persists between site builds.
Creating a live loader
Section titled “Creating a live loader”A live loader is an object with two methods: loadCollection() and loadEntry(). These methods handle errors gracefully and return either data or an Error object.
The standard pattern for a live loader is to export a function that returns this loader object, allowing you to pass configuration options like API keys or endpoints:
import type { LiveLoader } from 'astro/loaders';import { fetchFromCMS } from './cms-client.js';
interface Article { id: string; title: string; content: string; author: string;}
export function articleLoader(config: { apiKey: string }): LiveLoader<Article> { return { name: 'article-loader', loadCollection: async ({ filter }) => { try { const articles = await fetchFromCMS({ apiKey: config.apiKey, type: 'article', filter, });
return { entries: articles.map((article) => ({ id: article.id, data: article, })), }; } catch (error) { return { error: new Error(`Failed to load articles: ${error.message}`), }; } }, loadEntry: async ({ filter }) => { try { // filter will be { id: "some-id" } when the second parameter of getLiveEntry() is called with a string const article = await fetchFromCMS({ apiKey: config.apiKey, type: 'article', id: filter.id, });
if (!article) { return { error: new Error('Article not found'), }; }
return { id: article.id, data: article, }; } catch (error) { return { error: new Error(`Failed to load article: ${error.message}`), }; } }, };}Providing rendered content
Section titled “Providing rendered content”To render live content using the same render() function and <Content /> component available to build-time collections, your live loader must return a rendered property in the entry.
If the loader does not return a rendered property for an entry, the <Content /> component will render nothing.
// ...export function articleLoader(config: { apiKey: string }): LiveLoader<Article> { return { name: 'article-loader', loadEntry: async ({ filter }) => { try { const article = await fetchFromCMS({ apiKey: config.apiKey, type: 'article', id: filter.id, });
return { id: article.id, data: article, rendered: { // Assuming the CMS returns HTML content html: article.htmlContent, }, }; } catch (error) { return { error: new Error(`Failed to load article: ${error.message}`), }; } }, // ... };}You can then render both content and metadata from live collection entries in pages using the same method as built-time collections.
Type safety in live loaders
Section titled “Type safety in live loaders”You can provide type safety for live collections by using a Zod schema like you do in build-time collections.
Live loaders can also provide their own type safety by passing generic types to the LiveLoader interface. You can define the types for your collection and entry data, as well as custom filter types for querying, and custom error types for error handling in the live loader itself.
Defining data types
Section titled “Defining data types”Live loaders can choose to pass generic types to the LiveLoader interface for the data they return. This allows TypeScript to provide type checking and autocompletion when working with the data in your components.
import type { LiveLoader } from 'astro/loaders';import { fetchProduct, fetchCategory, type Product } from './store-client';
export function storeLoader(): LiveLoader<Product> { // ...}When you use getLiveCollection() or getLiveEntry() to access your live data, TypeScript will infer the types based on the loader’s definition:
---export const prerender = false; // Not needed in 'server' mode
import { getLiveEntry } from 'astro:content';const { entry: product } = await getLiveEntry('products', '123');// TypeScript knows product.data is of type Productconsole.log(product?.data.name);---Defining custom filter types
Section titled “Defining custom filter types”Live loaders can define custom filter types for both getLiveCollection() and getLiveEntry(). This enables type-safe querying that matches your API’s capabilities, making it easier for users to discover available filters and ensure they are used correctly. If you include JSDoc comments in your filter types, the user will see these in their IDE as hints when using the loader.
import type { LiveLoader } from 'astro/loaders';import { fetchProduct, fetchCategory, type Product } from './store-client';
interface CollectionFilter { category?: string; /** Minimum price to filter products */ minPrice?: number; /** Maximum price to filter products */ maxPrice?: number;}
interface EntryFilter { /** Alias for `sku` */ id?: string; slug?: string; sku?: string;}
export function productLoader(config: { apiKey: string; endpoint: string;}): LiveLoader<Product, EntryFilter, CollectionFilter> { return { name: 'product-loader', loadCollection: async ({ filter }) => { // filter is typed as CollectionFilter const data = await fetchCategory({ apiKey: config.apiKey, category: filter?.category ?? 'all', minPrice: filter?.minPrice, maxPrice: filter?.maxPrice, });
return { entries: data.products.map((product) => ({ id: product.sku, data: product, })), }; }, loadEntry: async ({ filter }) => { // filter is typed as EntryFilter | { id: string } const product = await fetchProduct({ apiKey: config.apiKey, slug: filter.slug, sku: filter.sku || filter.id, }); if (!product) { return { error: new Error('Product not found'), }; } return { id: product.sku, entry: product, }; }, };}Error handling in live loaders
Section titled “Error handling in live loaders”Live loaders return an Error subclass for errors. You can create custom error types and use them for more specific error handling if needed. If an error is thrown in the live loader, it will be caught and returned, wrapped in a LiveCollectionError.
Astro will generate some errors itself, depending on the response from the live loader:
- If
loadEntryreturnsundefined, Astro will return aLiveEntryNotFoundErrorto the user. - If a schema is defined for the collection and the data does not match the schema, Astro will return a
LiveCollectionValidationError. - If the loader returns an invalid cache hint, Astro will return a
LiveCollectionCacheHintError. ThecacheHintfield is optional, so if you do not have valid data to return, you can simply omit it.
import type { LiveLoader } from 'astro/loaders';import type { MyData } from "./types";import { MyLoaderError } from './errors.js';
export function myLoader(config): LiveLoader<MyData, undefined, undefined, MyLoaderError> { return { name: 'my-loader', loadCollection: async ({ filter }) => { // Return your custom error type return { error: new MyLoaderError('Failed to load', 'LOAD_ERROR'), }; }, // ... };}Creating live loader error types
Section titled “Creating live loader error types”You can create custom error types for errors returned by your loader and pass them as a generic to get proper typing:
import type { LiveLoader } from "astro/loaders";import type { MyData } from "./types"
export class MyLoaderError extends Error { constructor(message: string, public code?: string) { super(message); this.name = 'MyLoaderError'; }}
export function myLoader(config): LiveLoader<MyData, undefined, undefined, MyLoaderError> { return { name: 'my-loader', loadCollection: async ({ filter }) => { // Return your custom error type return { error: new MyLoaderError('Failed to load', 'LOAD_ERROR'), }; }, // ... };}When you use getLiveCollection() or getLiveEntry(), TypeScript will infer the custom error type, allowing you to handle it appropriately:
---export const prerender = false; // Not needed in 'server' mode
import { getLiveEntry } from 'astro:content';import { MyLoaderError } from "../my-loader";
const { entry, error } = await getLiveEntry('products', '123');
if (error) { if (error instanceof MyLoaderError) { console.error(`Loader error: ${error.message} (code: ${error.code})`); } else { console.error(`Unexpected error: ${error.message}`); } return Astro.rewrite('/500');}---Cache hints
Section titled “Cache hints”Live loaders can provide cache hints to help with response caching. You can use this data to send HTTP cache headers or otherwise inform your caching strategy.
import type { LiveLoader } from "astro/loaders";import { byLastModified } from "./sort";import { loadStoreProduct, loadStoreProducts } from "./store";import type { MyData } from "./types";
export function myLoader(config): LiveLoader<MyData> { return { name: 'cached-loader', loadCollection: async ({ filter }) => { const products = await loadStoreProducts(filter); return { entries: products.map((item) => ({ id: item.id, data: item, // You can optionally provide cache hints for each entry cacheHint: { tags: [`product-${item.id}`, `category-${item.category}`], }, })), cacheHint: { // All fields are optional, and are combined with each entry's cache hints // tags are merged from all entries // maxAge is the shortest maxAge of all entries and the collection // lastModified is the most recent lastModified of all entries and the collection lastModified: new Date(products.sort(byLastModified)[0].lastModified), tags: ['products'], maxAge: 300, // 5 minutes }, }; }, loadEntry: async ({ filter }) => { const item = await loadStoreProduct(filter); return { id: item.id, data: item, cacheHint: { lastModified: new Date(item.lastModified), tags: [`product-${item.id}`, `category-${item.category}`], maxAge: 3600, // 1 hour }, }; }, };}You can then use these hints in your pages:
---export const prerender = false; // Not needed in 'server' mode
import { getLiveEntry } from 'astro:content';
const { entry, error, cacheHint } = await getLiveEntry('products', Astro.params.id);
if (error) { return Astro.redirect('/404');}
// Apply cache hints to response headersif (cacheHint?.tags) { Astro.response.headers.set('Cache-Tag', cacheHint.tags.join(','));}if (cacheHint?.maxAge) { Astro.response.headers.set('Cache-Control', `s-maxage=${cacheHint.maxAge}`);}if (cacheHint?.lastModified) { Astro.response.headers.set('Last-Modified', cacheHint.lastModified.toUTCString());}---
<h1>{entry.data.name}</h1><p>{entry.data.description}</p>Cache hints only provide values that can be used in other parts of your project and do not automatically cause the response to be cached by Astro. You can use them to create your own caching strategy, such as setting HTTP headers or using a CDN.
Distributing your loader
Section titled “Distributing your loader”Loaders can be defined in your site or as a separate npm package. If you want to share your loader with the community, you can publish it to npm with the withastro and astro-loader keywords.
The live loader should export a function that returns the LiveLoader object, allowing users to configure it with their own settings.
Using Zod schemas with live collections
Section titled “Using Zod schemas with live collections”You can use Zod schemas with live collections to validate and transform data at runtime. This Zod validation works the same way as schemas for build-time collections.
When you define a schema for a live collection, it takes precedence over the live loader’s types when you query the collection:
import { z, defineLiveCollection } from 'astro:content';import { apiLoader } from './loaders/api-loader';
const products = defineLiveCollection({ loader: apiLoader({ endpoint: process.env.API_URL }), schema: z .object({ id: z.string(), name: z.string(), price: z.number(), // Transform the API's category format category: z.string().transform((str) => str.toLowerCase().replace(/\s+/g, '-')), // Coerce the date to a Date object createdAt: z.coerce.date(), }) .transform((data) => ({ ...data, // Add a formatted price field displayPrice: `$${data.price.toFixed(2)}`, })),});
export const collections = { products };When using Zod schemas with live collections, validation errors are automatically caught and returned as AstroError objects:
---export const prerender = false; // Not needed in 'server' mode
import { LiveCollectionValidationError } from 'astro/content/runtime';import { getLiveEntry } from 'astro:content';
const { entry, error } = await getLiveEntry('products', '123');
// You can handle validation errors specificallyif (LiveCollectionValidationError.is(error)) { console.error(error.message); return Astro.rewrite('/500');}
// TypeScript knows entry.data matches your Zod schema, not the loader's typeconsole.log(entry?.data.displayPrice); // e.g., "$29.99"---Accessing live data
Section titled “Accessing live data”Astro provides live collection helper functions to access live data on each request and return one (or more) content entries. These can be used similarly to their build-time collection counterparts.
getLiveCollection()fetches an entire collection and returns an array of entries.getLiveEntry()fetches a single entry from a collection.
These return entries with a unique id, and data object with all defined properties from the live loader. When using third-party or community loaders distributed as npm packages, check their own documentation for the expected shape of data returned.
You can use these functions to access your live data, passing the name of the collection and optionally filtering conditions.
---export const prerender = false; // Not needed in 'server' mode
import { getLiveCollection, getLiveEntry } from 'astro:content';
// Use loader-specific filtersconst { entries: draftArticles } = await getLiveCollection('articles', { status: 'draft', author: 'john-doe',});
// Get a specific product by IDconst { entry: product } = await getLiveEntry('products', Astro.params.slug);---Rendering content
Section titled “Rendering content”If your live loader returns a rendered property, you can use the render() function and <Content /> component to render your content directly in your pages, using the same method as build-time collections.
You also have access to any error returned by the live loader, for example, to rewrite to a 404 page when content cannot be displayed:
---export const prerender = false; // Not needed in 'server' mode
import { getLiveEntry, render } from 'astro:content';const { entry, error } = await getLiveEntry('articles', Astro.params.id);if (error) { return Astro.rewrite('/404');}
const { Content } = await render(entry);---
<h1>{entry.data.title}</h1><Content />Error handling
Section titled “Error handling”Live loaders can fail due to network issues, API errors, or validation problems. The API is designed to make error handling explicit.
When you call getLiveCollection() or getLiveEntry(), the error will be one of:
- The error type defined by the loader (if it returned an error)
- A
LiveEntryNotFoundErrorif the entry was not found - A
LiveCollectionValidationErrorif the collection data does not match the expected schema - A
LiveCollectionCacheHintErrorif the cache hint is invalid - A
LiveCollectionErrorfor other errors, such as uncaught errors thrown in the loader
You can use instanceof to check the type of an error at runtime:
---export const prerender = false; // Not needed in 'server' mode
import { LiveEntryNotFoundError } from 'astro/content/runtime';import { getLiveEntry } from 'astro:content';
const { entry, error } = await getLiveEntry('products', Astro.params.id);
if (error) { if (error instanceof LiveEntryNotFoundError) { console.error(`Product not found: ${error.message}`); Astro.response.status = 404; } else { console.error(`Error loading product: ${error.message}`); return Astro.redirect('/500'); }}---Generating Routes from Content
Section titled “Generating Routes from Content”Content collections are stored outside of the src/pages/ directory. This means that no pages or routes are generated for your collection items by default by Astro’s file-based routing.
You will need to manually create a new dynamic route if you want to generate HTML pages for each of your collection entries, such as individual blog posts. Your dynamic route will map the incoming request param (e.g. Astro.params.id in src/pages/blog/[...id].astro) to fetch the correct entry for each page.
The exact method for generating routes will depend on whether your pages are prerendered (default) or rendered on demand by a server.
Building for static output (default)
Section titled “Building for static output (default)”If you are building a static website (Astro’s default behavior) with build-time collections, use the getStaticPaths() function to create multiple pages from a single page component (e.g. src/pages/[id].astro) during your build.
Call getCollection() inside of getStaticPaths() to have your collection data available for building static routes. Then, create the individual URL paths using the id property of each content entry. Each page receives the entire collection entry as a prop for use in your page template.
---import { getCollection, render } from 'astro:content';// 1. Generate a new path for every collection entryexport async function getStaticPaths() { const posts = await getCollection('blog'); return posts.map(post => ({ params: { id: post.id }, props: { post }, }));}// 2. For your template, you can get the entry directly from the propconst { post } = Astro.props;const { Content } = await render(post);---<h1>{post.data.title}</h1><Content />This will generate a page route for every entry in the blog collection. For example, an entry at src/blog/hello-world.md will have an id of hello-world, and therefore its final URL will be /posts/hello-world/.
If your custom slugs contain the / character to produce URLs with multiple path segments, you must use a rest parameter (e.g. [...id]) in the .astro filename for this dynamic routing page.
Building routes on demand at request time
Section titled “Building routes on demand at request time”With an adapter installed for on-demand rendering, you can generate your dynamic page routes at request time. First, examine the request (using Astro.request or Astro.params) to find the slug on demand, and then fetch it using one of Astro’s content collection helper functions:
getEntry()for build-time collection pages that are generated once, upon first request.getLiveEntry()for live collection pages where data is (re)fetched at each request time.
---export const prerender = false; // Not needed in 'server' mode
import { getEntry, render } from "astro:content";
// 1. Get the slug from the incoming server requestconst { id } = Astro.params;if (id === undefined) { return Astro.redirect("/404");}
// 2. Query for the entry directly using the request slugconst post = await getEntry("blog", id);
// 3. Redirect if the entry does not existif (post === undefined) { return Astro.redirect("/404");}
// 4. Render the entry to HTML in the templateconst { Content } = await render(post);---<h1>{post.data.title}</h1><Content />Explore the src/pages/ folder of the blog tutorial demo code on GitHub to see full examples of creating dynamic pages from your collections for blog features like a list of blog posts, tags pages, and more!
Using JSON Schema files in your editor
Section titled “Using JSON Schema files in your editor”
新增於:
astro@4.13.0
Astro auto-generates JSON Schema files for collections, which you can use in your editor to get IntelliSense and type-checking for data files.
A JSON Schema file is generated for each collection in your project and output to the .astro/collections/ directory.
For example, if you have two collections, one named authors and another named posts, Astro will generate .astro/collections/authors.schema.json and .astro/collections/posts.schema.json.
Use JSON Schemas in JSON files
You can manually point to an Astro-generated schema by setting the $schema field in your JSON file.
The value should be a relative file path from the data file to the schema.
In the following example, a data file in src/data/authors/ uses the schema generated for the authors collection:
{ "$schema": "../../../.astro/collections/authors.schema.json", "name": "Armand", "skills": ["Astro", "Starlight"]}Use a schema for a group of JSON files in VS Code
In VS Code, you can configure a schema to apply to all files in a collection using the json.schemas setting.
In the following example, all files in the src/data/authors/ directory will use the schema generated for the authors collection:
{ "json.schemas": [ { "fileMatch": ["/src/data/authors/**"], "url": "./.astro/collections/authors.schema.json" } ]}Use schemas in YAML files in VS Code
In VS Code, you can add support for using JSON schemas in YAML files using the Red Hat YAML extension. With this extension installed, you can reference a schema in a YAML file using a special comment syntax:
# yaml-language-server: $schema=../../../.astro/collections/authors.schema.jsonname: Armandskills: - Astro - StarlightUse schemas for a group of YAML files in VS Code
With the Red Hat YAML extension, you can configure a schema to apply to all YAML files in a collection using the yaml.schemas setting.
In the following example, all YAML files in the src/data/authors/ directory will use the schema generated for the authors collection:
{ "yaml.schemas": { "./.astro/collections/authors.schema.json": ["/src/content/authors/*.yml"] }}See “Associating schemas” in the Red Hat YAML extension documentation for more details.
Learn