Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Ray Laravel Package

spatie/laravel-ray

Send debug output from any Laravel app to Ray, Spatie's desktop debugging tool. Inspect HTML, arrays, queries, and Markdown, measure performance, and pause execution—all with the familiar dump-style workflow.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install the desktop app first. spatie/laravel-ray is only a client. Everything it sends is an HTTP POST to Ray's local server on localhost:23517. If the Ray app isn't running, calls become silent no-ops — this is the single most common "it's broken" moment.

2. Install the package as a dev dependency:

composer require spatie/laravel-ray --dev

The service provider is auto-discovered. Nothing else is required to start, but you'll want the config file:

php artisan ray:publish-config

This creates config/ray.php and appends RAY_* variables to .env. Treat that file as your control panel: enable, host, port, remote_path/local_path, project_name, and the send_*_to_ray toggles for logs, dumps, exceptions, queries, views, jobs, requests, cache, events, mails, notifications, HTTP client requests, and console output.

3. First use case — stop reaching for dd():

ray('order placed', $order->id);
ray($order)->label('Order')->green();
ray($user)->model($user);       // Laravel-aware rendering
rd($payload);                    // send to Ray, then die

4. Second use case — see the framework, not just your variables. Drop this in a route or a local-only service provider:

ray()->showQueries();
ray()->showEvents();
ray()->showJobs();

5. Where to look first. The README is intentionally thin — it points to https://myray.app/docs/php/laravel. Read the config file top to bottom after publishing it; it's effectively inline documentation. Then skim CHANGELOG.md: many recent entries (Octane compatibility, Mail::fake() fixes, PHP 8.5 boot fixes, Laravel 12/13 support) describe behaviour you'd otherwise waste an hour rediscovering.

Implementation Patterns

Guard watchers behind a local check. showQueries() and friends register global listeners; in AppServiceProvider::boot() they'll run on every request in every environment you deploy to.

public function boot(): void
{
    if (! app()->isLocal()) {
        return;
    }

    ray()->showQueries();
    ray()->showCache();
    ray()->showJobs();
    ray()->showHttpClientRequests();
}

Prefer enabling watchers per-route or per-command where possible, so you're not paying the cost on unrelated requests.

Query debugging workflow. Three complementary tools:

  • ray()->showQueries() — live stream of every query with bindings and timing.
  • ray()->countQueries(fn () => $this->doWork()) — returns the count and reports it, ideal for hunting N+1s.
  • DB::table('users')->where(...)->ray() — the query-builder macro (added in 1.24.0) dumps the SQL and bindings before execution.

Model and collection inspection. ray()->model($user) and ray()->models($users) give you a structured, attribute-by-attribute view instead of a raw dump(). For collections, ray($rows)->table() is often more readable than the default array rendering.

Request/response cycle. showRequests(), showViews(), and showExceptions() together give you a per-request timeline. For a one-off look at what a controller actually returned:

return ray()->response($response);   // or ->html(), ->json(), ->testResponse($response)

testResponse() is chainable, which is why it also works well inside feature tests:

$this->get('/orders')->ray()->assertOk();

Performance and hard stops. ray()->measure() starts a timer you can read later; ray()->pause() halts execution and lets you inspect state in the app before continuing. Both are far more pleasant than sprinkling microtime() calls. In an HTTP context, pause() genuinely blocks the request — useful locally, disastrous if it slips into a PR.

Mail and notification previews. ray()->showMails() renders outgoing mail in Ray; ray()->mailable($mailable) previews a single mailable. This replaced a lot of "send to Mailtrap and squint" cycles. Note the changelog fixes around Mail::fake() — if mail watchers break your test suite, you're on an old patch release.

Non-HTTP contexts. Ray works in Artisan commands, queue workers, and scheduled tasks. Keep in mind the destination is whatever machine the process runs on: a worker in a container needs RAY_HOST pointing back at your host machine, not localhost.

AI-assisted debugging. The repo ships an agent skill documenting the raw JSON protocol Ray accepts (uuid, payloads[], origin, meta). That means generated HTML, markdown, or diffs from a coding agent can be pushed straight into Ray for visual review instead of pasted into a chat window — worth wiring up if you use Ray's MCP server.

Gotchas and Tips

Silent failure is the default. No Ray app running = no output and no error. Before debugging your code, verify connectivity from the same environment the app runs in:

curl -X POST http://localhost:23517 -H 'Content-Type: application/json' -d '{"uuid":"probe","payloads":[]}'

Run that inside the container if you use Sail, Docker, Homestead, or WSL.

Container and remote-host networking. Set RAY_HOST to host.docker.internal (macOS/Windows) or your host's LAN IP, and confirm port 23517 is reachable. For remote servers, RAY_HOST must point at your workstation and your firewall must allow the port — and think hard before enabling Ray on shared staging, because you'll be shipping request payloads, query bindings, and model attributes over the network.

Path mapping for clickable links. In containerised or remote setups, file paths in Ray won't resolve unless you configure local_path and remote_path in config/ray.php. Without it, you lose the jump-to-source link, which is half the value.

dump() gets hijacked. With dump-forwarding enabled, dump() output goes to Ray and disappears from the browser/console. Convenient, until a colleague wonders why nothing prints. Toggle it via the config (or RAY_* env) rather than editing vendor code.

Register watchers in boot(), not register(). Version 1.43.8 moved setProjectName() into boot() specifically to fix ReflectionException: Class "request" does not exist and unresolvable Settings dependencies during app boot on PHP 8.5. If you're on PHP 8.5, be on 1.43.8+; if you're pinned lower, expect noisy boot failures.

Octane and long-running workers. Watchers hold listener state. Under Octane, output can duplicate or leak across requests. Clear the screen between requests (ray()->clearScreen()) and prefer per-request enabling over global listeners in Octane apps.

Config caching bites. php artisan config:cache freezes your RAY_* values. Change .env, forget to re-cache, and you'll debug a ghost. php artisan config:clear is step one whenever Ray behaves inconsistently between tinker and the web server.

Watch payload size and sensitivity. ray($hugeCollection) serialises everything — including attributes you'd normally hide. Use ->table(), select fewer columns, or push big content through ray()->file() / ray()->html() instead of dumping megabytes. Also remember Ray is a plaintext HTTP channel to localhost; don't leave it streaming tokens or passwords onto a shared screen.

rd() in a web request kills the response. It's dd() with a network call attached. In an API or Blade route you'll get a broken page, not a message. Use ray() and return something real, or use pause() if you need to inspect mid-request without destroying the response.

Keep the two Spatie packages in sync. Recent releases bump spatie/ray (1.43.4 requires ^1.45.0). A stale transitive dependency produces confusing "method doesn't exist" errors when you copy snippets from current docs.

Extension points. When the built-in helpers don't fit, register a macro — Ray::macro('myThing', fn ($value) => ...) — or write a custom payload class and send it with ray()->payload(new YourPayload($data)). The latter is the clean route if your team wants domain-specific rendering (e.g. an order summary card) available everywhere in the codebase.

Don't let Ray define your debugging. It's a dev-only dependency with production toggles (enable, enable_in_production, send_exceptions_to_ray). Audit for stray ray() calls before merging, and keep RAY_ENABLED=false as the default in deployed environments so a forgotten call can't leak data or block a request via pause().

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
bernskioldmedia/laravel-matomo
bernskioldmedia/laravel-harvest
bernskiold/laravel-data-scrubber
bernskiold/laravel-currency-converter
bernskiold/laravel-campaign-monitor
bernskioldmedia/laravel-autotranslate
siberfx/telegram-bot
roy-sandip/laravel-view-extended
gregsphotography/site-builder-core
roy-sandip/laravel-view
anodyne/blade-fluentui-icons
webx-ui/module-catalog-collections
webx-ui/module-catalog-feeds
makidizajnerica/laravel-dto
matviib/scheduler
webx-ui/templates
anodyne/blade-tabler-icons
bref/laravel-websockets
anodyne/blade-hugeicons
1994/flatpack-statamic