How to Create a PDF from a URL in Laravel with Snappy
Install Laravel Snappy, configure wkhtmltopdf, and return a URL-rendered PDF safely—with inline, download, and file-saving examples.
Use Laravel Snappy’s PDF::loadFile($url) to ask wkhtmltopdf to fetch a webpage and render it as a PDF. Return it inline in the browser with inline(), prompt a download with download(), or save it to a path with Snappy’s generate() method. You must install the wkhtmltopdf executable separately and configure Laravel Snappy to find it.
1. Install Snappy and wkhtmltopdf
From your Laravel project, install the integration:
composer require barryvdh/laravel-snappy
Install wkhtmltopdf using a deployment method appropriate for your operating system, then verify that the executable runs on the application host. The Laravel package wraps KnpLabs Snappy, which calls this external binary; installing the Composer package alone does not provide a working renderer. See the Laravel Snappy README and KnpLabs Snappy README for package setup and binary examples.
Publish the package configuration when your application needs to customize it:
php artisan vendor:publish --provider="Barryvdh\\Snappy\\ServiceProvider"
Inspect config/snappy.php and set the PDF binary path to the wkhtmltopdf executable available in the deployed environment. The path may differ between a developer workstation, a container, and production. Check that the PHP process user can execute the binary, and deploy the same configuration alongside the binary installation.
2. Return a URL as a PDF
The facade examples below use the PDF alias shown by the package documentation. If the alias is not registered in your Laravel version or application setup, import or resolve the package’s PDF service according to its README.
Display the PDF inline
<?php
namespace App\\Http\\Controllers;
use Illuminate\\Http\\Request;
use PDF;
class PagePdfController
{
public function show(Request $request)
{
// Replace with a trusted, validated URL or an allowlisted destination.
$url = 'https://example.com';
return PDF::loadFile($url)->inline('page.pdf');
}
}
Register a route to reach the controller, for example:
use App\\Http\\Controllers\\PagePdfController;
use Illuminate\\Support\\Facades\\Route;
Route::get('/page.pdf', [PagePdfController::class, 'show']);
Prompt a download
return PDF::loadFile($url)->download('page.pdf');
The package also supports loading a Blade view for PDFs you generate from your own application data. That is different from loading a remote page URL; use loadFile() for a URL and the documented view-loading method when you want to render a Laravel view.
Save the result to a file
For jobs or workflows that need a stored artifact, resolve the lower-level Snappy PDF service and write to a controlled path:
$snappy = app('snappy.pdf');
$path = storage_path('app/page.pdf');
$snappy->generate('https://example.com', $path);
Ensure the destination directory exists and is writable by the application process. Avoid using a caller-supplied filename as a filesystem path.
3. Configure page size and rendering options
Snappy passes options to wkhtmltopdf. Set paper size, orientation, and margins through the wrapper as needed:
return PDF::loadFile($url)
->setPaper('a4')
->setOrientation('landscape')
->setOption('margin-bottom', 0)
->inline('page.pdf');
wkhtmltopdf documents A4 as its default paper size, portrait as its default orientation, and 10 mm defaults for left and right margins. Defaults and supported options depend on the binary version you deploy. Use the wkhtmltopdf usage manual as the reference for renderer options.
| Need | Configuration direction | Check |
|---|---|---|
| Different page format | Use a paper size such as a4, or a size supported by your wkhtmltopdf build. |
Check page breaks and scaling in the resulting document. |
| Landscape pages | Set orientation to landscape. |
Confirm wide tables and images fit within printable margins. |
| More room for content | Adjust top, bottom, left, and right margins with renderer options. | Check that headers, footers, and page content do not overlap. |
| Delayed JavaScript content | Consult the deployed wkhtmltopdf manual for JavaScript delay and load-error options. | Test against the actual page and binary; JavaScript-heavy sites may not render as expected. |
| Print styling | Review the manual’s print-media setting and the page’s print CSS. | Compare print output with the expected layout. |
| Headers and footers | Use the documented header and footer options for the deployed binary. | Check their spacing against page margins and page count. |
Remote assets are fetched by the renderer process, not by Laravel’s Blade renderer. The application host needs network access to the target URL and any required assets. Authentication, redirects, bot checks, JavaScript behavior, and asset access can vary by site, so verify those conditions in the deployment environment instead of assuming every browser-visible page produces the same PDF.
4. Validate destinations and protect the renderer
A URL-to-PDF endpoint makes your server fetch network destinations on behalf of a caller. Do not pass arbitrary user input directly to loadFile(). Prefer a fixed destination, a strict allowlist of hostnames, or a trusted record selected by an authenticated user. Validate the URL scheme and destination before rendering, and ensure the renderer cannot reach internal services or sensitive network addresses.
wkhtmltopdf’s project status page warns against processing untrusted HTML because of the security risks associated with its old rendering engine. The KnpLabs Snappy README specifically warns that --enable-local-file-access can be risky with untrusted HTML or JavaScript. Keep local file access disabled unless the use case requires it, and do not enable it merely to make an asset path work. If user-controlled content must be rendered, sanitize it and run the renderer with operating-system isolation such as AppArmor or SELinux. See the wkhtmltopdf status page and the Snappy security notes.
5. Troubleshoot common failures
| Symptom | Likely cause | What to check or change |
|---|---|---|
| Executable not found | The configured binary path does not match the deployed wkhtmltopdf location. | Check config/snappy.php, confirm the executable exists in that environment, and verify the PHP process user can run it. |
| Permission denied | The binary or output directory is not executable or writable for the application user. | Correct ownership and permissions for the binary and destination directory; avoid writing outside an application-controlled location. |
| Blank or incomplete PDF | The remote page, scripts, stylesheets, or images did not finish loading or were inaccessible to the renderer. | Check reachability from the server, asset URLs, renderer output, and the deployed binary’s documented load and JavaScript options. |
| Authentication page appears | The renderer received a login page or could not access a protected URL. | Use only documented cookie or header configuration where appropriate, and avoid exposing credentials in logs or user-controlled input. |
| Layout differs from the browser | PDF print styles, unsupported CSS, viewport assumptions, or renderer age can change layout. | Inspect print CSS, paper size, orientation, and margins; test with the actual wkhtmltopdf build. |
| Conversion hangs or fails on slow pages | The target or its dependencies are slow, blocked, or never reach the renderer’s completion conditions. | Check the renderer’s load-error and JavaScript-delay options, constrain the set of targets, and apply suitable job and request time limits in the application. |
| Local image or stylesheet cannot load | Local file access is disabled or the renderer cannot resolve the path. | Prefer reachable asset URLs or an explicitly supported, controlled asset setup. Understand the security consequences before changing local-file access. |
6. Performance, reliability, and cost
Each conversion starts an external renderer process and fetches the target page plus its dependencies. A page with many assets or slow scripts can take longer than a simple static page. For repeated or background work, save output to a controlled location and consider a queue so a slow render does not hold a user-facing request open. Set limits appropriate to your application, monitor failed conversions, and avoid launching unbounded concurrent renderer processes.
Reliability depends on the availability of the wkhtmltopdf binary, the application host’s network access, and the target site’s response and content. Pin and verify the binary used in each deployment. wkhtmltopdf’s project page notes that its Qt 4 base has not been supported since 2015 and its WebKit has not been updated since 2012; the project suggests considering Puppeteer for pages that depend on dynamic JavaScript, and WeasyPrint or Prince for controlled report generation. These are the project maintainer’s recommendations, not comparative benchmark results. Review current support and migration needs before changing renderers.
There is no per-conversion Snappy service price in this setup: the application runs the renderer itself. Budget for the compute, storage, deployment, and operational work needed to run the binary and handle generated files. For a hosted screenshot or PDF API instead, compare its request limits, billing rules, and rendering behavior with your workload.
7. A hosted alternative for URL capture
ScreenshotNeo is a website screenshot API and MCP server for developers. For URL capture without installing a browser renderer in your Laravel deployment, call its API. See the ScreenshotNeo API documentation for request options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`ScreenshotNeo request failed: ${res.status}`);
await Bun.write('shot.webp', res);
In Laravel, make the same request through your HTTP client and keep the API key in server-side configuration. Use a PDF output option from the API documentation if the result you need is a PDF. ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, failed loads, timeouts, and cache hits are not billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.
Sign up for 1,000 free screenshots a month, with no card required.
8. Frequently asked questions
Does Laravel Snappy install wkhtmltopdf?
No. Install the wkhtmltopdf executable separately, then configure the binary path that the Laravel package uses.
Can I use this for any public webpage?
Not reliably. The renderer must be able to reach the page and its assets, and the page’s scripts, authentication, and bot protections affect what it can produce.
Should I enable local file access to fix missing assets?
Only when the application has a specific need and the renderer is appropriately constrained. Local file access can expose files when handling untrusted content.
When should I choose another renderer?
Evaluate alternatives when your target depends on modern JavaScript, when the old wkhtmltopdf engine cannot meet layout requirements, or when you need a different support and security model.
Sources: Laravel Snappy documentation, KnpLabs Snappy documentation, wkhtmltopdf usage manual, and wkhtmltopdf status page.


