Skip to main content
URLs provide SEO-friendly slugs for models such as products, collections, and brands.

Overview

URLs provide SEO-friendly slugs for models such as products, collections, and brands. Instead of exposing database IDs in storefront routes:
/products/1
A URL slug allows routes like:
/products/apple-iphone
URLs are polymorphic, so any Eloquent model can support them. Each URL belongs to a language, and each model can have one default URL per language.
URLs are not to be confused with Laravel routes. They provide a slug-based lookup mechanism for storefront routing, not route definitions.

Model

Lunar\Models\Url

Fields

FieldTypeDescription
idbigIncrementsPrimary key
language_idforeignIdThe language this URL belongs to
element_typestringMorph type of the parent model
element_idunsignedBigIntegerMorph ID of the parent model
slugstringThe URL slug
defaultbooleanWhether this is the default URL for this element and language combination
created_attimestamp
updated_attimestamp

Relationships

RelationshipTypeRelated ModelDescription
elementMorphToPolymorphicThe parent model (e.g. Product, Collection, Brand)
languageBelongsToLunar\Models\LanguageThe language this URL is for

Scopes

ScopeDescription
default()Filter to URLs where default is true

Creating a URL

URLs can be created directly or through a model’s urls() relationship.
use Lunar\Models\Url;

Url::create([
    'slug' => 'apple-iphone',
    'language_id' => $language->id,
    'element_type' => $product->getMorphClass(),
    'element_id' => $product->id,
    'default' => true,
]);

// Or through the relationship
$product->urls()->create([
    'slug' => 'apple-iphone',
    'language_id' => $language->id,
    'default' => true,
]);

Default URL behavior

Only one URL can be the default per element and language combination. When a new URL is created or updated with default set to true, any existing default URL for that same element and language is automatically set to false.
$urlA = $product->urls()->create([
    'slug' => 'apple-iphone',
    'language_id' => 1,
    'default' => true,
]);

$urlA->default; // true

$urlB = $product->urls()->create([
    'slug' => 'apple-iphone-16',
    'language_id' => 1,
    'default' => true,
]);

$urlA->refresh()->default; // false
$urlB->default; // true
This behavior is scoped to each language independently. Setting a default for one language does not affect defaults in another language.
// This URL is for a different language, so $urlB remains the default for language 1
$urlC = $product->urls()->create([
    'slug' => 'apple-iphone-fr',
    'language_id' => 2,
    'default' => true,
]);

$urlB->refresh()->default; // true (still default for language 1)
$urlC->default; // true (default for language 2)

Deleting a URL

When a default URL is deleted, Lunar automatically promotes another URL for the same element and language to become the new default.
$urlA = $product->urls()->create([
    'slug' => 'apple-iphone',
    'language_id' => 1,
    'default' => true,
]);

$urlB = $product->urls()->create([
    'slug' => 'apple-iphone-16',
    'language_id' => 1,
    'default' => false,
]);

$urlA->delete();

$urlB->refresh()->default; // true

Adding URL support to models

Lunar ships with URL support on the following models:
  • Lunar\Models\Product
  • Lunar\Models\Collection
  • Lunar\Models\Brand
To add URL support to a custom model, use the HasUrls trait:
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Lunar\Base\Traits\HasUrls;

class MyModel extends Model
{
    use HasUrls;
}

Available relationships

The HasUrls trait provides the following relationships:
// All URLs for the model
$model->urls;

// The default URL (where default is true)
$model->defaultUrl;

// The URL for the application's current locale
$model->localeUrl;

// The URL for a specific locale (matches against the language code)
$model->localeUrl('fr')->first();
When a model using the HasUrls trait is deleted (hard delete only), all associated URLs are automatically removed.

Automatic URL generation

Lunar can automatically generate a URL when a model with the HasUrls trait is created. This is controlled by the generator option in config/lunar/urls.php.
<?php

use Lunar\Generators\UrlGenerator;

return [
    'required' => true,
    'generator' => UrlGenerator::class,
];
OptionTypeDefaultDescription
requiredbooltrueWhether URLs are required when creating or editing models in the admin panel. Has no effect if a generator is configured.
generatorstring|nullUrlGenerator::classThe class responsible for generating URLs on model creation. Set to null to disable automatic generation.

Default generator behavior

The built-in Lunar\Generators\UrlGenerator performs the following steps when a model is created:
  1. Checks whether the model already has any URLs (skips generation if it does).
  2. Reads the model’s name column, falling back to the name attribute (via attr('name')).
  3. Converts the name to a slug using Str::slug().
  4. Ensures uniqueness by appending a numeric suffix if the slug already exists (e.g. test-product, test-product-2, test-product-3).
  5. Creates the URL as the default for the system’s default language.

Custom generator

To customize URL generation, create a class with a handle method that accepts an Eloquent model:
<?php

namespace App\Generators;

use Illuminate\Database\Eloquent\Model;

class CustomUrlGenerator
{
    public function handle(Model $model)
    {
        // Custom URL generation logic
    }
}
Then reference it in the config:
// config/lunar/urls.php
return [
    'required' => true,
    'generator' => \App\Generators\CustomUrlGenerator::class,
];
To disable automatic generation entirely, set the generator to null:
return [
    'required' => true,
    'generator' => null,
];