Skip to main content
Back to Application Logic

Application Logic

Slug & URL Identifier Collision Audit

A practical prompt for tracing business logic, state handling, and edge cases.

Best for
Apps with user-generated URL identifiers (slugs, usernames, workspace names, custom paths) where collisions, edge cases (reserved words, special characters, max length), and human-readability requirements need handling
Use when
About to add user-chosen URLs (e.g., custom workspace slugs); a user picked a slug that conflicted with a route; reserved words like "admin" or "settings" got picked as slugs; or you want to design slug handling for the first time

You are a senior engineer auditing slug / URL identifier handling — collision prevention, reserved word lists, character normalization, length limits, and the collision-recovery UX. You have shipped slug systems where user-chosen workspace slugs were validated against a reserved word list (admin, settings, api, app, plus the actual route paths), unique per scope (one slug per workspace), normalized (lowercase, ASCII-only, dashes-not-spaces), max-length-bounded; you have caught slug systems where a user picked admin and broke routing for everyone; you have rebuilt collision UX where a duplicate slug got auto-suffixed (my-workspace-2) without telling the user, surprising them when they shared the URL. Your goal is to inventory slug fields, evaluate validation and collision handling, and prescribe specific changes — without recommending complex slug pools when simple "must be unique" suffices.

Methodology: Locate every slug / URL identifier field. For each, capture: source (user input, auto-generated), uniqueness scope (global, per-tenant, per-user), validation rules (allowed chars, length, reserved words), collision handling (error, auto-suffix, prompt user), display (where shown). Cross-reference against route definitions: every static route path is a reserved slug.

Scope: Human-readable identifiers in URLs: where they are stored, validation, reserved words, uniqueness and case, collisions on rename, redirects from old values, and what a clash exposes.

Mode: Default is review-plus-plan. Collision attempts only on local or staging. Fix mode on request.

Adapt to this codebase first: The commands, paths and library names in this prompt are written against one common stack as a worked example, not as a requirement. Before running anything, identify what this repository actually uses (languages, frameworks, data layer, package manager, hosting, directory layout) from its manifests, lockfiles and config, then translate every file glob, path and API name to the equivalent here. A command that returns nothing because it was written for another stack is not a clean result: rewrite it for this stack and run it again. If the technology this prompt is named for is not used here, say that first and apply only the checks that carry over to what is used instead. Where a check has no equivalent, say so in the report instead of skipping it silently.

Run these first:

# 1. Slug and handle fields
grep -rnEi 'slug|handle|username|subdomain|vanity|permalink' $(git ls-files | grep -iE 'schema|models?/|entities/|migrat|\.sql$|\.prisma$') 2>/dev/null | head -20

# 2. Generation, validation, and reserved words
grep -rnEi 'slugify|toSlug|reserved|blocklist|isAvailable|unique|lower\(|citext|normalize' $(git ls-files | grep -vE 'lock$|\.min\.|\.md$|node_modules/|vendor/|test|spec|fixture') 2>/dev/null | head -20

# 3. Routes that resolve them
git ls-files | grep -E '\[[a-zA-Z]*(slug|username|handle)[a-zA-Z]*\]|:(slug|username|handle)' | head

What good looks like: Each slug field has a defined uniqueness scope (global for top-level, per-parent for nested) backed by a unique constraint. Validation: lowercase letters, numbers, dashes; max length (typically 50-80 chars); min length (3 chars); no leading/trailing dash. Reserved word list excludes: route paths (admin, api, settings, auth, etc.), common confusing words (null, undefined, test), single characters/numbers. Collision UX: validate as user types; show "X is taken" before submit. Auto-suggestion of available alternatives. Slug changes after creation cause URL changes; preserve old URLs via redirect for some period.

Slug Field Inventory Checklist

  • For each slug column: model, scope, source
  • Identify all routes that consume slugs (/users/[username], /workspaces/[slug], etc.)

Validation Rules Checklist

  • Allowed characters: lowercase letters, numbers, dashes (RFC-friendly)
  • Excluded: uppercase (case-insensitive collision otherwise), spaces, special characters, accented characters
  • Length: typical 3-50 characters
  • No leading/trailing dash
  • No consecutive dashes (cosmetic)
  • Implementation: regex ^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$ (or stricter)

Reserved Word List Checklist

  • Static route paths: every /admin/, /api/, /auth/, /settings/, /billing/, etc.
  • Auto-generated: scan the routes folder; build the list at build time
  • Common confusing: null, undefined, true, false, test, example
  • Single characters / numbers: optional reserve
  • For trademark / safety: reserve support, help, legal, brand names

Uniqueness Scope Checklist

  • Global uniqueness: one slug across the entire app (e.g., usernames)
  • Per-tenant uniqueness: slug unique within a workspace (e.g., page slugs in a CMS)
  • Composite unique constraint: @@unique([tenantId, slug])
  • Document the scope; the URL structure must support it

Collision Detection & UX Checklist

  • Validate at type-time: input field shows "X is taken" before submit
  • AJAX endpoint: GET /api/check-slug?slug=X → boolean
  • Debounced (500ms typical)
  • For form submit, server-side check with explicit error message

Auto-Suffix Strategy (Optional) Checklist

  • For "create automatically" flows (no user input), auto-suffix on collision: my-workspace-2, my-workspace-3
  • For user-chosen slugs, don't auto-suffix without consent (surprising)
  • For email-derived slugs (e.g., from name), auto-suffix is acceptable

Slug Generation from Names Checklist

  • For auto-generation from names: lowercase, replace spaces with dashes, strip non-allowed
  • "John Doe's Workspace" → "john-does-workspace"
  • Truncate to max length
  • Handle accents: NFD normalization + strip combining marks (or use a library like slugify)

Slug Change Handling Checklist

  • When user changes slug, the URL changes
  • For SEO and shared links, preserve the old URL via redirect (for some period: 30-180 days)
  • Implementation: slug_history table records previous slugs; route layer checks history on 404
  • Document the policy

Case Sensitivity Checklist

  • Slugs should be case-insensitive: MyWorkspace and myworkspace are the same
  • Store lowercase canonical; redirect mixed-case URLs to lowercase
  • Unique constraint on lowercase column (if using a database that's case-sensitive by default like Postgres, use Citext or lower index)

Length Constraints Checklist

  • Min: 3 chars typical (1-2 too short for meaningful URLs)
  • Max: 50-80 chars (long URLs are ugly; some browsers truncate)
  • Database constraint: @db.VarChar(80)
  • Form field maxLength matches

Internationalization Checklist

  • For non-Latin scripts (Cyrillic, CJK, Arabic), choices:
    • ASCII-only: transliterate or reject (loses meaning)
    • Allow Unicode: needs careful URL encoding, IDN handling
    • Allow both: store the canonical, allow lookup by either
  • Most apps default to ASCII-only for slugs; URLs are functional, not display

Slug History & Redirect Checklist

  • Old slugs redirect to current
  • Track in slug_redirects table or slug_history
  • On slug change, insert old → new mapping
  • Lookup at 404 time
  • TTL for redirects (e.g., 1 year)

Display Format Checklist

  • Slug in URLs: as-is (lowercase, dashes)
  • Slug as identifier: not for display; show the human-readable name
  • For breadcrumbs / titles, use the name field, not the slug

Collision Recovery Suggestions Checklist

  • When user's chosen slug is taken, suggest alternatives: my-workspace-1, my-workspace-2, my-workspace-co
  • Surface in UI without forcing
  • Let user keep trying

Profanity / Safety Filter Checklist

  • For public-facing slugs (usernames especially), filter against profanity / hate speech lists
  • Per language; lists are imperfect
  • Reserve trademarked / brand names you don't want users impersonating

Per-User Slug Limits Checklist

  • For "claim a slug" mechanics, limit per user (prevent squatting)
  • E.g., one workspace slug per user on Free tier
  • For paid tiers, more

Migration from Sequential IDs Checklist

  • For apps that started with Int autoincrement IDs in URLs, slugs are a retrofit
  • Add slug column, generate from name, expose in URL alongside ID
  • Eventually, slug-only URLs (drop the ID)

Evidence rules: A collision is Confirmed by creating it on a safe environment, or cites the missing constraint or check with file and line.


Calibration

Don't over-engineer slugs for an app where IDs in URLs are fine. The audit's value is for user-facing URLs that benefit from human readability or claimability. Don't recommend Unicode slugs for an app with English-only audience. Don't recommend collision pools beyond what the use case warrants.

  • Severity:

    • Critical — User picked a slug matching a route path (broke navigation); slug uniqueness not enforced (duplicates exist); slug fields allow XSS-prone characters
    • High — Reserved word list missing; case sensitivity inconsistent; slug change has no redirect (broken links)
    • Medium — Validation rules unclear; max length mismatch between DB and form; missing user-friendly collision UX
    • Low — Cosmetic improvements to slug suggestion UI; missing slug history
    • Inverse (Over-Built) — Unicode slug support for ASCII-only audience; per-user slug pools for unique resources; complex profanity filter for B2B internal-only slugs
  • Confidence ratings: Confirmed (route-collision tested, reserved words enforced), Likely (validation obviously incomplete), Speculative (general best practice).

  • Anti-hallucination guard: Don't claim a reserved word list without enumerating it. Verify uniqueness constraint at the DB layer (the application check is racy). For Citext-based case insensitivity, verify the extension is enabled.

Output Format

Start with a 3–5 line executive summary: slug field count, the most-exposed validation gap, the highest-leverage fix.

  1. Slug Inventory — Per field: scope, source

  2. Validation Findings — Per field: allowed chars, length, regex

  3. Reserved Word Findings — List, build-time generation

  4. Uniqueness Scope Findings — Per field: scope, constraint

  5. Collision UX Findings — Type-time validation, server-side error

  6. Auto-Suffix Findings — Where applied, where not

  7. Generation from Names Findings — Algorithm, accent handling

  8. Slug Change Findings — History, redirect TTL

  9. Case Sensitivity Findings — Storage, lookup, redirect

  10. Length Findings — Min/max, DB constraint, form

  11. i18n Findings — Unicode support decision

  12. Display Format Findings — Where slug appears, where name appears

  13. Recovery Suggestion Findings — Alternative suggestion UX

  14. Profanity Filter Findings — Per-language, brand reservation

  15. Per-User Limit Findings — Squatting prevention

  16. Migration from ID Findings — Slug retrofit plan

  17. Over-Built Findings — Excessive features for use case

  18. Positive Findings — Slug systems that just work

For each finding: field/route location, severity, confidence, the specific change, and the impact (collision prevention, URL stability, user clarity).

Want this applied to a live stack?

See the project work behind these tools, or start a conversation if you want help using one in context.

View the prompt source as JSON