diff --git a/src/content/docs/en/guides/cms/keystatic.mdx b/src/content/docs/en/guides/cms/keystatic.mdx index 6d26abd9d3ff6..73a116d768d4e 100644 --- a/src/content/docs/en/guides/cms/keystatic.mdx +++ b/src/content/docs/en/guides/cms/keystatic.mdx @@ -183,7 +183,7 @@ Visit `http://127.0.0.1:4321/keystatic` in the browser to see the Keystatic Admi ## Rendering Keystatic content -Use Astro's Content Collections API to [query and display your posts and collections](/en/guides/content-collections/#querying-collections), just as you would in any Astro project. +[Query and display your posts and collections](/en/guides/content-collections/#querying-build-time-collections), just as you would in any Astro project. ### Displaying a collection list diff --git a/src/content/docs/en/guides/content-collections.mdx b/src/content/docs/en/guides/content-collections.mdx index 3ddbb82a57cd9..dad8a4660a577 100644 --- a/src/content/docs/en/guides/content-collections.mdx +++ b/src/content/docs/en/guides/content-collections.mdx @@ -3,6 +3,9 @@ title: Content collections description: >- Manage your content with type safety. i18nReady: true +tableOfContents: + minHeadingLevel: 2 + maxHeadingLevel: 4 --- import { FileTree, CardGrid, LinkCard, Steps } from '@astrojs/starlight/components'; import Since from '~/components/Since.astro' @@ -13,17 +16,12 @@ import ReadMore from "~/components/ReadMore.astro"

**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 v5.0 introduced the Content Layer API for defining and querying content collections. This performant, scalable API provides built-in content loaders for your local collections. For remote content, you can use third-party and community-built loaders or create your own custom loader and pull in your data from any source. -:::note -Projects may continue using the legacy Content Collections API introduced in Astro v2.0. However, we encourage you to [update any existing collections](/en/guides/upgrade-to/v5/#legacy-v20-content-collections-api) when you are able. -::: +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? -You can define a **collection** from a set of data that is structurally similar. This can be a directory of blog posts, a JSON file of product items, or any data that represents multiple items of the same shape. - -Collections stored locally in your project or on your filesystem can have entries of Markdown, MDX, Markdoc, YAML, TOML, or JSON files: +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/ @@ -35,11 +33,82 @@ Collections stored locally in your project or on your filesystem can have entrie - authors.json a single file containing all collection entries -With an appropriate collection loader, you can fetch remote data from any external source, such as a CMS, database, or headless payment system. +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](#types-of-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 `loader` to retrieve your content and metadata from wherever it is stored and make it available to your project through content-focused APIs. +- An optional collection `schema` that 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 [built-in build-time loaders](#build-time-collection-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](https://astro.build/integrations/?search=&categories%5B%5D=loaders) or by building a [custom build-time collection loader](#custom-build-time-loaders) or [live loader](#creating-a-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 + +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 of `import.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](/en/reference/content-loader-reference/) 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 + +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](/en/basics/astro-pages/) such as `src/pages/about.astro` with 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](/en/basics/project-structure/#public) 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 + +**[Build-time content 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. + +:::tip[Quick start] +See [the official Astro blog starter template](https://github.com/withastro/astro/tree/latest/examples/blog) to get up and running quickly with an example of using the [built-in `glob()` loader](#the-glob-loader) and [defining a schema](#defining-the-collection-schema) for a collection of local Markdown or MDX blog posts. +::: + +**[Live content collections](#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 -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`: +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`: ```json title="tsconfig.json" ins={5} {6} { @@ -52,72 +121,76 @@ Content collections rely on TypeScript to provide Zod validation, Intellisense a } ``` -## Defining Collections +## Build-time content collections -Individual collections use `defineCollection()` to configure: -- a `loader` for a data source (required) -- a `schema` for type safety (optional, but highly recommended!) +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. -### The collection config file - -To define collections, you must 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 use to configure your content collections based on the following structure: +Each individual collection configures: +- [a build-time `loader`](#build-time-collection-loaders) for a data source (required) +- [a build-time `schema`](#defining-the-collection-schema) for type safety (optional, but highly recommended!) ```ts title="src/content.config.ts" // 1. Import utilities from `astro:content` import { defineCollection, z } from 'astro:content'; // 2. Import loader(s) -import { glob, file } from 'astro/loaders'; +import { glob } from 'astro/loaders'; -// 3. Define your collection(s) -const blog = defineCollection({ /* ... */ }); -const dogs = defineCollection({ /* ... */ }); +// 3. Define a `loader` and `schema` for each collection +const 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, dogs }; +export const collections = { blog }; ``` -### Defining the collection `loader` +You can then use the dedicated `getCollection()` and `getEntry()` functions to [query your content collections data](#querying-build-time-collections) and render your content. -The Content Layer API allows you to fetch your content (whether stored locally in your project or remotely) and uses a `loader` property to retrieve your data. +You can choose to [generate page routes](#generating-routes-from-content) 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. -#### Built-in loaders +### Build time collection loaders -Astro provides [two built-in loader functions](/en/reference/content-loader-reference/#built-in-loaders) (`glob()` and `file()`) for fetching your local content, as well as access to the API to construct your own loader and fetch remote data. +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](/en/reference/content-loader-reference/) to construct your own loaders to fetch remote data. -The [`glob()` loader](/en/reference/content-loader-reference/#glob-loader) creates entries from directories of Markdown, MDX, Markdoc, JSON, YAML, or TOML files from anywhere on the filesystem. It accepts a `pattern` of entry files to match using glob patterns supported by [micromatch](https://github.com/micromatch/micromatch#matching-features), and a base file path of where your files are located. Each entry's `id` will be automatically generated from its file name. Use this loader when you have one file per entry. +#### The `glob()` loader -The [`file()` loader](/en/reference/content-loader-reference/#file-loader) creates multiple entries from a single local file. Each entry in the file must have a unique `id` key property. It accepts a `base` file path to your file and optionally a [`parser` function](#parser-function) for data files it cannot parse automatically. Use this loader when your data file can be parsed as an array of objects. +The [`glob()` loader](/en/reference/content-loader-reference/#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. -```ts title="src/content.config.ts" {5,9} -import { defineCollection, z } from 'astro:content'; -import { glob, file } from 'astro/loaders'; // Not available with legacy API +This loader accepts a `pattern` of entry files to match using glob patterns supported by [micromatch](https://github.com/micromatch/micromatch#matching-features), and a base file path of where your files are located. Each entry's `id` will be automatically generated from its file name. + +```ts title="src/content.config.ts" {5} +import { defineCollection } from 'astro:content'; +import { glob } from 'astro/loaders'; const blog = defineCollection({ loader: glob({ pattern: "**/*.md", base: "./src/data/blog" }), - schema: /* ... */ }); + +export const collections = { blog }; +``` + +#### The `file()` loader + +The [`file()` loader](/en/reference/content-loader-reference/#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 `base` file path to your file and optionally a [`parser` function](#parser-function) for data files it cannot parse automatically. Use this loader when your data file can be parsed as an array of objects. + +```ts title="src/content.config.ts" {5} +import { defineCollection } from 'astro:content'; +import { file } from 'astro/loaders'; + const dogs = defineCollection({ loader: file("src/data/dogs.json"), - schema: /* ... */ -}); - -const probes = defineCollection({ - // `loader` can accept an array of multiple patterns as well as string patterns - // Load all markdown files in the space-probes directory, except for those that start with "voyager-" - loader: glob({ pattern: ['*.md', '!voyager-*'], base: 'src/data/space-probes' }), - schema: z.object({ - name: z.string(), - type: z.enum(['Space Probe', 'Mars Rover', 'Comet Lander']), - launch_date: z.date(), - status: z.enum(['Active', 'Inactive', 'Decommissioned']), - destination: z.string(), - operator: z.string(), - notable_discoveries: z.array(z.string()), - }), }); -export const collections = { blog, dogs, probes }; +export const collections = { dogs }; ``` ##### `parser` function @@ -128,13 +201,15 @@ The `file()` loader will automatically detect and parse (based on their file ext 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: -```typescript title="src/content.config.ts" +```typescript title="src/content.config.ts" {3} "parser: (text) => parseCsv(text, { columns: true, skipEmptyLines: true })" 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 })}) + loader: file("src/data/cats.csv", { + parser: (text) => parseCsv(text, { columns: true, skipEmptyLines: true }), + }), }); ``` @@ -149,6 +224,9 @@ The `parser` argument also allows you to load a single collection from a nested You can separate these collections by passing a custom `parser` to the `file()` loader for each collection: ```typescript title="src/content.config.ts" +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 }) }); @@ -157,23 +235,25 @@ const cats = defineCollection({ }); ``` -#### Building a custom loader +#### 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, such as collection-specific API helpers such as `getCollection()` and `render()` to query and display your data, as well as schema validation. +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, such as collection-specific API helpers such as `getCollection()` and `render()` to [query and display your data](#querying-build-time-collections), as well as schema validation. :::tip Find community-built and third-party loaders in the [Astro integrations directory](https://astro.build/integrations/?search=&categories%5B%5D=loaders). ::: -##### Inline loaders +##### 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. ```ts title="src/content.config.ts" +import { defineCollection } from "astro:content"; + const countries = defineCollection({ loader: async () => { const response = await fetch("https://restcountries.com/v3.1/all"); @@ -184,7 +264,6 @@ const countries = defineCollection({ ...country, })); }, - schema: /* ... */ }); ``` @@ -192,9 +271,9 @@ The returned entries are stored in the collection and can be queried using the ` ##### 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. +For more control over the loading process, you can use the Content Loader API to create a [loader object](/en/reference/content-loader-reference/#object-loaders). 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](/en/reference/publish-to-npm/) that others can use in their projects. +Similar to creating an Astro integration or Vite plugin, you can [distribute your loader as an npm package](/en/reference/publish-to-npm/) that others can use in their projects. See the full [Content Loader API](/en/reference/content-loader-reference/) and examples of how to build your own loader. @@ -208,11 +287,11 @@ Schemas also power Astro's automatic TypeScript typings for your content. When y In order for Astro to recognize a new or updated schema, you may need to restart the dev server or [sync the content layer](/en/reference/cli-reference/#astro-dev) (s + enter) to define the `astro:content` module. ::: -Every frontmatter or data property of your collection entries must be defined using a Zod data type: +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](#defining-datatypes-with-zod): ```ts title="src/content.config.ts" {6-11,15-19} import { defineCollection, z } from 'astro:content'; -import { glob, file } from 'astro/loaders'; // Not available with legacy API +import { glob, file } from 'astro/loaders'; const blog = defineCollection({ loader: glob({ pattern: "**/*.md", base: "./src/data/blog" }), @@ -237,9 +316,9 @@ export const collections = { blog, dogs }; #### Defining datatypes with Zod -Astro uses [Zod](https://github.com/colinhacks/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 go to query content from inside your project. +Astro uses [Zod](https://github.com/colinhacks/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. +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. ```ts // Example: A cheatsheet of many common Zod datatypes @@ -271,8 +350,6 @@ defineCollection({ }) ``` -See [Zod's README](https://github.com/colinhacks/zod) for complete documentation on how Zod works and what features are available. - ##### Zod schema methods All [Zod schema methods](https://zod.dev/?id=schema-methods) (e.g. `.parse()`, `.transform()`) are available, with some limitations. Notably, performing custom validation checks on images using `image().refine()` is unsupported. @@ -281,7 +358,7 @@ All [Zod schema methods](https://zod.dev/?id=schema-methods) (e.g. `.parse()`, ` Collection entries can also "reference" other related entries. -With the [`reference()` function](/en/reference/modules/astro-content/#reference) 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. +With the [`reference()` function](/en/reference/modules/astro-content/#reference), 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: @@ -290,18 +367,18 @@ import { defineCollection, reference, z } from 'astro:content'; import { glob } from 'astro/loaders'; const blog = defineCollection({ - loader: glob({ pattern: '**/[^_]*.md', base: "./src/data/blog" }), + 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 `slug` + // 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" }), + loader: glob({ pattern: '**/*.json', base: "./src/data/authors" }), schema: z.object({ name: z.string(), portfolio: z.string().url(), @@ -325,9 +402,9 @@ relatedPosts: These references will be transformed into objects containing a `collection` key and an `id` key, allowing you to easily [query them in your templates](/en/guides/content-collections/#accessing-referenced-data). -### Defining custom IDs +#### Defining custom IDs -When using the `glob()` loader with Markdown, MDX, Markdoc, or JSON files, every content entry [`id`](/en/reference/modules/astro-content/#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. +When using the [`glob()` loader](#the-glob-loader) with Markdown, MDX, Markdoc, JSON, or TOML files, every content entry [`id`](/en/reference/modules/astro-content/#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](#generating-routes-from-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. @@ -347,16 +424,17 @@ Your blog post content here. } ``` -## Querying Collections +### Querying build-time collections -Astro provides helper functions to query a collection and return one (or more) content entries. +Astro provides helper functions to query a build-time collection and return one or more content entries. - [`getCollection()`](/en/reference/modules/astro-content/#getcollection) fetches an entire collection and returns an array of entries. - [`getEntry()`](/en/reference/modules/astro-content/#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. -```js +```astro title="src/pages/index.astro" +--- import { getCollection, getEntry } from 'astro:content'; // Get all entries from a collection. @@ -366,8 +444,7 @@ 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: @@ -384,9 +461,11 @@ const posts = (await getCollection('blog')).sort( See the full list of properties returned by the [`CollectionEntry` type](/en/reference/modules/astro-content/#collectionentry). -### Using content in Astro templates +#### Using content in Astro templates -After querying your collections, you can access each entry's content 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. +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: ```astro title="src/pages/index.astro" @@ -401,11 +480,12 @@ const posts = await getCollection('blog'); ))} ``` -#### Rendering body content -Once queried, you can render Markdown and MDX entries to HTML using the [`render()`](/en/reference/modules/astro-content/#render) function property. Calling this function gives you access to rendered HTML content, including both a `` component and a list of all rendered headings. +#### Rendering body content + +Once queried, you can render Markdown and MDX entries to HTML using the [`render()`](/en/reference/modules/astro-content/#render) function from `astro:content`. Calling this function gives you access to rendered HTML content, including both a `` component and a list of all rendered headings. -```astro title="src/pages/blog/post-1.astro" {5,8} +```astro title="src/pages/blog/post-1.astro" {2,9} --- import { getEntry, render } from 'astro:content'; @@ -422,7 +502,7 @@ const { Content, headings } = await render(entry); When working with MDX entries, you can also [pass your own components to ``](/en/guides/integrations-guide/mdx/#passing-components-to-mdx-content) to replace HTML elements with custom alternatives. -#### Passing content as props +##### Passing content as props A component can also pass an entire collection entry as a prop. @@ -440,41 +520,47 @@ const { post } = Astro.props; --- ``` -### Filtering collection queries +#### 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: -```js +```astro title="src/pages/blog.astro" +--- // 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: -```js +```astro title="src/pages/blog.astro" +--- // Example: Filter out content entries with `draft: true` only when building for production import { 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: -```js +```astro title="src/pages/blog.astro" +--- // Example: Filter entries by sub-directory in the collection import { getCollection } from 'astro:content'; const englishDocsEntries = await getCollection('docs', ({ id }) => { return id.startsWith('en/'); }); +--- ``` -### Accessing referenced data +#### Accessing referenced data Any [references defined in your schema](/en/guides/content-collections/#defining-collection-references) must be queried separately after first querying your collection entry. Since the [`reference()` function](/en/reference/modules/astro-content/#reference) 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. @@ -502,9 +588,572 @@ const relatedPosts = await getEntries(blogPost.data.relatedPosts); ))} ``` +## Live content collections + +Live collections use a different API than build-time content collections. Key differences include: + +1. **Execution time**: Run at request time instead of build time +2. **Configuration file**: Use `src/live.config.ts` instead of `src/content.config.ts` +3. **Collection definition**: Use `defineLiveCollection()` instead of `defineCollection()` +4. **Loader API**: Implement `loadCollection` and `loadEntry` methods instead of the `load` method +5. **Data return**: Return data directly instead of storing in the data store +6. **User-facing functions**: Use `getLiveCollection()`/`getLiveEntry()` instead of `getCollection()`/`getEntry()` + +Make sure you have an adapter configured for [on-demand rendering](/en/guides/on-demand-rendering/), then define your live collections in `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 `loader`](#creating-a-live-loader) for your data source, and optionally for type safety (required) +- a [live collection `schema`](#using-zod-schemas-with-live-collections) for 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](#creating-a-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](#type-safety-in-live-loaders). Therefore, [defining a Zod `schema`](#using-zod-schemas-with-live-collections) for live collections is optional. However, if you provide one, it will take precedence over the live loader's types. + +```ts title="src/live.config.ts" +// Define live collections for accessing real-time data +import { 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 const collections = { products }; +``` + +You can then use the dedicated `getLiveCollection()` and `getLiveEntry()` functions to [access your live data](#accessing-live-data) and render your content. + +You can [generate page routes](#generating-routes-from-content) 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](#build-time-content-collections) do. This is useful when accessing live, up-to-the-moment data is more important than achieving the performance of having your content available in a performant data storage layer that persists between site builds. + +### 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/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: + +```ts title="myloader.ts" +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
{ + 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 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 + +A live loader can add support for directly rendered content by returning [a `rendered` property](/en/reference/content-loader-reference/#rendered) in the entry. This allows you to use [the same `render()` function and `` component](/en/guides/content-collections/#rendering-body-content) available to build-time collections to render live content directly in your pages. + +If the loader does not return a `rendered` property for an entry, the `` component will render nothing. + +```ts title="myloader.ts" {16-19} +// ... +export function articleLoader(config: { apiKey: string }): LiveLoader
{ + 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](#rendering-content) in pages using the same method as built-time collections. + +#### Error handling in live loaders + +Live loaders return an [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) subclass for errors. You can create [custom error types](#creating-live-loader-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 `loadEntry` returns `undefined`, Astro will return a `LiveEntryNotFoundError` to 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`. The `cacheHint` field is optional, so if you do not have valid data to return, you can simply omit it. + +```ts title="my-loader.ts" {10-12} +import type { LiveLoader } from 'astro/loaders'; +import type { MyData } from "./types"; +import { MyLoaderError } from './errors.js'; + +export function myLoader(config): LiveLoader { + return { + name: 'my-loader', + loadCollection: async ({ filter }) => { + // Return your custom error type + return { + error: new MyLoaderError('Failed to load', 'LOAD_ERROR'), + }; + }, + // ... + }; +} +``` + +#### Type safety in live loaders + +You can provide type safety for live collections by [using a Zod schema](#using-zod-schemas-with-live-collections) 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](#defining-data-types) for your collection and entry data, as well as [custom filter types](#defining-custom-filter-types) for querying, and [custom error types](#creating-live-loader-error-types) for error handling in the live loader itself. + +##### 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. + +```ts title="store-loader.ts" "LiveLoader" "type Product" +import type { LiveLoader } from 'astro/loaders'; +import { fetchProduct, fetchCategory, type Product } from './store-client'; + +export function storeLoader(): LiveLoader { + // ... +} +``` + +When you use `getLiveCollection()` or `getLiveEntry()` to access your live data, TypeScript will infer the types based on the loader's definition: + +```astro +--- +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 Product +console.log(product?.data.name); +--- +``` + +##### 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. + +```ts title="store-loader.ts" "EntryFilter, CollectionFilter" {6,8} +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 { + 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, + }; + }, + }; +} +``` + +##### Creating live loader error types + +You can create custom error types for [errors returned by your loader](#error-handling-in-live-loaders) and pass them as a generic to get proper typing: + +```ts title="my-loader.ts" +import type { LiveLoader } from "astro/loaders"; +import type { MyData } from "./types" + +class MyLoaderError extends Error { + constructor( + message: string, + public code?: string + ) { + super(message); + this.name = 'MyLoaderError'; + } +} + +export function myLoader(config): LiveLoader { + 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: + +```astro +--- +export const prerender = false; // Not needed in 'server' mode + +import { getLiveEntry } from 'astro:content'; + +const { entry, error } = await getLiveEntry('products', '123'); + +if (error) { + if (error.name === 'MyLoaderError') { + console.error(`Loader error: ${error.message} (code: ${error.code})`); + } else { + console.error(`Unexpected error: ${error.message}`); + } + return Astro.rewrite('/500'); +} +--- +``` + +#### 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. + +```ts title="my-loader.ts" +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 { + 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: + +```astro title="src/pages/store/[id].astro" +--- +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 headers +if (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()); +} +--- + +

{entry.data.name}

+

{entry.data.description}

+``` + +:::note +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 + +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 `astro-component` and `astro-loader` keywords](/en/reference/publish-to-npm/#packagejson-data). + +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 + +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](#defining-the-collection-schema). + +When you define a schema for a live collection, it takes precedence over [the live loader's types](#type-safety-in-live-loaders) when you query the collection: + +```ts title="src/live.config.ts" +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: + +```astro title="src/pages/store/index.astro" +--- +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 specifically +if (LiveCollectionValidationError.is(error)) { + console.error(error.message); + return Astro.rewrite('/500'); +} + +// TypeScript knows entry.data matches your Zod schema, not the loader's type +console.log(entry?.data.displayPrice); // e.g., "$29.99" +--- +``` + +See [Zod's README](https://github.com/colinhacks/zod) for complete documentation on how Zod works and what features are available. + + +### 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](#querying-build-time-collections). + +- [`getLiveCollection()`](/en/reference/modules/astro-content/#getlivecollection) fetches an entire collection and returns an array of entries. +- [`getLiveEntry()`](/en/reference/modules/astro-content/#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. + +```astro title="src/pages/store/[slug].astro" +--- +export const prerender = false; // Not needed in 'server' mode + +import { getLiveCollection, getLiveEntry } from 'astro:content'; + +// Use loader-specific filters +const { entries: draftArticles } = await getLiveCollection('articles', { + status: 'draft', + author: 'john-doe', +}); + +// Get a specific product by ID +const { entry: product } = await getLiveEntry('products', Astro.params.slug); +--- +``` + +#### Rendering content + +If your live loader [returns a `rendered` property](#providing-rendered-content), you can use [the `render()` function and `` component](#rendering-body-content) 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](#error-handling-in-live-loaders), for example, to rewrite to a 404 page when content cannot be displayed: + +```astro title="src/pages/store/[id].astro" "render(entry)" "" +--- +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); +--- + +

{entry.data.title}

+ +``` + +#### 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 `LiveEntryNotFoundError` if the entry was not found +- A `LiveCollectionValidationError` if the collection data does not match the expected schema +- A `LiveCollectionCacheHintError` if the cache hint is invalid +- A `LiveCollectionError` for other errors, such as uncaught errors thrown in the loader + +These errors have a static `is()` method that you can use to check the type of error at runtime: + +```astro title="src/pages/store/[id].astro" "LiveEntryNotFoundError.is(error)" +--- +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 (LiveEntryNotFoundError.is(error)) { + 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 -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. +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](/en/guides/routing/) You will need to manually create a new [dynamic route](/en/guides/routing/#dynamic-routes) 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.slug` in `src/pages/blog/[...slug].astro`) to fetch the correct entry for each page. @@ -512,9 +1161,9 @@ The exact method for generating routes will depend on whether your pages are pre ### Building for static output (default) -If you are building a static website (Astro's default behavior), use the [`getStaticPaths()`](/en/reference/routing-reference/#getstaticpaths) function to create multiple pages from a single page component (e.g. `src/pages/[slug]`) during your build. +If you are building a static website (Astro's default behavior) with build-time collections, use the [`getStaticPaths()`](/en/reference/routing-reference/#getstaticpaths) function to create multiple pages from a single page component (e.g. `src/pages/[slug]`) 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 is passed the entire collection entry as a prop for [use in your page template](#using-content-in-astro-templates). +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](#using-content-in-astro-templates). ```astro title="src/pages/posts/[id].astro" "{ id: post.id }" "{ post }" --- @@ -541,9 +1190,9 @@ This will generate a page route for every entry in the `blog` collection. For ex If your custom slugs contain the `/` character to produce URLs with multiple path segments, you must use a [rest parameter (e.g. `[...slug]`)](/en/guides/routing/#rest-parameters) in the `.astro` filename for this dynamic routing page. ::: -### Building for server output (SSR) +### Building routes on demand at request time -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()`](/en/reference/modules/astro-content/#getentry). +If you have an adapter installed and are generating your routes at request time, you will not 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()`](/en/reference/modules/astro-content/#getentry) for build-time collection pages that are generated once, upon first request, or [`getLiveEntry()`](/en/reference/modules/astro-content/#getentry) for live collection pages where data is (re)fetched at each request time. ```astro title="src/pages/posts/[id].astro" @@ -568,10 +1217,10 @@ const { Content } = await render(post); ``` :::tip -Explore the `src/pages/` folder of the [blog tutorial demo code on GitHub](https://github.com/withastro/blog-tutorial-demo/tree/content-collections/src/pages) to see full examples of creating pages from your collections for blog features like a list of blog posts, tags pages, and more! +Explore the `src/pages/` folder of the [blog tutorial demo code on GitHub](https://github.com/withastro/blog-tutorial-demo/tree/content-collections/src/pages) to see full examples of creating dynamic pages from your collections for blog features like a list of blog posts, tags pages, and more! ::: -## Collection JSON Schemas +## Using JSON Schema files in your editor

@@ -580,7 +1229,7 @@ Astro auto-generates [JSON Schema](https://json-schema.org/) files for collectio 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 +

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. @@ -594,9 +1243,9 @@ In the following example, a data file in `src/data/authors/` uses the schema gen } ``` -#### Use a schema for a group of JSON files in VS Code +

Use a schema for a group of JSON files in VS Code

-In VS Code, you can configure a schema to apply for all files in a collection using the [`json.schemas` setting](https://code.visualstudio.com/docs/languages/json#_json-schemas-and-settings). +In VS Code, you can configure a schema to apply to all files in a collection using the [`json.schemas` setting](https://code.visualstudio.com/docs/languages/json#_json-schemas-and-settings). In the following example, all files in the `src/data/authors/` directory will use the schema generated for the `authors` collection: ```json @@ -610,7 +1259,7 @@ In the following example, all files in the `src/data/authors/` directory will us } ``` -### Use schemas in YAML files in VS Code +

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](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml) extension. With this extension installed, you can reference a schema in a YAML file using a special comment syntax: @@ -623,9 +1272,9 @@ skills: - Starlight ``` -#### Use schemas for a group of YAML files in VS Code +

Use schemas for a group of YAML files in VS Code

-With the Red Hat YAML extension, you can configure a schema to apply for all YAML files in a collection using the `yaml.schemas` setting. +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: ```json @@ -637,31 +1286,3 @@ In the following example, all YAML files in the `src/data/authors/` directory wi ``` See [“Associating schemas”](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml#associating-schemas) in the Red Hat YAML extension documentation for more details. - -## When to create a collection - -You can [create a collection](#defining-collections) any time you have a group of related data or content that shares a common structure. - -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 of `import.meta.glob()`) when importing and rendering content on your pages. -- A [Content Loader API](/en/reference/content-loader-reference/) for retrieving your content that provides both built-in loaders and access to the low-level API. There are several third-party and community-built loaders available, and you can build your own custom loader to fetch data from anywhere. -- Performance and scalability. The Content Layer API allows data to be cached between builds and is suitable for tens of thousands of content entries. - -[Define your data](#defining-collections) as a collection when: - -- You have multiple files or data to organize that share the same overall structure (e.g. 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 and Content Layer API instead of using `fetch()` or SDKs. -- You need to fetch (tens of) thousands of related pieces of data, and need a querying and caching method that handles at scale. - -### 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 pages. Consider [making individual page components](/en/basics/astro-pages/) such as `src/pages/about.astro` with 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](/en/basics/project-structure/#public) 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. -- You are using APIs that need to be updated in real time. Content collections are only updated at build time, so if you need live data, use other methods of [importing files](/en/guides/imports/#import-statements) or [fetching data](/en/guides/data-fetching/) with [on-demand rendering](/en/guides/on-demand-rendering/). diff --git a/src/content/docs/en/guides/imports.mdx b/src/content/docs/en/guides/imports.mdx index 2b7145046b4c6..132658cccef41 100644 --- a/src/content/docs/en/guides/imports.mdx +++ b/src/content/docs/en/guides/imports.mdx @@ -344,7 +344,7 @@ Additionally, glob patterns must begin with one of the following: ### `import.meta.glob()` vs `getCollection()` -[Content collections](/en/guides/content-collections/) provide a [`getCollection()` API](/en/reference/modules/astro-content/#getcollection) for loading multiple files instead of `import.meta.glob()`. If your content files (e.g. Markdown, MDX, Markdoc) are located in collections within the `src/content/` directory, use `getCollection()` to [query a collection](/en/guides/content-collections/#querying-collections) and return content entries. +[Content collections](/en/guides/content-collections/) provide [performant, content-focused APIs](/en/reference/modules/astro-content/) for loading multiple files instead of `import.meta.glob()`. Use `getCollection()` and `getLiveCollection()` to query your collections and return content entries. ## WASM diff --git a/src/content/docs/en/guides/integrations-guide/markdoc.mdx b/src/content/docs/en/guides/integrations-guide/markdoc.mdx index 9e6a0539580e1..755ee0da22de4 100644 --- a/src/content/docs/en/guides/integrations-guide/markdoc.mdx +++ b/src/content/docs/en/guides/integrations-guide/markdoc.mdx @@ -113,7 +113,7 @@ Markdoc files can only be used within content collections. Add entries to any co - quick-start.mdoc -Then, query your collection using the [Content Collection APIs](/en/guides/content-collections/#querying-collections): +Then, [query and display your posts and collections](/en/guides/content-collections/#querying-build-time-collections): ```astro title="src/pages/why-markdoc.astro" --- diff --git a/src/content/docs/en/guides/integrations-guide/mdx.mdx b/src/content/docs/en/guides/integrations-guide/mdx.mdx index 8848e087aa998..d821fc92967cd 100644 --- a/src/content/docs/en/guides/integrations-guide/mdx.mdx +++ b/src/content/docs/en/guides/integrations-guide/mdx.mdx @@ -96,9 +96,9 @@ It also adds extra features to standard MDX, including support for Markdown-styl `.mdx` files must be written in [MDX syntax](https://mdxjs.com/docs/what-is-mdx/#mdx-syntax) rather than Astro’s HTML-like syntax. -### Using MDX with content collections +### Using local MDX with content collections -To include MDX files in a content collection, make sure that your [collection loader](/en/guides/content-collections/#defining-the-collection-loader) is configured to load content from `.mdx` files: +To include your local MDX files in a content collection, make sure that your [collection loader](/en/guides/content-collections/#build-time-collection-loaders) is configured to load content from `.mdx` files: ```js title="src/content.config.ts" ins="mdx" import { defineCollection, z } from 'astro:content'; diff --git a/src/content/docs/en/guides/markdown-content.mdx b/src/content/docs/en/guides/markdown-content.mdx index f189181302590..baff39e40e234 100644 --- a/src/content/docs/en/guides/markdown-content.mdx +++ b/src/content/docs/en/guides/markdown-content.mdx @@ -399,7 +399,7 @@ When using the frontmatter `layout` property, you must include the ` no content collections configuration file -Create `src/content.config.ts` and [define your collections](/en/guides/content-collections/#defining-collections) in it. +Create `src/content.config.ts` and [define your collections](/en/guides/content-collections/#build-time-content-collections) in it.
@@ -205,7 +205,7 @@ Rename and move this file to `src/content.config.ts`
a collection that does not define a `loader`/ ([`ContentCollectionMissingALoaderError`](/en/reference/errors/content-collection-missing-loader/)) -Import [Astro's built-in `glob()` loader](/en/guides/content-collections/#built-in-loaders) and define the `pattern` and `base` for your collection entries: +Import [Astro's built-in `glob()` loader](/en/guides/content-collections/#the-glob-loader) and define the `pattern` and `base` for your collection entries: ```ts ins={3,6} // src/content.config.ts diff --git a/src/content/docs/en/reference/cli-reference.mdx b/src/content/docs/en/reference/cli-reference.mdx index d141e5a8e1b99..c0c58549549f1 100644 --- a/src/content/docs/en/reference/cli-reference.mdx +++ b/src/content/docs/en/reference/cli-reference.mdx @@ -410,7 +410,7 @@ astro --config config/astro.config.mjs dev

-Clear the [content layer cache](/en/guides/content-collections/#defining-the-collection-loader), forcing a full rebuild. +Clear the content layer cache, forcing a full rebuild. ### `--mode ` diff --git a/src/content/docs/en/reference/content-loader-reference.mdx b/src/content/docs/en/reference/content-loader-reference.mdx index 14a119663d97b..e8aa0e1a45287 100644 --- a/src/content/docs/en/reference/content-loader-reference.mdx +++ b/src/content/docs/en/reference/content-loader-reference.mdx @@ -5,19 +5,22 @@ sidebar: i18nReady: true --- import Since from '~/components/Since.astro'; +import ReadMore from '~/components/ReadMore.astro'; Astro's Content Loader API allows you to load your data from any source, local or remote, and interact with Astro's content layer to manage your [content collections](/en/guides/content-collections/). ## What is a loader? -Astro loaders allow you to load data into [content collections](/en/guides/content-collections/), which can then be used in pages and components. The [built-in `glob()` and `file()` loaders](/en/guides/content-collections/#built-in-loaders) are used to load content from the file system, and you can create your own loaders to load content from other sources. +Astro loaders allow you to load data into [content collections](/en/guides/content-collections/), which can then be used in pages and components. The [built-in `glob()` and `file()` loaders](#built-in-loaders) are used at built time to load content from the file system. You can also create your own [built-time loaders](/en/guides/content-collections/#custom-build-time-loaders) or [live loaders](/en/guides/content-collections/#creating-a-live-loader) to load content from other sources. -Each collection needs [a loader defined in its schema](/en/guides/content-collections/#defining-the-collection-loader). You can define a loader inline in your project's `src/content.config.ts` file, share one loader between multiple collections, or even [publish your loader to NPM as a package](/en/reference/publish-to-npm/) to share with others and be included in our integrations library. +Each collection needs to define a loader and an optional schema. For build-time collections, you can define a loader inline in your project's `src/content.config.ts` file or share an object loader between multiple collections. For live collections, you'll need to create your own live loader and import it in your `src/live.config.ts` file. In both cases, you can even [publish your loader to NPM as a package](/en/reference/publish-to-npm/) to share with others and be included in our integrations library. ## Built-in loaders Astro provides two built-in loaders to help you fetch your collections. Both offer options to suit a wide range of use cases. +Learn more about [using built-time loaders](/en/guides/content-collections/#build-time-content-collections) with guided explanations and example usage. + ### `glob()` loader

@@ -30,19 +33,17 @@ The `glob()` loader creates entries from directories of files from anywhere on t This loader accepts an object with the following properties: `pattern`, `base` (optional), and `generateId` (optional). -```ts title="src/content.config.ts" {2,6,11,17-21} +```ts title="src/content.config.ts" {2,6,10,15-19} import { defineCollection } from 'astro:content'; import { glob } from 'astro/loaders'; const pages = defineCollection({ /* Retrieve all Markdown files in your pages directory. */ loader: glob({ pattern: "**/*.md", base: "./src/data/pages" }), - schema: /* ... */ }); const blog = defineCollection({ /* Retrieve all Markdown and MDX files in your blog directory. */ loader: glob({ pattern: "**/*.(md|mdx)", base: "./src/data/blog" }), - schema: /* ... */ }); const authors = defineCollection({ /* Retrieve all JSON files in your authors directory while retaining @@ -52,7 +53,6 @@ const authors = defineCollection({ base: "./src/data/authors", generateId: ({ entry }) => entry.replace(/\.json$/, ''), }), - schema: /* ... */ }); ``` @@ -103,21 +103,19 @@ The `file()` loader creates entries from a single file that contains an array of This loader accepts a `fileName` property and an optional object as second argument: -```ts title="src/content.config.ts" {2,6,11-13} +```ts title="src/content.config.ts" {2,6,10-12} import { defineCollection } from 'astro:content'; import { file } from 'astro/loaders'; const authors = defineCollection({ /* Retrieve all entries from a JSON file. */ loader: file("src/data/authors.json"), - schema: /* ... */ }); const products = defineCollection({ /* Retrieve all entries from a CSV file using a custom parser. */ loader: file("src/data/products.csv", { parser: (fileContent) => { /* your parser logic */ }, }), - schema: /* ... */ }); ``` @@ -159,6 +157,8 @@ An inline loader is an async function that returns an array or object containing The function can be async and must return either an array of entries that each contain a unique `id` field, or an object where each key is a unique ID and each value is the entry. Whenever the loader is invoked, it will clear the store and reload all the entries. ```ts title="src/content.config.ts" +import { defineCollection } from "astro:content"; + const countries = defineCollection({ loader: async () => { const response = await fetch("https://restcountries.com/v3.1/all"); @@ -170,18 +170,17 @@ const countries = defineCollection({ ...country, })); }, - schema: /* ... */ }); ``` ### Object loaders -A loader is an object with a `load()` method that is called at build time to fetch data and update the data store. It allows entries to be updated incrementally, or for the store to be cleared only when necessary. It can also define a schema for the entries, which can be used to validate the data and generate static types. +A loader is an object with a [`load()` method](#load) that is called at build time to fetch data and update the data store. It allows [entries](#dataentry) to be updated incrementally, or for the store to be cleared only when necessary. It can also define a schema for the entries, which can be used to validate the data and generate static types. The recommended pattern is to define a function that accepts configuration options and returns the loader object, in the same way that you would normally define an Astro integration or Vite plugin. -```ts title=loader.ts +```ts title="src/loader.ts" import type { Loader, LoaderContext } from 'astro/loaders'; import { z } from 'astro:content'; import { loadFeedData } from "./feed.js"; @@ -210,21 +209,89 @@ export function myLoader(options: { url: string, apiKey: string }): Loader { These configuration options can then be set when defining a collection: ```ts title="src/content.config.ts" {2,5-8} -import { defineCollection, z } from 'astro:content'; -import myLoader from '../../loader.ts'; +import { defineCollection } from 'astro:content'; +import { myLoader } from './loader.ts'; -const blog = defineCollection({ +const blog = defineCollection({ loader: myLoader({ url: "https://api.example.com/posts", apiKey: "my-secret", - }), - schema: /* ... */ -}); + }), +}); +``` + +### Live loaders + +A live loader is an object with two methods: `loadCollection()` and `loadEntry()` that should handle errors gracefully and return either data or an `Error` object. + +```ts title="src/article-loader.ts" +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

{ + 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 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}`), + }; + } + }, + }; +} ``` +See [how to create a live loader](/en/guides/content-collections/#creating-a-live-loader) with example usage. + ## Object loader API -The API for [inline loaders](#inline-loaders) is very simple, and is shown above. This section shows the API for defining an object loader. +The API for [inline loaders](#inline-loaders) is very simple, and is shown above. This section shows the API for defining an [object loader](#object-loaders). ### The `Loader` object @@ -323,7 +390,7 @@ The full, resolved Astro configuration object with all defaults applied. See [th Validates and parses the data according to the collection schema. Pass data to this function to validate and parse it before storing it in the data store. -```ts title=loader.ts {14-17} +```ts title=loader.ts {15-18} import type { Loader } from "astro/loaders"; import { loadFeed } from "./feed.js"; @@ -337,8 +404,9 @@ export function feedLoader({ url }): Loader { store.clear(); for (const item of feed.items) { + const id = item.guid; const data = await parseData({ - id: item.guid, + id, data: item, }); store.set({ @@ -399,7 +467,7 @@ export function myLoader(settings): Loader { Generates a non-cryptographic content digest of an object or string. This can be used to track if the data has changed by setting [the `digest` field](#digest) of an entry. -```ts title=loader.ts {19,24} +```ts title=loader.ts {20,25} import type { Loader } from "astro/loaders"; import { loadFeed } from "./feed.js"; @@ -413,8 +481,9 @@ export function feedLoader({ url }): Loader { store.clear(); for (const item of feed.items) { + const id = item.guid; const data = await parseData({ - id: item.guid, + id, data: item, }); @@ -467,7 +536,10 @@ return { If the loader has been triggered by an integration, this may optionally contain extra data set by that integration. It is only set when the loader is triggered by an integration. See the [`astro:server:setup`](/en/reference/integrations-reference/#refreshcontent-option) hook reference for more information. -```ts title=loader.ts {5-8} +```ts title=loader.ts {8-11} +import type { Loader } from "astro/loaders"; +import { processWebhook } from "./lib/webhooks"; + export function myLoader(options: { url: string }): Loader { return { name: "my-loader", @@ -510,10 +582,11 @@ The returned object is a [`DataEntry`](#dataentry) object. Used after data has been [validated and parsed](#parsedata) to add an entry to the store, returning `true` if the entry was set. This returns `false` when the [`digest`](#digest) property determines that an entry has not changed and should not be updated. -```ts title=loader.ts {7-14} +```ts title=loader.ts {8-15} for (const item of feed.items) { + const id = item.guid; const data = await parseData({ - id: item.guid, + id, data: item, }); const digest = generateDigest(data); @@ -670,3 +743,67 @@ The format of the `RenderedContent` object is: ``` If the entry has Markdown content then you can use the [`renderMarkdown()`](#rendermarkdown) function to generate this object from the Markdown string. + +## Live loader API + +This section shows the API for defining a [live loader](#live-loaders). + +### The `LiveLoader` object + +A generic type to provide [type safety in your loader](/en/guides/content-collections/#type-safety-in-live-loaders). This describes an object and accepts four type parameters to describe, in this order: your data structure, your filters when querying a collection, your filters when querying a single entry, and errors. + +This object contains the following properties: + +#### `name` + +

+ +**Type:** `string` +

+ + +A unique name for the loader, used in logs. + +#### `loadCollection()` + +

+ +**Type:** `(context: LoadCollectionContext) => Promise | { error: TError; }>` +

+ +Defines a method to load a collection of entries. This function receives a [context object](#loadcollectioncontext) containing an optional `filter` property and must return the data associated with this collection or the errors. + +#### `loadEntry()` + +

+ +**Type:** `(context: LoadEntryContext) => Promise | undefined | { error: TError; }>` +

+ +Defines a method to load a single entry. This function receives a [context object](#loadentrycontext) containing a `filter` property and returns either the data associated with the requested entry, `undefined` when the entry cannot be found, or the errors. + +### `LoadCollectionContext` + +This object is passed to the [`loadCollection()` method](#loadcollection) of the loader and contains the following properties: + +#### `filter` + +

+ +**Type:** `Record` +

+ +An object describing the filters supported by your loader. + +### `LoadEntryContext` + +This object is passed to the [`loadEntry()` method](#loadentry) of the loader and contains the following properties: + +#### `filter` + +

+ +**Type:** `Record` +

+ +An object describing the filters supported by your loader. diff --git a/src/content/docs/en/reference/errors/content-collection-missing-loader.mdx b/src/content/docs/en/reference/errors/content-collection-missing-loader.mdx index 224406aa602c6..13652a54399f6 100644 --- a/src/content/docs/en/reference/errors/content-collection-missing-loader.mdx +++ b/src/content/docs/en/reference/errors/content-collection-missing-loader.mdx @@ -21,4 +21,4 @@ import DontEditWarning from '~/components/DontEditWarning.astro' A content collection is missing a loader definition. Make sure that each collection in your content config file has a loader. **See Also:** -- [Content collections configuration](/en/guides/content-collections/#defining-the-collection-loader) +- [Content collections configuration](/en/guides/content-collections/) diff --git a/src/content/docs/en/reference/errors/file-glob-not-supported.mdx b/src/content/docs/en/reference/errors/file-glob-not-supported.mdx index 9918fdd56ccb5..be9f4ce3a5767 100644 --- a/src/content/docs/en/reference/errors/file-glob-not-supported.mdx +++ b/src/content/docs/en/reference/errors/file-glob-not-supported.mdx @@ -19,6 +19,6 @@ import DontEditWarning from '~/components/DontEditWarning.astro' The `file` loader must be passed a single local file. Glob patterns are not supported. Use the built-in `glob` loader to create entries from patterns of multiple local files. **See Also:** -- [Astro's built-in loaders](/en/guides/content-collections/#built-in-loaders) +- [Astro's built-in `file()` loader](/en/guides/content-collections/#the-file-loader) diff --git a/src/content/docs/en/reference/errors/legacy-content-config-error.mdx b/src/content/docs/en/reference/errors/legacy-content-config-error.mdx index 8dcd318ea0da5..4da8ce6546963 100644 --- a/src/content/docs/en/reference/errors/legacy-content-config-error.mdx +++ b/src/content/docs/en/reference/errors/legacy-content-config-error.mdx @@ -23,4 +23,4 @@ A legacy content config file was found. Move the file to `src/content.config.ts` See the [Astro 6 migration guide](/en/guides/upgrade-to/v6/#removed-legacy-content-collections) for more information. **See Also:** -- [Content collections configuration](/en/guides/content-collections/#the-collection-config-file) +- [Content collections configuration](/en/guides/content-collections/#build-time-content-collections) diff --git a/src/content/docs/en/reference/modules/astro-content.mdx b/src/content/docs/en/reference/modules/astro-content.mdx index cc72bd2d2a523..bf4a2510da1db 100644 --- a/src/content/docs/en/reference/modules/astro-content.mdx +++ b/src/content/docs/en/reference/modules/astro-content.mdx @@ -12,7 +12,13 @@ import ReadMore from '~/components/ReadMore.astro';

-Content collections offer APIs to configure and query your Markdown or MDX documents in `src/content/`. For features and usage examples, [see our content collections guide](/en/guides/content-collections/). +Build-time content collections offer APIs to configure, query, and render your local Markdown, MDX, Markdoc, YAML, TOML, or JSON files, as well as remote content. + +

+ +Live content collections offer APIs to configure, query, and render fresh, up-to-the-moment live data from remote sources. + +For features and usage examples, [see our content collections guide](/en/guides/content-collections/). ## Imports from `astro:content` @@ -20,13 +26,17 @@ Content collections offer APIs to configure and query your Markdown or MDX docum import { z, defineCollection, + defineLiveCollection, getCollection, + getLiveCollection, getEntry, + getLiveEntry, getEntries, reference, render } from 'astro:content'; ``` + ### `defineCollection()`

@@ -35,7 +45,7 @@ import {

-`defineCollection()` is a utility to configure a collection in a `src/content.config.*` file. +A utility to configure a collection in a `src/content.config.*` file. ```ts title="src/content.config.ts" import { z, defineCollection } from 'astro:content'; @@ -64,9 +74,9 @@ This function accepts the following properties:

-A `loader` is either an object or a function that allows you to load data from any source, local or remote, into content collections. +Either an object or a function that allows you to load data from any source, local or remote, into a build-time content collection. (For live collections, see the [live `loader`](#loader-1) property.) -[See the `Content Collection` guide](/en/guides/content-collections/#defining-the-collection-loader) for example usage. +Learn about [build-time collection loaders](/en/guides/content-collections/#build-time-collection-loaders) with guided explanations and example usage. #### `schema` @@ -76,24 +86,77 @@ A `loader` is either an object or a function that allows you to load data from a

-`schema` is an optional Zod object to configure the type and shape of document frontmatter for a collection. Each value must use [a Zod validator](https://github.com/colinhacks/zod). +An optional Zod object or function that returns a Zod object to configure the type and shape of document frontmatter for a collection. Each value must use [a Zod validator](https://github.com/colinhacks/zod). (For live collections, see the [live `schema`](#schema-1) property.) + +Learn about [defining a collection schema](/en/guides/content-collections/#defining-the-collection-schema) using Zod with guided explanations, example usage, and common datatypes. + +### `defineLiveCollection()` + +

+ +**Type:** `(config: LiveCollectionConfig) => LiveCollectionConfig` + +

+ +A utility to configure a live collection in a `src/live.config.*` file. + +```ts title="src/live.config.ts" +import { defineLiveCollection } from 'astro:content'; +import { storeLoader } from '@example/astro-loader'; + +const products = defineLiveCollection({ + loader: storeLoader({ + apiKey: process.env.STORE_API_KEY, + endpoint: 'https://api.example.com/v1', + }), +}); + +// Expose your defined collection to Astro +// with the `collections` export +export const collections = { products }; +``` + +This function accepts the following properties: + +#### `loader` + +

+ +**Type:** `LiveLoader` + +

+ +An object that allows you to load data at runtime from a remote source into a live content collection. (For build-time collections, see the [build-time `loader`](#loader) property.) + +Learn how to [create a live loader](/en/guides/content-collections/#creating-a-live-loader) with guided explanations and example usage. -[See the `Content Collection` guide](/en/guides/content-collections/#defining-the-collection-schema) for example usage. +#### `schema` + +

+ +**Type:** ZodType + +

+ +An optional Zod object to configure the type and shape of your data for a live collection. Each value must use [a Zod validator](https://github.com/colinhacks/zod). (For build-time collections, see the [build-time `schema`](#schema) property.) + +When you define a schema, it will take precedence over the [live loader’s types](/en/reference/content-loader-reference/#live-loader-api) when you query the collection. + +Learn about [using Zod schemas with live collections](/en/guides/content-collections/#using-zod-schemas-with-live-collections) through guided explanations and usage examples. ### `reference()`

-**Type:** `(collection: string) => ZodEffects`
+**Type:** (collection: CollectionKey) => ZodEffects\

-The `reference()` function is used in the content config to define a relationship, or "reference," from one collection to another. This accepts a collection name and transforms the reference into an object containing the collection name and the reference id. - +A function used in the content config to define a relationship, or "reference", from one collection to another. This accepts a collection name and transforms the reference into an object containing the collection name and the reference id. This example defines references from a blog author to the `authors` collection and an array of related posts to the same `blog` collection: -```ts +```ts title="src/content.config.ts" import { defineCollection, reference, z } from 'astro:content'; import { glob, file } from 'astro/loaders'; @@ -114,6 +177,7 @@ const authors = defineCollection({ export const collections = { blog, authors }; ``` + Validation of referenced entries happens at runtime when using `getEntry()` or `getEntries()`: ```astro title="src/pages/[posts].astro" @@ -121,25 +185,25 @@ Validation of referenced entries happens at runtime when using `getEntry()` or ` const relatedPosts = await getEntries(blogPost.data.relatedPosts); ``` -[See the `Content Collection` guide](/en/guides/content-collections/#defining-collection-references) for example usage. +Learn how to [define and use collection references](/en/guides/content-collections/#defining-collection-references) with guided explanations and usage examples. ### `getCollection()`

-**Type:** `(collection: string, filter?: (entry: CollectionEntry) => boolean) => CollectionEntry[]` +**Type:** (collection: CollectionKey, filter?: (entry: CollectionEntry) => boolean) => CollectionEntry[]

-`getCollection()` is a function that retrieves a list of content collection entries by collection name. +A function that retrieves a list of content collection entries by collection name. It returns all items in the collection by default, and accepts an optional `filter` function to narrow by entry properties. This allows you to query for only some items in a collection based on `id` or frontmatter values via the `data` object. -```astro +```astro title="src/pages/blog/index.astro" --- import { getCollection } from 'astro:content'; -// Get all `src/content/blog/` entries +// Get all `src/data/blog/` entries const allBlogPosts = await getCollection('blog'); // Only return posts with `draft: true` in the frontmatter @@ -149,21 +213,47 @@ const draftBlogPosts = await getCollection('blog', ({ data }) => { --- ``` -[See the `Content Collection` guide](/en/guides/content-collections/#querying-collections) for example usage. +Learn how to [query build time collections](/en/guides/content-collections/#querying-build-time-collections) with guided explanations and example usage. + +### `getLiveCollection()` + +

+ +**Type:** `(collection: string, filter?: LiveLoaderCollectionFilterType) => Promise` + +

+ +A function that retrieves a list of live content collection entries by collection name. + +It returns all items in the collection by default, and accepts an optional `filter` object whose shape is defined by the collection's loader. This allows you to query for only some items in a collection or retrieve data in a different form, depending on your API's capabilities. + +```astro title="src/pages/shop/index.astro" +--- +import { getLiveCollection } from 'astro:content'; + +// Get all `products` entries from your API +const allProducts = await getLiveCollection('products'); + +// Only return `products` that should be featured +const featuredProducts = await getLiveCollection('products', { featured: true }); +--- +``` + +Learn how to [access live collections data](/en/guides/content-collections/#accessing-live-data) with guided explanations and example usage. ### `getEntry()`

**Types:** -* `(collection: string, id: string) => Promise | undefined>` -* `({ collection: string, id: string }) => Promise | undefined>` +* (collection: CollectionKey, id: string) => Promise\<CollectionEntry | undefined\> +* (\{ collection: CollectionKey, id: string \}) => Promise\<CollectionEntry | undefined\>

-`getEntry()` is a function that retrieves a single collection entry by collection name and the entry `id`. `getEntry()` can also be used to get referenced entries to access the `data` or `body` properties: +A function that retrieves a single collection entry by collection name and the entry `id`. `getEntry()` can also be used to get referenced entries to access the `data` or `body` properties: -```astro +```astro title="src/pages/index.astro" --- import { getEntry } from 'astro:content'; @@ -178,19 +268,43 @@ const enterpriseCaptainProfile = await getEntry(enterprisePost.data.captain); --- ``` -See the `Content Collections` guide for examples of [querying collection entries](/en/guides/content-collections/#querying-collections). +Learn more about [querying build time collections](/en/guides/content-collections/#querying-build-time-collections) with guided explanations and example usage. + +### `getLiveEntry()` + +

+ +**Type:** `(collection: string, filter: string | LiveLoaderEntryFilterType) => Promise` + +

+ +A function that retrieves a single live collection entry by collection name and an optional filter, either as an `id` string or as a type-safe object. + +```astro title="src/pages/blog/[id].astro" +--- +import { getLiveEntry } from 'astro:content'; + +const liveCollectionsPost = await getLiveEntry('blog', Astro.params.id); +const mattDraft = await getLiveEntry('blog', { + status: 'draft', + author: 'matt', +}); +--- +``` + +Learn how to [access live collections data](/en/guides/content-collections/#accessing-live-data) with guided explanations and example usage. ### `getEntries()`

-**Type:** `(Array<{ collection: string, id: string }>) => Array>` +**Type:** (\{ collection: CollectionKey, id: string \}[]) => CollectionEntry[]

-`getEntries()` is a function that retrieves multiple collection entries from the same collection. This is useful for [returning an array of referenced entries](/en/guides/content-collections/#defining-collection-references) to access their associated `data` and `body` properties. +A function that retrieves multiple collection entries from the same collection. This is useful for [returning an array of referenced entries](/en/guides/content-collections/#defining-collection-references) to access their associated `data` and `body` properties. -```astro +```astro title="src/pages/blog/enterprise/index.astro" --- import { getEntries, getEntry } from 'astro:content'; @@ -205,7 +319,7 @@ const enterpriseRelatedPosts = await getEntries(enterprisePost.data.relatedPosts

-**Type:** `(entry: CollectionEntry) => Promise` +**Type:** (entry: CollectionEntry) => Promise\

@@ -215,7 +329,7 @@ A function to compile a given entry for rendering. This returns the following pr - `headings` - A generated list of headings, [mirroring Astro's `getHeadings()` utility](/en/guides/markdown-content/#available-properties) on Markdown and MDX imports. - `remarkPluginFrontmatter ` - The modified frontmatter object after any [remark or rehype plugins have been applied](/en/guides/markdown-content/#modifying-frontmatter-programmatically). Set to type `any`. -```astro +```astro title="src/pages/blog/entry-1.astro" --- import { getEntry, render } from 'astro:content'; const entry = await getEntry('blog', 'entry-1'); @@ -228,7 +342,7 @@ const { Content, headings, remarkPluginFrontmatter } = await render(entry); --- ``` -[See the `Content Collection` guide](/en/guides/content-collections/#rendering-body-content) for example usage. +Learn how to [render the body content of entries](/en/guides/content-collections/#rendering-body-content) with guided explanations and example usage. ## `astro:content` types @@ -236,8 +350,6 @@ const { Content, headings, remarkPluginFrontmatter } = await render(entry); import type { CollectionEntry, CollectionKey, - ContentCollectionKey, - DataCollectionKey, SchemaContext, } from 'astro:content'; ``` @@ -250,45 +362,79 @@ Query functions including [`getCollection()`](#getcollection), [`getEntry()`](#g import type { CollectionEntry } from 'astro:content'; ``` -`CollectionEntry` is a generic type. Use it with the name of the collection you're querying. +A generic type to use with the name of the collection you're querying to represent a single entry in that collection. For example, an entry in your `blog` collection would have the type `CollectionEntry<'blog'>`. Each `CollectionEntry` is an object with the following values: #### `id` +

+ **Type:** `string` +

A unique ID. Note that all IDs from Astro's built-in `glob()` loader are slugified. #### `collection` -**Example Type:** `'blog' | 'authors' | ...` +

+ +**Type:** [`CollectionKey`](#collectionkey) +

-The name of a collection in which entries are located. This is the name used to reference the collection in your schema, and in querying functions. +The name of a collection in which entries are located. This is the name used to reference the collection in your schema and in querying functions. #### `data` +

+ **Type:** `CollectionSchema` +

An object of frontmatter properties inferred from your collection schema ([see `defineCollection()` reference](#definecollection)). Defaults to `any` if no schema is configured. #### `body` +

+ **Type:** `string` +

A string containing the raw, uncompiled body of the Markdown or MDX document. +#### `rendered` + +

+ +**Type:** `RenderedContent | undefined` +

+ +The rendered content of an entry as [stored by your loader](/en/reference/content-loader-reference/#rendered). For example, this can be the rendered content of a Markdown entry, or HTML from a CMS. + +#### `filePath` + +

+ +**Type:** `string | undefined` +

+ +The path to an entry relative to your project directory. This value is only available for local entries. + ### `CollectionKey` -

+

+ +**Example Type:** `'blog' | 'authors' | ...`
+ +

A string union of all collection names defined in your `src/content.config.*` file. This type can be useful when defining a generic function wrapping the built-in `getCollection()`. -```ts +```ts title="src/utils/collections.ts" import { type CollectionKey, getCollection } from 'astro:content'; -async function queryCollection(collection: CollectionKey) { +export async function queryCollection(collection: CollectionKey) { return getCollection(collection, ({ data }) => { return data.draft !== true; }); @@ -303,8 +449,9 @@ This includes the following property: - `image` - The `image()` schema helper that allows you [to use local images in Content Collections](/en/guides/images/#images-in-content-collections) -```ts +```ts title="src/content.config.ts" "SchemaContext" {4-8,12,15} import { defineCollection, z, type SchemaContext } from "astro:content"; +import { glob } from 'astro/loaders'; export const imageSchema = ({ image }: SchemaContext) => z.object({ @@ -313,7 +460,7 @@ export const imageSchema = ({ image }: SchemaContext) => }); const blog = defineCollection({ - loader: /* ... */, + loader: glob({ pattern: '**/*.md', base: './src/data/blog' }), schema: ({ image }) => z.object({ title: z.string(), permalink: z.string().optional(), diff --git a/src/content/docs/en/tutorial/6-islands/4.mdx b/src/content/docs/en/tutorial/6-islands/4.mdx index 06ad6a55abe89..066162404180c 100644 --- a/src/content/docs/en/tutorial/6-islands/4.mdx +++ b/src/content/docs/en/tutorial/6-islands/4.mdx @@ -147,7 +147,7 @@ Upgrade to the latest version of Astro, and upgrade all integrations to their la 1. Create a page file called `src/pages/posts/[...slug].astro`. Your Markdown and MDX files no longer automatically become pages using Astro's file-based routing when they are inside a collection, so you must create a page responsible for generating each individual blog post. -2. Add the following code to [query your collection](/en/guides/content-collections/#querying-collections) to make each blog post's slug and page content available to each page it will generate: +2. Add the following code to [query your collection](/en/guides/content-collections/#querying-build-time-collections) to make each blog post's slug and page content available to each page it will generate: ```astro title="src/pages/posts/[...slug].astro" --- diff --git a/src/content/docs/es/guides/media/cloudinary.mdx b/src/content/docs/es/guides/media/cloudinary.mdx index cfdc59827c996..06f370ff11486 100644 --- a/src/content/docs/es/guides/media/cloudinary.mdx +++ b/src/content/docs/es/guides/media/cloudinary.mdx @@ -134,7 +134,7 @@ export const collections = { } ``` -Luego puedes usar las [funciones de consulta `getCollection()` o `getEntry()`](/es/guides/content-collections/#querying-collections) para seleccionar una o varias imágenes o vídeos de tu colección. +Luego puedes usar las [funciones de consulta `getCollection()` o `getEntry()`](/es/guides/content-collections/#querying-build-time-collections) para seleccionar una o varias imágenes o vídeos de tu colección. Consulta la [documentación de `cldAssetsLoader` de Cloudinary](https://astro.cloudinary.dev/cldassetsloader/basic-usage) para más información. diff --git a/src/content/docs/es/tutorial/6-islands/4.mdx b/src/content/docs/es/tutorial/6-islands/4.mdx index 332658a62fa65..5a29231adb35a 100644 --- a/src/content/docs/es/tutorial/6-islands/4.mdx +++ b/src/content/docs/es/tutorial/6-islands/4.mdx @@ -145,7 +145,7 @@ Actualiza a la última versión de Astro y actualiza todas las integraciones a s 1. Crea un archivo de página llamado `src/pages/posts/[...slug].astro`. Tus archivos Markdown y MDX ya no se convierten automáticamente en páginas usando el enrutamiento basado en archivos de Astro cuando están dentro de una colección, por lo que debes crear una página responsable de generar cada publicación de blog individual. -2. Agrega el siguiente código para [consultar tu colección](/es/guides/content-collections/#querying-collections) para que el slug de cada publicación de blog y el contenido de la página estén disponibles para cada página que generará: +2. Agrega el siguiente código para [consultar tu colección](/es/guides/content-collections/#querying-build-time-collections) para que el slug de cada publicación de blog y el contenido de la página estén disponibles para cada página que generará: ```astro title="src/pages/posts/[...slug].astro" --- diff --git a/src/content/docs/it/tutorial/6-islands/4.mdx b/src/content/docs/it/tutorial/6-islands/4.mdx index 167a08a9c3038..0e5ca0a650933 100644 --- a/src/content/docs/it/tutorial/6-islands/4.mdx +++ b/src/content/docs/it/tutorial/6-islands/4.mdx @@ -144,7 +144,7 @@ Aggiorna all'ultima versione di Astro e aggiorna tutte le integrazioni alle loro 1. Crea un file pagina chiamato `src/pages/posts/[...slug].astro`. I tuoi file Markdown e MDX non diventano più automaticamente pagine usando il routing basato su file di Astro quando si trovano all'interno di una raccolta, quindi devi creare una pagina responsabile della generazione di ogni singolo articolo del blog. -2. Aggiungi il seguente codice per [interrogare la tua raccolta](/it/guides/content-collections/#querying-collections) per rendere disponibili lo slug e il contenuto della pagina di ogni articolo del blog a ogni pagina che genererà: +2. Aggiungi il seguente codice per [interrogare la tua raccolta](/it/guides/content-collections/#querying-build-time-collections) per rendere disponibili lo slug e il contenuto della pagina di ogni articolo del blog a ogni pagina che genererà: ```astro title="src/pages/posts/[...slug].astro" --- diff --git a/src/content/docs/ja/guides/integrations-guide/markdoc.mdx b/src/content/docs/ja/guides/integrations-guide/markdoc.mdx index edd950cf75a99..8272fb0f42381 100644 --- a/src/content/docs/ja/guides/integrations-guide/markdoc.mdx +++ b/src/content/docs/ja/guides/integrations-guide/markdoc.mdx @@ -113,7 +113,7 @@ Markdocファイルは、コンテンツコレクション内でのみ使用で - quick-start.mdoc -次に、[コンテンツコレクションAPI](/ja/guides/content-collections/#querying-collections)を使用してコレクションをクエリします。 +次に、[コンテンツコレクションAPI](/ja/guides/content-collections/#querying-build-time-collections)を使用してコレクションをクエリします。 ```astro title="src/pages/why-markdoc.astro" --- diff --git a/src/content/docs/ja/guides/migrate-to-astro/from-create-react-app.mdx b/src/content/docs/ja/guides/migrate-to-astro/from-create-react-app.mdx index 0f109b9fb6c85..940d7cc185bde 100644 --- a/src/content/docs/ja/guides/migrate-to-astro/from-create-react-app.mdx +++ b/src/content/docs/ja/guides/migrate-to-astro/from-create-react-app.mdx @@ -329,7 +329,7 @@ const randomUser = data.results[0]; ``` - `import.meta.glob()`によるローカルファイル取得 -- Collections APIのクエリ: [/ja/guides/content-collections/#コレクションのクエリ](/ja/guides/content-collections/#querying-collections) +- Collections APIのクエリ: [/ja/guides/content-collections/#コレクションのクエリ](/ja/guides/content-collections/#querying-build-time-collections) - リモートデータのフェッチ: [/ja/guides/data-fetching/](/ja/guides/data-fetching/) ### CRAのスタイリングをAstroへ diff --git a/src/content/docs/pt-br/tutorial/6-islands/4.mdx b/src/content/docs/pt-br/tutorial/6-islands/4.mdx index 4dc53475bb4c1..79ea4987e4ba6 100644 --- a/src/content/docs/pt-br/tutorial/6-islands/4.mdx +++ b/src/content/docs/pt-br/tutorial/6-islands/4.mdx @@ -144,7 +144,7 @@ Atualize para a última versão do Astro, e atualize todas as integrações para 1. Crie um arquivo de página chamado `src/pages/posts/[...slug].astro`. Seus arquivos Markdown e MDX não mais se tornarão páginas automaticamente usando o roteamento baseado em arquivos Astro quando estão dentro de uma coleção, então você precisa criar a página responsável para gerar cada postagem de blog individual. -2. Adicione o seguinte código para [consultar sua coleção](/pt-br/guides/content-collections/#querying-collections) para fazer cada slug e conteúdo de postagem de blog disponível para cada página que será gerada: +2. Adicione o seguinte código para [consultar sua coleção](/pt-br/guides/content-collections/#querying-build-time-collections) para fazer cada slug e conteúdo de postagem de blog disponível para cada página que será gerada: ```astro title="src/pages/posts/[...slug].astro" --- diff --git a/src/content/docs/ru/reference/errors/mixed-content-data-collection-error.mdx b/src/content/docs/ru/reference/errors/mixed-content-data-collection-error.mdx index a5986f5139e96..652b78ca9cc40 100644 --- a/src/content/docs/ru/reference/errors/mixed-content-data-collection-error.mdx +++ b/src/content/docs/ru/reference/errors/mixed-content-data-collection-error.mdx @@ -10,6 +10,3 @@ githubURL: https://github.com/withastro/astro/blob/main/packages/astro/src/core/ Коллекция контента не может содержать смешанные записи контента и данных. Вы должны хранить записи в отдельных коллекциях по типу. -**Смотрите также:** - -- [Определение коллекций контента](/ru/guides/content-collections/#defining-collections) \ No newline at end of file diff --git a/src/content/docs/ru/tutorial/6-islands/4.mdx b/src/content/docs/ru/tutorial/6-islands/4.mdx index 635c8eb29684d..bec07253313a2 100644 --- a/src/content/docs/ru/tutorial/6-islands/4.mdx +++ b/src/content/docs/ru/tutorial/6-islands/4.mdx @@ -144,7 +144,7 @@ import { Steps } from '@astrojs/starlight/components'; 1. Создайте файл страницы с именем `src/pages/posts/[...slug].astro`. Ваши файлы Markdown и MDX больше не становятся автоматически страницами при использовании маршрутизации на основе файлов в Astro, когда они находятся внутри коллекции, поэтому вы должны создать страницу, отвечающую за генерацию каждого отдельного поста блога. -2. Добавьте следующий код, чтобы [запросить вашу коллекцию](/ru/guides/content-collections/#querying-collections) и сделать slug и содержимое всех страниц доступными для генерации соответствующих постов. +2. Добавьте следующий код, чтобы [запросить вашу коллекцию](/ru/guides/content-collections/#querying-build-time-collections) и сделать slug и содержимое всех страниц доступными для генерации соответствующих постов. ```astro title="src/pages/posts/[...slug].astro" ---