Slug Registry

Atomic slug, handle, and username registry for Convex with scoped uniqueness, rename redirects, case folding, and typed rejection reasons.

Installation

npm install @vllnt/convex-slugs

About Slug Registry

@vllnt/convex-slugs maps a readable name to an opaque resource reference. Your app can reuse the same registry pattern for profiles, workspaces, content, or any resource that needs a unique human-readable identifier.

reserve enforces uniqueness within a scope. resolve finds the resource, and slugForResource performs a reverse lookup. rename records redirect mappings and collapses redirect chains. Configure length, character patterns, reserved words, and case sensitivity; Unicode NFC normalization is supported.

Your backend authorizes name changes and your router applies redirects. The component does not create web routes or handle impersonation policy. Normalization does not eliminate every visually similar Unicode name; choose validation rules appropriate to your product.

Maintained by [bntvllnt](https://bntvllnt.com) at [VLLNT](https://vllnt.com). Find the author on [GitHub](https://github.com/bntvllnt) and [X](https://x.com/bntvllnt).

Benefits

Use cases

how to enforce unique usernames in Convex without race conditions

The Slug Registry component (@vllnt/convex-slugs) provides a reserve() mutation that runs inside the Convex transaction, making double-reserve impossible by construction. It returns { ok: true } on success or { ok: false, reason } with a typed reason such as SLUG_TAKEN, so you can surface the exact failure to the user without a try/catch.

how to handle username renames with redirect preservation in Convex

The rename() method in @vllnt/convex-slugs updates the slug and records an old-to-new redirect in a single mutation. Redirect chains are collapsed automatically, so if a user renames from A to B and then to C, redirectFor(A) returns C directly rather than storing an intermediate hop.

how to namespace slugs per tenant or content type in Convex

Every method in @vllnt/convex-slugs accepts an optional scope parameter. By passing a tenant ID, locale, or content type string as the scope, you can enforce uniqueness independently per namespace without running separate tables or components.

how to look up which username a user currently holds in Convex

The slugForResource() query in @vllnt/convex-slugs performs a reverse lookup from an opaque resourceRef back to the slug currently assigned to it within a given scope. This lets you display or validate the current handle for a user without storing it redundantly in your own tables.

Frequently asked questions

Does @vllnt/convex-slugs prevent two users from claiming the same slug simultaneously?

@vllnt/convex-slugs runs the reserve() call inside a Convex mutation transaction, so uniqueness is enforced at the database level. Two concurrent reservation attempts for the same (scope, slug) pair cannot both succeed; the second will receive { ok: false, reason: 'SLUG_TAKEN' }.

How does case folding work in @vllnt/convex-slugs?

By default, @vllnt/convex-slugs lowercases slugs and applies Unicode NFC normalization before storing or comparing them. This prevents homoglyph collisions where visually similar characters would otherwise produce separate entries. Case folding can be disabled by passing foldCase: false to the Slugs constructor.

What is a resourceRef in @vllnt/convex-slugs?

A resourceRef is an arbitrary opaque string that the host application associates with a slug at reservation time, such as a Convex document ID or a user ID. The component stores and returns it without interpreting it, so authentication and authorization remain entirely in the host application.

How do rename redirects work and are they safe to chain?

When rename() is called in @vllnt/convex-slugs, a redirect record is written mapping the old slug to the new slug. If the same source slug is renamed again later, the chain is collapsed so that redirectFor() always returns the current canonical slug in one lookup, with no intermediate hops stored.

Does @vllnt/convex-slugs provide React hooks for checking slug availability?

Yes. The optional @vllnt/convex-slugs/react entrypoint exports useSlugAvailable() and useResolve(), which are thin wrappers over Convex useQuery. useSlugAvailable() returns { available, resourceRef } where available is undefined while loading, true when the slug is free, and false when it is held. React 18 or later is required as an optional peer dependency.

Links