ScreenshotNeo

BlogHow-to

How to Take Full-Page Screenshots with Splash

Use Splash Lua to wait for a page, resize to its full height, and capture PNG or JPEG output—with troubleshooting, alternatives, and API code.

By the ScreenshotNeo team29 September 20268 min read

How to Take Full-Page Screenshots with Splash

A normal Splash screenshot captures only the current viewport. To capture the entire document, load the page, wait for it to settle, call splash:set_viewport_full(), and then return splash:png() or splash:jpeg(). Splash also supports render_all=true as a shorthand that temporarily fits the full page during rendering.

The key ordering is important: resize after navigation and after a wait. Splash’s scripting reference documents this sequence and notes that resizing can change layout geometry such as window.innerWidth and window.innerHeight. The examples below follow the documented API behavior in the Splash scripting reference.

1. The minimal full-page Splash script

Run this Lua script through Splash’s /execute endpoint. Replace the URL with the page you need to capture.

function main(splash, args)
    assert(splash:go(args.url))
    assert(splash:wait(0.5))
    splash:set_viewport_full()
    return splash:png()
end

The 0.5-second delay is an example from the documentation, not a universal signal that every application has finished rendering. Increase it or wait for a site-specific condition when the page loads content asynchronously.

Calling the script with cURL

curl -X POST "http://localhost:8050/execute" \
  -H "Content-Type: application/json" \
  --data-binary @- > page.png <<'JSON'
{
  "lua_source": "function main(splash, args) assert(splash:go(args.url)); assert(splash:wait(0.5)); splash:set_viewport_full(); return splash:png() end",
  "args": {
    "url": "https://example.com"
  }
}
JSON

Splash installations can be hosted at a different address or behind a proxy; use that host in place of http://localhost:8050. The response is binary PNG data, so redirect it to a file rather than printing it in a terminal.

Calling Splash from Python

import requests

lua = """
function main(splash, args)
    assert(splash:go(args.url))
    assert(splash:wait(0.5))
    splash:set_viewport_full()
    return splash:png()
end
"""

response = requests.post(
    "http://localhost:8050/execute",
    json={"lua_source": lua, "args": {"url": "https://example.com"}},
    timeout=90,
)
response.raise_for_status()
with open("page.png", "wb") as output:
    output.write(response.content)

Calling Splash from Node.js

const lua = `
function main(splash, args)
    assert(splash:go(args.url))
    assert(splash:wait(0.5))
    splash:set_viewport_full()
    return splash:png()
end
`;

const response = await fetch('http://localhost:8050/execute', {
  method: 'POST',
  headers: {'content-type': 'application/json'},
  body: JSON.stringify({
    lua_source: lua,
    args: {url: 'https://example.com'}
  })
});
if (!response.ok) throw new Error(`Splash returned ${response.status}`);
const image = Buffer.from(await response.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('page.png', image));

2. How full-page resizing works

splash:set_viewport_full() measures the loaded document and resizes the viewport to fit it. The method returns the width and height used for the resized viewport, which can be useful for logging or deciding whether a page exceeded an operational limit.

Full-page capture depends on page state: wait for the content you need before resizing and rendering.
Full-page capture depends on page state: wait for the content you need before resizing and rendering.

Call it only after navigation and a settling operation:

  1. Navigate with splash:go(args.url).
  2. Wait for initial load and any application-specific rendering.
  3. Resize with splash:set_viewport_full().
  4. Capture with splash:png() or splash:jpeg().

Resizing can trigger responsive breakpoints and page resize handlers. If your page changes after the resize, schedule another asynchronous operation or short wait before the final capture so those handlers can run. A full viewport also does not force content that requires a click, scroll-triggered request, or later API response to appear; perform those actions before resizing.

The render_all shortcut

function main(splash, args)
    assert(splash:go(args.url))
    assert(splash:wait(0.5))
    return splash:png{render_all=true}
end

According to the reference, render_all=true behaves as if Splash calls set_viewport_full() immediately before rendering and restores the previous viewport afterward. Use this form when you do not need to inspect or reuse the resized dimensions.

3. Choosing PNG or JPEG

Format Use it when Relevant options
PNG You need lossless output, crisp text, transparency, or pixel-accurate visual comparison. render_all
JPEG A smaller photographic image is more important than lossless edges. quality from 0 to 100, plus render_all
function main(splash, args)
    assert(splash:go(args.url))
    assert(splash:wait(0.5))
    splash:set_viewport_full()
    return splash:jpeg{quality=85}
end

The documentation says JPEG is often faster than PNG and warns that quality values above 95 usually increase file size without a comparable visual benefit. Treat that as guidance rather than a promise for every page, network, or Splash deployment. Both methods return binary data. A screenshot can be empty and return nil, so use assert(splash:png()) or assert(splash:jpeg()) when an empty result should fail the request.

4. Waiting for dynamic pages

Waiting for the browser’s initial navigation is different from waiting for the content you want in the image. Single-page applications may render a shell first, then fetch cards, charts, or images. Choose a wait strategy that matches the page:

  • Fixed delay: splash:wait(2) is simple but can be either too short or unnecessarily slow.
  • Site-specific readiness: use Splash scripting to wait until an expected element or state exists before resizing.
  • Interaction first: click or scroll through controls that reveal content, then wait for the resulting request and layout change.

Do not assume that full-page geometry loads lazy images automatically. If images appear only after scrolling, trigger the page behavior before set_viewport_full(). Capture after the final content has arrived; resizing first can lock in a layout that later changes.

5. Capturing one element instead of the whole document

Full-page capture is appropriate for an entire article, dashboard, or landing page. For a component, select it and call its screenshot method:

function main(splash, args)
    assert(splash:go(args.url))
    assert(splash:wait(0.5))
    local element = splash:select(args.selector)
    assert(element)
    return element:png()
end

Send a selector such as #invoice in args.selector. Element capture avoids producing a very tall image when the requirement is a chart, card, or modal.

6. Common errors and fixes

Symptom Likely cause Fix
Only the visible screen is captured The script called png() or jpeg() without full-page handling. Call splash:set_viewport_full() after the wait, or pass render_all=true.
Bottom sections are blank Lazy content or scroll-triggered requests had not run. Trigger the page’s loading behavior and wait for the content before resizing.
Mobile layout appears unexpectedly The resized width crossed a responsive breakpoint. Set the desired initial viewport, inspect the returned dimensions, and allow resize handlers to finish before capture.
The response is empty or the script errors The screenshot method returned nil, or navigation failed. Wrap navigation and screenshot calls in assert; verify the URL and handle an empty image explicitly.
Output is unreadable in a terminal Binary image bytes were sent to standard output. Redirect the response to a file or write response.content as binary.
JPEG is much larger than expected Quality is set too high, or the page contains large photographic regions. Try a lower quality such as 80–90; values above 95 rarely justify their size.
Capture takes too long The page is waiting on third-party requests or an overly long fixed delay. Reduce unnecessary waits, wait for a concrete readiness condition, and block or remove resources that are irrelevant to the image where your Splash setup permits it.

7. Performance, reliability, and operating cost

Full-page screenshots require more layout work and produce larger responses than viewport captures. PNG preserves detail but usually costs more bandwidth and processing than JPEG. For a high-volume pipeline, choose the smallest format that meets your visual requirements, reuse a Splash service close to the calling application, and stream the binary response directly to object storage instead of buffering many tall images in memory.

The Splash sequence is navigate, wait, fit the viewport, then return image bytes.
The Splash sequence is navigate, wait, fit the viewport, then return image bytes.

Reliability improves when scripts make their sequencing explicit. Assert navigation, wait for content that matters, inspect the dimensions returned by set_viewport_full(), and treat nil as a failed capture. Record the target URL and script outcome so retries can distinguish a transient navigation failure from a page that genuinely has no renderable image.

Splash documentation describes API behavior, not a universal service-level guarantee or a fixed per-image price. Your actual cost depends on where Splash runs and the CPU, memory, storage, and bandwidth allocated to it. Measure representative pages in your own deployment before setting concurrency or timeout limits.

8. Playwright and Firefox alternatives

If Splash is not required, browser automation tools expose their own full-page controls. Playwright uses a fullPage: true screenshot option in its language APIs; see the official Playwright screenshot documentation. Firefox users can use Developer Tools or the Web Console’s :screenshot --fullpage command; see Mozilla’s Developer Tools documentation. These options belong to those environments and are not Splash syntax.

9. Or skip the browser setup

ScreenshotNeo provides a website screenshot API with one GET request for PNG, JPEG, WebP, or PDF output. Its full-page mode loads lazy images, and it can also capture a single CSS-selected element.

Use the API directly (see the ScreenshotNeo documentation):

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

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)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' }); const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and each response reports its result through X-Page-Verdict and X-Billed headers. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. You can also set viewport and device presets, retina scale, custom CSS and JavaScript, waits, headers, cookies, user agents, geolocation, caching TTL, signed links, asynchronous webhooks, and bulk capture.

There is a free allowance of 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots, and every feature is available on every plan. Create a free ScreenshotNeo account to try the API.

10. Full-page Splash checklist

  • Navigate with splash:go() and assert the result.
  • Wait for the content that must appear, not merely an arbitrary page age.
  • Call splash:set_viewport_full() after loading and waiting.
  • Allow responsive resize handlers to run before capturing.
  • Use render_all=true when temporary full-page fitting is sufficient.
  • Choose PNG for lossless output or JPEG for smaller photographic images.
  • Handle nil screenshots and failed navigation explicitly.
  • Write binary responses to files or object storage.

FAQ

Does splash:png() automatically capture the entire page?

No. Without full-page handling it captures the current viewport. Use set_viewport_full() or render_all=true.

Should I always wait 0.5 seconds?

No. That value is an illustrative documentation example. Dynamic pages may need a readiness condition or a longer wait.

Can full-page capture preserve a fixed desktop width?

The viewport is resized to fit the document, and responsive layout can change. Inspect the resulting dimensions and account for breakpoint behavior in your script.

When should I use element capture?

Use splash:select(selector):png() when the deliverable is one component rather than the whole document.

Is Splash the only way to automate full-page screenshots?

No. Playwright and Firefox provide native full-page controls in their own environments, and ScreenshotNeo offers a hosted API when you do not want to operate a browser service.