CIP-0001: Auto-generate slugs from titles to eliminate URL conflicts

Status

Summary

Replace manual slug entry in the Decap CMS with auto-generation from the content title. This closes the root cause of recurring URL conflicts and slug-format errors, which have required several batch fix campaigns in the repository’s history and caused a Jekyll build failure in July 2026.

Which requirements does this CIP address? REQ-0002 (slug validity, uniqueness, and stability).

Audit results (2026-07-24, scripts/audit-slugs.py): 445 content files across all collections.

Baseline URL test (2026-07-24, tests/test_slug_urls.py): 31 of the 37 slug URLs return HTTP 200 on the live site. 6 return 404 because their slug field contains characters that cannot form a valid URL (unencoded spaces, leading dash, leading whitespace, @):

These 6 are content bugs (broken live pages) and must be fixed first. The remaining 31 are live pages requiring redirect_from entries before migration.

Motivation

Slugs in the current setup are entered manually by editors in a free-text field. This creates three failure modes, all of which have occurred:

  1. Wrong format — spaces or uppercase characters create invalid URLs (e.g. AI for Science Research Showcase instead of ai-for-science-research-showcase), which Jekyll either rejects or normalises inconsistently.
  2. Duplicates — two content items with the same normalised slug map to the same output path, causing Jekyll’s “destination conflict” warning and unpredictable output.
  3. Drift — the frontmatter slug field and the file name (which is also derived from the slug) can disagree if an editor edits one without the other.

The July 2026 conflict between _events/chia-conference-shaping-the-future-of-ai.md and _events/ai-for-science-research-showcase.md is a clear example. Improving hint text (done in commit b623ef0) reduces the incidence but does not eliminate it.

Detailed Description

Current behaviour

Each content collection in admin/config.yml is configured as:

slug: ""          # collection-level: filename derived from frontmatter slug
identifier_field: "slug"  # CMS uses slug field as the unique key
fields:
  - name: "slug"           # editor types this manually
    widget: "string"
    hint: "lowercase and hyphens only..."

The editor types a slug. The filename becomes that slug. The frontmatter slug and the filename are the same string, but both are under manual control.

Proposed behaviour

Remove the manual slug field from all folder-based content collections. Instead, derive the filename from the title automatically:

slug: ""  # filename auto-generated from title
identifier_field: "title"    # CMS uses title as the unique key
# no slug field in fields:

Jekyll and the site templates that currently read page.slug from frontmatter will need to fall back to page.title | slugify instead, or a slug field will be computed in a Jekyll plugin/filter.

Design decisions

Option A — Remove slug field entirely, use title-derived URLs everywhere.

Option B — Keep slug field but auto-populate it from title (read-only in CMS).

Option C — Remove slug field, derive URL from title, treat title changes as breaking.

Recommended: Option C.

Migration

Existing content already has slug frontmatter fields. These must be handled carefully:

  1. Audit: confirm that for all existing content, slug equals title | slugify. Where they differ (e.g. custom short slugs), the existing slug takes precedence and a redirect from title | slugify may be needed.
  2. Once confident, remove slug from admin/config.yml field lists and update identifier_field.
  3. Update any Jekyll templates or includes that reference page.slug directly to use page.title | slugify or a computed value.
  4. Remove the slug frontmatter field from existing files only if it exactly matches title | slugify — otherwise leave it in place and let Jekyll use it as an override.

Redirect strategy

Jekyll does not handle redirects natively. The jekyll-redirect-from plugin (already a candidate given the redirect_from fields seen in some existing files) should be confirmed in the Gemfile and used for any titles changed post-publication.

Implementation Plan

  1. Audit is completescripts/audit-slugs.py has been run and results recorded in the Summary above. Re-run after any bulk content changes to keep the count accurate.
  2. Triage the 37 real mismatches — separate into two groups:
  3. Fix content data swaps — correct frontmatter in files where slug and title are mismatched. These are content bugs independent of the slug migration.
  4. Add redirect_from entries — for every remaining real mismatch (legitimate short slug), add redirect_from: ["/title-derived-url/"] to the frontmatter so the title-derived URL will redirect to the existing live URL. This step is a hard gate: do not proceed to step 6 until all real mismatches have a redirect. Required by REQ-0002.
  5. Add jekyll-redirect-from to Gemfile if not already present; document the redirect procedure for future title changes.
  6. Update admin/config.yml — remove slug fields from all folder collections, change collection-level slug templates to "", update identifier_field to "title".
  7. Clean up 78 malformed slug field values — update frontmatter in files where slug contains title text instead of a URL-safe string.
  8. Update _layouts/events-category.html — replace page.slug references (lines 116, 158) with page.title | slugify or equivalent.
  9. Test build — confirm no URL conflicts, no broken internal links, no missing redirects.
  10. Deploy and monitor — watch Netlify build logs and search console for unexpected 404s in the two weeks following deployment.

Backward Compatibility

Existing published URLs are stable as long as the existing slug frontmatter values are preserved in content files. The CMS UI changes (no slug field visible) do not affect the built output. The risk is in step 4 of the migration: if any slug is removed from frontmatter where it differs from title | slugify, that URL changes.

Testing Strategy

Implementation Status

References