Generate Slug Keyword from Title: Laravel + AJAX

If you work with project or blog where records require so-called slug (posts, pages etc.), it's convenient to generate slug immediately after title has been typed in. This article will show you how to do it in Laravel, with AJAX and with a help of one Laravel package.

First, what we're doing here. This is the example:

We have a form to add a page, and our goal is to have Slug field auto-generated, as we finish typing in the Title.

Not only that, slug should be unique for every post, even if title is the same.

Step 1. Blade Create Form

I will show you, how resources/views/admin/pages/create.blade.php looks like. To be exact, the part responsible for those two fields - title and slug:

<div class="form-group">
    <label for="title">Title*</label>
    <input type="text" id="title" name="title" class="form-control">
<div class="form-group">
    <label for="slug">Slug*</label>
    <input type="text" id="slug" name="slug" class="form-control">

Notice: this form code has been generated by our QuickAdminPanel, you should check it out.

So, the code is simple, nothing to comment here. Now, let's add some JavaScript, that is fired when title value is changed.

Step 2. AJAX Call On Title Change

In the "main" Blade layout file, I have a special @yield('scripts') code that allows to add any JavaScript to any other Blade template. So, that's exactly what we will do - add this code to the bottom of our pages/create.blade.php:

  $('#title').change(function(e) {
    $.get('{{ route('pages.check_slug') }}',
      { 'title': $(this).val() },
      function( data ) {

Now, we need to create the logic behind pages.check_slug route.

Step 3. Route/Controller to Return Slug

First, in routes/web.php we add this line:

Route::get('pages/check_slug', 'PagesController@check_slug')

Next, we create app/Http/Controllers/PagesController method check_slug(). We'll start from a simple version using Laravel's str_slug() helper.

public function check_slug(Request $request)
    $slug = str_slug($request->title);
    return response()->json(['slug' => $slug]);

Simple, right? We make a slug and return it. That could be the end of our tutorial, but we forgot one important rule - slugs should be unique. How to check it?

Step 4. Making Slug Unique

Not only that, we need to not only check uniqueness, but also automatically add -1, -2, -3 and other numbers to the end of the slug, to generate a new unique one.

For that, I've decided to not reinvent the wheel and to use an existing package cviebrock/eloquent-sluggable. It's not the only slug-related package on the market, but it has a very useful (in our case) feature of generating slug without saving it to the database.

We install the package with this command:

composer require cviebrock/eloquent-sluggable

Next, we need to prepare our Model app/Page.php to make it "sluggable":

use Cviebrock\EloquentSluggable\Sluggable;

class Page extends Model
    use Sluggable;

     * Return the sluggable configuration array for this model.
     * @return array
    public function sluggable()
        return [
            'slug' => [
                'source' => 'title'

We've created a method that defines to generate slug field from title field of pages database table.

And now, final step, we can replace old "simple" method of generating slug in PagesController:

use Cviebrock\EloquentSluggable\Services\SlugService;

public function check_slug(Request $request)
    // Old version: without uniqueness
    $slug = str_slug($request->title);

    // New version: to generate unique slugs
    $slug = SlugService::createSlug(Page::class, 'slug', $request->title);

    return response()->json(['slug' => $slug]);

And, that's it!

P.S. Oh, and don't forget to still check uniqueness on back-end validation when doing store() for the page, add rule 'slug' => unique:pages in validation. You never know if someone else is using the system and creates the same page at the same time from another computer :)

No comments yet…

Like our articles?

Become a Premium Member for $129/year or $29/month

Written by