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

Unused Scanner Laravel Package

insolita/unused-scanner

Laravel package that scans your project for unused classes and files, helping you spot dead code worth removing. Configure paths and exclusions, run the Artisan scanner, and review a clear report of cleanup candidates.

Deep Wiki
Context7

Getting Started

insolita/unused-scanner answers one question: which packages in composer.json are never referenced in my PHP source? It reads your dependency list, parses your code into tokens/AST, and matches class names it finds against the classes each installed package provides. Anything left over is reported as a candidate for removal.

  1. Install it as a dev tool, not a runtime dependency:

    composer require --dev insolita/unused-scanner
    

    Make sure composer install has already run — the scanner needs the package tree in vendor/ to know what each dependency contains.

  2. Create scanner.php in the project root. This is the file it looks for; copy the example config from the README and trim it down. A sensible starting shape for a Laravel app:

    return [
        'directories' => [__DIR__ . '/app', __DIR__ . '/config', __DIR__ . '/routes'],
        'excludeDirectories' => [__DIR__ . '/vendor', __DIR__ . '/storage', __DIR__ . '/tests'],
        'composerJson' => __DIR__ . '/composer.json',
        'excludePackages' => ['php', 'ext-*', 'lib-*', 'composer-plugin-api', 'composer-runtime-api'],
    ];
    

    Exact key names vary by version — check vendor/bin/unused-scanner --help and the README example in vendor/insolita/unused-scanner.

  3. Run it:

    vendor/bin/unused-scanner
    

    You get a list of packages with no detected class references. Verify by hand before touching composer.json.

First use case: the post-upgrade cleanup. After a Laravel major version bump or a feature removal, run the scanner, cross-check each hit with grep -rn "Vendor\\\\Package" app/ config/ routes/ resources/, then remove the confirmed dead ones. Start with a narrow directories list (app/ only) so you build trust in the output before widening the scope.

Implementation Patterns

Triage into a three-bucket allowlist. Every scan produces three groups: genuinely unused packages, packages used only dynamically, and noise. Encode the middle bucket in excludePackages (or a skipPackages-style key) with a comment explaining why each entry is there — 'spatie/laravel-permission' // middleware resolved by string in routes — otherwise the allowlist rots into "everything is excluded."

Run two configurations. One strict config scoped to app/ with minimal exclusions for signal; one broad config covering config/, routes/, database/, and Blade for completeness. The strict run finds real dead weight; the broad run catches things the strict one flags falsely. Diff the two result sets.

Pair it with icanhazstring/composer-unused. That tool uses Composer's class map and reflection; this one uses source tokens. When both agree a package is unused, that's a strong signal. When they disagree, the disagreement itself is informative — it usually means a string-based reference somewhere.

CI integration, staged. Add the scan to a CI job after composer install:

- run: composer install --no-interaction --prefer-dist
- run: vendor/bin/unused-scanner || true   # advisory at first

Run it advisory for a few weeks, tighten the allowlist until output is stable, then drop the || true and treat new unused dependencies as a failing check. Check the tool's actual exit-code behaviour with echo $? before gating — don't assume it returns non-zero on findings.

The removal loop. Never delete on the scanner's word alone:

vendor/bin/unused-scanner                 # candidate list
composer why vendor/package              # who requires it (incl. transitive)
composer remove vendor/package --dry-run # see the cascade
composer remove vendor/package && php artisan test

Some "unused" packages are transitively required by something you do use; composer why tells you that instantly.

Laravel-specific handling. Package discovery, service providers in config/app.php, facade aliases, middleware strings in routes, Blade component tags, and extra.laravel.providers are all string references the parser may not see. Either reference the provider/Facade class in a config file it does scan, or add the package to the allowlist. Prefer the former — it keeps the scanner honest.

Monorepos. Keep one scanner.php per package directory and run the binary from inside each package. Paths in the config should use __DIR__ so it works regardless of the invoking working directory.

Gotchas and Tips

  • String-referenced classes are invisible. 'App\\Providers\\FooServiceProvider', app('bar'), ::class built dynamically, container bindings by key, config values naming classes, JSON/YAML/env files — none of these are class references the parser can resolve. This is the single largest source of false positives, and it's why an allowlist is mandatory, not optional.
  • Packages that ship no PHP classes are always flagged. Asset-only, view-only, translation-only, migration-only, or global-helper packages can legitimately have zero class references. Exclude them permanently.
  • Platform packages must be excluded. php, ext-*, lib-*, composer-plugin-api, composer-runtime-api will otherwise show up as unused on every run.
  • Dev vs. non-dev dependencies. Whether require-dev is scanned depends on the version/flag — check --help. If you have dev-only tooling that's flagged, scan it with a separate config rather than polluting the production allowlist.
  • Stale release. The last release was January 2022. On PHP 8.3/8.4 and modern Laravel you may hit deprecation notices or nikic/php-parser version conflicts. If composer require --dev fails to resolve, try --with-all-dependencies; if it still fights your lockfile, install it in a throwaway Composer project that points at your app's composer.json rather than in the app itself.
  • Parse errors abort scans. One unparseable file in an included directory can break the whole run or silently drop results. Keep generated code, cache dumps, and storage/ out of directories.
  • Relative paths are a trap. Anchor every path to __DIR__ in scanner.php; running the binary from a different directory otherwise silently scans nothing and reports everything as unused.
  • Results can be cached. If output looks identical after a refactor that clearly changed things, look for a cache/artifact file and clear it before trusting the run.
  • Treat exit codes defensively. Test what the tool returns on both clean and dirty runs before wiring it into a pipeline; a tool from 2022 may return 0 regardless.
  • Never automate removal. The scanner is a candidate generator. The verification loop — composer why, --dry-run, test suite — is what makes removal safe.
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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings