The official PHP client for the HTML to Image API. Turn HTML and CSS into images, capture screenshots of live URLs, and render named templates, all returning a typed response object.
Every render runs in real Chrome, so flexbox, grid, custom properties, web fonts and inline JavaScript behave exactly as they do in the browser. The package is framework-agnostic and built on Guzzle, so it works in plain PHP and inside any framework. The full API reference lives in the documentation.
- Open Graph and social images, generated per page or post. See the Open Graph image template and Twitter/X post template.
- Business documents such as invoices, receipts, event tickets and certificates, as PNGs or as PDFs through the HTML to PDF API.
- Developer assets such as code screenshots and GitHub social previews.
- URL screenshots, full page or cropped to a single element, with CSS injection to hide cookie banners and chat widgets before capture.
Browse the full template library, or try the no-signup browser tools to see the output before you write any code.
- PHP 8.3 or newer
- A html2img API key, issued per account from your dashboard
Every account starts with 50 free credits and no card is needed to get started. Free-tier renders are hosted for seven days; on any paid plan they are hosted permanently, including everything you rendered before upgrading.
composer require html2img/html2img-phpuse Html2img\Html2imgClient;
use Html2img\Request\HtmlRequest;
$client = new Html2imgClient('your-api-key');
$response = $client->html(new HtmlRequest(
html: '<!doctype html><html><body><h1>Hello</h1></body></html>',
width: 1200,
height: 630,
));
echo $response->url; // https://i.html2img.com/abc123def456.pngThe API returns a JSON envelope containing the CDN URL of the generated image, not the raw bytes, so you can cache and re-serve it from your own infrastructure. New to the API? Start with the getting started guide.
The constructor takes the API key plus optional overrides:
use Html2img\Html2imgClient;
$client = new Html2imgClient(
apiKey: 'your-api-key',
baseUri: 'https://app.html2img.com', // default
timeout: 35.0, // seconds, default
);Authentication is sent on every request as the X-API-Key header. See the authentication docs for issuing and rotating keys.
Pass a pre-configured GuzzleHttp\ClientInterface to reuse your own middleware,
retry strategy or logging. The SDK still adds the X-API-Key, Accept and
Content-Type headers on every request.
use GuzzleHttp\Client;
use Html2img\Html2imgClient;
$guzzle = new Client([
'base_uri' => 'https://app.html2img.com',
'timeout' => 60,
// your own handler stack, middleware, proxy settings, etc.
]);
$client = new Html2imgClient('your-api-key', httpClient: $guzzle);POST /api/html. Send a complete HTML document and get back an image of the
rendered result. Inline your CSS in a <style> block, or reference remote
stylesheets and web fonts via <link> tags in the document head. See the
html parameter docs for the full input.
use Html2img\Enum\Format;
use Html2img\Request\HtmlRequest;
$response = $client->html(new HtmlRequest(
html: $document,
css: 'body { background: #0f172a; color: #fff; }', // injected after load
width: 794,
fullpage: true,
dpi: 2, // retina
format: Format::Png,
));POST /api/screenshot. Fetch a public URL in a real browser and capture it.
Use selector to crop to a single element, and css to hide cookie banners or
chat widgets before the capture. See the url parameter docs.
use Html2img\Request\ScreenshotRequest;
$response = $client->screenshot(new ScreenshotRequest(
url: 'https://example.com',
width: 1200,
height: 630,
selector: '#hero',
css: '.cookie-banner, .intercom-launcher { display: none !important; }',
dpi: 2,
));Set format to Format::Pdf on either request and the render comes back as an
A4 portrait vector PDF instead of a PNG: text stays selectable and searchable,
webfonts are embedded and long content paginates automatically. The API ignores
width, height, dpi, fullpage and selector in PDF mode, and the
response url points at a .pdf file. One credit, the same as an image. This
is the HTML to PDF API; see the
format parameter docs.
use Html2img\Enum\Format;
use Html2img\Request\HtmlRequest;
$response = $client->html(new HtmlRequest(
html: '<h1>Invoice #1042</h1><p>Due within 30 days.</p>',
format: Format::Pdf,
));
echo $response->url; // https://i.html2img.com/....pdfPOST /api/v1/templates/{slug}. Render one of your named templates from a JSON
data payload. The data is validated server-side per template. Browse the
templates to find a slug. Templates output PNG
only; format is not available on template renders.
$response = $client->template('invoice', [
'number' => 1042,
'amount' => '$240.00',
'due_date' => '2026-07-01',
]);
echo $response->template; // invoice
echo $response->url;Both HtmlRequest and ScreenshotRequest accept the following. Any option left
null is omitted from the request, so the server applies its own default. The
complete reference is in the parameter docs.
| Option | Type | Notes |
|---|---|---|
css |
string | Extra CSS injected after the page loads. |
width |
int | Viewport width in CSS pixels (1 to 5000). |
height |
int | Viewport height in CSS pixels (1 to 5000). Ignored when fullpage is true. |
fullpage |
bool | Capture the full scroll length instead of the viewport. |
dpi |
int | Device pixel ratio, 1 to 4. Use 2 for retina. |
webhookUrl |
string | Switch to async delivery (see below). |
msDelay |
int | Wait this many milliseconds after load before capturing (1 to 5000). |
waitForSelector |
string | Wait until this CSS selector appears before capturing. |
format |
Format |
Format::Png (default) or Format::Pdf. PDF output is A4 portrait and ignores the sizing options above. |
ScreenshotRequest also accepts selector (string) to crop the capture to a
single element. HtmlRequest does not, since you control the markup.
Custom fonts are loaded by referencing them with <link> tags in your HTML
document head, or by linking a web font from your captured page.
Every method returns a readonly Html2img\Response\RenderResponse:
$response->success; // bool
$response->id; // string|null, the render id
$response->url; // string|null, the CDN URL of the image
$response->expiresAt; // string|null, ISO 8601 expiry of a free-tier render; null on paid plans
$response->creditsRemaining; // int|null, credits left after this call
$response->status; // string|null, "processing" for async jobs
$response->message; // string|null
$response->template; // string|null, the template slug, when applicable
$response->isProcessing(); // bool
$response->raw(); // array, the full decoded JSON payloadSynchronous requests have a 30 second budget. For captures likely to exceed it,
pass a webhookUrl. The API responds immediately with status: "processing"
and url: null, then POSTs the final image URL to your endpoint once rendering
finishes. See the webhook_url docs.
$response = $client->screenshot(new ScreenshotRequest(
url: 'https://example.com/long-report',
fullpage: true,
webhookUrl: 'https://your-app.example.com/hooks/html2img',
));
if ($response->isProcessing()) {
// The final URL will arrive at your webhook, not on this response.
}Every failure throws an Html2img\Exception\Html2imgException. Catch that single
type to handle any error, or catch a specific subclass. No raw Guzzle exception
escapes the client.
use Html2img\Exception\Html2imgException;
use Html2img\Exception\ValidationException;
use Html2img\Exception\InsufficientCreditsException;
try {
$response = $client->html(new HtmlRequest(html: $document));
} catch (ValidationException $e) {
// 400 or 422: inspect the per-field messages
foreach ($e->details() as $field => $messages) {
// ...
}
} catch (InsufficientCreditsException $e) {
// 402: out of credits
$left = $e->creditsRemaining();
} catch (Html2imgException $e) {
// anything else
$e->statusCode(); // int|null
$e->errorCode(); // string|null, the API "code" field
$e->payload(); // array, the decoded body
}| Exception | When |
|---|---|
AuthenticationException |
401, missing or invalid API key. |
InsufficientCreditsException |
402, no credits remaining. |
NotSubscribedException |
403, no active subscription. |
NotFoundException |
404, for example an unknown template slug. |
ValidationException |
400 or 422, with details() per field. |
TimeoutException |
504, the synchronous render budget was exceeded. |
ServerException |
5xx, an unexpected renderer error. |
ConnectionException |
the request never reached a response. |
Html2imgException |
base type for all of the above. |
The same API has official packages for other languages and platforms, all available now:
- SDKs: JavaScript and TypeScript · Python · Ruby
- Frameworks: Laravel · Django
- CMS plugins: WordPress · Statamic · Craft CMS
- Automation and AI: GitHub Action · MCP server (paid plans)
There are also worked guides for React and Vue, and anything that can make an HTTP request works with the API. Every integration is listed on the integrations hub.
This package uses ddev for a containerised PHP environment. It is optional, and you can use vanilla PHP or whatever you use for local dev if you prefer.
ddev composer install
ddev exec vendor/bin/pest # tests
ddev exec vendor/bin/phpstan analyse
ddev exec vendor/bin/pint --testHTML to Image API · Screenshot API · HTML to PDF API · Documentation · PHP guide · Templates · Tools · Features · Comparisons · Articles · Pricing
MIT. See LICENSE.
