Generate PDFs in PHP

This guide shows how to use PDFCrowd's HTML to PDF API in a plain PHP application. You can add a PDF download to an existing page or generate a PDF from a template for storage or email. The examples use the PHP client library and do not require a framework.

Both examples use invoices to illustrate a pattern that also works for reports and other documents built from application data.

Set up the PHP client

Install the PHP client with Composer:

composer require pdfcrowd/pdfcrowd

The examples use working demo API credentials, so you can try them without setting up an account.

Add a PDF download to an existing page

An existing PHP page can display its usual HTML and return a PDF when the user clicks a download button. Reuse the page's template, data preparation, and access checks.

Choose what to send to PDFCrowd:

  • Rendered HTML uses content PHP has already prepared, such as an invoice for the logged-in user.
  • The page URL loads the page through its normal address.

To include changes made in the browser, such as filled-in form fields, consider WebSave as PDF in content mode.

Convert rendered HTML

This example extends an invoice page that selects the invoice using an id query parameter. getInvoiceForCurrentUser($id) represents your application's input validation, invoice lookup, and permission checks; substitute your own logic here. The included template reads the $invoice array.

<?php
require __DIR__ . '/vendor/autoload.php';

$invoiceId = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
$invoice = getInvoiceForCurrentUser($invoiceId);

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['download_pdf'])) {
    $pdfBaseUrl = defined('PDF_BASE_URL') ? PDF_BASE_URL : null;
    ob_start();
    try {
        require __DIR__ . '/templates/invoice.php';
        $html = ob_get_contents();
    } finally {
        ob_end_clean();
    }

    try {
        $client = new \Pdfcrowd\HtmlToPdfClient('demo', 'demo');
        $client->setContentViewportWidth('balanced');
        $pdf = $client->convertString($html);
    } catch (\Pdfcrowd\Error $error) {
        // Add your application's error handling here.
        throw $error;
    }

    header('Content-Type: application/pdf');
    header('Content-Disposition: attachment; filename="invoice-' . $invoiceId . '.pdf"');
    echo $pdf;
    exit;
}

require __DIR__ . '/templates/invoice.php';

Add this form to the template, outside any existing form. Clicking its download button sends a POST request to the current page. The pdfcrowd-remove class keeps the button visible on the webpage but excludes it from the PDF.

<form method="post">
    <button type="submit" name="download_pdf" value="1"
            class="pdfcrowd-remove">Download PDF</button>
</form>

GET requests continue to display the HTML page. A POST containing download_pdf captures the same template's output as an HTML string, converts it, and returns a PDF download. The $pdfBaseUrl value supports the CSS and image setup below. The finally block closes the output buffer even if rendering fails.

To display the PDF in the browser, change attachment to inline. Customize page size, margins, or other PDF options on the client before convertString().

The catch block provides a place for your application's error handling. As written, throw $error propagates the original exception. See client error handling for details.

Output sent before header() can prevent the PDF headers from being set; output mixed with $pdf can corrupt the download.

CSRF handling is omitted here. Apply your application's CSRF protection to the form and POST handler before generating the PDF. Restricting requests to POST alone does not prevent CSRF.

Make CSS and images available

When you send rendered HTML to PDFCrowd, relative paths to CSS and images need a base URL so PDFCrowd can locate the files and include them in the PDF.

Check the asset URLs in the rendered HTML. If all asset URLs are complete, such as https://cdn.example.com/styles.css, or the template already provides a correctly configured <base> element, no additional setup is needed.

For relative paths, define the site or directory URL in your project configuration:

define('PDF_BASE_URL', 'https://www.example.com/');

Then add this to the template's <head>, before stylesheet links:

<?php if (!empty($pdfBaseUrl)): ?>
<base href="<?= htmlspecialchars($pdfBaseUrl, ENT_QUOTES, 'UTF-8') ?>">
<?php endif; ?>

The page handler makes this setting available to the template as $pdfBaseUrl when generating a PDF. The resulting <base> applies to relative asset URLs and hyperlinks.

You can also bundle the rendered HTML with local CSS, images, or fonts in a ZIP archive and pass its path to convertFile(). Keep paths consistent with the archive's folders and omit the website <base> so those paths resolve within the archive.

Convert the page URL instead

With URL conversion, PDFCrowd opens the page at the supplied address.

Keep the POST check, client initialization, error handling, and PDF response from the page handler above. Replace its HTML rendering and convertString() call with convertUrl():

$pageUrl = 'https://www.example.com' . $_SERVER['REQUEST_URI'];
$pdf = $client->convertUrl($pageUrl);

Use your site's scheme and host, without a trailing slash, in place of https://www.example.com. REQUEST_URI supplies the current path and query parameters, including the invoice ID. PDFCrowd makes a separate GET request, so the handler runs its normal access checks and returns HTML. Only the original POST triggers PDF generation.

The server must be able to handle this GET while the original POST waits for conversion. PHP's built-in development server handles one request at a time by default, so this approach can time out there.

PDFCrowd must be able to reach the URL. A localhost or 127.0.0.1 URL cannot be used to reach your development machine from PDFCrowd's servers.

PDFCrowd's request does not inherit the user's PHP session. For protected pages, configure cookies or HTTP authentication as appropriate, or use the rendered-HTML approach above.

When forwarding the user's PHP session cookie, call session_write_close() before convertUrl() so the separate GET can access the session without waiting for the original request to release its lock.

Generate a PDF from a template

For scheduled reports or invoice emails, you can generate a PDF directly from application data without a browser request. Use an existing PHP template or a separate template designed for the document.

The function below renders a template with the supplied data and returns the PDF as bytes. It uses the same asset configuration as the page example:

<?php
require_once __DIR__ . '/vendor/autoload.php';

function renderPdf(string $templatePath, array $context): string
{
    $pdfBaseUrl = defined('PDF_BASE_URL') ? PDF_BASE_URL : null;
    extract($context, EXTR_SKIP);
    ob_start();
    try {
        require $templatePath;
        $html = ob_get_contents();
    } finally {
        ob_end_clean();
    }

    $client = new \Pdfcrowd\HtmlToPdfClient('demo', 'demo');
    $client->setContentViewportWidth('balanced');
    return $client->convertString($html);
}

Pass all data the template needs in $context. Its keys become template variables, so ['invoice' => $invoice] makes $invoice available to the included template. EXTR_SKIP keeps those keys from replacing the function's existing variables, such as $templatePath. Escape values inserted as HTML text or attributes with htmlspecialchars().

The function uses PHP's output buffering to capture the HTML without sending it to the browser. Conversion errors propagate to the calling script, which can use the same try/catch pattern as the page handler.

Save or email the PDF

For example, given an $invoice array, render it with templates/invoice.php and save the PDF:

$pdf = renderPdf(__DIR__ . '/templates/invoice.php', ['invoice' => $invoice]);
$destination = __DIR__ . '/invoice.pdf';
if (file_put_contents($destination, $pdf) !== strlen($pdf)) {
    throw new \RuntimeException('Could not save the PDF.');
}

file_put_contents() replaces an existing file at that path. For email delivery, pass the same PDF bytes to your mail library. For example, an existing PHPMailer message can attach them directly:

$mail->addStringAttachment($pdf, 'invoice.pdf', 'base64', 'application/pdf');

Generate a batch of invoices

In a command-line script or background job, loop over the $invoices selected by your application. Each record has a numeric id; the invoices output directory must exist and be writable:

foreach ($invoices as $invoice) {
    $pdf = renderPdf(__DIR__ . '/templates/invoice.php', ['invoice' => $invoice]);
    $destination = __DIR__ . '/invoices/' . (int) $invoice['id'] . '.pdf';
    if (file_put_contents($destination, $pdf) !== strlen($pdf)) {
        throw new \RuntimeException('Could not save the PDF.');
    }
}

The same function can generate a single PDF when an invoice is issued.

WebSave alternative

For a download button without PHP conversion code, see WebSave as PDF.