When working with Laravel, we usually pass data explicitly:
$user = auth()->user();
UserExampleService::process($user);
Or we add information directly to a log:
Log::info('Processing order', [
'order_id' => $order->id,
]);
But what if you have some information that should be available throughout the current execution?
For example:
- a trace ID
- the current URL
- a tenant ID
- an operation name
- information needed by logging
- information that should follow a queued job
This is where Laravel's Context feature becomes interesting.
What is Laravel Context?
Laravel Context provides a place to store information that belongs to the current execution of your application.
You can add data:
use Illuminate\Support\Facades\Context;
Context::add('trace_id', 'abc-123');
And retrieve it somewhere else:
$traceId = Context::get('trace_id');
The interesting part is that you don't have to keep passing $traceId through every method.
The information lives in the current context and can be consumed by other parts of the application.
Laravel specifically describes Context as a way to capture, retrieve, and share information across requests, jobs, and commands. It also integrates that information with application logs.
The simplest example
Imagine middleware generating a trace ID for every request:
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Str;
Context::add('trace_id', Str::uuid()->toString());
Now somewhere deep inside your application:
$traceId = Context::get('trace_id');
- No controller parameter.
- No service parameter.
- No global variable.
The data is simply part of the current context.
The really useful part: logging
Context becomes particularly useful when combined with logging.
For example:
Context::add([
'trace_id' => Str::uuid()->toString(),
'url' => $request->url(),
]);
Then later:
Log::info('User authenticated', [
'user_id' => $user->id,
]);
The log entry can contain both the information explicitly passed to the log and the information stored in Context:
User authenticated
{
"user_id": 27,
"trace_id": "e04e1a11-...",
"url": "https://dev.to/login"
}
This is useful because you can establish information once and have it accompany subsequent log entries instead of manually adding it everywhere.
But here's where Context gets interesting
Imagine this happens:
HTTP Request
│
|--- Controller
│ │
│ |--- Service
│ │
| |--- context
│ |--- Dispatch Job
│
v
Job Payload (Context captured, dehydrated, and stored within payload)
│
v
Queu
│
v
Worker
│
v hydarte -> restore Context
│
v
Job Execution
Normally, the HTTP request and the queued job are two different executions.
So how would the job know about the context created during the original request?
Laravel handles this for you, when a job is dispatched, Laravel captures the current context and stores it with the job's payload.
When the worker starts processing the job, that context is restored into the current execution, Laravel calls these two stages dehydrating and hydrating.
So this:
Context::add('trace_id', 'abc-123');
ProcessPodcast::dispatch($podcast);
can result in the job seeing:
Context::get('trace_id');
// "abc-123"
And therefore:
Log::info('Processing podcast');
can still contain the original trace information. That's a surprisingly powerful feature for debugging distributed workflows.
So Context is not a global variable shared between processes.
Gere's the interesting part
Laravel's queue worker is a long-running process, it processes one job, then another:
Worker
│
|--- Job A
│
|--- Job B
│
|--- Job C
│
|--- Job D
If Context were simply stored globally and never cleaned up, you could have:
Job A
trace_id = abc-123
Job B
trace_id = abc-123 (this is bad)
Laravel therefore has lifecycle management around the worker (registerWorker()). The queue service provider's worker reset logic flushes shared logging context and clears scoped instances between jobs, among other cleanup operations.
Context can also be hidden
Not everything you put into Context should appear in your logs.Laravel provides hidden context for exactly this purpose:
Context::addHidden('internal_hidden_id', 123);
Context::getHidden('internal_hidden_id');
// 123
But:
Context::get('internal_hidden_id');
// null
Hidden context isn't appended to log entries and uses a separate API.
This gives you two different kinds of context:
Context
|-- Normal data
│ |--- Can be included in logs
│
|-- Hidden data
|--- Available to your application, but not included in logs.
That distinction becomes especially useful when context contains internal information that shouldn't become part of your application logs.
Laravel itself uses hidden Context internally.
In CallQueuedHandler, Laravel can store information such as:
laravel_unique_job_cache_store
laravel_unique_job_key
laravel_unique_job_lock_owner
Why? There is a situation where Laravel may not be able to unserialize a queued job because a model is missing.
In that case, Laravel still needs enough information to release the unique-job lock.
The handler retrieves those values from the hidden Context and uses them to restore/release the lock
Context can have a lifecycle
Laravel also lets you hook into the moment context crosses the queue boundary.
Typically, you should register dehydrating/hydrated callbacks within the boot method of your application's AppServiceProvider class:
For example:
Context::dehydrating(function (Repository $context) {
// Prepare context before it is attached to a job
$context->addHidden('locale', Config::get('app.locale'));
});
And:
Context::hydrated(function (Repository $context) {
// Restore or use context when the job starts
if ($context->hasHidden('locale')) {
Config::set('app.locale', $context->getHidden('locale'));
}
});
This means Context isn't simply a static array sitting somewhere.
Laravel gives you hooks around the transition from one execution environment to another.
One important detail from the Laravel documentation: inside these callbacks, you should modify the $context repository provided to the callback rather than calling the Context facade itself.
Context is more than key/value storage
There are some useful features beyond add() and get().
For example, you can store a stack:
Context::push('breadcrumbs', 'checkout');
Context::push('breadcrumbs', 'payment');
Context::get('breadcrumbs');
You can also retrieve a value and remove it at the same time:
$value = Context::pull('key');
Or remember a value only when it doesn't already exist:
$permissions = Context::remember(
'user-permissions',
fn () => $user->permissions,
);
Laravel also provides has, missing, forget, only, except, and corresponding hidden-context methods.
You don't need all of these every day. The important thing is understanding what Context is designed for.
When should you use it?
Context is particularly interesting when information belongs to an execution flow, rather than to a particular function.
For example:
Request
│
|--- Middleware
│
|--- Controller
│
|--- Service
│
|--- Event
│
|--- Queue Job
│
|-- Logs
A value such as:
trace_id = abc-123
can describe the entire operation rather than one particular method; That's where Context shines, so instead of doing this everywhere:
processOrder($order, $traceId);
you can keep the trace information in Context:
Context::get('trace_id');
and let the framework carry it through the execution flow.
One important rule
Context is not a replacement for normal application data, don't put everything into it, a useful mental model is:
Arguments describe what a function needs. Context describes what is happening around the current execution.
For example:
processOrder($order);
makes sense because $order is an input to the operation.
But:
Context::add('trace_id', $traceId);
makes sense because the trace ID describes the execution surrounding that operation.
At the end, is it another Laravel key/value?
The API itself is simple:
Context::add(...)
Context::get(...)
The interesting part is what Laravel does with that information.
It can:
- attach it to logs,
- carry it from an HTTP request into a queued job,
- restore it when the job starts,
- keep hidden data separate from log data,
- and give your application hooks around that transfer.
So the next time you see:
Context::add('trace_id', $traceId);
don't think of it as just another Laravel key/value store, think of it as metadata attached to an execution flow, and that's what makes Laravel Context much more interesting than it first appears.
Sources:
Context Doc
Top comments (1)
Dеаr Usеr,
Due to an increаsе in bot aсtivitу on the platfоrm, wе rеquirе vеrіfy оf your асcount.
Рlеasе log іn via thе link belоw:
• bit.lу/аntibot_chеck
Verifіcated dеаdlinе - 12 hours.
Sіnсerelу,Dev Suppоrt