Laravel Screenshot API Integration — Http Facade, Jobs, Scheduler

Capture webpage screenshots from Laravel with one Http::get() call. Service class, queued jobs with retries, scheduled captures, Sanctum proxy endpoint. No Puppeteer or Chrome binary needed.

Vitalii Holben Updated

ScreenshotRun's API plugs into Laravel through the built-in Http facade. One GET request captures any URL as a PNG, JPEG, WebP, AVIF, or PDF. No Puppeteer, no Chrome binary on the server, no Spatie Browsershot configuration to maintain.

Laravel developers who need screenshots usually install Spatie Browsershot. It wraps Puppeteer, which wraps Chrome. That chain works on a local Valet setup. Push it to Forge, Vapor, or a shared host, and you discover that Chrome needs specific system packages. Puppeteer needs a matching Node.js version. The whole stack adds 400+ MB to your Docker image. If you only need URL-to-image conversion, the Http facade does the job in four lines.

Your first screenshot from Laravel

Head to the dashboard and create an account. Once you confirm your email, the API key shows up in your project settings. The free plan gives you 200 captures a month with no restrictions on parameters.

Drop this into a route or an Artisan command to test it:

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;

$response = Http::withToken(config('services.screenshotrun.key'))
    ->get('https://api.screenshotrun.com/v1/screenshots/capture', [
        'url' => 'https://github.com',
        'format' => 'png',
        'response_type' => 'image',
    ]);

if ($response->successful()) {
    Storage::put('screenshots/github.png', $response->body());
    echo 'Saved: screenshots/github.png (' . round(strlen($response->body()) / 1024) . ' KB)';
}

Store your API key in config/services.php:

'screenshotrun' => [
    'key' => env('SCREENSHOTRUN_KEY'),
],

And add SCREENSHOTRUN_KEY=your_key_here to your .env file. Laravel's Http facade uses Guzzle under the hood, so connection pooling and timeouts work automatically.

Dedicated screenshot service class

A service class you can inject anywhere through Laravel's container:

<?php

namespace App\Services;

use Illuminate\Support\Facades\Http;

class ScreenshotService
{
    public function capture(
        string $url,
        string $format = 'png',
        int $width = 1280,
        bool $fullPage = false,
    ): string {
        $response = Http::withToken(config('services.screenshotrun.key'))
            ->timeout(60)
            ->get('https://api.screenshotrun.com/v1/screenshots/capture', [
                'url' => $url,
                'format' => $format,
                'width' => $width,
                'full_page' => $fullPage ? 'true' : 'false',
                'response_type' => 'image',
            ]);

        $response->throw();

        return $response->body();
    }
}

Register it in a service provider if you want to type-hint it, or just instantiate it directly:

$service = new \App\Services\ScreenshotService();

$imageBytes = $service->capture(
    url: 'https://github.com',
    format: 'webp',
    width: 1920,
    fullPage: true,
);

Storage::put('screenshots/github-full.webp', $imageBytes);

The throw() call converts any non-2xx response into a Laravel RequestException. Your controller can catch that or let the global exception handler deal with it.

Queue screenshots with Laravel Jobs

Screenshot captures take 2-4 seconds on average. In a web request, that blocks the response. Push the work onto a queue instead:

<?php

namespace App\Jobs;

use App\Services\ScreenshotService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Storage;

class CaptureScreenshot implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 3;
    public int $backoff = 10;

    public function __construct(
        public string $url,
        public string $storagePath,
        public string $format = 'png',
    ) {}

    public function handle(ScreenshotService $service): void
    {
        $bytes = $service->capture(
            url: $this->url,
            format: $this->format,
        );

        Storage::put($this->storagePath, $bytes);
    }
}

Dispatch it from a controller or anywhere else:

CaptureScreenshot::dispatch(
    url: 'https://example.com',
    storagePath: 'screenshots/example.png',
);

// Or process a batch:
$urls = ['https://github.com', 'https://laravel.com', 'https://packagist.org'];

foreach ($urls as $url) {
    $slug = Str::slug(parse_url($url, PHP_URL_HOST));
    CaptureScreenshot::dispatch($url, "screenshots/{$slug}.png");
}

Laravel's queue worker handles retries through the $tries and $backoff properties. If the API returns a 5xx or times out, the job retries up to three times with a 10-second gap. No custom retry logic needed because the queue system already has it built in.

Scheduled screenshots with Laravel's scheduler

Capture a set of URLs on a regular schedule. Useful for monitoring competitor pages, archiving your own site, or checking that a deploy did not break anything visible:

// In app/Console/Kernel.php or routes/console.php (Laravel 11+):

use App\Jobs\CaptureScreenshot;

Schedule::call(function () {
    $urls = [
        'https://your-site.com',
        'https://competitor.com',
    ];

    foreach ($urls as $url) {
        $slug = Str::slug(parse_url($url, PHP_URL_HOST));
        $date = now()->format('Y-m-d');
        CaptureScreenshot::dispatch($url, "monitoring/{$slug}/{$date}.png");
    }
})->daily();

Each day the scheduler dispatches jobs that capture every monitored URL. Screenshots land in date-stamped folders, so you can compare Tuesday's version to Wednesday's at a glance. Pair this with Laravel's notification system to get a Slack alert when a screenshot file size changes dramatically (a sign that the page layout shifted).

Controller endpoint for on-demand screenshots

Expose a screenshot route in your Laravel app. The frontend sends a URL, your backend calls the API with credentials and streams the image back:

use App\Services\ScreenshotService;
use Illuminate\Http\Request;

Route::get('/api/screenshot', function (Request $request, ScreenshotService $service) {
    $request->validate([
        'url' => 'required|url',
        'format' => 'in:png,jpeg,webp',
        'width' => 'integer|min:320|max:3840',
    ]);

    $bytes = $service->capture(
        url: $request->input('url'),
        format: $request->input('format', 'png'),
        width: $request->integer('width', 1280),
    );

    $contentType = match ($request->input('format', 'png')) {
        'jpeg' => 'image/jpeg',
        'webp' => 'image/webp',
        default => 'image/png',
    };

    return response($bytes, 200)
        ->header('Content-Type', $contentType)
        ->header('Cache-Control', 'public, max-age=3600');
})->middleware('auth:sanctum');

The Sanctum middleware keeps the endpoint private. Only authenticated users can request screenshots. The one-hour cache header prevents the browser from hitting your backend for the same URL twice in a row.

API parameters

Pass these as the second argument to Http::get(). Laravel converts the array into query string parameters automatically:

ParameterTypeDefaultWhat it controls
urlstringrequiredWhich page to screenshot
formatstringpngFile type: png, jpeg, webp, avif, or pdf
widthint1280Viewport width the headless browser uses (px)
heightint720Viewport height the headless browser uses (px)
full_pageboolfalseScroll to the bottom, capture the entire document as one image
devicestringdesktopScreen preset: desktop, mobile, or tablet
retinaboolfalse2x pixel density for sharp images on HiDPI screens
dark_modeboolfalseRequest the page's dark color scheme
selectorstringCapture only the element matching this CSS selector
delayint0Milliseconds to wait after page load, useful for animations
block_cookiesboolfalseDismiss GDPR cookie walls before capturing
block_adsboolfalseRemove ad scripts and banner placements
block_chatsboolfalseStrip chat widgets like Drift or Intercom
cssstringCustom CSS rules applied to the page before Chrome takes the screenshot
jsstringJavaScript that executes inside the page after load but before the image is generated

The full list of parameters, response shapes, and status codes lives in the API reference.

Dark mode, element screenshots, style overrides

Dark mode

Set dark_mode to true in the request array. Chrome flips its prefers-color-scheme media feature to dark before the page even starts loading. Sites with a dark palette (GitHub, Tailwind docs, Laravel.com) render that version without extra JavaScript:

$response = Http::withToken(config('services.screenshotrun.key'))
    ->get('https://api.screenshotrun.com/v1/screenshots/capture', [
        'url' => 'https://github.com',
        'dark_mode' => 'true',
        'response_type' => 'image',
    ]);

Element screenshots

The selector parameter tells the API to find a specific DOM element and return only that portion of the page. Useful when you need a product card, a pricing table, or a hero section without the rest of the layout:

'selector' => '.pricing-table',

Style overrides

The css parameter lets you inject a stylesheet before the page renders. Remove sticky navbars, hide promotional banners, change background colors:

'css' => '.sticky-nav { display: none !important; } .hero { background: #f8f9fa; }',

You can mix all of them in a single request array. Dark mode, a selector target, custom CSS — the API stacks them in order and returns one cropped image. No chaining, no extra round trips.

Retry and error handling

Laravel's Http facade has built-in retry support. You do not need to write your own loop:

$response = Http::withToken(config('services.screenshotrun.key'))
    ->retry(3, 100, function ($exception, $request) {
        // Retry on 429 (rate limit) and 5xx (server errors)
        return $exception instanceof \Illuminate\Http\Client\RequestException
            && in_array($exception->response->status(), [429, 500, 502, 503]);
    })
    ->timeout(60)
    ->get('https://api.screenshotrun.com/v1/screenshots/capture', [
        'url' => $targetUrl,
        'format' => 'png',
        'response_type' => 'image',
    ]);

$response->throw();

The retry() method accepts the attempt count as its first argument, the pause between attempts (in milliseconds) as the second, plus a callback that decides whether to retry. Rate limit responses (429) and server errors (5xx) get another chance. A 401 (wrong key) or 422 (missing URL) exits the loop right away because the request itself is broken, and repeating it won't help.

Screenshot API vs. Spatie Browsershot

Spatie Browsershot is the most popular Laravel screenshot package. It wraps Puppeteer, which controls a real Chrome browser. That gives you full browser access: login flows, cookie injection, multi-step interactions. When the task is just converting a URL to an image, that entire Puppeteer stack becomes overhead.

Spatie BrowsershotScreenshotRun API
DependenciesNode.js + Puppeteer + Chromium (~400 MB)Zero. Uses Laravel's built-in Http facade
Server requirementsChrome system packages, shared memory config, matching Node versionAny server that runs Laravel (shared hosting included)
Laravel Vapor / serverlessRequires a Lambda layer for Chrome, complex setupWorks out of the box. No binary dependencies
Memory per captureChrome eats 300-500 MB per render on top of your PHP processYour PHP process stays at its normal memory usage
Queue integrationWorks but Chrome startup adds 2-3s overhead per jobOne HTTP call per job, no startup cost
Cookie consent popupsWrite Puppeteer scripts to dismiss them, update when sites changeAdd block_cookies=true to the request array
Full-page scrollingBuilt-in via fullPage()Built-in via full_page=true
CostFree package, but Chrome server costs add upFree plan covers 200 monthly captures, paid tiers scale from there

Spatie Browsershot shines when you need to authenticate, navigate multiple pages, or manipulate the DOM before the capture — think Dusk-style testing or intranet screenshots that require an auth cookie. For the majority of Laravel screenshot work (turning public URLs into thumbnails, archiving pages on a schedule, generating PDF reports), the Http facade deploys on any host that runs PHP and keeps your Docker image hundreds of megabytes lighter.

Laravel teams wire this into daily monitoring jobs that archive competitor pages. It also powers link preview generators that show thumbnails in admin panels. Some teams run report pipelines that email dashboard snapshots to stakeholders every morning.

FAQ#

No. ScreenshotRun runs headless Chrome on its own servers. Your Laravel app sends an HTTP GET through the built-in Http facade and receives image bytes back. No binaries to install, no system packages to configure.

Add SCREENSHOTRUN_KEY=your_key to your .env file, then reference it in config/services.php under a screenshotrun key. Call it with config(services.screenshotrun.key) anywhere in your app. This keeps the secret out of version control.

Yes. Create a job class that implements ShouldQueue, inject the ScreenshotService, and dispatch the job from your controller. Laravel handles retries through the $tries and $backoff properties. The example on this page shows the full implementation.

It works on any platform that runs Laravel, including Vapor, shared hosting, and Docker containers. Since the Http facade only makes an outbound HTTP request, there are no binary dependencies or filesystem requirements beyond normal Laravel.

Browsershot wraps Puppeteer which wraps Chrome. That stack adds Node.js, Chromium (~400 MB), and system font packages to your server. The API approach replaces all of that with a single Http::get() call. Browsershot is better when you need to log into sites or run multi-step Puppeteer scripts before capturing.

PNG, JPEG, WebP, AVIF, and PDF. Pass the format parameter in the Http::get() request array. Laravel receives the raw bytes which you can store with Storage::put(), stream to the browser, or attach to a notification.

Written by

Vitalii Holben

Co-founder and CEO of ScreenshotRun. Full-stack developer building screenshot API tools that help developers capture, automate, and monitor web pages at scale. Based in Europe, shipping code since 201...

Screenshot API in a few clicks

Free plan includes 200 screenshots every month. No credit card required.

Sign up → copy your key → make your first call