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

  1. Look up the reported sys.id in Contentful, fill in the slug, and re-export
  2. Confirm the slug value is in the en-US locale (field() reads item.fields.slug["en-US"])
  3. If draft noise is the cause, publish/complete or remove the placeholder entries and re-export
  4. 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

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


AI-assisted analysis of unionlabs/union@031785bb6d (2026-08-16). Data as JSON: /api/errors/8bf1abea324f11b4. Report an issue: GitHub.