How to Use wkhtmltoimage with PHP
Install wkhtmltoimage, render URLs or HTML from PHP, configure output and timing, and troubleshoot common Linux deployment failures.

wkhtmltoimage is a headless command-line renderer that converts a URL or local HTML file into an image. From PHP, the shortest maintainable route is KnpLabs Snappy: install the package, point it at the wkhtmltoimage executable, set the options you need, then generate a PNG or JPEG. For a reliable deployment, first verify the binary works under the same operating-system user as PHP, pin its version and system dependencies, and keep local-file access disabled unless the input is trusted.
wkhtmltoimage is part of the wkhtmltopdf project and uses Qt WebKit. It does not require a display server. Its rendering behavior therefore reflects a legacy browser engine; modern page JavaScript and CSS may not work as expected. The upstream repository is archived, so treat the binary and its environment as a compatibility-bound component. Upstream project documentation
1. Install and verify the renderer
Install a wkhtmltopdf distribution that includes wkhtmltoimage, or build it from source using the upstream project’s instructions. The executable and its dependencies must exist in the environment where PHP runs: a binary available in your interactive shell may not be available to PHP-FPM or a queue worker.
which wkhtmltoimage
wkhtmltoimage --version
wkhtmltoimage --extended-help
Use --extended-help on the target host to confirm the options supported by that exact build. Available switches vary across releases and packages. A command-line smoke test helps separate renderer problems from PHP integration problems:
wkhtmltoimage --format png --width 1280 https://example.com /tmp/example.png
Open the resulting file and confirm the dimensions and content. On Linux, install the fonts and shared libraries expected by your selected binary. On Windows, make sure the wkhtmltox DLL is accessible through PATH. If you deploy with a package that offers bundled binaries or a Docker fallback, pin the image tag and verify the architecture and libraries in your own deployment. See the Snappy packaging project for its packaging notes.
2. Install KnpLabs Snappy
Snappy wraps the command-line program with a PHP object, option setters, and methods that can write to a file or return image bytes. Install it with Composer:
composer require knplabs/knp-snappy
Make sure the destination directory exists and is writable by the PHP service account. For example, create an application-owned output directory during deployment rather than relying on a developer’s local temporary directory. Snappy’s README documents executable configuration and image output methods: KnpLabs Snappy.
3. Render a URL or HTML string
The following runnable PHP example renders a URL to PNG, then renders a small HTML string to another file. Adjust the absolute binary path for your host. The HTML example contains no external resources, so it works without enabling local-file access.

<?php
require __DIR__ . '/vendor/autoload.php';
use Knp\Snappy\Image;
$binary = '/usr/local/bin/wkhtmltoimage';
$outputDir = __DIR__ . '/var';
if (!is_dir($outputDir) && !mkdir($outputDir, 0775, true) && !is_dir($outputDir)) {
throw new RuntimeException('Could not create output directory');
}
$image = new Image($binary);
$image->setOption('format', 'png');
$image->setOption('width', 1280);
$image->setOption('javascript-delay', 300);
$image->generate('https://example.com', $outputDir . '/example.png');
$html = '<!doctype html><html><head><meta charset="utf-8">'
. '<style>body{font:16px sans-serif;padding:32px}</style>'
. '</head><body><h1>Invoice</h1><p>Ada Lovelace</p></body></html>';
$image->generateFromHtml($html, $outputDir . '/invoice.png');
For content that your application already renders as a template, render the template to an HTML string first and pass that string to generateFromHtml(). Keep user-provided markup separate from trusted templates and validate any values interpolated into HTML.
Return the image from a web endpoint
If you want to stream an image instead of save it permanently, ask Snappy for the output bytes and set the matching content type. The endpoint should still authenticate and authorize access to any protected page whose contents are being rendered.
<?php
use Knp\Snappy\Image;
use Symfony\Component\HttpFoundation\Response;
function cardResponse(Image $image, string $html): Response
{
$image->setOption('format', 'png');
$bytes = $image->getOutputFromHtml($html);
return new Response($bytes, 200, [
'Content-Type' => 'image/png',
'Content-Disposition' => 'inline; filename="card.png"',
'Cache-Control' => 'private, max-age=60',
]);
}
The concrete response class and dependency injection setup vary by framework. Ensure the returned content type and filename agree with the chosen format.
4. Choose options for the capture
Set only options supported by the installed binary. The Snappy wrapper passes options through to wkhtmltoimage; consult its extended help on the actual host when diagnosing an option error. Common image controls documented by the Debian manual include:
| Need | Option or approach | Practical note |
|---|---|---|
| Choose file encoding | format, such as png or jpeg |
Use an extension and HTTP content type that match the format. |
| Set output size | width and height |
Check the installed help for sizing and cropping behavior. |
| Reduce JPEG file size | quality |
Applies to lossy image output; compare the result at the target dimensions. |
| Crop part of a page | crop-x, crop-y, crop-w, crop-h |
Confirm coordinate and dimension behavior for your version. |
| Wait for client rendering | javascript-delay |
Use a bounded delay; old Qt WebKit may not support modern APIs. |
| Handle load failures | load-error-handling |
Choose behavior deliberately; ignoring failures can produce incomplete output. |
| Render authenticated content | Cookies or custom headers | Keep secrets out of logs and do not accept arbitrary caller-supplied credentials. |
| Route through a proxy | Proxy options | Use only a controlled proxy and validate which destinations callers can request. |
For example, these settings request a 1200-pixel-wide JPEG, quality 88, a short JavaScript wait, and permissive handling of page load errors:
$image->setOptions([
'format' => 'jpeg',
'quality' => 88,
'width' => 1200,
'javascript-delay' => 500,
'load-error-handling' => 'ignore',
]);
Do not use an error-ignoring policy as a substitute for checking output. If the target is important, inspect the generated bytes or validate that the output file exists and is nonempty. Cookies and headers can expose account data, so restrict access to them and redact them from logs.
5. Render local HTML and its assets safely
Local files are a special case. By default, local CSS, fonts, and images may not load because the renderer restricts local file access. If your own trusted template needs local assets, enable access narrowly and allow only the directory containing those assets:
wkhtmltoimage --enable-local-file-access \
--allow /var/www/app/public \
/var/www/app/public/card.html \
/tmp/card.png
Do not turn on local-file access for arbitrary HTML or JavaScript. The option can expose files and, with untrusted input, create a path toward remote code execution. Sanitize markup, never accept arbitrary filesystem paths, run the renderer as a low-privilege user, and use AppArmor, SELinux, or container isolation where practical. See the Snappy security notes and the installed binary’s help for local access controls.
6. Configure the Symfony bundle
In Symfony, the KnpSnappyBundle provides an image service and central configuration. Install it with Composer, then set the binary path and sensible defaults. Keep process timeout values appropriate for the pages you render:

composer require knplabs/knp-snappy-bundle
# config/packages/knp_snappy.yaml
knp_snappy:
image:
enabled: true
binary: /usr/local/bin/wkhtmltoimage
options:
format: png
width: 1280
process_timeout: 20
The bundle can return bytes from HTML and your controller can return them as a framework response. If you use a JPEG response class, configure JPEG output and an appropriate extension/content type:
public function card(Knp\Snappy\Image $knpSnappyImage): Response
{
$html = $this->renderView('card.html.twig', ['name' => 'Ada']);
return new Response(
$knpSnappyImage->getOutputFromHtml($html),
200,
['Content-Type' => 'image/png']
);
}
Bundle configuration keys and service wiring depend on the bundle version. Use the documentation for your installed release: KnpLabs SnappyBundle.
Or skip the browser setup
If maintaining a legacy renderer, Linux libraries, and process isolation is more work than the screenshot needs, ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request returns a PNG, JPEG, WebP, or PDF; see the API documentation.
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(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', new Uint8Array(await res.arrayBuffer()));
Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and responses identify the page verdict and billing status in headers. An MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, no card required.
7. Troubleshoot common failures
| Symptom | Likely cause | Fix |
|---|---|---|
| Binary not found | The PHP service has a different PATH than your shell. | Set Snappy’s binary to an absolute path and run which wkhtmltoimage as the PHP-FPM or worker user. |
| Exit code 126 or permission denied | The executable bit is missing, or the filesystem disallows execution. | Make the binary executable and install it on a mount that permits execution. |
| Blank image or missing glyphs | Fonts or shared libraries are absent, or the CLI runs with a different environment. | Install required fonts/libraries and reproduce the command under the service account. |
| Local CSS or images missing | Local-file access is disabled or the asset path is unreadable. | Prefer absolute asset URLs; if local access is required, allow only the smallest trusted directory. |
| JavaScript content is absent | JavaScript is disabled, the wait is too short, or Qt WebKit lacks modern API support. | Confirm JavaScript is enabled, add a bounded delay, and simplify or pre-render unsupported client code. |
| Request hangs or exceeds timeout | The page or one of its resources never finishes loading, or capture blocks a web request. | Set a process timeout, bound the workload, and queue slow renders outside the normal request path. |
| Output file is empty | The process failed, destination is unwritable, or load errors were ignored. | Check process errors and permissions; validate the file before serving it. |
8. Plan for performance, reliability, and cost
Rendering cost depends on the page, its resources, the machine, and output dimensions; no single delay value fits all pages. Avoid rendering synchronously on latency-sensitive endpoints when pages are large or unpredictable. Put longer captures on a queue, cap concurrency to the capacity of the host, and set a process timeout. Where practical, restrict unnecessary resource loading and use a deterministic render-complete signal for pages you control instead of increasing a fixed delay indefinitely.
For reliable output, pin the wkhtmltoimage version, operating-system image, fonts, and shared libraries. Keep a small representative set of pages and compare their rendered output when changing any of those inputs. Since the upstream repository is archived, plan maintenance around compatibility constraints and test upgrades before rollout. Snappy reduces PHP process plumbing but does not change the renderer’s engine or security boundary.
Direct CLI calls have no wrapper dependency, but your PHP code must correctly escape arguments, handle temporary files, enforce timeouts, and collect errors. Snappy centralizes much of that integration. A hosted API moves binary installation and process management out of your application, with usage-based plan limits instead of local compute and operations costs. Choose based on rendering compatibility, deployment burden, security requirements, and expected capture volume.
9. FAQ
Does wkhtmltoimage need X11 or a display server?
No. The project documents it as a headless command-line renderer; it can run without a display service.
Can it render a page that requires login?
It can receive cookies or custom headers when supported by the installed version. Treat those values as credentials: restrict their source, protect them in transit, and never log them.
Will it render every modern website correctly?
No. It uses Qt WebKit, so pages that depend on newer browser features can render differently or omit content. Test representative pages before choosing it for production.
Should I use it for user-submitted HTML?
Only with strict input validation and isolation. Local-file access and untrusted markup create security risks; use a low-privilege process, narrow filesystem access, and sandboxing.
How do I find the exact supported image switches?
Run wkhtmltoimage --extended-help on the same binary installed in production. That output is the authority for the host’s specific build.


