unionlabs/union · error · Error
Blog entry ${item.sys.id} has no slug
Error message
Blog entry ${item.sys.id} has no slug What it means
Every entry of content type "blog" must expose a non-empty localized slug because the slug becomes the markdown filename under site/src/content/blog (join(blogDirectory, `${slug}.md`)). field() reads fields.slug["en-US"]; a blog entry whose en-US slug is absent makes the import abort with the entry's sys.id.
Source
Thrown at site/scripts/import-contentful-export.mjs:513
`updatedAt: ${yamlString(metadata.updatedAt)}`,
"---",
"",
].join("\n")
const body = renderNode(field(item, "copy"), { terms: true })
await writeFile(
join(legalDirectory, `${slug}.md`),
`${frontmatter}${body}\n`,
)
}
for (
const item of preview.entries.items.filter(
(item) => entryType(item) === "blog",
)
) {
const slug = field(item, "slug")
if (!slug) {
throw new Error(`Blog entry ${item.sys.id} has no slug`)
}
const metadata = sourceMetadata(item)
const cover = assetFromLink(field(item, "cover"))
const frontmatter = [
"---",
`title: ${yamlString(field(item, "title") || "")}`,
`date: ${yamlString(field(item, "date") || "")}`,
`author: ${yamlString(field(item, "author") || "union_build")}`,
`description: ${yamlString(field(item, "description") || "")}`,
`cover: ${yamlString(cover?.path || "")}`,
`coverAlt: ${yamlString(cover?.description || cover?.title || "")}`,
`hidden: ${field(item, "hidden") === true}`,
`published: ${metadata.published}`,
`sourceId: ${yamlString(metadata.sourceId)}`,
`createdAt: ${yamlString(metadata.createdAt)}`,
`updatedAt: ${yamlString(metadata.updatedAt)}`,
"---",View on GitHub (pinned to 031785bb6d)
Solutions
- Look up the reported sys.id in Contentful, fill in the slug, and re-export
- Confirm the slug value is in the en-US locale (field() reads item.fields.slug["en-US"])
- If draft noise is the cause, publish/complete or remove the placeholder entries and re-export
- Keep slugs unique — duplicate slugs overwrite each other's markdown files
Example fix
// before
const slug = field(item, "slug")
if (!slug) {
throw new Error(`Blog entry ${item.sys.id} has no slug`)
}
// after — report every offender in one pass instead of only the first
const blogs = preview.entries.items.filter((item) => entryType(item) === "blog")
const missing = blogs.filter((item) => !field(item, "slug")).map((item) => item.sys.id)
if (missing.length > 0) {
throw new Error(`Blog entries without an en-US slug: ${missing.join(", ")}`)
} Defensive patterns
Strategy: validation
Validate before calling
# List blog entries lacking an en-US slug before importing
jq -r '.items[] | select(.sys.contentType.sys.id == "blog") | select((.fields.slug // {})["en-US"] // "" | length == 0) | .sys.id' "<preview-dir>/entries.json" Type guard
const hasSlug = (item) => Boolean(item?.fields?.slug?.["en-US"])
Prevention
- Make slug required in the Contentful blog content model
- Always fill the slug in the en-US locale the script reads
- Keep slugs unique — they become markdown filenames that silently overwrite on collision
- Re-export after fixing entries; the script only reads the export files
When it happens
Trigger: A blog entry saved without a slug (the Contentful model does not enforce it), a slug entered under a locale other than en-US, or a draft/placeholder blog entry present in the preview export.
Common situations: Editors leaving slug empty on drafts that still land in the preview export; multilingual spaces with the slug filled only in another locale; entries created via the Content API without the slug field; content model where slug is optional.
Related errors
- Asset ${asset.id} size mismatch: expected ${asset.size}, rec
- The Contentful export is missing the landing or learn entry
- Usage: node site/scripts/import-contentful-export.mjs <previ
- Failed to download asset ${asset.id}: ${response.status} ${r
AI-assisted analysis of unionlabs/union@031785bb6d (2026-08-16).
Data as JSON: /api/errors/8bf1abea324f11b4.
Report an issue: GitHub.