The Problem It Solves

When a Laravel request is slow, the time is going into one of six places, and they need completely different fixes:

StageIf it is slow, the cause is usually
BootstrapToo many service providers, uncached config, autoloader not optimised
MiddlewareA global middleware doing a query or an HTTP call on every request
RoutingUncached routes, or an expensive route model binding
ControllerN+1 queries, a slow external API, unbounded collections
ResponseHeavy Blade rendering, serialising a large payload
TerminatePost-response work that is not actually deferred

Without a timeline you guess, and people almost always guess “controller” because that is the code they wrote. A surprising amount of the time it is a global middleware nobody remembers adding.

Laravel Time Machine is a free, MIT-licensed profiler that records a full lifecycle timeline per request, profiles every SQL query with its bindings and duration, and renders it as a visual dashboard. It is the most popular of my packages at 68 stars and 2,900+ downloads.

Installing It

composer require jaydeep/laravel-time-machine

That is enough to start. It follows APP_DEBUG by default, so it is on locally and off in production without you configuring anything. Publish the config when you want to change that:

php artisan vendor:publish --tag=time-machine-config
RequirementSupported
PHP7.4 and 8.x
Laravel8, 9, 10, 11, 12, 13
LicenseMIT

The Dashboard

Visit /time-machine. You get a list of recent requests with method, URL, status code, total duration, memory and query count. Filter and search by any of those, then click a request to open its timeline.

The filtering is what makes it useful on a real application. “Show me POST requests to /api/orders that took over 500 ms” narrows a thousand captured requests down to the four that matter.

Reading the Timeline

Each request renders as a Gantt-style chart of its stages:

Request: POST /api/orders                      Total: 1,840ms

Bootstrap      ██                                    42ms
Middleware     ████                                  95ms
Routing        █                                     11ms
Controller     ████████████████████████████████   1,610ms
  ├─ SQL (34 queries)                              1,204ms
  ├─ external-api span                               310ms
  └─ (php)                                            96ms
Response       ███                                    68ms
Terminate      █                                      14ms

That output answers the question in one line: 34 queries taking 1.2 seconds inside the controller. Now you know to go looking for an N+1, and you know the external API is a secondary concern rather than the cause.

Look at the query count before the query time. Thirty-four queries on an endpoint that should need three is a structural problem — and fixing it usually removes more time than optimising any individual query would.

SQL Query Profiling

Every query is captured with its SQL, its bindings and its duration, and anything over the slow-query threshold is highlighted. Bindings matter more than people expect — a query that is fast for one value and slow for another is invisible without them.

// config/time-machine.php
'collectors' => [
    'queries' => true,
],

'thresholds' => [
    'slow_request' => 500,   // ms — highlight the request
    'slow_query'   => 50,    // ms — highlight the query
],

Duplicate queries are the tell for N+1. If you see the same statement with only the bound ID changing, thirty times in a row, that is an eager load you did not write — and there is a whole article on fixing it.

Custom Spans and Marks

The built-in stages cover the framework. For your own code, the facade lets you instrument anything:

use Jaydeep\LaravelTimeMachine\Facades\TimeMachine;

// A point in time
TimeMachine::mark('cache primed');

// Measure a callable and get its return value back
$report = TimeMachine::measure('generate-report', function () {
    return Report::build();
});

// Manual start/end when the work is not in one block
TimeMachine::startSpan('external-api');
$response = Http::timeout(10)->get($endpoint);
TimeMachine::endSpan('external-api');

measure() is the one to reach for — it wraps a closure, times it, and returns whatever the closure returned, so adding instrumentation does not restructure your code:

public function store(StoreOrderRequest $request)
{
    $order = TimeMachine::measure('create-order', fn () =>
        $this->orders->create($request->validated())
    );

    TimeMachine::measure('charge-card', fn () =>
        $this->payments->charge($order)
    );

    TimeMachine::measure('render-invoice', fn () =>
        $this->invoices->render($order)
    );

    return new OrderResource($order);
}

Three lines of instrumentation and the timeline now attributes the controller’s 1.6 seconds to a specific step instead of leaving it as one opaque bar.

Configuration

KeyDefaultPurpose
enabledfollows APP_DEBUGMaster on/off switch
dashboard.pathtime-machineDashboard URI prefix
dashboard.middleware['web']Dashboard access guards
dashboard.per_page15Requests per page
storage.max_records100Profiles retained
storage.pathstorage/time-machineWhere profiles are written
collectors.queriestrueCapture DB queries
ignore_pathsassets, telescopeNever profiled
thresholds.slow_request500 msRequest highlighting
thresholds.slow_query50 msQuery highlighting

storage.max_records defaults to 100 and keeps a rolling window, so the profile directory cannot grow without bound while you are debugging.

Using It Safely

Profiling data is sensitive: query bindings contain real values, URLs contain real IDs, and the dashboard is a window into your traffic.

// config/time-machine.php
'enabled' => env('TIME_MACHINE_ENABLED', env('APP_DEBUG', false)),

'dashboard' => [
    'middleware' => ['web', 'auth', 'can:viewTimeMachine'],
],

'ignore_paths' => [
    'telescope*',
    'horizon*',
    '_debugbar*',
    'build/*',
    '*.css',
    '*.js',
],

The package is designed for zero overhead when disabled, and the safe default is to leave it that way in production. If you must profile a live issue, gate it behind a flag you can flip for a short window, restrict the dashboard to an admin gate, and turn it off again when you are done.

vs Telescope and Debugbar

Time MachineTelescopeDebugbar
FocusLifecycle timingEverything — jobs, mail, cache, eventsIn-page debug info
Timeline viewGantt per stageNoNo
Custom spansYesNoLimited
StorageFiles, rolling windowDatabaseSession / files
SetupOne composer requireMigration + configOne composer require
OverheadNone when disabledMeaningful — writes to DBLow

They are complementary rather than competing. Telescope is the better general-purpose inspector — if you want to see queued jobs, sent mail and cache hits, use Telescope. Time Machine answers one question specifically, which is where did the milliseconds go, and it is the only one of the three that draws you a timeline.