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

Doctor Laravel Package

laravel/doctor

Laravel Doctor diagnoses common configuration, environment, and infrastructure problems in your Laravel application. Run php artisan doctor to see each failing check, then apply safe, deterministic fixes with --fix or follow the remediation steps for issues that can't be repaired automatically.

View on GitHub
Deep Wiki
Context7

Getting Started

Install as a dev dependency — Doctor is a developer/CI tool, not a runtime dependency:

composer require laravel/doctor --dev

Nothing else is required to start; the service provider registers a doctor Artisan command automatically.

First run, on the project you're currently debugging:

php artisan doctor

Run it before you start guessing at a broken app. The default suite covers the failure modes that produce confusing, non-obvious symptoms: missing .env/APP_KEY, unwritable storage, missing storage:link, unreachable database/cache/queue drivers, uncached config in production, missing Composer dependencies, and debug mode mismatched to the environment. Each result is one diagnostic with one status (pass, notice, warn, fail, skip, error), plus remediation text or a fix.

First use case — new-machine onboarding. After git clone + composer install, run php artisan doctor --fix. The deterministic first-party fixes cover exactly the things a fresh clone gets wrong: missing .env, missing APP_KEY, missing public storage link, unwritable storage directories. Anything that can't be repaired safely (like a failed asset build) is reported with steps instead.

Second step — control the exit code. By default the command exits non-zero on fail or error only. Warnings do not fail. To make CI strict:

php artisan doctor --fail-on=warn

Publish the config once you care about which diagnostics run:

php artisan vendor:publish --tag=doctor-config

This gives you persistent only/except selectors and the environments map — the two things you'll almost certainly need to tune for a real project.

Implementation Patterns

Onboarding script / post-clone bootstrap

Put Doctor at the end of your setup docs or a composer setup script. Order it after composer install and key:generate so it validates the result rather than racing your setup steps:

composer install
cp .env.example .env
php artisan doctor --fix
php artisan migrate

--fix never prompts, so it's safe in a script — but it also silently skips choice-based fixes (see Gotchas).

CI gate

Two useful shapes:

# Strict: warnings break the build too
php artisan doctor --fail-on=warn --bail

# Report-only: never mutate, never fail
php artisan doctor --fail-on=never

--bail stops at the first fail/error, which keeps CI logs short while you're cleaning up a backlog. Drop it once the suite is green so one run surfaces every regression.

For machine consumption, switch to the JSON or GitHub output format and note that --fix is rejected with these formats by design — machine-readable reports never mutate the app. That's the right split: --fix in local bootstrap, read-only in CI.

Pre-deploy / post-deploy verification

Doctor's environment modes make it genuinely useful outside local dev. Anything not listed in the environments config is treated as production, so a staging or QA run is held to production expectations (debug off, async queues, cached config/bootstrap). Run it after deploy as a smoke check:

php artisan doctor --only=configuration,security,queue --fail-on=warn

Group selectors let you slice by concern. Selectors accept class names, groups, packages, and package wildcards, and multiple values can be comma-separated or repeated:

php artisan doctor --only=storage
php artisan doctor --only=StorageIsWritable
php artisan doctor --only=laravel/doctor
php artisan doctor --except='laravel/*'
php artisan doctor --except=laravel/doctor

Config as a team baseline, CLI as a per-run override

Treat the published config as the team's opinion and the CLI flags as the developer's. The interaction is worth internalizing:

  • configured only is a persistent allowlist; --only at runtime narrows it further (it cannot re-add something the config excluded)
  • configured except and --except are unioned

So a common shape is: config excludes noisy environment-specific diagnostics that only one team member needs, and CI adds --only for a focused job.

Custom diagnostics for project-specific invariants

make:diagnostic scaffolds into app/Doctor/Diagnostics:

php artisan make:diagnostic HorizonIsRunning --fixable

This is the highest-leverage pattern. Anything your team checks manually on every deploy — Horizon running, a required queue connection, a feature flag present in config, an external service reachable — becomes one class with one check() method returning a result. The messages() registry keeps copy in one place and gives each outcome a stable machine-readable code (class-slug.message-key) that you can assert on or alert on.

A diagnostic is deliberately shaped like an Artisan command: $name and $group as properties, defaults derived from the class name. Branch on mode when a fact is only wrong in some environments:

use Laravel\Doctor\EnvironmentMode;

public function check(): DiagnosticResult
{
    if (config('queue.default') !== 'sync') {
        return $this->pass('async');
    }

    if (EnvironmentMode::current()->isProduction()) {
        return $this->warn('sync-in-production');
    }

    return $this->pass('sync-local');
}

Fixes: scope them to where they're safe

Implement Laravel\Doctor\Contracts\Fixable, mark failures with ->fixable(), and pass EnvironmentMode to constrain where the repair may run:

return $this->fail('missing')->fixable(EnvironmentMode::Local);

The contract between Doctor and your fix() is narrow and worth respecting: only explicit fail results marked ->fixable() from a diagnostic implementing Fixable are ever auto-repaired. warn, notice, skip, and error are never touched. That means a --fix run can never silently change a passing system.

Keep fix() idempotent and verify the outcome before reporting success — the README's key example re-reads config('app.key') after Artisan::call() and returns fixFailed(...)->withDetails(...) if it didn't take. Do the same: call the tool, then re-assert.

Links and remediation copy

Use Link::docs() for Laravel docs and Link::to() for everything else. Docs links are versionless in human output and automatically receive the .md variant in JSON intended for agents:

Message::make(
    summary: 'Queued jobs run synchronously in production.',
    remediation: 'Configure a background queue driver.',
)->link(Link::docs('queues', 'connections-vs-queues'));

Put the why in remediation and the how much do you care in the status. Interpolate run-time values with {placeholder} tokens supplied at the return site rather than building strings in the message registry.

Gotchas and Tips

--fix skips anything requiring a decision. It never prompts, so choice-based fixes (e.g. "which of your configured cache stores actually works?") are downgraded to ordinary failures carrying their remediation text. If the interactive flow matters, run php artisan doctor without --fix and answer the prompt.

--fix is rejected with JSON and GitHub formats. This is intentional — machine-readable reports are read-only. Scripts that pipe output to JSON must not also pass --fix; do the fixing in a separate CLI-format invocation.

Unlisted environments are treated as production. Defaults cover local, production, staging; anything else is held to the strictest expectations. A developer with APP_ENV=dev will get production-mode warnings (debug mode, sync queues, uncached bootstrap) until they add it:

'environments' => [
    'local' => ['local', 'dev'],
    'production' => ['production', 'staging', 'qa'],
],

An unknown mode name, a non-array environment list, or an environment listed under both modes throws at resolution time — a startup-time config error, not a diagnostic failure, so it won't appear in the report.

Debug mode "failing" is often correct, not a bug. Because mode changes the verdict and not just the message, the same state can pass locally and warn in production. Uncached bootstrap files warn in production but pass locally; present caches pass in production but emit a notice locally, since a stale cache is a common cause of "my change isn't showing up." Read the status against the mode before assuming a false positive.

--only cannot escape the configured allowlist. Configured only is an allowlist and runtime --only narrows it. If a diagnostic never appears in CI, check the config before blaming the selector syntax.

--bail only stops on fail/error. Warnings, notices, passes, and skips don't halt the run — so with default flags, --bail may run the whole suite.

--fail-on and exit codes matter more than the visual output. Default: fail and error fail the build. --fail-on=warn adds warnings. --fail-on=never reports without failing. Wire this explicitly in CI rather than relying on the default.

A throwing diagnostic becomes error, not a crash. That's convenient but can hide a broken custom diagnostic behind a red line in a long report — if your own diagnostic never prints its expected message, look for error and inspect the captured detail.

Message codes are an API. Codes derive from class and message key (e.g. application-key-is-set.missing). Renaming the class or a message key silently breaks anything asserting on them. If you build alerting on codes, treat renames as a breaking change.

It's a dev dependency, so it's absent from --no-dev installs. composer install --no-dev in your deploy pipeline removes the doctor command entirely. Run Doctor in a CI stage that installs dev dependencies, or explicitly composer require it in the stage that needs it — don't put it in a production post-deploy hook and expect it to exist.

Fixes touch the filesystem and .env. Doctor will create .env, generate keys, append to .gitignore, and repair storage permissions. Commit or stash before running --fix so you can review exactly what changed, and never run it against production data.

Order and shared state. Diagnostics run in-process against your booted application. A custom diagnostic that mutates config, files, or connections can change the verdict of later ones. Prefer read-only checks, and if you must write, gate it behind a mode.

Diagnostics should be fast and side-effect-free. They run on every doctor invocation including CI. Avoid network calls with long timeouts; if you must probe an external service, keep the timeout short and return skip when the dependency is irrelevant to the current environment.

Extension points beyond Fixable are thin by design. The lever you have is the messages() registry plus Link::docs()/Link::to() for remediation, and EnvironmentMode::current() for verdict branching. When you need more behavior, encode it in the status you return — resist reaching for output formatting.

Check php artisan doctor --help for the exact format flag in the version you install before baking it into scripts; the named formats (agent, json, GitHub) are documented, but the flag spelling is the kind of thing that shifts between releases.

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.
vento/laravel-airbrake
wfeller/laravel-batch
maco-studios/mage-os-installer
busarm/armie
siberfx/laravel-logger
kfteam1991/dcat-admin
dashed/dashed-laposta
dashed/dashed-ecommerce-montaportal
dashed/dashed-ecommerce-etsy
neohq/string-blade-compiler
log1x/acf-composer
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager