Pay-per-use · No subscriptions

HTML to PDF in Laravel — without Dompdf or wkhtmltopdf

Render Blade templates to PDF via one Http call. Full Chromium. Queue-friendly.

Generate PDFs from Blade templates in Laravel 10+ with nothing more than the Http facade. No Dompdf CSS limitations, no wkhtmltopdf binary on the server, no Snappy configuration. Just render your view, POST to the API, return a download response. Works identically on Forge, Vapor, Envoyer, Laravel Cloud, or your own VPS.

Why developers choose HTML2DocHub

Works on Laravel 10, 11, 12 — and older versions with Guzzle directly
Render Blade templates with view()->render() then POST to the API
No binary dependencies — nothing to install on Forge / Vapor / your Docker image
Queue-friendly — dispatch an async render job, get notified via webhook
Full Chromium rendering — flexbox, grid, web fonts, custom properties all work
Works on Laravel Vapor serverless without extra Chromium packages
Idempotency keys align with Laravel's Queue retry semantics
Per-job page count + charge visible in dashboard for accounting

Code Examples

Laravel 10+ — Http facade in a controllerphp
<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\Http;

class InvoicePdfController extends Controller
{
    public function show(int $id)
    {
        $invoice = Invoice::findOrFail($id);
        $html = view("invoices.pdf", compact("invoice"))->render();

        $res = Http::withHeaders([
            "X-API-Key" => config("services.html2dochub.key"),
        ])->post("https://api.html2dochub.com/v1/render", [
            "type"    => "pdf",
            "html"    => $html,
            "options" => ["format" => "A4", "margin" => "18mm"],
        ])->throw()->json();

        $pdf = Http::get($res["download_url"])->body();

        return response($pdf, 200, [
            "Content-Type"        => "application/pdf",
            "Content-Disposition" => 'attachment; filename="invoice-' . $id . '.pdf"',
        ]);
    }
}
Config — config/services.phpphp
// config/services.php
return [
    // ...
    "html2dochub" => [
        "key" => env("HTML2DOCHUB_API_KEY"),
    ],
];

// .env
HTML2DOCHUB_API_KEY=sk_live_YOUR_KEY
Queued job — safe retries with idempotencyphp
<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Http;

class GenerateReportPdf implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 3;
    public int $backoff = 30;

    public function __construct(public int $reportId) {}

    public function handle(): void
    {
        $report = Report::findOrFail($this->reportId);
        $html = view("reports.pdf", compact("report"))->render();

        $res = Http::withHeaders(["X-API-Key" => config("services.html2dochub.key")])
            ->timeout(30)
            ->post("https://api.html2dochub.com/v1/render", [
                "type"            => "pdf",
                "mode"            => "async",
                "html"            => $html,
                "webhook_url"     => route("webhooks.pdf_ready"),
                "tag"             => "report:" . $this->reportId,
                "idempotency_key" => "report-" . $this->reportId . "-v" . $report->version,
            ])
            ->throw()
            ->json();

        $report->update(["html2dochub_job_id" => $res["id"]]);
    }
}
Webhook receiver for async rendersphp
<?php

// routes/web.php
Route::post("/webhooks/pdf-ready", [PdfWebhookController::class, "handle"])
    ->withoutMiddleware([\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class])
    ->name("webhooks.pdf_ready");

// app/Http/Controllers/PdfWebhookController.php
public function handle(Request $request)
{
    $payload = $request->all();

    if ($payload["status"] !== "completed") {
        return response()->noContent();
    }

    $report = Report::where("html2dochub_job_id", $payload["id"])->firstOrFail();
    $pdfBytes = Http::get($payload["download_url"])->body();

    Storage::disk("s3")->put("reports/{$report->id}.pdf", $pdfBytes);
    $report->update(["status" => "ready"]);

    return response()->noContent();
}

Simple, transparent pricing

Pay only for pages rendered. No subscriptions. No minimum monthly fee.

1 page PDF:~₹0.10
10 page PDF:~₹0.80
100 pages/day:~₹8/day
See full pricing details

Frequently Asked Questions

Why not use barryvdh/laravel-dompdf?+
Dompdf is pure PHP and supports only a small subset of CSS — no flexbox, no grid, no custom properties beyond basic ones, limited web-font support. Templates that look great in the browser often break in Dompdf. HTML2DocHub uses real Chromium, so what renders in Chrome renders identically in your PDF.
How is this different from barryvdh/laravel-snappy?+
Snappy wraps wkhtmltopdf, which was archived in 2023 and no longer receives security updates. It also requires the wkhtmltopdf binary on every deploy target (hard on Laravel Vapor). HTML2DocHub is a REST API — nothing to install, always on the latest Chromium.
Does it work on Laravel Vapor?+
Yes, and it's actually the easier option there. Vapor's 512 MB Lambda limits make self-hosted Chromium difficult. With HTML2DocHub, your Vapor function just makes an HTTPS call — no custom runtime or Chromium package needed.
Can I stream the PDF directly to the browser?+
Yes. Fetch the bytes from the download_url with Http::get() and return them as a response. For very large PDFs, store in S3 first and return a signed redirect.
Is there a Laravel package?+
An official html2dochub/laravel package is planned for 2026 Q3. For now, the Http facade covers 99% of use cases and gives you explicit control over retries and queue behaviour.

Start rendering PDFs today

Free account. No credit card required. API ready in minutes.

Get your free API key