Slugify a CSV Column
A slug is the readable, URL-safe form of a piece of text: Crème Brûlée Kit becomes creme-brulee-kit. This page turns a whole column into slugs, folding accents rather than deleting them, and can number the duplicates so the result is usable as a key. It runs in this browser tab with nothing uploaded.
Slugging the header row instead of the values? The header cleaner does that.
The bug that eats the vowels
Almost every slug implementation is one line: lowercase the text, replace everything outside [a-z0-9] with a hyphen, collapse the runs. It works beautifully on English product names and destroys everything else.
Crème Brûlée → cr-me-br-l-e (the naive version)
Crème Brûlée → creme-brulee (folding first)
The accented characters are not in [a-z0-9], so they are thrown away along with the spaces, and what is left is a URL nobody can read and no search engine can do anything with. A catalogue of French or Spanish products becomes a hundred pages of hyphens.
Folding first fixes it, and folding is more than stripping combining marks. Unicode normalization splits é into an e plus a combining acute, so removing the marks leaves the letter behind. That covers most of Europe. It does not cover the letters where the mark is welded into the glyph, and those get spelled out by name first:
ß → ss Straße Map → strasse-map
ø → o Ørsted → orsted
æ → ae Æthelred → aethelred
ł → l Łódź → lodz
þ → th þing → thing
Without that list, Straße comes out as stra-e and quietly refuses to match strasse anywhere it is used as a key.
Worked example: a product catalogue
The example button loads products.csv:
sku,title,path,updated
A-1,"Crème Brûlée Kit",/shop/creme-brulee-kit,2024-01-15
A-2,"R&D Sample Pack",/shop/rd-sample-pack,2024-02-02
A-3,"Straße Map, 2024",/shop/strasse-map-2024,2024-02-19
A-4,"Blue Shirt",/shop/blue-shirt,2024-03-04
A-5,"blue shirt",/shop/blue-shirt,2024-03-21
Tick title and take every default:
title title_slug
Crème Brûlée Kit creme-brulee-kit
R&D Sample Pack r-and-d-sample-pack
Straße Map, 2024 strasse-map-2024
Blue Shirt blue-shirt
blue shirt blue-shirt
Three things worth pointing at. The accents folded rather than vanishing. The ampersand became the word and instead of disappearing, because r-d-sample-pack loses the meaning of the name. And rows 4 and 5 produced the same slug, which is fine for a heading and fatal for a key.
Switch Duplicate slugs to Number them and row 5 becomes blue-shirt-2, with the stats strip reporting 1 collision numbered. That count is the thing to check before you use a slug column as an identifier, because a catalogue with two products called the same thing in different cases is completely normal and a URL that serves two products is not.
Separators, and the length cap
Four separators, and the choice is usually made for you by whatever is going to read the slug.
- Hyphen for anything that will appear in a URL. Search engines treat a hyphen as a word boundary and an underscore as a letter, which is the entire reason web slugs settled on hyphens.
- Underscore for a value that is about to become a filename, a table name or a field in code.
- Dot for hierarchical keys, the kind that end up in a config file or a metrics namespace.
- None when the slug is a compact identifier and every character costs.
Max length exists because plenty of systems cap a slug field, and truncation is where slug tools get careless. Cutting at a fixed character count leaves you with creme-brulee-k, which reads like a mistake. The cut here backs up to the last separator when one is within reach, so a slug capped at 14 characters comes back as creme-brulee on a whole word, and any separator left dangling at the cut is removed.
One consequence worth knowing: truncation creates collisions. Two products whose names differ only after character 30 produce the same 30-character slug. Turn the numbering on whenever the cap is on.
When a slug comes back empty
A slug is built from the ASCII letters and digits that survive folding, so a value with none of those produces nothing at all. Two cases cause it in real files.
A cell holding only punctuation or symbols, which is usually junk data worth finding. And a value written in a script the ASCII fold cannot reach: Chinese, Japanese, Korean, Arabic, Hebrew, Thai, Devanagari. Folding turns accented Latin into plain Latin; it has nothing to turn a Han character into, so those values come back empty.
The page counts them and says so, rather than handing you a key column with silent blanks in it. If your catalogue is genuinely multilingual, a slug built from the title is the wrong identifier and a product code is the right one. Something like merging the SKU into the slug, or falling back to it, is the usual pattern.
Blank cells are a separate case: they are skipped before the transform runs and counted on their own, so an empty title does not become a slug of nothing that looks like a real value.
Details
- Numbering is per column. Two different columns can each hold
blue-shirtwithout one of them being renamed, because a collision only means something within a single column. - The numbering is first-come. The first occurrence in file order keeps the plain slug and later ones get
-2,-3. That is stable across runs on the same file, so a re-run does not reshuffle your URLs. - Invisible characters are removed first. Zero-width characters and non-breaking spaces are cleared before folding, so a title pasted out of a web page does not produce a slug with a phantom separator in it.
- Case can be left alone. Switch Letter case to Leave alone when the slug is going somewhere case-sensitive that you do not control. Lowercase is the default because URLs are compared case-sensitively by most servers and mixed-case slugs are a support ticket waiting to happen.
- The original is kept by default, with the slug in a new column named
<column>_slugright beside it, so you can eyeball the pairs before committing.
Frequently Asked Questions
Why does my accented text come out as hyphens in other tools?
Because they strip everything outside a to z and 0 to 9 without folding first, so an accented character is thrown away along with the spaces. Creme Brulee becomes cr-me-br-l-e. Folding converts the accented letter to its plain form before anything is removed, which is why accent folding is on by default here.
What happens to letters like the German sharp s?
They are spelled out. Unicode normalization splits an accented letter into a base plus a combining mark, but a handful of letters have the mark welded into the glyph and do not decompose. Those are handled by name first: the sharp s becomes ss, the Scandinavian slashed o becomes o, the ae ligature becomes ae, and the Polish crossed l becomes l.
Why did an ampersand become the word and?
Because r-d-sample-pack loses the meaning of the name and r-and-d-sample-pack keeps it. The ampersand is the one symbol worth spelling out rather than dropping, since it stands for a word in almost every name it appears in.
How do I stop two rows producing the same slug?
Switch Duplicate slugs to Number them. The first occurrence in file order keeps the plain slug and later ones get a numeric suffix, so blue-shirt is followed by blue-shirt-2. The stats strip reports how many collisions it numbered, which is the number to check before using the column as a key.
Does the length cap cut mid-word?
No, not when it can avoid it. The cut backs up to the last separator within reach, so a slug capped at 14 characters comes back on a whole word rather than as creme-brulee-k, and any separator left dangling at the cut is removed. Note that truncation creates collisions, so turn the numbering on whenever the cap is on.
Which separator should I use?
A hyphen for anything appearing in a URL, since search engines treat a hyphen as a word boundary and an underscore as a letter. An underscore for filenames, table names and code identifiers. A dot for hierarchical keys. None when the slug is a compact identifier.
Why is the slug empty for some rows?
Because nothing in the value survives the character filter. Either the cell holds only punctuation, which is usually junk worth finding, or it is written in a script the ASCII fold cannot reach, such as Chinese or Arabic. Those are counted and reported rather than left as silent blanks in a key column.
Is the file uploaded?
No. There is no upload endpoint on this page. The file is read, slugified and rewritten by JavaScript in your own tab, and nothing is kept between visits.
Related
Readable URLs, in one pass
Free, no account, no upload. Pick the column, check the collisions, take the CSV.
Back to the slugifier