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

Link Checker Laravel Package

chrisrhymes/link-checker

A Laravel package for scanning pages and detecting broken links, with configurable crawling, concurrent checking, and clear reports to help keep your site's links healthy.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package as a dev dependency — it belongs in require-dev because its main job is catching broken links during development and CI, not serving production traffic:

composer require chrisrhymes/link-checker --dev

Then publish what you need and run the migrations. The package ships a config file and (if you want durable results) a table for recorded link checks, so publish both tags and migrate before your first run:

php artisan vendor:publish --provider="ChrisRhymes\LinkChecker\LinkCheckerServiceProvider"
php artisan migrate

If your app keeps dev-only migrations out of production, publish the migration into database/migrations and guard it with the usual app()->runningUnitTests() / environment checks — you almost certainly don't want a link_checks-style table on your production database.

Where to look first: the published config/link-checker.php and the package README's quickstart, then the facade (ChrisRhymes\LinkChecker\Facades\LinkChecker). Those two files tell you the whole surface area: how to point a check at a URL, what gets recorded, and which status codes are recorded.

First use case: put a test in tests/Feature that walks your key public routes and asserts every link on them resolves. That's the highest-value, lowest-effort win — it catches the classic "renamed a route, forgot a menu item" regression that normal feature tests miss because they only assert the page itself renders.

it('has no broken links on the homepage', function () {
    LinkChecker::check(route('home'))->assertNoBrokenLinks();
});

(Confirm the exact fluent methods in the README for the version you install — the facade has been the stable entry point, but assertion names are the part most likely to differ from your memory.)

Implementation Patterns

Test-driven checking (recommended default). Wire link checks into Pest/PHPUnit rather than a standalone crawler. You get failures in the same place as your other regressions, with the same CI gating and the same IDE integration. A good shape:

  • One smoke test per top-level section (home, marketing pages, docs index) — fast, runs on every PR.
  • A broader sweep over external links in a separate, slower test or a nightly scheduled run.
  • Use datasets to list pages rather than looping inside a single test, so a failure names the offending page.

Artisan / scheduled runs. For sitemap-style coverage of a large site, drive the checks from a console command and schedule it (e.g. nightly) writing results to the database. This gives you a history: you can see when a link started 404ing, which is far more useful than a single red build.

Recording results as a dashboard. If you enable persistence, treat the results table as an observability table, not a source of truth. Common pattern: a small admin-only route listing distinct URLs grouped by status code, ordered by most recent failure. Prune old rows on a schedule (model:prune or a nightly delete of rows older than N days) so it doesn't grow unbounded.

Skipping noisy status codes (v0.10). The new option lets you exclude specific status codes from being recorded, so transient or expected responses don't drown the table. Typical entries:

  • 429 — rate-limited external hosts you hammer during a crawl.
  • 503 / 502 — third-party services that are legitimately flaky.
  • 401 / 403 — links behind auth that your crawler is never going to satisfy.
// config/link-checker.php
'skip_status_codes' => [429, 503],

Keep this list short and justified. Every code you add is a broken link you've agreed to stop seeing. Prefer skipping specific URLs/hosts over skipping a code globally where the package gives you that granularity.

CI integration. Run the fast test suite on PRs; run the full external crawl on a schedule or on main merges only. External crawling is the single most common cause of flaky pipelines in Laravel apps, and users blame the package. Separate the two so a timeout on someone else's marketing site doesn't block a hotfix.

Gotchas and Tips

External links are the flakiness source, not the package. Timeouts, DNS, bot-blocking (Cloudflare returning 403 to non-browser user agents), and rate limits will generate false failures. Set a sane timeout, cap concurrency, and be aggressive about scoping. If a host blocks you, assert on your own application's URLs and drop the external ones to the scheduled job.

Record-vs-assert are different concerns. Skipping recording a status code does not necessarily stop a test from asserting on it, and vice versa. Be explicit about which behaviour you want: "I don't care that this 403 happened" and "this 403 shouldn't fail my build" should be configured in the right place, or you'll get a clean results table and a red build (or the reverse) and waste an afternoon on it.

Config caching. If you run php artisan config:cache anywhere in your pipeline — including in a Docker build step that CI reuses — your edit to skip_status_codes won't take effect until you re-cache. This is the #1 "the new option doesn't work" report for any Laravel config flag.

Migrations in the test database. RefreshDatabase will try to run the package migration. If you published it and then hand-edited it, your test DB and prod DB can drift. Keep published migrations untouched.

Pest version note. v0.10 updates the package's own test suite to Pest v4. That's internal — it does not force Pest v4 on your application, and your app can stay on Pest 3. The only consequence: if you fork the package or vendor its tests as a reference, those tests now assume Pest v4 APIs. Nothing in your composer.json should need to move for this release.

Upgrade safety. v0.10 is additive: a new config option plus internal test changes. No deprecations, no signature changes, no migration changes. composer update chrisrhymes/link-checker and republish the config only if you want the new commented default (--force will overwrite your edits — diff first).

Debugging a surprising failure. Log the resolved URL and the actual response body on failure, not just the status code. A 200 with a "Page not found" body, or a 301 to a soft-404, is the failure mode most link checkers miss and most developers spend hours chasing.

Extension point. If you need custom request behaviour (auth headers, a specific user agent, cookies for a gated section), look at whether the package allows swapping the underlying HTTP client or per-check options before you reach for forking — that's the seam most worth understanding early, because internal-link crawling of authenticated areas is the natural second-phase requirement.

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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core