ScreenshotNeo

BlogHow-to

How to Set Page Content in Puppeteer

Use Puppeteer’s `page.setContent()` to load an HTML string, then wait for the lifecycle event or app state your next step requires.

By the ScreenshotNeo team4 October 20266 min read

Use Puppeteer’s page.setContent(html) to replace the current page’s markup with an HTML string. Await it before querying or interacting with the page:

await page.setContent(`<!doctype html>
<html>
  <head><title>Example</title></head>
  <body><main><h1>Hello</h1></main></body>
</html>`);

setContent returns a promise that resolves when its configured wait condition is met. It takes HTML markup, not a URL. See the Puppeteer Page.setContent API.

1. Set content and read it

This runnable example creates a page, sets a complete document, reads the heading, and closes the browser. Install Puppeteer in your project with npm install puppeteer, then save the code as set-content.cjs and run node set-content.cjs.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage();
    await page.setContent(`<!doctype html>
<html>
  <head><title>Example</title></head>
  <body><main><h1>Hello from setContent</h1></main></body>
</html>`);

    const heading = await page.$eval('h1', element => element.textContent);
    console.log(heading);
  } finally {
    await browser.close();
  }
})().catch(error => {
  console.error(error);
  process.exitCode = 1;
});

A fragment is also valid when you only need content inside the page context:

await page.setContent('<main><h1>Rendered markup</h1></main>');
const heading = await page.$eval('h1', element => element.textContent);

Use a complete document when you need document-level structure or metadata such as a title. The method assigns the supplied markup to the page; it does not navigate to a remote URL.

2. Choose the wait condition and timeout

setContent accepts an optional options object. The documented default waitUntil condition is load, and the documented default timeout is 30,000 milliseconds. The supported lifecycle conditions are load, domcontentloaded, networkidle0, and networkidle2. When you provide an array of conditions, all of them must fire. See Puppeteer WaitForOptions.

Option What it controls When to use it
waitUntil The lifecycle event or events that must occur before the operation completes. Pick the event that matches what the next operation needs. For example, domcontentloaded can be sufficient when you only need parsed markup; use a later readiness check for application-specific rendering.
timeout Maximum wait in milliseconds for the operation. Set a per-call limit when this page needs a different bound from other operations.
await page.setContent(html, {
  waitUntil: 'load',
  timeout: 30_000,
});

Do not increase the timeout automatically when a call stalls. First decide which lifecycle event matters. A lifecycle event does not guarantee that your own asynchronous application work is finished. When later code needs a specific element or state, wait for that explicitly.

You can also set a shared navigation timeout on the page. Puppeteer documents that setDefaultNavigationTimeout applies to page.setContent as well as navigation methods; a per-call timeout can still express the bound for this operation. See Page.setDefaultNavigationTimeout.

page.setDefaultNavigationTimeout(45_000);
await page.setContent(html, { waitUntil: 'load' });

3. Wait for the content your task needs

Use a selector wait when the next step depends on an element appearing. For a condition that cannot be represented by a selector, use waitForFunction.

await page.setContent('<div id="app"></div>');
await page.waitForSelector('#app');
await page.setContent('<script>setTimeout(() => { window.appReady = true; }, 100)</script>');
await page.waitForFunction(() => window.appReady === true);

waitForFunction waits for a browser-context function to return a truthy value. waitForSelector waits for a matching element, with options for visibility, hidden state, timeout, and an abort signal. Read the waitForFunction reference and waitForSelector reference for their current option details.

For interactions, Puppeteer’s locator API can wait for an element to be present and in the right state. Use a locator when the task is an interaction; use a selector or function wait when you need a particular readiness condition. See the Page interactions guide.

4. Set content inside a frame

When the target is an iframe, call setContent on its Frame, not on the top-level page. Find the intended frame and handle the case where it is absent:

const frame = page.frames().find(candidate => candidate.name() === 'preview');
if (!frame) {
  throw new Error('Preview frame not found');
}
await frame.setContent('<main><p>Frame content</p></main>');

The frame method accepts an HTML string and optional wait options, like the page method. See Puppeteer Frame.setContent.

5. Common errors and fixes

Symptom Likely cause Fix
The next query finds no element. The code did not await setContent, queried the wrong selector, or expected app work that had not completed. Await the call, check the selector against the supplied markup, and wait for the required selector or app state.
setContent times out. The selected lifecycle event did not fire before the timeout, perhaps because the page’s loading behavior does not match the chosen condition. Choose an appropriate waitUntil condition and inspect what the content loads. Increase the timeout only when the expected work legitimately needs more time.
The frame is missing. The frame name or lookup condition does not identify the intended frame, or it has not been created yet. Check page.frames(), identify the right frame, and wait for the frame to exist before calling frame.setContent.
The page looks incomplete despite the promise resolving. The lifecycle wait completed, but application code or a delayed resource had not reached the state the task requires. Wait for a task-specific selector or predicate after setContent.
External images, styles, or scripts do not appear as expected. The supplied markup may reference remote resources whose availability, URLs, or loading behavior differ from assumptions. Check resource URLs and browser access, and wait for the actual resource-dependent condition your task needs.

6. Reliability, performance, and safe input

setContent itself does not make external resources deterministic. If the HTML references network assets or scripts, their availability and timing can affect what subsequent code sees. For repeatable automation, keep markup self-contained where practical, make readiness conditions explicit, and set a timeout appropriate to the work.

A shorter lifecycle wait may let your script proceed sooner, but it is only correct if the following operation does not need later loading work. Avoid using network-idle conditions as a generic guarantee of application readiness: background requests can keep a page active, while app state can still need a separate check.

Treat untrusted markup as untrusted browser content. The setContent API reference describes assigning the string as page markup; it does not promise to sanitize input or guarantee that scripts and remote resources cannot run. Apply the controls required by your application before loading untrusted content.

7. Or skip the browser setup

If your goal is a screenshot rather than browser automation, ScreenshotNeo captures a URL with one API request. Its options include full-page and element captures, image formats, PDF, custom CSS and JavaScript, waits, headers, cookies, caching, and more. See the ScreenshotNeo 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}`);

Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed, and response headers identify the page verdict and billing status. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000.

Create a free ScreenshotNeo account and get 1,000 screenshots a month with no card.

8. FAQ

Does page.setContent accept a URL?

No. It accepts an HTML string. To load a URL, use Puppeteer navigation such as page.goto(url).

Does setting content clear the current page?

It assigns the supplied markup as the page content. Include the structure your task requires in the string.

Should I use page.setContent or frame.setContent?

Use the page method for the top-level page and the frame method when the content belongs in a particular frame.

Is a resolved promise proof that my app is ready?

It means the configured wait condition completed. If your next step depends on an app-specific state, wait for that state separately.