Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

PHP cURL does not render a web page into a PDF by itself. It sends a URL or HTML document to a rendering engine, receives PDF bytes, and then saves or streams those bytes. For a reliable full-page result, choose a renderer (a hosted HTML-to-PDF API, local wkhtmltopdf, or headless Chrome), wait for the page’s assets and JavaScript, set print options explicitly, and validate the binary response before writing it to disk.

Choose the rendering path first

Your choice affects CSS and JavaScript fidelity, access to private pages, deployment work, and recurring cost.

Approach How PHP is involved Best fit Main trade-off
Hosted HTML-to-PDF API PHP cURL posts a URL or HTML and receives a PDF Production apps that prefer managed browsers Authentication, network dependency and provider fees
Local wkhtmltopdf PHP starts the installed command-line process Self-hosted, predictable command-line workflows Older Qt WebKit rendering and process packaging
Headless Chromium PHP invokes Chrome directly or uses a PHP client/managed service Modern CSS and JavaScript-heavy pages Browser installation, sandbox and lifecycle management

There is no independently comparable speed or fidelity benchmark across these options in the published documentation, so test your own pages, fonts, charts and long tables.

Before you write code

  • Public versus private input: a renderer must be able to reach every URL, image, font and script. For private content, send generated HTML or use documented authentication headers, cookies or a renderer that can log in.
  • Absolute assets: relative paths such as /css/app.css need a correct page origin or a provider baseUrl. Cross-origin resources must also be reachable from the renderer.
  • Readiness: a load event can occur before client-rendered content, lazy images or web fonts are ready. Use a bounded delay or a documented networkidle0/networkidle2 condition.
  • Print styling: define paper size, orientation, margins and background printing. Add @page, page-break rules and print-color-adjust where your design requires them.
  • Output handling: a PDF is binary. Never concatenate debug text, PHP notices or an HTML error page into the file.

Method 1: request a PDF from a hosted API with PHP cURL

HTML PDF API documents a POST request to https://htmlpdfapi.com/api/v1/pdf. Supply exactly one input: url, file or html. The example below sends a public URL, enables backgrounds and sets a 1280×900 viewport.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Complete PHP example

<?php
$token = getenv('HTMLPDFAPI_TOKEN');
$pageUrl = 'https://example.com/report';
$output = __DIR__ . '/report.pdf';

$ch = curl_init('https://htmlpdfapi.com/api/v1/pdf');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        'Authentication: Token ' . $token,
        'Content-Type: application/x-www-form-urlencoded',
    ],
    CURLOPT_POSTFIELDS => http_build_query([
        'url' => $pageUrl,
        'background' => 'true',
        'viewport_size' => '1280x900',
    ]),
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_TIMEOUT => 60,
]);

$pdf = curl_exec($ch);
$curlError = curl_error($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
curl_close($ch);

if ($pdf === false) {
    throw new RuntimeException('Transport error: ' . $curlError);
}
if ($status >= 400) {
    throw new RuntimeException("PDF service returned HTTP $status");
}
if (stripos((string) $contentType, 'application/pdf') === false || strlen($pdf) === 0) {
    throw new RuntimeException('Response was not a non-empty PDF');
}

if (file_put_contents($output, $pdf) === false) {
    throw new RuntimeException('Could not write ' . $output);
}
echo "Saved $outputn";

The vendor’s documented cURL pattern also posts a URL and redirects the binary response to result.pdf. Keep the authentication header and field names exactly as specified by the provider’s current documentation; do not assume another API accepts the same names.

Posting generated HTML instead

For a private page or a report assembled in PHP, replace url with the provider’s documented html field (or upload a file). Inline critical CSS or provide a valid base URL for relative assets. Use the service’s controls for links, viewport, print media, headers, footers, spacing and page numbering when you need them.

Streaming the result to a browser

<?php
// After the same validation shown above:
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="report.pdf"');
header('Content-Length: ' . strlen($pdf));
echo $pdf;
exit;

Send these headers before any output. For a forced download, change inline to attachment.

Method 2: run wkhtmltopdf locally

wkhtmltopdf is an open-source (LGPLv3) command-line tool that renders HTML to PDF with Qt WebKit and runs headlessly without a display service. Its basic form is wkhtmltopdf http://google.com google.pdf.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Invoke it safely from PHP

<?php
$url = 'https://example.com/report';
$output = __DIR__ . '/report.pdf';
$binary = '/usr/local/bin/wkhtmltopdf'; // Pin the path you installed and verified.

$command = sprintf(
    '%s %s %s',
    escapeshellarg($binary),
    escapeshellarg($url),
    escapeshellarg($output)
);
$descriptor = [
    0 => ['pipe', 'r'],
    1 => ['pipe', 'w'],
    2 => ['pipe', 'w'],
];
$process = proc_open($command, $descriptor, $pipes);
if (!is_resource($process)) {
    throw new RuntimeException('Could not start wkhtmltopdf');
}
fclose($pipes[0]);
$stdout = stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);
$exitCode = proc_close($process);

if ($exitCode !== 0 || !is_file($output) || filesize($output) === 0) {
    throw new RuntimeException("wkhtmltopdf failed ($exitCode): $stderr");
}
echo "Created $outputn";

Use an absolute binary path, capture standard error, and inspect the exit code. A maintained PHP wrapper can expose page size, orientation, margins, headers and footers; configure its binary option explicitly. Some wrapper features require an X server, which is unavailable on many headless servers, so verify your deployment rather than assuming every option works.

Typical print options

Add the renderer’s documented switches for paper size, landscape orientation, margins, JavaScript delay and background graphics. Keep untrusted URLs and shell arguments escaped; never interpolate user input into an unescaped command.

Method 3: print with headless Chrome

Chrome’s command line can print a URL directly:

chrome --headless --print-to-pdf=report.pdf --no-pdf-header-footer --timeout=30000 https://developer.chrome.com/

--no-pdf-header-footer removes browser-generated date, URL and page-number decorations. Increase --timeout for pages that need more time to render, but keep a finite limit.

Using a PHP Chrome client

The chrome-php library supports navigation, waitForNavigation(), setHtml(), PDF options, and saveToFile()/saveToStream(). Its PDF settings include printBackground, paper dimensions, margins, scale and header/footer templates. A managed PHP client such as ChromeHeadless.io accepts html or url, readiness values including load, domcontentloaded, networkidle0 and networkidle2, plus format, orientation, margins, page ranges, backgrounds and templates. Follow the selected library’s installation and current method signatures; those differ between releases.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Make a capture genuinely full-page

  1. Wait for content: use networkidle0 or networkidle2 where supported, or a bounded delay. Also wait for a known selector when your application exposes one.
  2. Load lazy content: scroll or use the renderer’s full-page/lazy-image option so below-the-fold images are requested before printing.
  3. Enable backgrounds: set the API or browser option that prints CSS backgrounds and images.
  4. Set geometry: choose paper format, portrait or landscape, margins and scale. Wide dashboards often need landscape or a smaller scale.
  5. Control pagination: use @page, break-before, break-after and break-inside. Avoid placing a sticky header or fixed overlay over every page unless that is intentional.
  6. Check fonts and assets: wait for web fonts, use stable absolute URLs, and ensure the renderer can access authenticated resources.
  7. Inspect the result: verify HTTP status, content type and nonzero length, then open the PDF and check the final page, tables, links and images.

Example print CSS

@page { size: A4; margin: 14mm; }
@media print {
  .screen-only { display: none !important; }
  thead { display: table-header-group; }
  tr, img, figure { break-inside: avoid; }
  * { print-color-adjust: exact; -webkit-print-color-adjust: exact; }
}

Or skip the browser setup

ScreenshotNeo is a hosted website screenshot API and MCP server. It can return a PDF from one GET request while handling browser setup for you:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

For a PDF response, request the PDF output according to the ScreenshotNeo API documentation. The service can load lazy images, wait for a selector, delay or network idle, set viewport and device options, run custom JavaScript, click elements, hide selectors, supply cookies or headers, set timezone and geolocation, and capture full pages. It also supports PDF paper size, margins, landscape mode and page ranges.

  • Cookie and consent banners, newsletter popups and chat widgets are removed before the shot; each cleanup step can be disabled.
  • Bot checks/CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing status.
  • An MCP server provides take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients.
  • The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Every feature is available on every plan.

Create a free ScreenshotNeo account to try it without a card.

Troubleshooting

The file is HTML, empty or unreadable

Log the HTTP status and Content-Type. A 401/403 usually means an invalid token or inaccessible page; a 200 response with text/html may be an API error page. Do not save until the response is a non-empty PDF.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Styles, images or fonts are missing

Use absolute asset URLs, confirm DNS/TLS access from the renderer, provide cookies or authorization where documented, and wait for fonts and network activity. For generated HTML, set the correct base URL.

JavaScript content is absent

Capture after a selector appears or after networkidle0/networkidle2; a plain load event can be too early. Increase a bounded timeout only after confirming the page eventually settles.

Background colors disappear

Enable background printing in the API, Chrome client or wkhtmltopdf options, and include print-color-adjust: exact in print CSS.

Pages are clipped or have large blank areas

Set paper size, orientation, margins and scale explicitly. Check for fixed-width containers, oversized images and CSS that only targets screen media.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

wkhtmltopdf fails on a server

Verify the absolute binary path and executable permissions, inspect stderr and exit code, and check whether the selected wrapper option requires an X server. Move to headless Chrome or a managed renderer if the binary cannot run in your environment.

The PHP request times out

Use a realistic cURL timeout, reduce unnecessary page work, and use an asynchronous job/webhook workflow when the provider offers one. Do not remove timeouts entirely; a permanently loading page should fail predictably.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance, reliability and cost decisions

  • Local engines: avoid per-request vendor charges, but you own browser binaries, fonts, sandboxing, upgrades, concurrency and crash recovery.
  • Managed APIs: reduce operations work and commonly expose readiness, authentication and PDF controls, but add network latency, service authentication and recurring usage cost.
  • Caching: cache only when the page can be stale; use a TTL and invalidate it when source data changes. A cached response must still be validated before serving.
  • Concurrency: limit simultaneous browser processes, set memory/time budgets and queue large jobs. Record renderer version, URL, options, status and output size for diagnosis.
  • Security: restrict outbound destinations if users can submit URLs, protect API tokens in environment variables, and avoid logging sensitive HTML, cookies or authorization headers.

FAQ

Can PHP cURL convert HTML to PDF without another program?

No. cURL transports bytes; a PDF renderer must interpret HTML, CSS and JavaScript.

Should I send a URL or HTML?

Send a URL for a public page that the renderer can access. Send HTML when content is private or generated dynamically, while supplying a base URL or absolute assets.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How do I prevent browser-added headers and footers?

With headless Chrome, use --no-pdf-header-footer; with an API or library, disable its header/footer templates and configure margins.

Is wkhtmltopdf equivalent to Chrome?

No. wkhtmltopdf uses Qt WebKit, while Chrome uses a modern Chromium engine. CSS and JavaScript behavior can differ, so test the exact documents you publish.

Frequently Asked Questions

Can PHP cURL convert HTML to PDF without another program?

No. cURL transports bytes; a PDF renderer must interpret HTML, CSS and JavaScript.

Should I send a URL or HTML?

Send a URL for a public page that the renderer can access. Send HTML when content is private or generated dynamically, while supplying a base URL or absolute assets.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How do I prevent browser-added headers and footers?

With headless Chrome, use –no-pdf-header-footer; with an API or library, disable its header/footer templates and configure margins.

Is wkhtmltopdf equivalent to Chrome?

No. wkhtmltopdf uses Qt WebKit, while Chrome uses a modern Chromium engine. CSS and JavaScript behavior can differ, so test the exact documents you publish.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.