Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Unique Urls Laravel Package

vlados/laravel-unique-urls

Laravel package for generating and managing unique URLs, helping you create collision-free slugs and SEO-friendly permalinks for models and content with automatic uniqueness checks and customizable URL patterns.

View on GitHub
Deep Wiki
Context7

Getting Started

This package generates and maintains unique, controller-backed URLs for your Eloquent models, driven by two artisan commands: urls:generate (writes the URL records) and urls:doctor (validates that every discovered model still resolves to a working handler).

  1. Install the package and publish its config: php artisan vendor:publish for the package's config tag. Skim the published config first — model_paths (new in v2.2.0) and the handler/strategy defaults live there.
  2. Run php artisan urls:generate. On a fresh install with no configuration it scans app/ for models using the package's URL trait and creates a record per model.
  3. Run php artisan urls:doctor. It re-resolves everything and tells you which models have no reachable handler. As of v2.2.0 a clean run reports Everything is ok — checked 12 models in 2 paths, so you can confirm what it inspected rather than trusting a bare "ok".
  4. First real use case: add the trait to a single model, give it a urlHandler() returning your controller and a urlStrategy() returning the slug path, run urls:generate --model=YourModel, and hit the generated route. Validate with urls:doctor --model=YourModel before running it across the whole app.

The two files worth reading in the source are the config and LaravelUniqueUrlsController — the latter defines exactly how a URL is resolved at request time, and v2.2.0 aligned urls:doctor's checks to it.

Implementation Patterns

Scanning is now module-aware. Previously urls:generate and urls:doctor used Spatie's ModelFinder with the default app_path(), so anything under Modules/Blog/app/Models was silently invisible. v2.2.0 adds Services\ModelDiscoveryService, which resolves scan directories together with their PSR-4 root and namespace prefix. Defaults are app/ plus every module under config('modules.paths.modules') (or base_path('Modules') if nwidart/laravel-modules isn't installed). Both the modern Modules/{Module}/app layout and the legacy Modules/{Module}/Models / Modules/{Module}/Entities layouts are covered.

Overriding discovery with model_paths. The new config key accepts three shapes:

'model_paths' => [
    app_path(),                                        // bare dir, namespace guessed from composer PSR-4
    'Modules\Blog' => base_path('Modules/Blog/app'),   // explicit namespace => path
    ['path' => base_path('src/Domain'), 'namespace' => 'App\Domain', 'base_path' => base_path('src')],
],

This list replaces the automatic scan entirely — it is not additive. Use it when your PSR-4 map is unusual, when you want to exclude a noisy module, or when models live outside app/ and Modules/.

CI pattern. Add php artisan urls:doctor --strict to your pipeline. Without --strict an empty scan still exits zero; with it, an empty whole-or-partial scan fails the build. Pair it with php artisan urls:generate in the same job so newly added models are covered.

Targeted runs. --model now behaves identically in both commands and accepts a fully qualified class name: php artisan urls:doctor --model="Modules\Blog\Models\Post". A bare name still resolves against App\Models first, then falls back to the discovered model set — so --model=Post keeps working after a model moves into a module. If a short name matches multiple discovered models, both commands list the candidates and refuse to guess. Use FQCNs in scripts to stay deterministic.

Handler conventions. urlHandler() is now validated through the same ControllerResolver the live request uses. __invoke() is checked for Livewire-style controllers; otherwise the declared method, with show() and index() as fallbacks. If you rely on the fallbacks, doctor will no longer flag you as broken — but you should still declare the method explicitly for clarity.

Gotchas and Tips

  • A wrong namespace prefix fails silently. The discovery service maps each directory to a PSR-4 prefix; if the prefix doesn't match your composer.json map, every class under it is dropped without error. This is the single most common cause of "why isn't my module being scanned". Mitigation: v2.2.0's doctor now names any path that resolves to zero models even when other paths found some — read those lines, they are almost always this bug. Run composer dump-autoload after changing PSR-4 config, since bare-directory namespace guessing reads the composer map.
  • Non-existent directories are skipped, not reported. A typo in model_paths degrades to a smaller scan. Combine with --strict in CI to catch it.
  • Expect more findings after upgrading. Model discovery for urls:doctor now uses class_uses_recursive(), so models inheriting the trait from a parent are checked too, and module models are no longer invisible. A project that moved models into modules should expect doctor to report more than before — it was blind, not clean. Don't treat the first post-upgrade run as a regression; reconcile the list once.
  • urlHandler() is now instantiated, not just class_exists()-checked. Resolving goes through ControllerResolver, which builds the controller. A controller with an unresolvable constructor dependency is reported as an error. Keep handler constructors container-resolvable (or bind them), and don't assume doctor's check is cheap — it may boot services.
  • One broken model no longer aborts the run. Both controller resolution and model instantiation failures are recorded as errors and the scan continues, so you get full coverage in one pass. Previously a single bad model cost you every model after it in the scan order.
  • urlStrategy() failures are deliberately silent. Doctor evaluates strategies against an empty model instance, so slug generation routinely fails on missing relations or null attributes. Those failures are swallowed on purpose — do not expect doctor to validate your slug logic. Test urlStrategy() yourself with real fixtures; use urls:generate --model=... on a seeded database to catch real strategy errors.
  • Ambiguous short names are an error, not a guess. --model=Post matching several models will now stop with a candidate list. In scripts always pass the FQCN.
  • Livewire components pinned by name are no longer misreported — if you had a known-false doctor warning about a pinned component, remove any allowlist entry you added for it.
  • Extension point: if your modules live outside the conventions, bind or extend ModelDiscoveryService rather than fighting model_paths — it's the single seam both commands share, so a fix there covers generate and doctor at once.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource