Opt-in performance across Laravel's hot paths β a menu of independent, byte-identical tiers spanning the whole request lifecycle, from Eloquent to the router, the view finder, and the query grammar. Take the ones whose hot paths you run.
Independent, opt-in tiers across Laravel's hot paths β model, events, Blade, config, validation, query grammar, container, request, router. Take the ones whose hot paths you run; skip the rest. The model trait is the zero-config on-ramp, not the whole package.
πͺ
Byte-identical output
Same bytes as vanilla β or it's a failing test. Asserted across every tier in the parity suite: every cast, edge value, dirty-check, resolved SQL string, served response. That promise is the whole product.
π§±
Compute once, reuse
Every tier removes the same waste β work that's a pure function of stable inputs, recomputed per row, per attribute, per component, per render, per request, per query. Grease computes each fact once and reuses it.
π
Across the whole request lifecycle
Container resolution β request input β route-middleware resolve β config reads β query compilation β hydration & casting β Blade render β serialization. A faster path at each stage, all stacking on one real request.
β‘
The model trait is the easiest win
hydrate β54% Β· toArray β53% Β· set+dirty β62% Β· read β27% Β· enum cast β44% Β· date serialization β87%. `use HasGrease;` and done β no config, no provider. Marginal in isolation; compounded on a real request, they aren't.
π§
Eager caches make once-per-request work ~free
`grease:config-cache`, `grease:route-cache`, `grease:view-cache` β drop-in twins of Laravel's `*:cache` that precompute resolution into opcache-interned files. Config reads, middleware, and view lookups become hash hits, server-wide.
Nothing is auto-discovered beyond the trait you add. Every tier is a deliberate opt-in; code that doesn't take one runs pure vanilla. PHP 8.2+, Laravel 12 / 13.
use Grease\Concerns\HasGrease;class User extends Model{ use HasGrease; }
An endpoint that lists 100 users and serializes them to JSON drops from 3.58 ms to 0.46 ms β real SQL included, not a micro-benchmark. That's :memory: SQLite, where Eloquent is most of the request; against a networked database the same ~3.1 ms come off a larger total, so the percentage is smaller while the time removed holds (how to read these honestly).
That's the model and event tiers together. The model trait alone is the easiest win and a fine place to stop β but Grease is a menu, and the rest of it lives across the request, not just in Eloquent:
Model β use HasGrease; (or extend Grease\GreasedModel): hydration, casting, serialization, dirty-checking. Per-row Γ per-attribute. Zero config.
App-entry swaps β a greased container, request, and router: a one-line edit where each is constructed, before any provider runs.
Deploy caches β grease:config-cache, grease:route-cache, grease:view-cache: drop-in twins of Laravel's own *:cache that take once-per-request resolution to ~free.
The same idea runs under all of them. Laravel re-derives the same stable facts over and over β the casts array and a fresh ReflectionClass per hydrated row, the merged input map on every $request->input(), a config key's dot-walk on every config(), a route's middleware resolve-and-sort and a view's nameβpath stat-walk on every request, an identifier's quoting on every query. None of it changes for the life of the process. Grease computes each fact once and reuses it β byte-for-byte the same result, a fraction of the work.
Each of these was proposed to Laravel core and declined on reasonable grounds β every one "marginal in isolation." Individually, maybe. Layered on a real request β booted, routed, hydrated, rendered, serialized β they move the number you actually pay for. Grease is where that declined work lives β see the history.
Byte-identical to vanilla, or it's a failing test.