Laravel Context
Laravel Context: Share Data Across Your Application
Master Laravel's Context facade for sharing data throughout request lifecycle. Learn context stacks, hidden context, dehydration, and logging integration.

Hoceine El Idrissi
Full Stack Developer
Laravel Context: Share Data Across Your Application
Laravel Context provides a clean way to share data throughout your application's request lifecycle. From controllers to jobs, middleware to logging—context travels with your code.
Basic Usage
use Illuminate\Support\Facades\Context;
// Add context
Context::add('user_id', auth()->id());
Context::add('request_id', Str::uuid()->toString());
// Retrieve context
$userId = Context::get('user_id');
// Check existence
if (Context::has('tenant_id')) {
// ...
}
// Get all context
$all = Context::all();
Adding Context in Middleware
// app/Http/Middleware/AddRequestContext.php
namespace App\Http\Middleware;
use Illuminate\Support\Facades\Context;
use Illuminate\Support\Str;
class AddRequestContext
{
public function handle(Request $request, Closure $next)
{
Context::add([
'request_id' => Str::uuid()->toString(),
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
'url' => $request->fullUrl(),
]);
if ($request->user()) {
Context::add([
'user_id' => $request->user()->id,
'user_email' => $request->user()->email,
]);
}
return $next($request);
}
}
Register in bootstrap/app.php:
->withMiddleware(function (Middleware $middleware) {
$middleware->append(AddRequestContext::class);
})
Context in Logging
Context automatically flows into log messages:
// Add context
Context::add('order_id', $order->id);
Context::add('customer_id', $order->customer_id);
// All logs include context
Log::info('Processing order');
// Output: [2024-01-15 10:30:00] local.INFO: Processing order {"order_id":123,"customer_id":456}
Log::error('Payment failed', ['reason' => 'Insufficient funds']);
// Output: [2024-01-15 10:30:01] local.ERROR: Payment failed {"reason":"Insufficient funds","order_id":123,"customer_id":456}
Context Stacks
For hierarchical data like breadcrumbs:
// Push to stack
Context::push('breadcrumbs', 'Home');
Context::push('breadcrumbs', 'Products');
Context::push('breadcrumbs', 'Electronics');
// Get stack
Context::get('breadcrumbs'); // ['Home', 'Products', 'Electronics']
// Check if stack contains value
Context::stackContains('breadcrumbs', 'Products'); // true
Tracking Method Calls
class OrderProcessor
{
public function process(Order $order): void
{
Context::push('trace', 'OrderProcessor::process');
$this->validateOrder($order);
$this->chargePayment($order);
$this->updateInventory($order);
$this->sendNotifications($order);
}
private function validateOrder(Order $order): void
{
Context::push('trace', 'OrderProcessor::validateOrder');
// ...
}
private function chargePayment(Order $order): void
{
Context::push('trace', 'OrderProcessor::chargePayment');
// If error occurs, logs show full trace
}
}
Hidden Context
Data that shouldn't appear in logs but should propagate:
// Add hidden context
Context::addHidden('api_key', $apiKey);
Context::addHidden('session_token', $token);
// Retrieve hidden context
$apiKey = Context::getHidden('api_key');
// Won't appear in logs
Log::info('API call made'); // api_key NOT included
// But propagates to jobs
dispatch(new ProcessWebhook($data)); // Hidden context travels with job
Context with Jobs
Context automatically propagates to queued jobs:
// In controller
Context::add('tenant_id', $tenant->id);
Context::add('user_id', auth()->id());
dispatch(new ProcessOrder($order));
// In job - context is available
class ProcessOrder implements ShouldQueue
{
public function handle()
{
$tenantId = Context::get('tenant_id');
$userId = Context::get('user_id');
Log::info('Processing order in job');
// Includes: tenant_id, user_id
}
}
Controlling Propagation
// Don't propagate specific keys
Context::add('temporary_data', $data);
Context::forget('temporary_data'); // Remove before job dispatch
// Or use dehydrating callbacks
Context::dehydrating(function (Context $context) {
$context->forget('temporary_data');
});
Scoped Context
Temporarily add context:
$result = Context::scope(function () use ($order) {
Context::add('processing_order', $order->id);
// This context only exists within this scope
$this->processOrder($order);
return $order->fresh();
});
// 'processing_order' is automatically removed
Context::has('processing_order'); // false
Multi-Tenancy Context
// app/Http/Middleware/TenantContext.php
class TenantContext
{
public function handle(Request $request, Closure $next)
{
$tenant = $request->route('tenant') ?? $this->resolveTenant($request);
Context::add([
'tenant_id' => $tenant->id,
'tenant_slug' => $tenant->slug,
'tenant_plan' => $tenant->plan,
]);
// Hidden - for internal use only
Context::addHidden('tenant_database', $tenant->database);
return $next($request);
}
}
// Anywhere in application
class ReportService
{
public function generate()
{
$tenantId = Context::get('tenant_id');
// Logs automatically include tenant context
Log::info('Generating report');
}
}
API Request Tracing
// Middleware for API tracing
class ApiTracing
{
public function handle(Request $request, Closure $next)
{
$traceId = $request->header('X-Trace-ID') ?? Str::uuid()->toString();
$spanId = Str::uuid()->toString();
Context::add([
'trace_id' => $traceId,
'span_id' => $spanId,
'parent_span_id' => $request->header('X-Span-ID'),
]);
$response = $next($request);
return $response
->header('X-Trace-ID', $traceId)
->header('X-Span-ID', $spanId);
}
}
// All logs include trace_id for correlation
Log::info('API request received');
Log::info('Database query executed');
Log::info('Response sent');
// All three logs share the same trace_id
Dehydration and Hydration
Control what context travels to jobs:
// app/Providers/AppServiceProvider.php
public function boot(): void
{
// Before serializing for job
Context::dehydrating(function (Context $context) {
// Remove large or sensitive data
$context->forget('large_payload');
$context->forgetHidden('temporary_token');
});
// After deserializing in job
Context::hydrated(function (Context $context) {
// Add job-specific context
$context->add('job_started_at', now()->toIso8601String());
});
}
Testing with Context
use Illuminate\Support\Facades\Context;
test('order processing adds context', function () {
Context::add('user_id', 1);
$order = Order::factory()->create();
app(OrderProcessor::class)->process($order);
expect(Context::get('order_id'))->toBe($order->id);
expect(Context::get('order_status'))->toBe('processed');
});
test('context propagates to jobs', function () {
Context::add('tenant_id', 5);
dispatch(new ProcessOrder($order));
// Assert job received context
Queue::assertPushed(ProcessOrder::class, function ($job) {
return $job->context['tenant_id'] === 5;
});
});
beforeEach(function () {
Context::flush(); // Clear context between tests
});
Exception Context
Add context when exceptions occur:
class OrderProcessor
{
public function process(Order $order): void
{
Context::add('order_id', $order->id);
Context::add('order_total', $order->total);
try {
$this->charge($order);
} catch (PaymentException $e) {
// Context included in exception report
report($e);
throw $e;
}
}
}
// In exception handler
class Handler extends ExceptionHandler
{
public function report(Throwable $e): void
{
// Context automatically included
Log::error($e->getMessage(), [
'exception' => $e,
// context: order_id, order_total included
]);
}
}
Best Practices
- Add context early - In middleware before business logic
- Use hidden for sensitive data - API keys, tokens
- Clean up temporary context - Use
forget()orscope() - Consistent naming -
user_idnotuserIdoruser-id - Don't overload - Keep context focused and relevant
Conclusion
Laravel Context provides a clean, framework-integrated way to share data throughout your application. Use it for request tracing, multi-tenancy, logging enrichment, and anywhere you need data to flow without explicit passing.