php erb 109 lines · 3 tabs

Stream a PDF Invoice from an Order with Dompdf in Laravel

Shared by codesnips Aug 2026
3 tabs
<?php

namespace App\Http\Controllers;

use App\Models\Order;
use App\Services\InvoicePdfService;
use Illuminate\Http\Response;

class InvoiceController extends Controller
{
    public function __construct(private InvoicePdfService $invoices)
    {
    }

    public function show(Order $order): Response
    {
        $this->authorize('view', $order);

        $dompdf = $this->invoices->render($order);
        $filename = $this->invoices->filename($order);

        return response($dompdf->output(), 200, [
            'Content-Type' => 'application/pdf',
            'Content-Disposition' => 'inline; filename="' . $filename . '"',
            'Cache-Control' => 'private, no-store',
        ]);
    }
}
3 files · php, erb Explain with highlit

This snippet shows a focused, production-shaped way to render an order into a PDF invoice and push it straight to the browser without ever writing a temporary file to disk. The work is split across three collaborating files: a service that builds the PDF, a Blade template that defines its layout, and a thin controller that authorizes the request and streams the bytes.

In InvoicePdfService, the rendering logic lives behind a single render(Order $order) method that returns a Dompdf instance. Isolating Dompdf here keeps the controller ignorant of the PDF engine, so swapping libraries or adding a caching layer later touches only one class. The service eager-loads items.product and customer up front to avoid N+1 queries while the template iterates, computes a filename() from the order number, and configures Dompdf with isRemoteEnabled so the template can reference a logo over HTTP. The HTML is produced by rendering a Blade view to a string via view(...)->render(), which lets the invoice reuse the framework's templating instead of concatenating markup by hand.

The invoice.blade.php tab is a self-contained print layout. Dompdf understands a practical subset of CSS, so styles are kept inline in a <style> block rather than pulled from an external stylesheet, and layout leans on tables because Dompdf's flex/grid support is limited. It formats currency with number_format, escapes user data through Blade's {{ }} syntax, and renders the line items in a loop.

The InvoiceController ties it together. show() uses route-model binding to receive the Order, calls authorize('view', $order) so only the owner can pull an invoice, then delegates to the service. Rather than Dompdf::stream() (which calls exit), it returns a Laravel Response built from $dompdf->output(), setting Content-Type: application/pdf and a Content-Disposition of inline so the browser previews the file; switching to attachment would force a download instead. Returning a real response object keeps middleware, testing, and exception handling intact, which raw streaming would bypass. The dompdf->output() approach buffers the whole document in memory, a reasonable trade-off for invoices but something to reconsider for very large documents, where a queued job writing to storage would fit better.


Related snips

Share this code

Here's the card — post it anywhere.

Stream a PDF Invoice from an Order with Dompdf in Laravel — share card
Link copied