netgen/htmlpdfapi
Netgen HTMLPDFAPI is a PHP client for the HTMLPDFAPI service that converts HTML to PDF. Send raw HTML or a URL, configure your API credentials, and generate PDFs for invoices, reports, and other documents from your application.
This package is a thin client around the HTMLPDFAPI.com REST service — it does not render PDFs itself, it serializes HTML to a remote endpoint and streams back a PDF binary. Because the repo is archived and the underlying SaaS has had availability issues, treat it as a legacy integration, not a greenfield choice.
Minimal steps:
composer require netgen/htmlpdfapi:"^1.0"); unpinned installs may fail because archived branches are not tagged against modern PHP. If you're on PHP 8.1+, expect to add symfony/polyfill or skip the bundle registration and instantiate the client class manually.html_pdf_api.api_key, fed from %env(HTMLPDF_API_KEY)%.services.yaml yourself).storage/app/pdf/invoice-{$id}.pdf.First place to look: the client class (HtmlPdfApiClient or similar) and its getPdf() method signature. Everything else in the package is glue — the actual behaviour is defined by the remote API's accepted parameters (html, css, js, filename, format, orientation).
Wrap the client behind your own interface. Since the package is archived and the SaaS is fragile, never inject HtmlPdfApiClient directly into controllers or jobs. Define PdfGeneratorInterface::generate(string $html, array $options): string and make the package one implementation. That way, when you inevitably migrate to dompdf, weasyprint, or gotenberg, you change one class.
Build HTML with Twig, not string concatenation. Render your invoice/report template with Twig (or Blade) first, pass the rendered string to the API. This keeps layout logic in templates and lets you test rendering without touching the network. Pass base CSS inline or as a second API parameter — the service cannot fetch relative asset URLs.
Prefer the CSS/JS-inlined payload. If the API accepts separate css and js arrays, use them; if you inline everything yourself, minify first. The remote renderer has a hard payload ceiling (typically a few hundred KB) — base64-encoding logos is usually where teams blow past it.
Queue PDF generation for anything user-facing. Generating a 5–15 second PDF synchronously inside a web request is the fastest route to 504s. Dispatch a GeneratePdf job, store the resulting bytes in S3/local disk, and notify the user via email or a polling UI. Store only the path, never the bytes, in the primary database row.
Cache aggressively by content hash. PDFs are deterministic for identical HTML + options. Hash the input and store pdf/{sha256}.pdf; skip the API call entirely on repeat requests. This alone cuts costs by an order of magnitude on transactional documents (terms, receipts) that don't change.
Test with a fake client. Unit tests should never hit HTMLPDFAPI. Substitute a FakePdfGenerator that returns a fixture string. For the boundary, do one integration test behind a @group integration tag so CI only runs it when HTMLPDF_API_KEY is present.
Log the request payload on failure. The HTMLPDFAPI service rarely returns useful error bodies. When a call fails, log the HTML (scrubbed of PII) and options to a rotating channel so you can reproduce locally.
Guzzle/HttpClient version it depends on, nobody will fix it. Vendor the two or three files you actually use, or reimplement — the surface area is tiny.@font-face (URLs must be absolute or fonts base64'd). Design PDFs with tables, floats, and explicit widths. Test every template against a real output — browser preview will lie to you.@font-face need absolute HTTPS URLs the service can fetch. Self-host in S3 or a CDN; Google Fonts URLs frequently time out.{{ asset() }} yields /images/logo.png — the remote service cannot resolve that. Use {% autoescape false %}{{ absolute_url(asset('images/logo.png')) }}{% endautoescape %} or base64-encode small logos.config('app.timezone'), or you'll ship invoices with the wrong date to overseas customers.return response($pdf)->header('Content-Type', 'application/pdf') and get garbage, check that an exception handler isn't prepending HTML to the response, and that the client isn't decoding JSON error bodies as PDF bytes..env / a secrets manager, never in config files committed to Git, and never be sent from the browser. Rotate it if it ever appears in logs.pdftoppm/weasyprint rendering to see what the service should produce. If local renderers also fail, the HTML is the problem; if only the service fails, it's a payload size, asset URL, or CSS-compat issue.How can I help you explore Laravel packages today?