Solvedeleventy Slug filter doesn't create url safe slugs.

I've been using the slug filter to convert regular strings to strings suitable for use as urls. However I've realised it's not set up to remove apostrophes (and possibly other characters?).

So it's a test becomes it's-a-test, and when rendered with eleventy the url is /tags/it's-a-test/ - the ampersand breaks the url. It also looks ugly!

I see that the package supports supplying a list of characters to remove.

If the intention is that this filter is used to create 'safe' urls, could Eleventy remove these by default?


As an alternative I've added my own filter using the slugify() option of the string library. I might stick with this anyway as it seems to do a good job of creating 'pretty' slugs.

39 Answers

✔️Accepted Answer

yup @zachleat , it does replace the slug, so I just do remove some chars to make it little bit prettier tho

const slugify = require("slugify");
eleventyConfig.addFilter("slug", (input) => {
  const options = {
    replacement: "-",
    remove: /[&,+()$~%.'":*?<>{}]/g,
    lower: true
  };
  return slugify(input, options);
});

Other Answers:

I’m going to move this into the new feature queue and it is logged for the next major version milestone.

@bridgestew are you using eleventy-base-blog?

If so your opts defined here (docs: https://github.com/valeriangalliat/markdown-it-anchor):
https://github.com/11ty/eleventy-base-blog/blob/master/.eleventy.js#L43

  let opts = {
    permalink: true,
    permalinkClass: "direct-link",
    permalinkSymbol: "#"
};

would be something like:

const slugify = require("slugify");
let opts = {
    permalink: true,
    permalinkClass: "direct-link",
    permalinkSymbol: "#",

    // this is the same function shared above
    slugify: function(input) {
      const options = {
        replacement: "-",
        remove: /[&,+()$~%.'":*?<>{}]/g,
        lower: true
      };
      return slugify(input, options);
    }
};

Does that make sense?

For anybody else curious, here's the difference between the default slug filter (which uses slugify; currently slugify@1.5.3 in Eleventy 0.12) vs @sindresorhus/slugify@1:

default slug filter (via slugify@1.5.3) custom slugify filter (via @sindresorhus/slugify@1.1.2)
{{ Ä-ä | slug }} = "a-a" {{ Ä-ä | slugify }} = "ae-ae"
{{ Ö-ö | slug }} = "o-o" {{ Ö-ö | slugify }} = "oe-oe"
{{ Ü-ü | slug }} = "u-u" {{ Ü-ü | slugify }} = "ue-ue"
{{ ẞ-ß | slug }} = "ss-ss" {{ ẞ-ß | slugify }} = "ss-ss"
const slugify = require("@sindresorhus/slugify");

module.exports = (eleventyConfig) => {
  eleventyConfig.addFilter("slugify", slugify);
  return {...};
};

Or, if you want to replace the built-in Eleventy "slug" filter with a different implementation, just name your custom slugify filter "slug":

const slugify = require("@sindresorhus/slugify");
eleventyConfig.addFilter("slug", slugify);

NOTE: Only @sindresorhus/slugify v1 is supported (npm i @sindresorhus/slugify@1). v2 uses ESM modules and you'll get errors when trying to use it:

Error was thrown:
   Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /private/tmp/11ty-liquid-or/node_modules/@sindresorhus/slugify/index.js
   require() of ES modules is not supported.


UPDATE: For my own future reference, I managed to get async filters working in Nunjucks using addNunjucksAsyncFilter() (async filter support wasn't added into LiquidJS until v9.1.3, per harttle/liquidjs#232) and the v2/ESM version of @sindresorhus/slugify:

  eleventyConfig.addNunjucksAsyncFilter("slugify", async function(str, callback) {
    // Where @sindresorhus/slugify is v2.1.0, which is an ESM module.
    const slugify = await import("@sindresorhus/slugify");
    callback(null, slugify.default(str.toString()));
  });

If you're using Eleventy v1/Canary (which currently has liquidjs@9.25.0), this code works for an async LiquidJS filter or an .11ty.js template, but will return a Promise object in Nunjucks:

  eleventyConfig.addFilter("slugify", async (str) => {
    const slugify = await import("@sindresorhus/slugify");
    return slugify.default(str.toString());
  });
// test.11ty.js
module.exports = {
  async render(data) {
    return `slug=${ await this.slugify("Peter deHaan 'tis an idiot (11ty.js)") }`;
  }
};
STRING LANGUAGE RESULT
slug={{ "Peter deHaan 'tis an idiot (LiquidJS)" | slugify }} LiquidJS slug=peter-de-haan-tis-an-idiot-liquid-js
slug=${ await this.slugify("Peter deHaan 'tis an idiot (11ty.js)") } .11ty.js slug=peter-de-haan-tis-an-idiot-11ty-js
slug={{ "Peter deHaan 'tis an idiot (Nunjucks)" | slugify }} Nunjucks slug=[object Promise]

This PR is merged and slugify will ship with 1.0, thanks y’all!

More Issues: