spatie/ray
Send debug output from any PHP app to Ray, Spatie’s desktop debugging tool. Inspect arrays, HTML, queries, and more with a consistent API across Laravel/PHP/JS. Measure performance, pause execution, and keep fast, interactive feedback.
Installation:
composer require spatie/ray
For Laravel, use spatie/laravel-ray instead.
Configuration:
php artisan vendor:publish --provider="Spatie\Ray\RayServiceProvider"
.env with your Ray app credentials:
RAY_APP_ID=your_app_id
RAY_APP_SECRET=your_app_secret
First Usage:
Replace dd() or dump() with Ray’s methods:
use Spatie\Ray\Ray;
Ray::info('Debugging started');
Ray::dump($user); // Inspects a variable
Ray::table(['Name' => 'John', 'Age' => 30]); // Displays tabular data
Run Ray:
https://myray.app (no firewall blocking).Replace dd() with Ray::dump() for interactive inspection:
Ray::dump($query->get(), 'User Query Results');
Ray::inspect() for deep inspection without halting execution.Attach context (e.g., user ID, request data) to logs:
Ray::info('Order processed', [
'user_id' => auth()->id(),
'order_id' => $order->id,
]);
use Spatie\Ray\Laravel\RayRequestLogger;
class LogRayRequests extends RayRequestLogger {}
Measure execution time with Ray::spy():
Ray::spy('Fetching user data', function () {
return User::find(1);
});
Ray::spy('API Call', function () {
Ray::spy('Database Query', fn() => User::find(1));
});
Display data in tables or JSON:
Ray::table($users->toArray(), 'Users List');
Ray::json($config, 'App Config');
Use Ray::when() to debug only in specific environments:
Ray::when(config('app.debug'), function () {
Ray::dump($this->data);
});
Log requests automatically:
public function handle(Request $request, Closure $next)
{
Ray::info('Request received', ['path' => $request->path()]);
return $next($request);
}
Log exceptions with stack traces:
use Spatie\Ray\Exceptions\Handler;
class RayExceptionHandler extends Handler
{
public function report(Throwable $exception)
{
Ray::error($exception, 'Unhandled Exception');
}
}
Debug CLI commands:
protected function handle()
{
Ray::info('Running migration...');
Artisan::call('migrate');
}
Log events with payloads:
public function handle(OrderCreated $event)
{
Ray::info('Order created', ['order_id' => $event->order->id]);
}
Use Ray::fake() in tests to mock Ray calls:
Ray::fake();
$response = $this->get('/users');
Ray::assertLogged('User fetched');
Apply themes or colors:
Ray::warning('Deprecation notice', [], 'dark');
Ray::success('Operation completed', [], 'green');
Copy data to clipboard:
Ray::copy($user->email, 'User Email');
Inspect Livewire properties or Blade templates:
Ray::dump($this->properties, 'Livewire Properties');
Debug server-side code from your local Ray app:
.env:
RAY_REMOTE_DEBUG=true
Ray::remote() to send data to your local Ray instance.myray.app is blocked by a firewall or proxy.myray.app in your server’s firewall or use a VPN.Ray::limit(100) or paginate data:
Ray::table($users->take(100)->toArray());
Ray::mask() to redact sensitive fields:
Ray::dump($user->only(['id', 'name']), 'User Data');
Ray::spy() calls slow down production.if (app()->environment('local')) {
Ray::spy('Slow operation', fn() => $this->process());
}
php artisan cache:clear
php artisan view:clear
RAY_APP_ID and RAY_APP_SECRET are correct.php artisan ray:test to verify connectivity.Ray::inspect($variable) or cast to an array:
Ray::dump((array) $resource);
Ray::limit() or disable images in Ray settings..env and Ray is initialized:
$this->app->make(\Spatie\Ray\Ray::class);
.env Overrides.env > config/ray.php.RAY_ENDPOINT=https://your-private-ray-server.com
RAY_ENABLED=false in .env.if (!$request->hasHeader('X-Debug')) {
app()->forgetInstance(\Spatie\Ray\Ray::class);
}
use Spatie\Ray\Formatters\Formatter;
class CustomFormatter implements Formatter
{
public function format($payload): array
{
return ['custom' => json_encode($payload)];
}
}
Register in config/ray.php:
'formatters' => [
\Spatie\Ray\Formatters\DefaultFormatter::class,
\App\Formatters\CustomFormatter::class,
],
Replace the default HTTP transport (e.g., for local debugging):
Ray::extend(function ($app) {
$app->singleton(\Spatie\Ray\Ray::class, function () {
return new \Spatie\Ray\Ray(new \App\Transports\LocalRayTransport());
});
});
Use afterSendCallbacks to modify payloads:
Ray::afterSend(function ($payload) {
$payload['custom_metadata'] = ['timestamp' => now()];
return $payload;
});
Remove Ray calls before production:
vendor/bin/rector process src --dry-run
Configure in rector.php:
use Spatie\Ray\Rector\RemoveRayCallRector;
$rules = [
new RemoveRayCallRector(),
];
Add PHPStan rules to detect Ray calls:
How can I help you explore Laravel packages today?