What Causes This Error?
This error occurs when you define a slug field in your content collection schema. The slug field is reserved by Astro and handled automatically - it should not be included in your schema.
The Problem
// src/content/config.ts
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
// ❌ slug is reserved and should not be in schema
slug: z.string(),
}),
});
The Fix
Remove Slug from Schema
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
date: z.date(),
// ✅ No slug field - Astro handles it automatically
}),
});
export const collections = { blog };
Common Scenarios
Slug is Auto-Generated
# File: src/content/blog/my-first-post.md
# Slug is automatically "my-first-post" from filename
---
title: "My First Post"
---
Content here...
---
import { getCollection } from 'astro:content';
const posts = await getCollection('blog');
posts.forEach(post => {
console.log(post.slug); // ✅ "my-first-post" - available automatically
console.log(post.data.title); // "My First Post"
});
---
Custom Slug in Frontmatter
---
# You can still override slug in frontmatter
# Just don't define it in the schema
title: "My Post"
slug: "custom-url-slug"
---
The slug in frontmatter overrides the filename-based slug.
Accessing Slug
---
import { getCollection, getEntry } from 'astro:content';
// Get all entries - slug is on the entry object
const posts = await getCollection('blog');
posts.forEach(post => {
console.log(post.slug); // Entry's slug
console.log(post.data); // Schema-defined data
});
// Get single entry
const post = await getEntry('blog', 'my-post');
console.log(post.slug); // "my-post"
---
Using Slug in Routes
---
// src/pages/blog/[...slug].astro
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map((post) => ({
params: { slug: post.slug }, // ✅ Use entry.slug
props: { post },
}));
}
const { post } = Astro.props;
---
<h1>{post.data.title}</h1>
If You Need Slug Validation
// Instead of validating slug in schema,
// validate in getStaticPaths or when accessing
import { getCollection } from 'astro:content';
const posts = await getCollection('blog', (entry) => {
// Filter or validate entries
return entry.slug.startsWith('2024/');
});
Reserved Fields
// These fields are reserved and should NOT be in schema:
// - slug
// - id
// - collection
// - body (for content collections)
// - data (contains your schema fields)
const blog = defineCollection({
type: 'content',
schema: z.object({
// ❌ Don't include these:
// slug: z.string(),
// id: z.string(),
// collection: z.string(),
// body: z.string(),
// ✅ Only include your custom fields:
title: z.string(),
description: z.string(),
author: z.string(),
}),
});
Migration from Old Schema
// Before (wrong)
const blog = defineCollection({
schema: z.object({
title: z.string(),
slug: z.string(), // Remove this
permalink: z.string(), // Use slug instead
}),
});
// After (correct)
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
// slug removed - use entry.slug instead
// If you need permalink, compute it from slug
}),
});
Quick Checklist
- Remove
slugfrom schema definition - Access slug via
entry.slugnotentry.data.slug - Custom slugs go in frontmatter, not schema
- Other reserved fields:
id,collection,body - Filename becomes default slug automatically