withastro/astro · error · AstroError

InvalidContentEntrySlugError

InvalidContentEntrySlugError

Error message

${collection} → ${entryId} has an invalid slug. `slug` must be a string.

What it means

Thrown when a content collection entry's reserved `slug` frontmatter field is present but is not a string. Astro runs `z.string().default(generatedSlug).parse(frontmatterSlug)`: an undefined slug falls back to the generated default, but any other non-string type (number, boolean, array, object) fails the Zod parse and is caught and re-thrown as this AstroError. The `slug` field is reserved by Astro for URL generation and is only valid as a string.

Source

Thrown at packages/astro/src/content/utils.ts:145

export type ContentConfig = z.infer<typeof contentConfigParser> & { digest?: string };

type EntryInternal = { rawData: string | undefined; filePath: string };

export function parseEntrySlug({
	id,
	collection,
	generatedSlug,
	frontmatterSlug,
}: {
	id: string;
	collection: string;
	generatedSlug: string;
	frontmatterSlug?: unknown;
}) {
	try {
		return z.string().default(generatedSlug).parse(frontmatterSlug);
	} catch {
		throw new AstroError({
			...AstroErrorData.InvalidContentEntrySlugError,
			message: AstroErrorData.InvalidContentEntrySlugError.message(collection, id),
		});
	}
}

export async function getEntryData<
	TInputData extends Record<string, unknown> = Record<string, unknown>,
	TOutputData extends TInputData = TInputData,
>(
	entry: {
		id: string;
		collection: string;
		unvalidatedData: TInputData;
		_internal: EntryInternal;
	},
	collectionConfig: CollectionConfig,
	shouldEmitFile: boolean,

View on GitHub (pinned to d081033d5f)

Solutions

  1. Open the entry file named in the error message and change the `slug` value to a quoted string (e.g. `slug: "my-post"`).
  2. If you intended a numeric ID, keep the number in a custom field (e.g. `id: 42`) and set `slug` to a string, or omit `slug` entirely to use the generated slug.
  3. Run `astro check` or your editor's YAML linter to catch type coercion before build.
  4. If the slug is generated dynamically, ensure the generator returns a string at runtime.

Example fix

---
# before
slug: 42
---

---
# after
slug: "post-42"
---
Defensive patterns

Strategy: validation

Validate before calling

// Before writing content, validate slug type in your frontmatter linter
function validateFrontmatterSlug(frontmatter: Record<string, unknown>): string | null {
  if ('slug' in frontmatter && frontmatter.slug !== undefined) {
    if (typeof frontmatter.slug !== 'string') {
      return `slug must be a string, got ${typeof frontmatter.slug}`;
    }
  }
  return null;
}

Type guard

function isValidSlug(value: unknown): value is string | undefined {
  return value === undefined || typeof value === 'string';
}

Try / catch

try {
  const entries = await getCollection('blog');
} catch (e) {
  if (e instanceof AstroError && e.name === 'InvalidContentEntrySlugError') {
    console.error('Fix the slug in the entry file:', e.message);
  }
  throw e;
}

Prevention

When it happens

Trigger: A Markdown/MDX content entry has a frontmatter `slug:` key whose YAML value parses to a non-string type — e.g. `slug: 42` (number), `slug: true` (boolean), `slug: [a, b]` (array), or `slug: {x: 1}` (object). The `parseEntrySlug` function is called from `getEntryData` / `getEntryInfo` during content processing.

Common situations: A developer writes `slug: 123` in frontmatter thinking any unique identifier works. YAML auto-coerces unquoted values (e.g. `slug: 007` becomes the number 7). Migrating from another CMS that used numeric slugs. Accidentally indenting `slug:` under another key so YAML parses it as a nested map.

Related errors


AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12). Data as JSON: /api/errors/0097d5e8655cd6b5. Report an issue: GitHub.