9.8 KiB
Create New Astro Content Collection
This command guides you through creating a new content collection in an Astro project with proper schema definition, directory structure, and TypeScript integration.
Instructions
Follow these steps to create a new content collection:
1. Gather Requirements
Ask the user for:
- Collection name (e.g., 'blog', 'docs', 'products', 'team')
- Collection purpose (what type of content?)
- Required fields and their types
- Optional fields
- File format preference (markdown, JSON, YAML, etc.)
2. Design Schema Fields
Based on the collection purpose, determine appropriate fields:
For blog/articles:
- title (string, required)
- description (string, required)
- pubDate (date, required)
- author (string, required)
- tags (array of strings)
- draft (boolean, default false)
- image (object with url and alt)
For documentation:
- title (string, required)
- description (string, required)
- sidebar (object with order, label)
- lastUpdated (date, optional)
- contributors (array of strings)
For products:
- name (string, required)
- description (string, required)
- price (number, required)
- category (enum)
- features (array of strings)
- inStock (boolean)
- images (array of objects)
For team members:
- name (string, required)
- role (string, required)
- bio (string, required)
- avatar (string, required)
- social (object with URLs)
- startDate (date, required)
3. Locate or Create Config File
-
Check if config exists:
- Look for
src/content/config.ts - Or
src/content.config.ts
- Look for
-
If it doesn't exist, create
src/content/config.ts -
Ensure imports are present:
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
4. Define Collection Schema
Create the collection definition with appropriate Zod schema:
const [collectionName] = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/[collectionName]" }),
schema: z.object({
// Define fields based on requirements
})
});
Use appropriate Zod types:
z.string()- Text fieldsz.number()- Numeric fieldsz.boolean()- True/false fieldsz.coerce.date()- Date fields (with auto-conversion)z.array(z.string())- Arraysz.object({...})- Nested objectsz.enum(['value1', 'value2'])- Fixed options- Add
.optional()for optional fields - Add
.default(value)for default values
5. Add to Collections Export
Update the export statement:
export const collections = {
// existing collections...
[collectionName]: [collectionName]
};
6. Create Collection Directory
Create the directory structure:
mkdir -p src/content/[collectionName]
7. Create Sample Content File
Create a sample file to test the schema:
For markdown:
src/content/[collectionName]/sample.md
For JSON:
src/content/[collectionName]/sample.json
For YAML:
src/content/[collectionName]/sample.yml
Include frontmatter/data that matches the schema.
8. Validate Schema
- Start the dev server:
npm run dev - Check for TypeScript errors
- Verify the sample file validates correctly
- Check
.astro/types.d.tsfor generated types
9. Document Usage
Create a comment block in the config explaining the collection:
/**
* [Collection Name] Collection
*
* Purpose: [Description]
*
* Required fields:
* - field1: description
* - field2: description
*
* Optional fields:
* - field3: description
*/
const [collectionName] = defineCollection({...});
10. Provide Query Examples
Show the user how to query the new collection:
import { getCollection, getEntry } from 'astro:content';
// Get all items
const items = await getCollection('[collectionName]');
// Get published items (if applicable)
const published = await getCollection('[collectionName]', ({ data }) => {
return !data.draft;
});
// Get single item
const item = await getEntry('[collectionName]', 'slug');
Complete Example
Collection Config (src/content/config.ts):
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
/**
* Blog Posts Collection
*
* Stores blog articles and tutorials
*/
const blog = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }),
schema: z.object({
title: z.string().max(80),
description: z.string().max(160),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
author: z.string(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
featured: z.boolean().default(false),
image: z.object({
url: z.string(),
alt: z.string()
}).optional()
})
});
/**
* Documentation Collection
*
* Technical documentation and guides
*/
const docs = defineCollection({
loader: glob({ pattern: "**/*.mdx", base: "./src/content/docs" }),
schema: z.object({
title: z.string(),
description: z.string(),
sidebar: z.object({
order: z.number(),
label: z.string().optional(),
hidden: z.boolean().default(false)
}).optional(),
lastUpdated: z.coerce.date().optional(),
contributors: z.array(z.string()).default([])
})
});
export const collections = { blog, docs };
Sample Blog Post (src/content/blog/first-post.md):
---
title: 'My First Post'
description: 'This is my first blog post'
pubDate: 2024-01-15
author: 'John Doe'
tags: ['intro', 'blogging']
draft: false
featured: true
---
# Hello World
This is my first post!
Definition of Done
- Collection requirements gathered from user
- Schema fields identified and typed
- Config file located or created
- Collection definition added with proper schema
- Loader configured for correct file pattern
- Zod types match requirements
- Collection exported in collections object
- Collection directory created
- Sample content file created
- Sample file validates against schema
- Dev server starts without errors
- TypeScript types generated correctly
- Documentation comments added
- Query examples provided to user
Schema Best Practices
String Validation
title: z.string().min(1).max(100)
email: z.string().email()
url: z.string().url()
slug: z.string().regex(/^[a-z0-9-]+$/)
Number Validation
price: z.number().positive()
age: z.number().int().min(0).max(120)
rating: z.number().min(1).max(5)
Dates
pubDate: z.coerce.date() // Converts strings to Date
deadline: z.date() // Requires Date object
Arrays
tags: z.array(z.string()).min(1) // At least one
images: z.array(z.string()).max(10) // Maximum 10
authors: z.array(z.string()).default([])
Enums
status: z.enum(['draft', 'published', 'archived'])
priority: z.enum(['low', 'medium', 'high'])
Optional Fields
subtitle: z.string().optional()
updatedDate: z.coerce.date().optional()
Default Values
draft: z.boolean().default(false)
views: z.number().default(0)
tags: z.array(z.string()).default([])
Nested Objects
author: z.object({
name: z.string(),
email: z.string().email(),
url: z.string().url().optional()
})
Records (Dynamic Keys)
metadata: z.record(z.string()) // Any string keys
settings: z.record(z.boolean()) // Boolean values
Loader Patterns
Markdown Files
loader: glob({ pattern: "**/*.md", base: "./src/content/blog" })
MDX Files
loader: glob({ pattern: "**/*.mdx", base: "./src/content/docs" })
Multiple Formats
loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/mixed" })
JSON Files
loader: glob({ pattern: "**/*.json", base: "./src/content/data" })
YAML Files
loader: glob({ pattern: "**/*.{yml,yaml}", base: "./src/content/config" })
Single File
loader: file("src/data/settings.json")
Common Collection Types
Blog
const blog = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }),
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
author: z.string(),
tags: z.array(z.string()),
draft: z.boolean().default(false)
})
});
Portfolio Projects
const projects = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/projects" }),
schema: z.object({
title: z.string(),
description: z.string(),
technologies: z.array(z.string()),
liveUrl: z.string().url().optional(),
githubUrl: z.string().url().optional(),
thumbnail: z.string(),
date: z.coerce.date()
})
});
Product Catalog
const products = defineCollection({
loader: glob({ pattern: "**/*.json", base: "./src/content/products" }),
schema: z.object({
name: z.string(),
description: z.string(),
price: z.number().positive(),
category: z.enum(['software', 'hardware', 'service']),
features: z.array(z.string()),
inStock: z.boolean()
})
});
Error Prevention
- Always use
z.coerce.date()for date strings - Provide defaults for booleans and arrays
- Use enums for fixed value sets
- Validate URLs and emails with built-in validators
- Test schema with sample content first
- Restart dev server after schema changes
- Export all collections in the collections object
- Use consistent naming (kebab-case for directories)
Tips
- Start with required fields, add optional later
- Use TypeScript comments for documentation
- Create sample content immediately
- Test validation with invalid data
- Consider future needs when designing schema
- Use subdirectories for organization
- Keep schemas focused and specific
- Reuse common patterns across collections