--- title: "Publishing polyglot jobs from Laravel" description: "Add BabelQueue to a Laravel app and publish jobs that Go, Python, Node, Java and .NET services consume natively — without changing your broker." source: https://babelqueue.com/blog/publishing-polyglot-jobs-from-laravel/ pubDate: 2026-07-16T00:00:00.000Z --- Your Laravel app already dispatches jobs onto Redis or RabbitMQ. Here's how to make some of those jobs readable by a Go, Python, Node, Java, or .NET service — over the same broker, with no `serialize()` on the wire, and without touching the standard Laravel jobs you already run. BabelQueue adds a custom queue connection. Dispatch to it and the message goes out as a strict JSON envelope keyed by a URN; dispatch to any other connection and Laravel behaves exactly as before. The only thing that becomes polyglot is the connection you opt in. ## Install Pull in the Laravel driver and publish its config: ```bash composer require babelqueue/laravel php artisan vendor:publish --tag=babelqueue-config ``` That writes `config/babelqueue.php`, which holds the URN → handler map, the unknown-URN policy, and the dead-letter settings. You need PHP 8.2+, Laravel 11 or 12, and a running Redis or RabbitMQ broker. ## Configure a polyglot connection BabelQueue is a Laravel queue driver, so its connection lives in `config/queue.php` next to your existing ones. Add a `babelqueue` connection on the Redis transport: ```php // config/queue.php 'connections' => [ 'babelqueue' => [ 'driver' => 'babelqueue-redis', 'connection' => 'default', 'queue' => env('BABELQUEUE_QUEUE', 'default'), 'retry_after' => 90, ], ], ``` Prefer RabbitMQ? Swap the driver and point it at the broker: ```php 'babelqueue' => [ 'driver' => 'babelqueue-rabbitmq', 'host' => env('RABBITMQ_HOST', '127.0.0.1'), 'port' => env('RABBITMQ_PORT', 5672), 'queue' => env('BABELQUEUE_QUEUE', 'default'), ], ``` Your `redis`, `sync`, `database`, and `sqs` connections stay as they are. A job dispatched onto one of those is still a normal Laravel job, serialized the normal way. Only `babelqueue` is polyglot. ## Map URNs to handlers Consumers route on a URN — `urn:babel::` — not a PHP class name. That's what lets a non-PHP service consume the message: the identity on the wire is a stable string, not a class only PHP can resolve. Map each URN to the handler that processes it in `config/babelqueue.php`: ```php // config/babelqueue.php return [ // Map each incoming URN to the handler that consumes it. 'handlers' => [ 'urn:babel:orders:created' => App\Babel\Handlers\OrderCreatedHandler::class, ], // What to do when an incoming URN has no registered handler: // 'fail' | 'delete' | 'release' | 'dead_letter' 'on_unknown_urn' => 'dead_letter', // Where exhausted or unroutable messages go. Failures from "orders" // are republished to "orders.dlq" as the same envelope plus an // additive `dead_letter` block, so any SDK can triage them. 'dead_letter' => [ 'enabled' => true, 'suffix' => '.dlq', ], ]; ``` `on_unknown_urn` decides the fate of a message whose URN you haven't mapped. Routing it to `dead_letter` keeps it for triage instead of dropping it — useful while a polyglot fleet is still settling on its URNs. ## Produce a message Two ways to publish, both emitting the identical envelope. For a job with its own shape and behavior, implement `ShouldQueuePolyglot`: `getBabelUrn()` returns the URN, `toPayload()` returns the `data` body. ```php use BabelQueue\Contracts\ShouldQueuePolyglot; final class OrderCreated implements ShouldQueuePolyglot { public function __construct( private int $orderId, private float $amount, ) {} public function getBabelUrn(): string { return 'urn:babel:orders:created'; } public function toPayload(): array { return [ 'order_id' => $this->orderId, 'amount' => $this->amount, ]; } } dispatch(new OrderCreated(1042, 99.90)); ``` For a one-off publish, the `BabelQueue` facade takes the URN and payload directly and returns the message id: ```php use BabelQueue\Facades\BabelQueue; $id = BabelQueue::publish('urn:babel:orders:created', [ 'order_id' => 1042, 'amount' => 99.90, ]); ``` Either path produces the canonical `schema_version: 1` envelope. `data` is pure JSON, `job` is the URN, `trace_id` is stamped on publish, and `meta` carries the routing context: ```json { "job": "urn:babel:orders:created", "trace_id": "7b3f9c2a-e41d-4f88-9b2a-1c0d5e6f7a8b", "data": { "order_id": 1042, "amount": 99.90 }, "meta": { "id": "f1e2d3c4-b5a6-4789-90ab-cdef01234567", "queue": "default", "lang": "php", "schema_version": 1, "created_at": 1749132727000 }, "attempts": 0 } ``` This is the contract. A Go, Python, Node, Java, or .NET service reading the same queue parses these exact bytes — only `meta.lang` changes to name the consuming SDK. Nothing in here is PHP-specific, so nothing needs PHP to decode it. ## Consume in Laravel A PHP consumer maps the same URN to a handler. The handler's `handle()` receives the decoded `data`, the `meta` block, and the `trace_id` for cross-service correlation. The optional `failed()` hook runs when processing throws: ```php namespace App\Babel\Handlers; final class OrderCreatedHandler { public function handle(array $data, array $meta, string $traceId): void { logger()->withContext(['trace_id' => $traceId]) ->info('Order received', $data); // ... your business logic } public function failed(array $data, ?\Throwable $e): void { report($e); } } ``` `$traceId` is the same value the producer stamped, forwarded unchanged. Log it on every hop and one query reconstructs the whole path a job took, across every language that touched it. ## Run the worker BabelQueue is a drop-in driver, so you consume with the standard Laravel worker — point it at the polyglot connection: ```bash php artisan queue:work babelqueue ``` Your other workers keep running against their own connections, untouched. Exhausted retries and unmapped URNs follow the `on_unknown_urn` and `dead_letter` policy from your config. ## What you have now One connection in your Laravel app speaks the canonical envelope. A job published there is consumable by all six SDKs — PHP, Python, Go, Node, Java, and .NET — at 1.0, over the Redis or RabbitMQ broker you already run. Everything outside that connection is the Laravel you already know. To consume from another language, register the same URN in that SDK and point its worker at the same queue. Read [consuming messages](/docs/laravel/1.x/consuming-messages/) for the full PHP loop, and the [wire contract](/docs/spec/1.x/envelope/) for the byte-level shape every SDK agrees on.