PHPackages                             jeremyharris/slugger - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. jeremyharris/slugger

AbandonedArchivedCakephp-plugin[Utility &amp; Helpers](/categories/utility)

jeremyharris/slugger
====================

Slugger is a plugin for CakePHP that automatically generates slugs

2.0(11y ago)191.4k4PHP

Since Jul 30Pushed 11y ago3 watchersCompare

[ Source](https://github.com/jeremyharris/slugger)[ Packagist](https://packagist.org/packages/jeremyharris/slugger)[ Docs](https://github.com/jeremyharris/slugger)[ RSS](/packages/jeremyharris-slugger/feed)WikiDiscussions master Synced today

READMEChangelogDependencies (1)Versions (3)Used By (0)

[![build status](https://camo.githubusercontent.com/4c63bff304e76491f8f4d89a0f6135414fab79e22bdc94e5ac39644d63d2b8c3/68747470733a2f2f7472617669732d63692e6f72672f6a6572656d796861727269732f736c75676765722e7376673f6272616e63683d6d6173746572)](https://travis-ci.org/jeremyharris/slugger)

Slugger
=======

[](#slugger)

> BREAKING CHANGE The original Slugger used named parameters exclusively. It now defaults to passed arguments. The configuration array has also changed. Please read the README for information on the new configuration. To use the original version, checkout the version 1.0 tag.

[Slugger](http://42pixels.com/blog/slugs-ugly-bugs-pretty-urls) is a plugin that basically rewrites cake urls (using routing) into slugged urls automatically:

```
'/posts/view/12'

```

automatically becomes

```
'/posts/view/my-post-title'

```

This avoids the need to store a slug in the db, manage it, check for duplicates, etc. It also avoids the `Model::findBySlug()` solution that many people use. Search for your post using the primary key instead! (Initial development sparked by [Mark Story's blog](http://mark-story.com/posts/view/using-custom-route-classes-in-cakephp)).

The slug is then transparently reverted back into the proper format for your controller action.

Requirements
------------

[](#requirements)

- CakePHP 2.0.x (check tags for older versions of CakePHP)

Installation
------------

[](#installation)

### Manual

[](#manual)

- Download this:
- Unzip that download.
- Copy the resulting folder to app/Plugin/Slugger/

### GIT Submodule

[](#git-submodule)

In your app directory type:

```
git submodule add git://github.com/jeremyharris/slugger.git Plugin/Slugger
git submodule update --init

```

### Composer

[](#composer)

Ensure `require` is present in `composer.json`. This will install the plugin into Plugin/Slugger:

```
{
    "require": {
        "jeremyharris/slugger": "dev-master"
    }
}

```

If you need the old version of slugger, make sure to replace "dev-master" with "1.0".

Usage
-----

[](#usage)

```
App::uses('SluggableRoute', 'Slugger.Routing/Route');

Router::connect(/posts/:action/:Post,
    array(),
    array(
        'routeClass' => 'Slugger.SluggableRoute',
        'models' => array('Post')
    )
);

```

This is the minimal default configuration. We're using the SluggableRoute class for this route, and checking for the Post model to generate slugs. The `:Post`key is our passed key (`$id` in the action).

### Options

[](#options)

```
Router::connect(/posts/:action/:Post,
    array(),
    array(
        'routeClass' => 'Slugger.SluggableRoute',
        'models' => array(
            '' => array(
                'slugField' => '',
                'param' => ''
            )
        ),
        'slugFunction' => ''
    )
);

```

- `` **Required** At least one model name is required. These models will be searched when pulling and generating the slug.
- `` By default, Slugger slugs the `$displayField` set on the model. If you wish to use a different field as the slug, define it here.
- `` By default, `:` is taken from the route and replaced with a slug. If it is undefined, it assumes the first passed arg. You can also configure it to use named parameters.
- `` A callable. By default, uses `Inflector::slug`.

#### Defining the slug param

[](#defining-the-slug-param)

The slug parameter is what Slugger pulls from the routed URL and replaces with your slug. By default, it checks for the `:` passed key.

`:Post` Slugs the `:Post` route element:

```
/post/view/5 --> /post/view/my-post-title

```

`Post` Slugs the `Post` named param:

```
/post/view/Post:5 --> /post/view/my-post-title

```

#### Custom slug function

[](#custom-slug-function)

You can define a custom function to use when slugging your urls by setting the 'slugFunction' key in the route options. This key accepts a php [callback](http://us.php.net/manual/en/language.pseudo-types.php#language.types.callback)and passes one argument, the string to slug. It expects a string to be returned.

For example, to use a custom function:

```
function my_custom_iconv_slugger($str) {
    $str = preg_replace('/[^a-z0-9 ]/i', '', iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $str));
    $quotedReplacement = preg_quote($replacement, '/');
    $merge = array(
        '/[^\s\p{Ll}\p{Lm}\p{Lo}\p{Lt}\p{Lu}\p{Nd}]/mu' => ' ',
        '/\\s+/' => $replacement,
        sprintf('/^[%s]+|[%s]+$/', $quotedReplacement, $quotedReplacement) => '',
    );
    return strtolower(preg_replace(array_keys($merge), array_values($merge), $str));
}
Router::connect('/posts/:action/*',
    array(),
    array(
        'routeClass' => 'SluggableRoute',
        'models' => array('Post'),
        'slugFunction' => 'my_custom_iconv_slugger'
    )
);

```

[iconv](http://us.php.net/manual/en/function.iconv.php) is a PHP module that encodes strings in a different character set, thereby stripping invalid characters. It's much faster but depends on your system's setup.

### Using Slugger in your application

[](#using-slugger-in-your-application)

Create links using Cake's helpers and Router to take advantage of automatically generated slugs:

```
array(
    'controller' => 'posts',
    'action' => 'view',
    12
)

```

turns into a url string like `/posts/view/my-post-title`, then back into the proper request for your controller to handle by putting `12` back into the passed arguments. In your controller, get the post id by checking:

```
function view($id = null) {
    $post = $this->Post->read(null, $id);
    // do controller stuff
}

```

If you have defined a custom ``, Slugger will replace whatever parameter type you chose and put the original route array back together.

Caching
-------

[](#caching)

Slugger caches by default. When you update records that the Sluggable route uses, you'll need to remove the cache. For example, updating a User's username

```
App::uses('SlugCache', 'Slugger.Cache');

$this->User->id = 3;
$this->User->saveField('username', 'newUsername');
// invalidate user slugs
SlugCache::invalidate('User');

```

To invalidate a single user you must regenerate the slug yourself and save it in the cache:

```
$this->User->id = 3;
$newSlug = 'my-new-slug';
$userSlugs = SlugCache::get('User');
$userSlugs[$this->User->id] = $newSlug;
SlugCache::set('User', $userSlugs);

```

Slugger uses the 'Slugger' cache config to cache, so customize that configuration to change caching engines.

Examples
--------

[](#examples)

### Passed Argument example using first arg (default bake)

[](#passed-argument-example-using-first-arg-default-bake)

```
Router::connect(/posts/:action/*,
    array(),
    array(
        'routeClass' => 'Slugger.SluggableRoute',
        'models' => array('Post')
    )
);

```

Using the above route

```
array(
    'controller' => 'posts',
    'action' => 'view',
    12
)

```

Becomes the `/posts/view/sluggable-is-cool`, and is accessed in the controller as so:

```
function view($id) {
    $post = $this->Post->read(null, $id);
    // do controller stuff
}

```

### Passed Argument example using keyed passed arg

[](#passed-argument-example-using-keyed-passed-arg)

```
Router::connect(/posts/:action/:post_id/*,
    array(),
    array(
        'pass' => array('post_id'),
        'routeClass' => 'Slugger.SluggableRoute',
        'models' => array(
            'Post' => array(
                'param' => ':post_id'
            )
        )
    )
);

```

Using the above route

```
array(
    'controller' => 'posts',
    'action' => 'view',
    'anotherArg',
    'post_id' => 12
)

```

Becomes the `/posts/view/anotherArg/sluggable-is-cool`, and is accessed in the controller as so:

```
function view($id, $anotherArgWillBeHere) {
    $post = $this->Post->read(null, $id);
    // do controller stuff
}

```

#### Caveats

[](#caveats)

A couple of things to note if using keyed passed args in your routes.

- You cannot use regex to validate route elements using this method because routes are parsed before Slugger rewrites them, and they would fail due to the url string not matching an expected integer regex
- Missing slugs (i.e., `/posts/missing-title` where `missing-title` isn't found as a slug) will still add the `:key` parameter to the route params because regex validation cannot be done

### Named Parameter example

[](#named-parameter-example)

```
Router::connect(/posts/:action/*,
    array(),
    array(
        'routeClass' => 'Slugger.SluggableRoute',
        'models' => array(
            'Post' => array(
                'slugField' => 'post_title',
                'param' => 'Post'
            ),
            'Author' => array(
                'param' => 'Author'
            )
        )
    )
);

```

Using the above route

```
array(
    'controller' => 'posts',
    'action' => 'view',
    'Post' => 12,
    'Author' => 1
)

```

Becomes the `/posts/view/jeremy/sluggable-is-cool`, and is accessed in the controller as so:

```
function view() {
    $post = $this->Post->read(null, $this->request->params['named']['Post']);
    $author = $this->Post->Author->read(null, $this->request->params['named']['Author']);
    // do controller stuff
}

```

Notes and Features
------------------

[](#notes-and-features)

- More than one model can be passed via the `models` param in the route options.
- If a model has (what will become) duplicate slugs, sluggable route will automatically prepend the id to the slug so it doesn't conflict
- If no slug is found, it will fall back to the original url so you don't have to change anything in your database
- Don't think of this as permalinks! These are just to make your url's a little prettier

Limitations
-----------

[](#limitations)

- Can conflict with multiple models with the same slug. A solution would be not to slug more than one model per route
- If someone was to bookmark a slugged url and after the fact you added a post with the same name, the bookmarked url would no longer work because the id would be prepended to it. In order to avoid this from ever happening, pass `'prependPk' => true` to the route options and the id will always be prepended to the slug

License
-------

[](#license)

Licensed under The MIT License Redistributions of files must retain the above copyright notice.

###  Health Score

34

—

LowBetter than 77% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity25

Limited adoption so far

Community13

Small or concentrated contributor base

Maturity64

Established project with proven stability

 Bus Factor1

Top contributor holds 85.7% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~6 days

Total

2

Last Release

4295d ago

Major Versions

1.0 → 2.02014-08-06

### Community

Maintainers

![](https://www.gravatar.com/avatar/6e2fcf5bb870eab1f8efd260dfa93fb00961d1c6addc787a93bf0b5d84c78524?d=identicon)[jeremyharris](/maintainers/jeremyharris)

---

Top Contributors

[![jeremyharris](https://avatars.githubusercontent.com/u/184903?v=4)](https://github.com/jeremyharris "jeremyharris (18 commits)")[![real34](https://avatars.githubusercontent.com/u/75968?v=4)](https://github.com/real34 "real34 (3 commits)")

---

Tags

pluginslugcakephpseo

### Embed Badge

![Health badge](/badges/jeremyharris-slugger/health.svg)

```
[![Health](https://phpackages.com/badges/jeremyharris-slugger/health.svg)](https://phpackages.com/packages/jeremyharris-slugger)
```

###  Alternatives

[sybrew/the-seo-framework-extension-manager

A WordPress plugin that allows you to manage extensions for The SEO Framework.

8490.3k](/packages/sybrew-the-seo-framework-extension-manager)[koehlersimon/slug

Helps you managing the URL slugs of your TYPO3 site

2965.7k](/packages/koehlersimon-slug)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
