html2img/html2img-laravel

Official Laravel integration for the html2img HTML-to-image API: a facade, config and Storage helpers around the html2img PHP SDK.

Maintainers

Package info

github.com/html2img/html2img-laravel

Homepage

Documentation

pkg:composer/html2img/html2img-laravel

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-06-07 14:37 UTC

This package is auto-updated.

Last update: 2026-06-07 14:51:21 UTC


README

html2img — HTML to image API, rendered in real Chrome

html2img for Laravel

Packagist Version PHP Version Laravel Version Total Downloads License

The official Laravel integration for the html2img.com API. Turn HTML and CSS into images, capture screenshots of live URLs, and render named templates, all behind a clean facade with zero-config auto-discovery.

It wraps the framework-agnostic html2img PHP SDK (source) and adds the Laravel pieces you would otherwise write yourself: a service provider, a published config file, a facade, container bindings, an artisan health check, and one-line saving of a render to any filesystem disk.

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 full API reference lives in the documentation, with a Laravel-specific guide at html2img.com/docs/usage/laravel.

Contents

What you can build

Browse the full template library, or try the no-signup browser tools to see the output before you write any code.

Requirements

  • PHP 8.3 or newer
  • Laravel 11 or 12
  • A html2img API key, issued per account from your dashboard

Installation

composer require html2img/html2img-laravel

The service provider and the Html2img facade are registered automatically through package discovery. Add your API key to .env:

HTML2IMG_API_KEY=your-api-key

That is the whole setup. See the authentication docs for issuing and rotating keys, and the getting started guide for a tour of the API.

Optionally publish the config file:

php artisan vendor:publish --tag=html2img-config

Configuration

The published config/html2img.php reads from your environment:

return [
    'api_key'  => env('HTML2IMG_API_KEY'),
    'base_uri' => env('HTML2IMG_BASE_URI', 'https://app.html2img.com'),
    'timeout'  => env('HTML2IMG_TIMEOUT', 35),
    'storage'  => [
        'disk' => env('HTML2IMG_DISK'),
    ],
];
Variable Default Purpose
HTML2IMG_API_KEY none Your key, sent as the X-API-Key header.
HTML2IMG_BASE_URI https://app.html2img.com API base URI. You rarely need to change this.
HTML2IMG_TIMEOUT 35 Request timeout in seconds.
HTML2IMG_DISK default disk Disk used by Html2img::store().

Custom HTTP client

The integration is built on Guzzle. To add your own retry middleware, logging or proxy settings, bind a configured GuzzleHttp\ClientInterface as html2img.http, for example in a service provider:

use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;

$this->app->bind('html2img.http', fn () => new Client([
    'base_uri' => config('html2img.base_uri'),
    'timeout'  => config('html2img.timeout'),
    // your own handler stack, middleware, proxy settings, etc.
]));

The package still sends the X-API-Key, Accept and Content-Type headers on every request.

Usage

Reach the API through the Html2img facade. Each method returns a readonly Html2img\Response\RenderResponse. The request objects come from the underlying SDK, so import them from the Html2img\Request namespace.

Render HTML

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.

use Html2img\Laravel\Facades\Html2img;
use Html2img\Request\HtmlRequest;

$response = Html2img::html(new HtmlRequest(
    html: view('og.post', ['post' => $post])->render(),
    css: 'body { background: #0f172a; color: #fff; }', // injected after load
    width: 1200,
    height: 630,
    dpi: 2,          // retina
));

return $response->url; // https://i.html2img.com/abc123def456.png

Rendering a Blade view into the image, as above, keeps your markup where the rest of your app lives.

Capture a screenshot

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 and the selector docs.

use Html2img\Laravel\Facades\Html2img;
use Html2img\Request\ScreenshotRequest;

$response = Html2img::screenshot(new ScreenshotRequest(
    url: 'https://example.com',
    width: 1200,
    height: 630,
    selector: '#hero',
    css: '.cookie-banner, .intercom-launcher { display: none !important; }',
    dpi: 2,
));

Render a template

POST /api/v1/templates/{slug}. Render one of the built-in templates from a JSON data payload. The data is validated server-side per template.

use Html2img\Laravel\Facades\Html2img;

$response = Html2img::template('invoice-image', [
    'number'   => 1042,
    'amount'   => '$240.00',
    'due_date' => '2026-07-01',
]);

return $response->url;

Saving renders to a disk

The API returns the CDN URL of the image rather than the raw bytes, so you can cache and re-serve it from your own infrastructure. When you would rather keep a copy, store() downloads the image and writes it to any filesystem disk in one line:

use Html2img\Laravel\Facades\Html2img;
use Html2img\Request\HtmlRequest;

$response = Html2img::html(new HtmlRequest(html: $document, width: 1200, height: 630));

// Returns the stored path; uses the HTML2IMG_DISK disk, or your default disk.
$path = Html2img::store($response, "og/{$post->id}.png");

// Or target a specific disk.
Html2img::store($response, "og/{$post->id}.png", 's3');

$post->update(['og_image_path' => $path]);

You can also pass a URL string directly, or grab the raw bytes without storing them:

$bytes = Html2img::download($response);          // or a URL string
$path  = Html2img::store('https://i.html2img.com/abc.png', 'thumb.png');

Queues and jobs

Renders are a natural fit for a queued job, especially full-page captures that take a few seconds. Resolve the facade or type-hint the manager:

use Html2img\Laravel\Html2img;
use Html2img\Request\HtmlRequest;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;

class GenerateOgImage implements ShouldQueue
{
    use Queueable;

    public function __construct(public Post $post) {}

    public function handle(Html2img $html2img): void
    {
        $response = $html2img->html(new HtmlRequest(
            html: view('og.post', ['post' => $this->post])->render(),
            width: 1200,
            height: 630,
        ));

        $this->post->update([
            'og_image_path' => $html2img->store($response, "og/{$this->post->id}.png"),
        ]);
    }
}

For very large captures, prefer asynchronous delivery over a long-running job.

Render options

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 Docs
css string css
width int dimensions (1 to 5000)
height int dimensions (ignored when fullpage)
fullpage bool fullpage
dpi int dpi (1 to 4, use 2 for retina)
webhookUrl string webhook-url
msDelay int ms_delay (1 to 5000)
waitForSelector string wait_for_selector

ScreenshotRequest also accepts selector 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.

The response

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->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 payload

Asynchronous delivery

Synchronous 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.

use Html2img\Laravel\Facades\Html2img;
use Html2img\Request\ScreenshotRequest;

$response = Html2img::screenshot(new ScreenshotRequest(
    url: 'https://example.com/long-report',
    fullpage: true,
    webhookUrl: route('hooks.html2img'),
));

if ($response->isProcessing()) {
    // The final URL will arrive at your webhook, not on this response.
}

Error handling

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 package.

use Html2img\Laravel\Facades\Html2img;
use Html2img\Request\HtmlRequest;
use Html2img\Exception\Html2imgException;
use Html2img\Exception\ValidationException;
use Html2img\Exception\InsufficientCreditsException;

try {
    $response = Html2img::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.
RateLimitException 429, rate or quota exceeded.
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.

Verifying your setup

Confirm your key and configuration with the bundled artisan command, which renders a small test image:

php artisan html2img:test

It prints the resulting image URL and your remaining credits, or a clear error if the key is missing or rejected. The check uses one credit. There is also a testing guide for the API itself.

Other languages

Not on Laravel? The same API has worked guides for plain PHP, Ruby on Rails, Python, JavaScript and Node.js, React and Vue.

Development

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 --test

Links

Website · Documentation · Laravel guide · Templates · Tools · Features · Comparisons · Articles · Pricing · PHP SDK

Licence

MIT. See LICENSE.