Handling duplicate term slugs

June 5, 2026

WordPress stores every term (category, tag, attribute value, etc.) with both a human-readable name and a URL-safe slug. The name keeps spaces, capitals, and most punctuation; the slug is generated by sanitize_title(), which strips characters that can’t appear in URLs.

That sanitisation occasionally collapses two visibly different CSV values onto the same slug — and SSI’s import has to decide what to do about it.

The problem

Take two CSV values:

CSV name WP slug after sanitize_title()
Cp cp
C+P cp
C & P c-p
C-P c-p

The first two collide on cp. The last two collide on c-p. To WordPress, the slug is the unique identifier — so you can’t have two terms with slug cp in the same taxonomy. One has to win.

This isn’t a bug. It’s how WordPress has always worked. But for an importer running unsupervised across millions of rows, the behaviour needs to be predictable.

SSI’s policy options

In Additional Options → Duplicate term-slug behaviour:

Option What it does When to use
Alert (default) Treats a collision as an error. Logs the affected rows and (if “Continue on error” is unticked) aborts the import. You want to know about CSV data-quality issues. Default for new imports.
Merge Silently treats all CSV spellings that share a slug as the same term. Whichever spelling WordPress created first becomes the canonical name; all CSV rows referring to any spelling get assigned to that term. As of 2.55.2 it OR-joins on both slug AND name, so it also fixes WordPress’s auto-uniquified-slug case (see below). The CSV is messy on purpose (vendor-generated, can’t be cleaned), and you’d rather get a working catalog than an aborted import.

When you’ll hit this

In practice, slug collisions are rare for products with English-language categories — short, ASCII, lowercase names rarely collapse. They surface most often with:

  • Brand names containing special characters: B&Q vs B and Q, AT&T vs ATT.
  • CSV exports from multiple sources merged together: one vendor writes C+P Company, another writes Cp Company for the same brand. Pre-merging the data, the slugs differ; after merging they collide.
  • Localised characters: Café and Cafe both slug to cafe under most collations.
  • Multi-language sites: especially common when category names get translated and the translations collapse.

If you have a small catalog of curated category names, you’ll probably never see this. If you import vendor feeds at scale, you’ll see it within the first 10k rows.

What the error looks like

Under Alert mode there’s no dedicated “collision” message. A colliding term simply fails to back-fill its term_taxonomy_id during import-taxonomies — so it shows up as a failed hierarchical term. The import-taxonomies stage then reports every failed term and (with “Continue on error” off) aborts:

Failed to import hierarchical terms:
Array
(
    [0] => stdClass Object
        (
            [term_name] => Cp
            [taxonomy] => product_brand
            [current_hierarchy] => Cp
            [term_taxonomy_id] =>
            [diagnostic] =>
              term_name hex:        4370
              current_hier hex:     4370
              aux col collation:    utf8mb4_unicode_ci
              cte cast collation:   utf8mb4_unicode_ci
              diagnosis:            1 wp_terms candidate(s) match by name. Back-fill join didn't match — likely collation, whitespace, or full_hierarchy mismatch.
                candidate: term_id=42 tt_id=58 slug=cp parent=0 name_hex=432B50
        )
)

Each failed row is dumped with diagnostics: the hex of the term name and hierarchy (so invisible characters show up), the aux-column collation vs the CTE CAST collation, and any matching wp_terms candidates. That’s usually enough to see that another spelling already claimed the slug.

If “Continue on error” is on, the same dump is logged but the import continues instead of aborting.

The affected rows live in the per-import hierarchies table, not in any validation-errors table — slug collisions don’t go through validation. The import log already prints these rows with diagnostics, so you usually don’t need to query anything by hand, but if you want to list them directly:

wp db query "SELECT term_name, taxonomy, current_hierarchy FROM wp_super_speedy_imports_hierarchies_<import_id> WHERE term_taxonomy_id IS NULL LIMIT 20"

How to fix collisions

Option 1 — clean the CSV (best long-term)

Pick one canonical spelling for each conflicting concept and update every row. For B&Q vs B and Q, decide which the site should use and normalise. This is the cleanest answer and avoids confusion in the storefront.

If the CSV is auto-generated, fix the upstream generator — otherwise the same problem returns on every export.

Option 2 — switch to Merge mode

Set Duplicate term-slug behaviour to Merge and re-run. SSI then assigns every colliding spelling to the same WP term — whichever spelling WordPress created first. The other spellings simply route to that term. (As of 2.55.8 import-taxonomies runs single-worker, so term creation is serialised: Merge wires colliding spellings onto whichever term WordPress created first, with no cross-worker race to reason about.)

Merge handles two distinct scenarios:

  1. Identical-slug collapse — two different CSV spellings sanitise to the same slug (Cp and C+P both become cp). Merge joins them onto one term by slug.
  2. WordPress auto-uniquified slugs — when a slug is already taken, WordPress’s wp_unique_term_slug() appends a suffix. For example, T-Shirts under Women gets the slug t-shirts-women because Men > T-Shirts already claimed t-shirts. The CSV-parsed slug (t-shirts) no longer matches the stored slug, so a slug-only match would fail. Since 2.55.2, Merge also OR-joins on the term name, so it correctly wires T-Shirts under Women to the auto-uniquified term.

This is fine when:

  • You don’t care which spelling appears on the storefront (or all spellings are equally acceptable).
  • The merged group is genuinely the same concept (B&Q and B and Q ARE the same brand).

Not fine when:

  • The collisions are unrelated concepts that happen to share a slug — you’d be silently merging unrelated data into one bucket.

Option 3 — write a mapData template override

For very messy upstream data, the right answer is sometimes a per-row transform that normalises before the slug pipeline sees it. See the developer Creating custom templates article — mapData runs once per CSV row and lets you rewrite values before they hit import-taxonomies.

Why this policy isn’t just a toggle in the admin UI but is configurable per-import

A multi-site WP install might run imports of two different catalogs — one curated (where collisions are bugs and should abort) and one vendor-aggregated (where collisions are noise and should be merged). The per-import setting lets each policy match its data source.

What’s next

  • Mapping hierarchical and multi-path taxonomies — the upstream context for where slug collisions surface.
  • Choosing your unique identifier — the analogous problem at the post level (duplicate SKUs).
  • Stage-by-stage failure recipes — for what to check if the import aborts here.
×
1/1