Skip to content

Quickstart — first edge render

On this page, you turn one Hypertext Markup Language (HTML) string into a Portable Document Format (PDF) file. Each call maps to a method, and the behavior of each method is verified in tests/Unit/Cloudflare/CloudflareHtmlRendererTest.php.

  • A Worker endpoint that serves the render contract over Hypertext Transfer Protocol Secure (HTTPS).
  • A bearer token accepted by the Worker.
  • A PHP Standards Recommendation 18 (PSR-18) client and PHP Standards Recommendation 17 (PSR-17) factories on the path (see /integrations/cloudflare/install/).

The Worker contract, as observed from CloudflareResponseParser: on success, it returns Hypertext Transfer Protocol (HTTP) 200. The response carries either Content-Type: application/pdf (raw PDF bytes) or Content-Type: application/json with a base64 pdf field. Any non-200 status, or any body that does not start with the %PDF marker, is treated as a failure.

<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Cloudflare\CloudflareRendererConfig;
$config = new CloudflareRendererConfig(
workerUrl: 'https://pdf-renderer.example.workers.dev/render',
apiToken: getenv('CF_PDF_TOKEN') ?: throw new RuntimeException('CF_PDF_TOKEN not set'),
);

The token comes from the environment and is never hard-coded. workerUrl must use HTTPS. If you pass an http:// URL, the bridge rejects it with Worker URL must use HTTPS before it sends any request.

use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use NextPDF\Cloudflare\CloudflareHtmlRenderer;
$httpFactory = new HttpFactory();
$renderer = new CloudflareHtmlRenderer(
config: $config,
httpClient: new Client(), // PSR-18 ClientInterface
requestFactory: $httpFactory, // PSR-17 RequestFactoryInterface
streamFactory: $httpFactory, // PSR-17 StreamFactoryInterface
logger: null, // optional PSR-3 LoggerInterface
responseFactory: $httpFactory, // PSR-17; enables the pinned transport
);

The constructor requires four arguments: the config, the PSR-18 client, the request factory, and the stream factory. Four more arguments are optional: the logger, the local-renderer factory, an explicit HTML security policy, and the response factory. Supplying the response factory enables the pinned client URL (cURL) transport if a resolved Internet Protocol (IP) set or a Subject Public Key Info (SPKI) pin set is present (see /integrations/cloudflare/security-and-operations/).

use NextPDF\Cloudflare\Exception\CloudflareNotAvailableException;
use NextPDF\Cloudflare\Exception\CloudflareRenderException;
try {
$result = $renderer->render('<h1>Hello from the edge</h1>');
if (!$result->isValid()) {
throw new RuntimeException('Worker did not return a valid PDF');
}
file_put_contents('output.pdf', $result->pdfData);
printf("Wrote %d bytes from edge %s in %.1f ms\n",
$result->size(),
$result->renderLocation !== '' ? $result->renderLocation : 'unknown',
$result->renderTimeMs,
);
} catch (CloudflareRenderException $e) {
// Worker answered but the render failed (HTTP error or malformed body).
fwrite(STDERR, 'Render failed: ' . $e->getMessage() . PHP_EOL);
exit(1);
} catch (CloudflareNotAvailableException $e) {
// Worker unreachable and no usable fallback.
fwrite(STDERR, 'Edge unavailable: ' . $e->getMessage() . PHP_EOL);
exit(2);
}

render() defaults to A4 width (595.28 PDF points) and auto-detected height (heightPt: 0). The two exception types are intentionally separate. CloudflareRenderException is a Worker-side failure, and it is not retried with a fallback. CloudflareNotAvailableException means the edge could not be reached and no local fallback was available.

CloudflareRenderResult is final readonly. The fields below are filled from response headers on the binary path, or from JavaScript Object Notation (JSON) fields on the JSON path.

MemberSource
pdfDataRaw PDF bytes
widthPtThe width you requested
heightPtX-Pdf-Height-Pt header / JSON heightPt; defaults to 841.89 (A4 height) when absent or non-positive
contentHeightPxX-Content-Height-Px / JSON contentHeightPx
renderLocationDerived from the CF-Ray header suffix (binary path) or JSON renderLocation
renderTimeMsX-Render-Time-Ms / JSON renderTimeMs
size()strlen($pdfData)
isValid()true when the bytes start with %PDF

Optional — custom paper size, fonts, CSS

Section titled “Optional — custom paper size, fonts, CSS”
$result = $renderer->render(
html: '<div style="font-family: NotoSansCJK;">繁體中文文件</div>',
widthPt: 595.28,
heightPt: 841.89, // explicit A4; 0 lets the Worker auto-detect
fontFiles: ['NotoSansCJKtc-Regular.ttf'],
);

fontFiles is meaningful only when the config sets r2FontBucket. The payload then carries the bucket name and the requested font file paths so the Worker can load them.

if ($renderer->isAvailable()) {
$result = $renderer->render($html);
}

isAvailable() sends an authenticated HTTP HEAD to the Worker URL. It returns true when the status is below 500. It returns false without throwing when the config is invalid or the request fails. Treat it as a hint, not a guarantee. The Worker can still fail the subsequent POST.

  • /integrations/cloudflare/production-usage/ — fallback wiring, telemetry, and R2 archival.
  • /integrations/cloudflare/troubleshooting/ — every failure mapped to its exception and message.
  • /integrations/cloudflare/configuration/ — full field reference.