October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PCOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
HTML

How to Get HTML from a NodeList with Puppeteer

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use page.$$eval() to collect the markup for every element matching a selector: await page.$$eval('.item', elements => elements.map(element => element.outerHTML)). The result is an array of HTML strings returned to Node.js. Use outerHTML when you want each element including its own tag; use innerHTML for only its children, and page.content() for the whole document.

Get the HTML for every matching element

In Puppeteer, page.$$eval(selector, callback) finds all elements matching the CSS selector, passes them to a callback in the browser page, and returns the callback’s serializable result to Node.js. Map those elements to outerHTML to get one string per match:

const htmlByElement = await page.$$eval('.item', elements =>
  elements.map(element => element.outerHTML)
);

console.log(htmlByElement);

For example, if the page contains two elements matching .item, htmlByElement is an array with two strings, each containing one element’s markup. The array is empty when there are no matches. This is usually simpler than first obtaining a NodeList yourself: $$eval already supplies the matching elements to its callback.

The returned values cross from the page context into Node.js. Strings and arrays of strings are suitable results; return the HTML strings rather than DOM elements, which are page objects rather than ordinary serializable data.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Runnable Node.js example

This example starts a browser, opens a page, waits for matching elements to exist, extracts their markup, and closes the browser even if extraction fails. Install Puppeteer in your project first with npm install puppeteer.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: true });

  try {
    const page = await browser.newPage();
    await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });

    await page.waitForSelector('.item');
    const htmlByElement = await page.$$eval('.item', elements =>
      elements.map(element => element.outerHTML)
    );

    console.log(htmlByElement);
  } finally {
    await browser.close();
  }
})().catch(error => {
  console.error(error);
  process.exitCode = 1;
});

Replace https://example.com and .item with the page and selector you need. waitForSelector makes the example wait for at least one match; omit it if an empty array is an acceptable result. Puppeteer’s API reference pages retrieved for $$eval, $eval, evaluate, and $$ identified different versions (25.9.0, 25.12.0, and 25.11.0). Check the API reference corresponding to your installed Puppeteer version before relying on version-specific signature details.

Choose between outerHTML, innerHTML, and the full document

“HTML from a NodeList” can mean several different scopes. Choose based on exactly what markup you need, not just how many elements you select.

Need Puppeteer API or property What you get
Markup for all selector matches page.$$eval(selector, elements => elements.map(element => element.outerHTML)) An array of strings, one for each match; an empty array if none match.
Markup for the first match page.$eval(selector, element => element.outerHTML) A single string. $eval throws if nothing matches.
Only the contents inside an element page.$eval(selector, element => element.innerHTML) A string of the element’s child markup, without the element’s own opening and closing tags.
The entire page page.content() The page’s full HTML contents, including the DOCTYPE.
Element handles for later Puppeteer operations page.$$(selector) An array of ElementHandle objects; an empty array if there are no matches.

All matches: outerHTML

An element’s outerHTML includes the element itself and its descendants. For example, extracting a matched <article> returns the article tag along with its nested markup. When the requested result is the markup for each selected element, this is generally the property to use.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const articles = await page.$$eval('article', elements =>
  elements.map(element => element.outerHTML)
);

Children only: innerHTML

innerHTML excludes the selected element’s own tag. If you only need the contents inside a matched container, use:

const contents = await page.$$eval('.content', elements =>
  elements.map(element => element.innerHTML)
);

This still produces an array for all matches. For a single container, use page.$eval('.content', element => element.innerHTML).

The first match or the whole page

Use $eval when you want one match, not a collection. It evaluates the callback with the first matching element and throws when the selector finds none, so handle that case if the selector is optional. Use page.content() when your target is the complete page rather than a selected element.

Use a NodeList you already have

If your code is already running in the browser page and has a DOM NodeList, convert it to an array and map it. Array.from accepts a mapping callback, so it can produce the HTML strings directly:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const htmlByElement = Array.from(
  document.querySelectorAll('.item'),
  element => element.outerHTML
);

Within Puppeteer, you can do the equivalent inside evaluate:

const htmlByElement = await page.evaluate(() =>
  Array.from(
    document.querySelectorAll('.item'),
    element => element.outerHTML
  )
);

For this selector-based task, $$eval is the direct alternative: it supplies the matching elements to the callback, so a separate document.querySelectorAll() call is unnecessary.

Understand where the callback runs

Puppeteer evaluates the callback in the browser page context, not in the Node.js process. The function is converted and evaluated in that context. It cannot automatically access ordinary Node-side variables, imported modules, or helper functions from the surrounding file.

Keep the callback self-contained, as in elements => elements.map(element => element.outerHTML). If the callback needs a value calculated in Node.js, pass it as an argument supported by the relevant evaluation method rather than assuming a lexical variable is visible in the page. This distinction matters when an expression works in your Node.js file but fails after being sent to the browser.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use $$ or $$eval?

Choose based on whether you need HTML data or live element handles:

  • Use page.$$eval() when you want to calculate and return serializable results, such as an array of markup strings, in one evaluation.
  • Use page.$$('selector') when you need an array of ElementHandle objects for follow-up Puppeteer operations on individual elements. It resolves to an empty array when there are no matches.

A handle is useful when subsequent work requires Puppeteer to interact with the actual page element. If all you need is a string of HTML, $$eval avoids managing a collection of handles.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Wait for dynamic content when necessary

A selector may not match immediately after navigation if the page adds content asynchronously. Decide whether an empty result is valid. If it is, call $$eval directly and accept an empty array. If at least one matching element must appear, wait for the selector first:

await page.waitForSelector('.item');
const htmlByElement = await page.$$eval('.item', elements =>
  elements.map(element => element.outerHTML)
);

This wait establishes that a match appeared; it does not guarantee that a site will never update the element’s content afterward. Choose a wait condition that matches the page behavior you need, and use a site-specific readiness signal if the content has additional asynchronous steps.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Common problems and fixes

  • The result is empty. The selector may not match the current document, or the elements may not have been added yet. Check the selector and the page state; if a match is required, wait with waitForSelector.
  • $eval throws because no element exists. That API expects one match. Use $$eval if zero matches should produce an empty array, or wait for the single element before using $eval.
  • The string omits the selected tag. You likely read innerHTML, which contains only the children. Use outerHTML to include the selected element itself.
  • You received the whole document instead of separate strings. page.content() returns the complete page markup. Use $$eval with a map over the matching elements for one string per match.
  • A Node.js variable or helper is undefined in the callback. The callback runs in the page context. Put the needed logic inside it or pass required values as arguments; do not rely on Node-side lexical scope.
  • You need to interact with each match after selecting it. Return handles with page.$$() rather than strings with $$eval. The former yields an array of element handles and is empty when there are no matches.

Or skip the browser setup

If your actual goal is a screenshot or PDF rather than HTML strings, ScreenshotNeo can return one with a single GET request. This does not return a NodeList or HTML; it is an alternative for capturing a page visually.

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

See the ScreenshotNeo API documentation for the request options and response details. ScreenshotNeo accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each of those steps can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots.

Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month with no card.

FAQ

Does outerHTML include the selected element’s attributes?

Yes. It represents the element’s markup, including its own tag and attributes, as well as its descendants.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Can I return the selected DOM elements to Node.js instead of their HTML?

DOM elements belong to the browser page context. Return serializable data such as strings when you need a value in Node.js, or use Puppeteer element handles when you need to continue operating on the page elements.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.