novius/laravel-linkable
Manage “linkable” Eloquent models in Laravel: define per-model link configuration (URL callback or route), labels/groups/search, and query customization. Includes a Linkable Nova field plus publishable config and language files.
Install the Package:
composer require novius/laravel-linkable
Publish optional config and language files if needed:
php artisan vendor:publish --provider="Novius\LaravelLinkable\LaravelLinkableServiceProvider" --tag=config
php artisan vendor:publish --provider="Novius\LaravelLinkable\LaravelLinkableServiceProvider" --tag=lang
Apply the Trait to a Model:
Add the Linkable trait to your Eloquent model (e.g., Post) and define a linkableConfig() method:
use Novius\LaravelLinkable\Traits\Linkable;
class Post extends Model {
use Linkable;
public function linkableConfig(): LinkableConfig {
return new LinkableConfig(
routeName: 'post.show',
routeParameterName: 'post',
optionLabel: 'title',
optionGroup: 'Content'
);
}
}
Define a Route:
Ensure the route referenced in linkableConfig() exists in your routes/web.php:
Route::get('/posts/{post}', [PostController::class, 'show'])->name('post.show');
Use the Model Methods: Generate URLs dynamically:
$post = Post::first();
echo $post->url(); // Generates the route URL for the post
echo $post->previewUrl(); // Generates a preview URL if configured
Integrate with Nova/Filament (Optional):
Add the Linkable field to your Nova resource or Filament form:
// Nova Example
Linkable::make('Related Post', 'related_post')
->optionsClasses([Post::class])
Attach/Detach Links:
Use the linkable() relationship to manage links between models:
$post = Post::find(1);
$post->linkable()->attach(Post::find(2)); // Link to another post
$post->linkable()->detach(Post::find(2)); // Remove the link
Query Linked Models: Retrieve models linked to or from another model:
$linkedPosts = Post::find(1)->linkedTo; // Posts linked *to* this post
$postsLinkedFrom = Post::find(1)->linkedFrom; // Posts *linking from* this post
Dynamic URLs:
Leverage url() and previewUrl() for consistent URL generation:
$url = $post->url(['locale' => 'fr']); // Generates URL with locale
$previewUrl = $post->previewUrl(); // Generates preview URL if token exists
Custom URL Logic:
Override the default route() behavior in your AppServiceProvider:
Linkable::setRouteCallback(function (string $name, array $parameters = [], ?string $locale = null) {
return route($name, $parameters, true, $locale);
});
Nova Field:
Use the Linkable field to create relationships in the admin panel:
Linkable::make('Author', 'author')
->optionsClasses([User::class])
->optionLabel('name')
->optionGroup('Users');
Filament Form:
Add the Linkable component to forms:
Linkable::make('Featured Post')
->setLinkableClasses([Post::class])
->setLocale(request()->locale);
Locale-Aware Links:
Configure resolveQuery and resolveNotPreviewQuery to filter by locale:
resolveQuery: function (Builder $query) {
$query->where('locale', app()->getLocale());
},
Custom Locale Resolution:
Override getLocale() in your model if locale isn’t stored directly:
public function getLocale() {
return $this->parent?->locale;
}
Autoload Models:
Define directories or models to autoload in config/laravel-linkable.php:
'autoload_models_in' => app_path('Models'),
'linkable_models' => [Vendor\Model::class],
Route Overrides: Add custom route names for non-model links:
'linkable_routes' => [
'home' => 'Home Page',
],
linkableConfig():
optionsQuery: function (Builder $query) {
$query->whereNotIn('id', [1, 2, 3]); // Exclude specific IDs
},
// Example: Only allow admins to link posts
public function authorizeLink($user, $post) {
return $user->isAdmin();
}
public function test_link_attachment() {
$post1 = Post::factory()->create();
$post2 = Post::factory()->create();
$post1->linkable()->attach($post2);
$this->assertTrue($post1->linkedTo->contains($post2));
}
Optimize Queries:
Use with() to eager-load linked models:
$posts = Post::with('linkedTo')->get();
Cache URLs: Cache generated URLs if they’re static:
$url = Cache::remember("post_url_{$post->id}", now()->addHours(1), function () use ($post) {
return $post->url();
});
php artisan vendor:publish --provider="Novius\LaravelLinkable\LaravelLinkableServiceProvider" --tag=config
disable_localization is true or getLocale() isn’t implemented.
Fix: Ensure getLocale() is defined in your model or set disable_localization to false in the config.->distinct() or limit query depth:
$linkedPosts = Post::find(1)->linkedTo()->limit(10)->get();
linkableConfig() may conflict with existing routes.
Fix: Use unique route names or override the routeCallback to handle conflicts.Linkable field not appearing in Nova/Filament.
Fix: Ensure the model is autoloaded or manually added to linkable_models in the config. Verify the optionLabel and optionGroup are correctly set.dd($post->url(['extra' => 'param']));
toSql() to check the query being executed:
$query = Post::query()->where('locale', app()->getLocale());
dd($query->toSql(), $query->getBindings());
linkableConfig() is correctly defined and all required fields (optionLabel, optionGroup) are set.previewUrl() returns null, verify the previewTokenField is correctly configured and the token exists in the database.Linkable trait or creating a custom field.optionsQuery or create a custom resolver:
optionsQuery: function (Builder $query) {
$query->where('published_at
How can I help you explore Laravel packages today?