Laravel integration

Generate PDFs from Blade views in Laravel

Render a Blade view to HTML, send it to the CheapPDF API with Laravel’s Http client, and return or store the PDF. Configuration lives in .env; slow or bulk work runs on your queue.

app/Services/CheapPdf.php
<?php

namespace App\Services;

use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Http;
use RuntimeException;

class CheapPdf
{
    /** Convert an HTML string to raw PDF bytes. */
    public function fromHtml(string $html, string $filename = 'document.pdf', array $options = []): string
    {
        return $this->send(['fileorhtmlorurl' => $html, 'filename' => $filename] + $options);
    }

    /** Convert a public URL to raw PDF bytes. */
    public function fromUrl(string $url, string $filename = 'page.pdf', array $options = []): string
    {
        return $this->send(['fileorhtmlorurl' => $url, 'filename' => $filename] + $options);
    }

    private function send(array $fields): string
    {
        $key = config('services.cheappdf.key');
        if (! $key) {
            throw new RuntimeException('CHEAPPDF_API_KEY is not configured.');
        }

        $response = Http::asForm()
            ->connectTimeout(10)
            ->timeout(config('services.cheappdf.timeout'))
            // Retry network errors and 5xx only. A 400 (bad key, quota) will not succeed on retry.
            ->retry(2, 1000, fn ($e) => $e instanceof ConnectionException
                || ($e instanceof RequestException && $e->response->serverError()), throw: false)
            ->post(config('services.cheappdf.endpoint'), ['apikey' => $key] + $fields);

        $json = $response->json();
        if (! $response->ok() || ! ($json['success'] ?? false)) {
            throw new RuntimeException(sprintf(
                'CheapPDF failed [%s]: %s',
                $json['error'] ?? $response->status(),
                $json['message'] ?? 'Unknown error'
            ));
        }

        $pdf = base64_decode($json['data']['base64_pdf'] ?? '', true);
        if ($pdf === false || ! str_starts_with($pdf, '%PDF')) {
            throw new RuntimeException('CheapPDF returned an invalid PDF.');
        }

        return $pdf;
    }
}

Configuration through environment variables

Put the API key in .env and expose it through config/services.php, so application code reads config('services.cheappdf.key'). That keeps working after php artisan config:cache, which makes direct env() calls outside config files return null.

There is no official Laravel package. Laravel’s built-in HTTP client is all you need, so there is nothing extra to install.

<?php
// config/services.php
return [
    // ...
    'cheappdf' => [
        'key'      => env('CHEAPPDF_API_KEY'),
        'endpoint' => 'https://www.cheap-pdf.com/generate-pdf-public',
        'timeout'  => 60,
    ],
];

These examples use only documented Laravel APIs (Http::asForm(), retry() with a callback, queued jobs) and are written for current Laravel versions. Adjust namespaces to match your app, and test in your own project before deploying.

The service class

The class at the top of this page keeps all API details in one place. It sends the request as a form post, retries only connection errors and 5xx responses, checks success, decodes data.base64_pdf and verifies the bytes start with %PDF. Laravel’s container resolves it automatically, so you can type-hint CheapPdf in controllers and jobs.

Use it in a controller

InvoicePdfController.php
<?php

namespace App\Http\Controllers;

use App\Models\Invoice;
use App\Services\CheapPdf;

class InvoicePdfController extends Controller
{
    public function show(Invoice $invoice, CheapPdf $pdf)
    {
        $this->authorize('view', $invoice);

        // Render a normal Blade view to an HTML string
        $html = view('invoices.pdf', ['invoice' => $invoice])->render();

        $bytes = $pdf->fromHtml($html, "invoice-{$invoice->number}.pdf", ['width' => 800, 'height' => 1100]);

        return response($bytes, 200, [
            'Content-Type'        => 'application/pdf',
            'Content-Disposition' => "attachment; filename=\"invoice-{$invoice->number}.pdf\"",
        ]);
    }
}

Queue-based generation

Generate in the background when a PDF is not needed immediately, or when you create many at once such as month-end invoices. A queued job keeps web requests fast, retries failures with backoff, and lets you control how many jobs run in parallel with your worker count.

GenerateInvoicePdf.php
<?php

namespace App\Jobs;

use App\Models\Invoice;
use App\Services\CheapPdf;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Storage;

class GenerateInvoicePdf implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable;

    public int $tries = 3;
    public array $backoff = [30, 120];

    public function __construct(public int $invoiceId) {}

    public function handle(CheapPdf $pdf): void
    {
        $invoice = Invoice::findOrFail($this->invoiceId);
        $html = view('invoices.pdf', ['invoice' => $invoice])->render();

        $bytes = $pdf->fromHtml($html, "invoice-{$invoice->number}.pdf", ['width' => 800, 'height' => 1100]);

        Storage::disk('local')->put("invoices/{$invoice->number}.pdf", $bytes);
        $invoice->update(['pdf_path' => "invoices/{$invoice->number}.pdf"]);
    }
}

// Dispatch for one invoice, or a whole batch:
// GenerateInvoicePdf::dispatch($invoice->id);
// Invoice::whereNull('pdf_path')->each(fn ($i) => GenerateInvoicePdf::dispatch($i->id));

Plan limits. The free plan allows 10 conversions per day, so a large batch needs the Premium plan. When the limit is reached the API returns free_plan_limit_reached; consider failing that job permanently instead of retrying.

Production considerations

Authorize before rendering

Check the user may see the document before generating it, as the controller does with authorize().

Make absolute asset URLs

Use asset() or full URLs for images and CSS in the PDF view, because the renderer loads them over the public internet.

Store, don&rsquo;t regenerate

Save finished PDFs to a disk and serve them, instead of converting on every download.

Fake it in tests

Use Http::fake() to return a canned success response, so tests never call the API.

Laravel FAQ

Is there an official CheapPDF Laravel package?

No. Laravel's built-in Http client is enough, as shown on this page.

How do I generate a PDF from a Blade view?

Render the view with view('name', )->render() to get an HTML string, then send it as the fileorhtmlorurl field and decode data.base64_pdf from the response.

Should I generate PDFs in a queue?

For bulk or slow work, yes. A queued job keeps requests fast and gives you retries with backoff. For a single document the user is waiting for, generating inline is fine.

Why use config() instead of env()?

After php artisan config:cache, env() returns null outside config files, so read the key through config('services.cheappdf.key').

Ship PDF generation in your Laravel app

Create an API key, add the service class and generate your first PDF.

Subscribe to Our Newsletter for Fast HTML to PDF Updates

Stay updated on new features for our fast, simple, and affordable HTML to PDF API.