NestboltNestbolt

@nestbolt/sluggable

Unique Slugs

How slug collision handling works and how to customize or disable it.

By default, @nestbolt/sluggable ensures every slug is unique within its entity table. When a collision is detected, a numeric suffix is appended automatically.

How It Works

When generating a slug, the subscriber:

  1. Generates the base slug from source fields (e.g., "hello-world")
  2. Checks if that slug already exists in the database
  3. If no collision, uses the base slug as-is
  4. If a collision exists, finds all matching slugs with numeric suffixes (e.g., hello-world-1, hello-world-2)
  5. Appends the next number: hello-world-3
await repo.save(repo.create({ title: "Hello World" }));
// slug: "hello-world"

await repo.save(repo.create({ title: "Hello World" }));
// slug: "hello-world-1"

await repo.save(repo.create({ title: "Hello World" }));
// slug: "hello-world-2"

Suffix Separator

The separator between the base slug and the suffix number defaults to "-". Customize it globally:

SluggableModule.forRoot({ suffixSeparator: "_" })
// "hello-world", "hello-world_1", "hello-world_2"

Excluding the Current Entity

When regenerating a slug on update, the current entity is excluded from the collision check so it doesn't collide with itself:

// post.slug is currently "hello-world"
post.title = "Hello World"; // same title, regenerate
await repo.save(post);
// slug stays "hello-world" -- the entity doesn't collide with its own slug

This works automatically in the subscriber. When using SluggableService.generateUniqueSlug() directly, pass the entity ID:

const slug = await sluggable.generateUniqueSlug(
  Post,
  "slug",
  "hello-world",
  post.id, // exclude this entity from collision check
);

Disabling Unique Slugs

Set unique: false on the @Sluggable() decorator to skip collision checking entirely:

@Sluggable({ from: "title", unique: false })
@Entity("tags")
export class Tag {
  @PrimaryGeneratedColumn("uuid")
  id!: string;

  @Column()
  title!: string;

  @Column({ default: "" })
  slug!: string;
}

With unique: false:

  • No database queries are made to check for collisions
  • Multiple entities can have the same slug
  • Useful for tags, categories, or cases where slug uniqueness is not required

Manual Slugs

If you set the slug field before saving, the subscriber skips generation entirely:

const post = repo.create({ title: "Hello World", slug: "custom-slug" });
await repo.save(post);
console.log(post.slug); // "custom-slug" -- no auto-generation

This means manually-set slugs bypass collision checking. If you need uniqueness for manual slugs, check it yourself or use SluggableService.generateUniqueSlug().