Slug generation

Hello,

I was wondering if there is a built-in slugify function, but it doesn’t seem so.

This is what I use to generate a slug

{{String($json.nom)
.replace(/[øØ]/g, 'o')
.replace(/[æÆ]/g, 'ae')
.replace(/[ßẞ]/g, 'ss')
.normalize("NFD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/\b\w'/g, "")
.replace(/ẞ/g, 'ß')
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '')}}

It allows to turn something like L'été, les événements débordent d'énergie créative!
into ete-les-evenements-debordent-energie-creative to create a slug.

Hope that helps,

Cheers,
Joachim

3 Likes

This may be slightly simplified now using the expression transformation function replaceSpecialChars() - Data transformation functions for strings - n8n Documentation

A slugify function would be a good feature request though

2 Likes

Thank you for the tip !

Hello everyone,

I now use a code node, here is the script:

const title = $node['name of your node'].json['field to transform to slug'];

let slug = title.normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/\/|\s/g, '-').toLowerCase().replace(/[^a-z0-9-]+/g, '');

slug = slug.replace(/-+/g, '-');

const words = slug.split('-');
const deduped = [...new Set(words)];
slug = deduped.join('-');

return [{ json: { slug } }];
  1. First, it retrieves the string from the specified field in your node (replace 'name of your node' and 'field to transform to slug' with your specific node name and field).
  2. It then uses the normalize() function in JavaScript to decompose the title into its base characters and associated accent marks. The replace(/[\u0300-\u036f]/g, "") part of the script removes any accents from characters.
  3. After that, it replaces slashes and spaces in the title with hyphens, converts the title to lowercase, and removes any characters that are not alphanumeric or hyphens.
  4. The script then replaces any occurrence of consecutive hyphens with a single hyphen.
  5. Finally, it will remove duplicate keywords in your slug. To do so, it splits the slug into individual words (separated by hyphens), removes any duplicate words and reassembles the slug.

Hope that helps some of you :slight_smile:

1 Like

This sounds interesting. I did not understand it though… What is the best user case for this?

You input an article title, a first and last name for a directory, etc. and it outputs a slug that shall be somewhat seo optimized, at scale.

François Hervé > directory.com/francois-herve
code & code snippets > website.com/code-snippets

2 Likes

Hello everyone,

Here are is the new code with a bunch of improvements:

  • Native Unicode support - Replaced hardcoded character ranges with Unicode property escapes (\p{L}, \p{N}) for universal language compatibility

  • Proper ligature handling - Added NFKD normalization to correctly convert œ→oe, æ→ae automatically

  • Fixed apostrophe word boundaries - Now treats “Côte d’Azur” as separate words → “cote-azur” (not “cote-dazur”)

  • French article removal - filters common articles (d’, de, la, le) for cleaner slugs

  • Batch processing - Processes multiple items efficiently with $input.all() while preserving original data

  • Locale-aware transformations - Uses toLocaleLowerCase('fr-FR') for proper French character handling

  • Comprehensive whitespace handling - Covers tabs, newlines, non-breaking spaces, not just regular spaces

  • Better error handling - Added try-catch with fallback values and descriptive error messages

  • Configurable parameters - Centralized CONFIG for field name/locale customization

  • Performance optimized - Reduced redundant regex operations and consolidated transformation pipeline

Here is the code:

/**
 * Slug Generator
 */

const CONFIG = {
  inputField: 'name',
  outputField: 'slug',
  maxLength: 100,
  locale: 'fr-FR'
};

// French articles/contractions to remove (after apostrophes)
const FRENCH_ARTICLES = new Set([
  'd', 'l', 'de', 'le', 'la', 'les', 'du', 'des', 'au', 'aux',
  's', 'c', 'n', 'j', 'm', 't', 'qu'  // Common contractions
]);

/**
 * Generate slug with article removal
 */
const generateSlug = (text) => {
  if (!text?.trim?.()) return '';
  
  return (
    text
      .normalize('NFKD')                         // Decompose everything
      .replace(/\p{Diacritic}/gu, '')            // Remove diacritics
      .toLocaleLowerCase(CONFIG.locale)          // Lowercase
      .replace(/[^\p{L}\p{N}]+/gu, '-')         // Non-alphanumeric → hyphen
      .split('-')                                // Split into words
      .filter(word => word.length > 1 || !FRENCH_ARTICLES.has(word))  // Remove articles
      .join('-')                                 // Rejoin
      .replace(/-+/g, '-')                       // Clean up
      .replace(/^-|-$/g, '')                     // Trim edges
      .slice(0, CONFIG.maxLength)
      .replace(/-[^-]*$/, '')
  ) || 'slug-vide';
};

/**
 * Deduplicate consecutive words
 */
const deduplicateConsecutive = (slug) => {
  const parts = slug.split('-');
  return parts.filter((part, i) => part !== parts[i + 1]).join('-');
};

// Main execution
try {
  const input = $input.all();
  
  return input.map(item => {
    const value = item.json[CONFIG.inputField];
    const slug = deduplicateConsecutive(generateSlug(value));
    
    return {
      json: {
        ...item.json,
        [CONFIG.outputField]: slug
      }
    };
  });
  
} catch (error) {
  throw new Error(`Slug generation failed: ${error.message}`);
}