diff --git a/src/i18n/en/nav.ts b/src/i18n/en/nav.ts
index 683cf0a5bf404..458e498eb4aca 100644
--- a/src/i18n/en/nav.ts
+++ b/src/i18n/en/nav.ts
@@ -62,6 +62,11 @@ export default [
key: 'guides/server-side-rendering',
},
{ text: 'Authoring Content', slug: 'guides/content', key: 'guides/content' },
+ {
+ text: 'Content Collections (Experimental)',
+ slug: 'guides/content-collections',
+ key: 'guides/content-collections',
+ },
{ text: 'Connecting a CMS', slug: 'guides/cms', key: 'guides/cms' },
{ text: 'Images', slug: 'guides/images', key: 'guides/images' },
{ text: 'Fonts', slug: 'guides/fonts', key: 'guides/fonts' },
diff --git a/src/pages/en/guides/content-collections.md b/src/pages/en/guides/content-collections.md
new file mode 100644
index 0000000000000..56d10ad8f2f3c
--- /dev/null
+++ b/src/pages/en/guides/content-collections.md
@@ -0,0 +1,401 @@
+---
+layout: ~/layouts/MainLayout.astro
+title: Content Collections (Experimental)
+description: Content collections help organize your Markdown and type-check your frontmatter with schemas.
+i18nReady: false
+setup: |
+ import FileTree from '~/components/FileTree.astro'
+ import Since from '~/components/Since.astro'
+---
+
+
+
+
+
+Content collections help organize your Markdown or MDX and type-check your frontmatter with schemas. Collections may be helpful if you:
+
+- **Plan to use Markdown content in multiple areas** of your site (landing pages, footers, navigation, etc).
+- **Want Astro to enforce frontmatter fields,** and fail if fields are missing (e.g. every blog post should have a title and description).
+
+## Getting started
+
+Content Collections are experimental. To enable this feature, set the `experimental.contentCollections` flag in your Astro config:
+
+```js
+// astro.config.mjs
+import { defineConfig } from 'astro';
+
+export default defineConfig({
+ experimental: {
+ contentCollections: true,
+ },
+});
+```
+
+You will also need to update `tsconfig.json`. Add `"strictNullChecks": true` under `compilerOptions`.
+
+```json title="tsconfig.json" ins={3}
+{
+ "compilerOptions": {
+ "strictNullChecks": true
+ }
+}
+```
+
+## The content directory
+
+Astro treats the `src/content/` directory as special. This is where **collections** (folders) of Markdown/MDX **entries** (files) can be stored, with a single configuration file to define each collection's **schema** (frontmatter data types and shape). Files other than your `.md`/`.mdx` content are not permitted inside `src/content/`.
+
+## Collections
+
+A collection is a directory in `src/content/` containing Markdown or MDX fields. Every Markdown or MDX file in `src/content/` **must** belong to a collection directory, since Astro [provides built-in functions](#querying-content-collections) for querying your content by the collection directory name.
+
+Content within a collection should share the same frontmatter shape and types. You can optionally enforce these types [by configuring a schema](/en/guides/content-collections/#defining-a-collection-schema).
+
+To create a collection, add a new directory to `src/content/`. Then, add Markdown or MDX entries that share frontmatter properties. The following example shows two collections: `blog` and `newsletter`.
+
+
+- src/content/
+ - **blog/** All blog posts have the same frontmatter properties
+ - columbia.md
+ - endeavour.md
+ - enterprise.md
+ - **newsletter/** All newsletters have the same frontmatter properties
+ - week-1.md
+ - week-2.md
+ - week-3.md
+
+
+### Collections with nested directories
+
+Collections are **top-level folders** within `src/content/`. You cannot nest collections, but you may use nested directories within a collection to better organize a collection's content. All nested directories will share the same schema defined for the top-level collection.
+
+For example, you can use this structure for internationalization:
+
+
+- src/content/
+ - docs/ docs schema applies to all nested directories
+ - en/
+ - es/
+ - ...
+
+
+## Defining a collection schema
+
+Schemas are an optional way to enforce frontmatter types in a collection. Astro uses [Zod](https://github.com/colinhacks/zod) to validate your frontmatter with schemas in the form of [Zod objects](https://github.com/colinhacks/zod#objects).
+
+To configure schemas, create a `src/content/config.ts` file (`.js` and `.mjs` extensions are also supported). This file should:
+
+1. Import the `defineCollection` and `z` utilities from `astro:content`.
+2. Define a `schema` for each collection.
+2. Export a single `collections` object, with each object key corresponding to the collection's folder name.
+
+For example, say you maintain two collections: one for release announcements and one for blog content. Your entries at `src/content/announcements` should include a `title` and `version`. Your `src/content/engineering-blog/` collection entries should have a `title`, list of `tags`, and an optional `image` URL.
+
+You can specify each expected property in the `schema` field of `defineCollection`:
+
+```ts
+// src/content/config.ts
+import { z, defineCollection } from 'astro:content';
+
+const releases = defineCollection({
+ schema: {
+ title: z.string(),
+ version: z.number(),
+ },
+});
+
+const engineeringBlog = defineCollection({
+ schema: {
+ title: z.string(),
+ tags: z.array(z.string()),
+ image: z.string().optional(),
+ },
+});
+
+export const collections = {
+ releases: releases,
+ // Don't forget 'quotes' for collection names containing dashes
+ 'engineering-blog': engineeringBlog,
+};
+```
+
+### Schema data types with Zod
+
+Markdown and MDX frontmatter can contain booleans, strings, numbers, objects, and arrays. When defining a schema, you must include every frontmatter property along with its data type. To define and validate this schema, we use a library called [Zod](https://github.com/colinhacks/zod), which is available via the `z` import.
+
+You can extend any of these types with `.optional()` if a frontmatter property is not always required or `.defaultValue(value)` to provide a value to use when the property is not set in frontmatter. If only a limited set of values is valid for a property, you can specify these using [the `.enum()` method](https://github.com/colinhacks/zod#zod-enums).
+
+The following schema illustrates each of these data types in use:
+
+```ts
+import { z, defineCollection } from 'astro:content';
+
+defineCollection({
+ schema: {
+ isDraft: z.boolean(),
+ title: z.string(),
+ sortOrder: z.number(),
+ image: z.object({
+ src: z.string(),
+ alt: z.string(),
+ }),
+ tags: z.array(z.string()), // An array of strings
+ footnote: z.string().optional(),
+ author: z.string().default('Anonymous'),
+ language: z.enum(['en', 'es']),
+ }
+})
+```
+
+### Advanced schema features
+
+You can use all of Zod’s properties and methods with content schemas. This includes transforming a frontmatter value into another value, checking the shape of string values with built-in regexes, and more.
+
+```ts
+{
+ // Allow only strings representing email addresses
+ authorContact: z.string().email(),
+ // Allow URL strings only (e.g. `https://example.com`)
+ canonicalURL: z.string().url(),
+ // Parse publishDate as a browser-standard `Date` object
+ publishDate: z.string().transform(str => new Date(str)),
+}
+```
+
+📚 See [Zod’s documentation](https://github.com/colinhacks/zod) for a complete list of features.
+
+## Querying content collections
+
+Astro provides two functions to query collections:
+
+### `getCollection()`
+
+`getCollection()` returns multiple entries in a collection. It requires the name of a `collection` as a parameter. By default, it returns all items in the collection.
+
+It can also take a second, optional parameter: a filter function based on schema properties. This allows you to query for only some items in a collection based on `id`, `slug`, or frontmatter values via the `data` object.
+
+```astro
+---
+import { getCollection } from 'astro:content';
+
+// Get all `src/content/blog/` entries
+const allBlogPosts = await getCollection('blog');
+
+// Only return posts with `draft: true` in the frontmatter
+const draftBlogPosts = await getCollection('blog', ({ data }) => {
+ return data.draft === true;
+});
+---
+```
+
+#### Querying nested directories
+
+The filter function can also be used to query for 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:
+
+```astro
+---
+import { getCollection } from 'astro:content';
+const enDocs = await getCollection('docs', ({ id }) => {
+ // Return all entries in `src/content/docs/en/`
+ return id.startsWith('en/');
+});
+---
+```
+
+### `getEntry()`
+
+`getEntry()` is function that returns a specific entry in a collection by entry ID (file path relative to the collection). Both of these are required parameters.
+
+```astro
+---
+import { getEntry } from 'astro:content';
+
+const enterprise = await getEntry('blog', 'enterprise.md');
+---
+```
+
+### Data returned from a collection query
+
+`getCollection()` and `getEntry()` will return entries that include:
+ - `id` - a unique ID using the file path relative to `src/content/[collection]`
+ - `slug` - a URL-ready slug. Defaults to the ID without the file extension.
+ - `data` - an object of frontmatter properties inferred from your collection schema. Defaults to `any` if no schema is configured.
+ - `body` - a string containing the raw, uncompiled body of the Markdown or MDX document.
+ - `render()` - a function that returns the compiled body of the Markdown or MDX document via the `` component ([See complete documentation](#rendering-entry-content)).
+
+Querying your content files with `getCollection()`or `getEntry()` allows you to use frontmatter properties from an entry's `data` object in [JSX-like expressions](/en/core-concepts/astro-components/#jsx-like-expressions) or pass props to other components, such as a layout. You can optionally add type safety with a built-in utility.
+
+For example, you can use a `getCollection()` query to filter and then display a list of links to all your published blog posts:
+```astro
+---
+// src/pages/index.astro
+import { getCollection } from 'astro:content';
+
+// Get all published blog posts
+const blogPosts = await getCollection('blog', ({ data }) => {
+ return data.status === 'published';
+});
+---
+
+```
+
+### Collection entry types
+
+If a page or component uses content from a `getCollection()` or `getEntry()` query, you can use the `CollectionEntry` utility to type its props:
+
+```astro /CollectionEntry([<.+>])?/
+---
+// src/components/BlogCard.astro
+import type { CollectionEntry } from 'astro:content';
+
+interface Props {
+ // Get type of a `blog` collection entry
+ post: CollectionEntry<'blog'>;
+}
+
+// `post.data` will match your collection schema
+const { post } = Astro.props;
+---
+```
+
+## Rendering entry content
+
+Every collection entry includes a `render()` function that gives you access to the contents of the Markdown or MDX file. This includes a `` component for rendering the document body, as well as the document headings and injected frontmatter.
+
+```astro {5}
+---
+// src/pages/render-example.astro
+import { getEntry } from 'astro:content';
+const entry = await getEntry('blog', 'post-1.md');
+const { Content, headings, injectedFrontmatter } = await entry.render();
+---
+```
+
+### Access the `` component from `render()`
+
+To render the content of a Markdown or MDX entry, use the `` component returned by its `render()` function. This allows you to generate pages from your content entries (see [Generating pages from content collections](#generating-pages-from-content-collections)), add post previews to your homepage, or display your content elsewhere on your site.
+
+For example, this page renders the contents of `content/announcements/welcome.md` and uses some of its frontmatter properties:
+
+```astro "render()"
+---
+// src/pages/welcome-announcement.astro
+import Layout from '../../layouts/Layout.astro';
+import { getEntry } from 'astro:content';
+const announcementPost = await getEntry('announcements', 'welcome.md');
+const { Content } = await announcementPost.render();
+---
+
+
{announcementPost.data.title}
+
Written by: {announcementPost.data.author}
+
+
+```
+
+### Access headings from `render()`
+
+Astro [generates a list of headings](/en/guides/markdown-content/#exported-properties) for Markdown and MDX documents. You can access this list using the `headings` property from `render()`:
+
+```astro "{ headings }"
+---
+import { getCollection } from 'astro:content';
+const blogPosts = await getCollection('blog');
+---
+
+{blogPosts.map(async (post) => {
+ const { headings } = await post.render();
+ const h1 = headings.find(h => h.depth === 1);
+ return
{h1}
+})}
+```
+
+### Access injected frontmatter from `render()`
+
+Astro allows you to [inject frontmatter using remark or rehype plugins.](/en/guides/markdown-content/#example-injecting-frontmatter) You can access these values using the `injectedFrontmatter` property from `render()`:
+
+```astro "{ injectedFrontmatter }"
+---
+import { getCollection } from 'astro:content';
+const blogPosts = await getCollection('blog');
+---
+
+{blogPosts.map(async (post) => {
+ const { injectedFrontmatter } = await post.render();
+ return
+})}
+```
+
+Assuming `readingTime` was injected ([see our reading time example](/en/guides/markdown-content/#example-calculate-reading-time)), it will be available on the `injectedFrontmatter` object.
+
+
+**🙋 Why don't `getCollection()` and `getEntry()` contain these values?**
+
+The remark and rehype pipelines are only run when your content is **rendered.** This lets `render()` access anything generated by these plugins like injected frontmatter. To stay performant, `getCollection()` and `getEntry()` do not have this capability.
+
+
+
+## Generating pages from content collections
+
+You can create pages based on your content collections using [dynamic routes](/en/core-concepts/routing/#dynamic-routes).
+
+Use `getCollection()` inside a [`getStaticPaths()`](/en/reference/api-reference/#getstaticpaths) function to query your content entries and provide the `slug` parameter for each page.
+
+For example, you can dynamically create a page for each entry in a `blog` collection, including nested directories, by creating a `.astro` page with a [rest parameter in its filename](/en/core-concepts/routing/#rest-parameters) to match file paths of any depth.
+
+ ```astro "{ slug: entry.slug }"
+ ---
+ // src/pages/posts/[...slug].astro
+ import { getCollection } from 'astro:content';
+
+ export async function getStaticPaths() {
+ const blog = await getCollection('blog');
+ return blog.map(entry => ({
+ params: { slug: entry.slug },
+ }));
+ }
+ ---
+ ```
+
+This will generate page routes for every entry in the `blog` collection, mapping each entry’s slug to a URL. For example, an entry at `src/content/blog/hello-world.md` will have a slug of `hello-world` and the collection entry `src/content/blog/en/intro.md` will have a slug of `en/intro`.
+
+Because this dynamic route is in `src/pages/posts/`, the final URLs will be `/posts/hello-world/` and `/posts/en/intro/`.
+
+### Rendering post contents
+
+When you pass each page route an entry via `props` in your `getStaticPaths()` function, you have access to the entry from `Astro.props`. You can use its frontmatter values via the `data` object and use `render()` to render its content via a ` component. You can optionally add type safety using the `CollectionEntry` utility.
+
+```astro "render()" "props: entry"
+---
+// src/pages/blog/[...slug].astro
+import { getCollection, CollectionEntry } from 'astro:content';
+
+export async function getStaticPaths() {
+ const docs = await getCollection('docs');
+ return docs.map(entry => ({
+ // Pass blog entry via props
+ params: { slug: entry.slug, props: { entry } },
+ }));
+}
+
+interface Props {
+ // Optionally use `CollectionEntry` for type safety
+ entry: CollectionEntry<'docs'>;
+}
+
+const { entry } = Astro.props;
+const { Content } = await entry.render();
+---
+
+