Fix: Invalid Content Entry Slug in Astro

Error message:
Invalid content entry slug.
Content Collections 2025-01-25

What Causes This Error?

This error occurs when a content entry has an invalid slug value in its frontmatter. The slug must be a string that can be used in URLs.

The Problem

---
# ❌ Invalid slug types
slug: 123
slug: true
slug: null
slug:
  - part1
  - part2
---

The Fix

Use Valid String Slug

---
# ✅ Valid string slugs
slug: "my-blog-post"
slug: "2024/01/my-post"
slug: "category/subcategory/post"
---

Common Scenarios

Default Slug from Filename

# File: src/content/blog/my-first-post.md
# Default slug is: "my-first-post" (from filename)

# No slug field needed - uses filename
---
title: "My First Post"
---

Custom Slug Override

---
# File: src/content/blog/post-001.md

# ✅ Override the filename-based slug
title: "Welcome to My Blog"
slug: "welcome"
---

<!-- URL will be /blog/welcome instead of /blog/post-001 -->

Slug with Special Characters

---
# ❌ Invalid characters in slug
slug: "My Post Title!"

# ✅ URL-safe slug
slug: "my-post-title"
---

Nested Slug Paths

---
# ✅ Slugs can include path separators
slug: "2024/january/new-year-post"
---

<!-- URL will be /blog/2024/january/new-year-post -->

Computed Slug in Schema

// src/content/config.ts
import { defineCollection, z } from 'astro:content';

const blog = defineCollection({
  type: 'content',
  schema: z.object({
    title: z.string(),
    // Don't include slug in schema - it's handled automatically
  }),
});

Slug Validation

---
# ✅ Good slugs
slug: "post-title"
slug: "category/post"
slug: "2024-01-15-post"

# ❌ Bad slugs
slug: ""              # Empty string
slug: "   "           # Whitespace only
slug: "/leading"      # Leading slash
slug: "trailing/"     # Trailing slash
slug: "double//slash" # Double slashes
---

Using Slug in Pages

---
// src/pages/blog/[...slug].astro
import { getCollection } from 'astro:content';

export async function getStaticPaths() {
  const posts = await getCollection('blog');
  return posts.map((post) => ({
    // slug is automatically generated or from frontmatter
    params: { slug: post.slug },
    props: { post },
  }));
}
---

Ensuring Unique Slugs

---
# File 1: src/content/blog/post-a.md
slug: "my-unique-slug"
---

---
# File 2: src/content/blog/post-b.md
slug: "my-unique-slug"  # ❌ Duplicate - will cause error
---

Slug from Title

// If you want to generate slugs from titles, do it in the page
import { getCollection } from 'astro:content';
import slugify from 'slugify';

const posts = await getCollection('blog');
const postsWithSlugs = posts.map(post => ({
  ...post,
  computedSlug: slugify(post.data.title, { lower: true }),
}));

Internationalized Slugs

---
# ✅ Slugs can include Unicode (URL-encoded)
slug: "über-uns"
slug: "artículo-español"
---

Quick Checklist

  • Slug must be a string
  • Use URL-safe characters
  • No leading or trailing slashes
  • Each slug must be unique within collection
  • Empty filename = uses file name as slug
  • Keep slugs lowercase and hyphenated