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:
A URL slug allows routes like:
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
Fields
| Field | Type | Description |
|---|
id | bigIncrements | Primary key |
language_id | foreignId | The language this URL belongs to |
element_type | string | Morph type of the parent model |
element_id | unsignedBigInteger | Morph ID of the parent model |
slug | string | The URL slug |
default | boolean | Whether this is the default URL for this element and language combination |
created_at | timestamp | |
updated_at | timestamp | |
Relationships
| Relationship | Type | Related Model | Description |
|---|
element | MorphTo | Polymorphic | The parent model (e.g. Product, Collection, Brand) |
language | BelongsTo | Lunar\Models\Language | The language this URL is for |
Scopes
| Scope | Description |
|---|
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,
];
| Option | Type | Default | Description |
|---|
required | bool | true | Whether URLs are required when creating or editing models in the admin panel. Has no effect if a generator is configured. |
generator | string|null | UrlGenerator::class | The 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:
- Checks whether the model already has any URLs (skips generation if it does).
- Reads the model’s
name column, falling back to the name attribute (via attr('name')).
- Converts the name to a slug using
Str::slug().
- Ensures uniqueness by appending a numeric suffix if the slug already exists (e.g.
test-product, test-product-2, test-product-3).
- 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,
];