Files
crewli/dev-docs/TELESCOPE.md
bert.hausmans f38c7ece97 chore: install laravel telescope as dev-only debugging dashboard
Installs laravel/telescope ^5.0 (v5.12.5) as a dev-dependency.
Three-layer production safety adapted to Laravel 11 layout (no
Kernel.php; routing/schedule in bootstrap/app.php +
routes/console.php):

  1. composer.json `extra.laravel.dont-discover` lists
     laravel/telescope. After editing, `php artisan package:discover`
     regenerates bootstrap/cache/packages.php — without this step
     the auto-discovery cache still registers the vendor provider.
  2. AppServiceProvider::register() gates registration to local +
     testing environments. Registers BOTH the vendor
     Laravel\Telescope\TelescopeServiceProvider (routes, migrations,
     publishing) AND the project's App\Providers\TelescopeService
     Provider (gate + filter) — they're sibling classes that extend
     ServiceProvider independently, not parent/child, so both must
     register for the dashboard to work. bootstrap/providers.php
     deliberately does NOT list either Telescope provider.
  3. .env TELESCOPE_ENABLED flag (false in .env.example). Runtime
     toggle that disables Telescope even when the providers are
     registered.

Production safety verified via simulated APP_ENV=production check:
confirms no Telescope-* providers are loaded.

Authorization: viewTelescope gate restricts dashboard to users
with the super_admin Spatie Permission role. Even in local
environments, only super_admin can view. Default was an email
allow-list stub — replaced with `$user->hasRole('super_admin')`.

Pruning: Schedule::command('telescope:prune --hours=48') added in
routes/console.php (Laravel 11's schedule location), environment-
gated to local + testing only.

Documentation: /dev-docs/TELESCOPE.md added; CLAUDE.md gets a
Development-tooling section. The doc explicitly calls out the
dual-provider registration (vendor + app) which differs from the
single-provider pattern in older Laravel versions.

Migrations applied: telescope_entries, telescope_entries_tags,
telescope_monitoring tables. Route registration verified in local
(42 telescope.* routes).

Tests: 1208/1208 passing — Telescope loads in the testing
environment as well, so the suite exercised it without issues.

Deployment note (flag for separate docs): a production operator
who runs `php artisan migrate` manually will still apply the
Telescope migrations — but because the providers never register
in production, the tables stay empty.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 04:03:31 +02:00

3.3 KiB

Laravel Telescope (dev-only)

Local debugging dashboard for queries, jobs, mails, redis, events, exceptions. Never registered in production.

Access

  • Local URL: http://localhost:8000/telescope
  • Auth requirement: authenticated user with super_admin role (Spatie Permission). The viewTelescope gate is the access control; even in local, only super_admin can view.

Production safety — three-layer defense

Crewli runs Laravel 11 (no Kernel.php; routing/schedule live in bootstrap/app.php + routes/console.php). The defense layers adapted to that layout are:

  1. composer.json extra.laravel.dont-discover lists laravel/telescope, so Laravel's auto-discovery never registers the vendor Laravel\Telescope\TelescopeServiceProvider. After editing this list, run php artisan package:discover once to refresh bootstrap/cache/packages.php.
  2. AppServiceProvider::register() gates manual registration to local + testing only, and registers BOTH the vendor provider (routes/migrations/publishing) and the project's App\Providers\TelescopeServiceProvider (gate + filter):
    if ($this->app->environment('local', 'testing')) {
        $this->app->register(\Laravel\Telescope\TelescopeServiceProvider::class);
        $this->app->register(\App\Providers\TelescopeServiceProvider::class);
    }
    
    bootstrap/providers.php deliberately does NOT list either Telescope provider — both registrations live behind this environment gate.
  3. .env TELESCOPE_ENABLED flag. false in .env.example. Runtime toggle that disables Telescope even when the providers are registered (e.g. to silence Telescope locally during a profiling session).

If you ever see Telescope on production: revert immediately, audit the three layers above, and treat as a security incident. The dashboard exposes every query, payload, and job — including secrets in payloads.

Verifying production safety

APP_ENV=production php -r "
require 'vendor/autoload.php';
\$app = require 'bootstrap/app.php';
\$app->make('Illuminate\Contracts\Console\Kernel')->bootstrap();
foreach (array_keys(\$app->getLoadedProviders()) as \$p) {
    if (stripos(\$p, 'telescope') !== false) echo 'LOADED: ' . \$p . PHP_EOL;
}
"

Expected output: nothing. Any line means a layer is breached.

Pruning

Scheduled in routes/console.php (Laravel 11 layout):

Schedule::command('telescope:prune --hours=48')
    ->daily()
    ->environments(['local', 'testing']);

48-hour retention. Adjust if dev DB is filling up.

What Telescope captures

  • All Eloquent queries with bindings + execution time (N+1 detection)
  • Background jobs with payload + status + retry history
  • Sent mails with rendered HTML + plaintext + recipient
  • Redis commands
  • Cache reads/writes
  • Events fired with listeners
  • Exceptions with stack traces
  • HTTP requests with headers + payload

What NOT to use Telescope for

  • Production debugging — never enabled there
  • Long-term audit trails — Spatie ActivityLog handles that (see ARCH-FORM-BUILDER §17.1)
  • Performance benchmarking — use APM tools designed for it

Disabling temporarily

If Telescope is slowing down a specific dev workflow:

TELESCOPE_ENABLED=false php artisan serve

Or set globally in .env and restart your server. Re-enable when done.