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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Generating a PDF and sharing it are two separate operations. Your PHP code first renders document bytes with a PDF library such as Dompdf. It then stores those bytes in private object storage and creates a time-limited signed download URL. The browser response from $dompdf->stream() is only an immediate download; it is not a persistent share link.

This guide uses Dompdf and Google Cloud Storage for a complete implementation, then explains the equivalent S3 approach, layout and security limits, troubleshooting, and an alternative when your real input is a web page rather than application data.

What the PHP workflow looks like

  1. Build trusted HTML from your application data.
  2. Render the HTML to PDF bytes.
  3. Upload the bytes to a private storage object.
  4. Create a signed GET URL with a deliberate expiration.
  5. Return the URL to your application, email it, or show it to the recipient.

Keeping rendering and distribution separate lets you replace Dompdf or change cloud providers without redesigning the rest of the application.

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

Prerequisites and version checks

  • PHP with Composer and the extensions required by your chosen renderer and cloud SDK.
  • A Google Cloud Storage bucket (or an existing S3-compatible storage setup).
  • Cloud credentials configured outside user-submitted form fields.
  • A private object path for each generated document.

The Dompdf project’s current 3.0.x line, including release 3.0.2 checked on September 29, 2026, requires PHP 7.1 or newer, MBString, GD for image processing, and its listed Composer dependencies. Verify the release and runtime requirements when deploying because they can change.

Install Dompdf and the Google Cloud client

From your project directory, install both packages:

composer require dompdf/dompdf google/cloud-storage

Load Composer’s autoloader in every entry point that renders or uploads documents:

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

Render HTML to PDF bytes in PHP

Dompdf converts HTML to PDF but is not a complete browser engine. It supports a substantial subset of CSS; its documented limitations include no CSS Grid or flexbox support, and table rows must fit on a page. Design print-oriented templates and test them with the actual data and images you will use.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<?php
require __DIR__ . '/vendor/autoload.php';

use DompdfDompdf;
use DompdfOptions;

$options = new Options();
// Enable this only when you have validated the HTML and restricted its resources.
$options->set('isRemoteEnabled', false);

$dompdf = new Dompdf($options);

$customerName = htmlspecialchars($customerName ?? 'Example customer', ENT_QUOTES, 'UTF-8');
$invoiceNumber = htmlspecialchars($invoiceNumber ?? 'INV-1001', ENT_QUOTES, 'UTF-8');

$html = '<!doctype html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    @page { margin: 28px; }
    body { font-family: DejaVu Sans, sans-serif; font-size: 12px; color: #222; }
    h1 { font-size: 22px; margin: 0 0 16px; }
    table { width: 100%; border-collapse: collapse; }
    th, td { border: 1px solid #ccc; padding: 8px; text-align: left; }
  </style>
</head>
<body>
  <h1>Invoice ' . $invoiceNumber . '</h1>
  <p>Prepared for ' . $customerName . '</p>
  <table>
    <tr><th>Description</th><th>Amount</th></tr>
    <tr><td>Example service</td><td>$100.00</td></tr>
  </table>
</body>
</html>';

$dompdf->loadHtml($html);
$dompdf->setPaper('A4', 'portrait');
$dompdf->render();
$pdfBytes = $dompdf->output();

// For an immediate browser download only:
// $dompdf->stream('invoice.pdf', ['Attachment' => true]);

output() is the important method for sharing: it gives your application the bytes to upload. Use stream() when you only need to send a one-time response to the current browser.

Remote images, local files, and untrusted HTML

Dompdf requires isRemoteEnabled plus cURL or allow_url_fopen to fetch remote resources. Local files must be inside configured chroot paths. Do not enable remote loading merely to hide a broken template: allow only the domains and files your application needs.

Embedded PHP in documents from users is a security risk. Keep it disabled for untrusted input, escape data before inserting it into HTML, and use a controlled template rather than accepting arbitrary markup.

Upload the PDF to private Google Cloud Storage

The following function writes the rendered bytes to a private object. The signing identity must have permission to access the bucket and sign URLs according to your Google Cloud configuration.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<?php
use GoogleCloudStorageStorageClient;

$storage = new StorageClient(); // Uses your configured application credentials.
$bucketName = getenv('GCS_BUCKET');
$objectName = 'pdfs/' . bin2hex(random_bytes(16)) . '.pdf';

$bucket = $storage->bucket($bucketName);
$object = $bucket->upload($pdfBytes, [
    'name' => $objectName,
    'metadata' => [
        'contentType' => 'application/pdf',
        'contentDisposition' => 'inline; filename="document.pdf"',
    ],
]);

Use a generated, unguessable object name rather than a customer name or sequential invoice number. Keep the object private; the signed URL is the controlled access mechanism.

Create a signed PDF download URL

Google’s PHP helper creates a V4 signed GET URL by selecting a bucket and object and passing an expiration time. This example grants access for 15 minutes:

$expiresAt = new DateTimeImmutable('+15 minutes');

$signedUrl = $object->signedUrl($expiresAt, [
    'version' => 'v4',
    'method' => 'GET',
]);

echo json_encode([
    'download_url' => $signedUrl,
    'expires_at' => $expiresAt->format(DateTimeInterface::ATOM),
], JSON_THROW_ON_ERROR);

A signed URL is a bearer link: anyone who possesses it can perform the permitted action on the specified object until it expires. Google documents a maximum signed-URL duration of 604800 seconds (seven days). Choose a shorter period for invoices, password-reset attachments, or one-time sharing. A URL is not user authentication and should not be treated as a permanent public address.

Signed upload URLs

If a separate worker or browser must upload the PDF, Google’s PHP client also exposes signedUploadUrl(). Scope the URL to one object, require HTTPS, and give it the shortest practical lifetime. A resumable-upload session URI is itself an authentication token, so transmit it only over HTTPS.

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

Complete endpoint example

This compact endpoint combines rendering, storage, and signing. In production, obtain the invoice data from your database, authorize the requesting user, and move long-running rendering to a queue if necessary.

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

use DompdfDompdf;
use DompdfOptions;
use GoogleCloudStorageStorageClient;

header('Content-Type: application/json');

try {
    $options = new Options();
    $options->set('isRemoteEnabled', false);
    $dompdf = new Dompdf($options);
    $dompdf->loadHtml('<h1>Shareable report</h1><p>Generated by PHP.</p>');
    $dompdf->setPaper('A4', 'portrait');
    $dompdf->render();
    $bytes = $dompdf->output();

    $storage = new StorageClient();
    $bucket = $storage->bucket(getenv('GCS_BUCKET'));
    $objectName = 'pdfs/' . bin2hex(random_bytes(16)) . '.pdf';
    $object = $bucket->upload($bytes, [
        'name' => $objectName,
        'metadata' => ['contentType' => 'application/pdf'],
    ]);

    $expiresAt = new DateTimeImmutable('+15 minutes');
    $url = $object->signedUrl($expiresAt, [
        'version' => 'v4',
        'method' => 'GET',
    ]);

    echo json_encode(['url' => $url, 'expires_at' => $expiresAt->format(DateTimeInterface::ATOM)], JSON_THROW_ON_ERROR);
} catch (Throwable $e) {
    http_response_code(500);
    echo json_encode(['error' => 'PDF generation failed']);
    error_log($e->getMessage());
}

Using Amazon S3 instead

S3 presigned URLs provide the same conceptual pattern: upload a specific private object, then ask the AWS SDK to presign a GET (or PUT) request for a limited period. Keep the renderer independent from this code and use the SDK already authorized in your application. Do not combine Google and AWS signing calls in one implementation; provider-specific credentials, permissions, and expiry rules differ.

Security and lifecycle decisions

  • Bearer access: do not put signed URLs in unnecessary logs, analytics parameters, or public repositories.
  • Revocation: expiry is the normal control. To stop access sooner, delete or replace the object, disable the signing credentials, or issue access through an application-controlled download endpoint.
  • HTTPS: send URLs only over HTTPS.
  • Retention: attach a lifecycle rule to delete old PDFs so temporary shares do not become permanent archives.
  • Authorization: check that the current user may generate or retrieve the document before signing anything.
  • Content type: set application/pdf and choose inline or attachment behavior deliberately.

Performance and reliability

Rendering consumes CPU and memory, especially for long tables or large images. Resize images before embedding them, avoid enormous base64 data URLs, and queue large jobs instead of holding an HTTP request open indefinitely. Record a document ID, object name, render status, and expiry timestamp, but avoid recording the complete bearer URL.

Test page breaks with the largest realistic dataset. Dompdf’s non-pageable table rows can create unexpected blank space or overflow; split very large tables into logical sections. If your design fundamentally depends on flexbox, grid, JavaScript, or browser-specific layout, validate a different renderer against the real template before committing.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Troubleshooting common failures

“Class DompdfDompdf not found”

Run Composer in the deployed project and require vendor/autoload.php from the correct path. Confirm that the deployment includes the vendor directory or runs composer install.

Images or CSS are missing

Use absolute, permitted paths. For remote assets, enable remote loading only after configuring cURL or allow_url_fopen; for local assets, place them under the configured chroot. Check TLS certificates and response content types.

Layout differs from the browser

This is expected when the template uses unsupported CSS. Replace grid or flexbox with tables or block layout, define print CSS and page margins, and test long content for page breaks.

Google signing fails with a permission error

Verify the bucket and object name, the active credentials, the signing identity’s permissions, and the SDK configuration. The identity must be allowed to perform the operation represented by the URL.

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

The link returns “expired”

Generate a new URL using a future expiration time and check server clock accuracy. Do not exceed Google’s documented seven-day maximum.

The PDF downloads but has the wrong filename

Set object metadata such as contentDisposition, or serve the object through an application response that sets a controlled Content-Disposition header.

Or skip the browser setup

If your starting point is an existing web page rather than PHP-generated HTML, ScreenshotNeo can return a screenshot or PDF through one request. Its cleanup step accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. It also offers an MCP server with take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.

For a PDF or image capture, call the API as documented at https://screenshotneo.com/docs/:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Which approach should you choose?

Requirement Best fit Reason
Invoices, reports, or application data Dompdf plus private object storage You control the HTML, retention, permissions, and signed-link lifetime.
Existing page with browser-style rendering ScreenshotNeo It captures a URL without you maintaining a browser automation stack.
Complex CSS or JavaScript-heavy templates Evaluate a browser-based renderer Dompdf does not implement all browser layout features.
Existing Google Cloud application Google signed URLs Use the provider and credentials already operated by the application.
Existing AWS application S3 presigned URLs Keep storage and signing inside the infrastructure you already manage.

Frequently Asked Questions

Can I make the PDF URL permanent?

A signed URL is intentionally temporary. For permanent access, keep the object private and issue a fresh signed URL from an authenticated application endpoint.

Does generating a PDF automatically upload it somewhere?

No. Dompdf creates bytes in PHP memory. You must explicitly upload those bytes to object storage before creating a share link.

What is Google Cloud’s maximum signed-URL lifetime?

Google documents a maximum expiration of 604800 seconds, or seven days, for its signed URLs.

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

Can Dompdf render any HTML page?

No. It implements a subset of CSS and is not a full browser engine; CSS Grid and flexbox are documented limitations.

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.