October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
browser automation

How to Remove Links from a Page with Puppeteer

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

To remove every ordinary <a> element from the current page, use page.$$eval('a', ...) to select the anchors in one page-context callback and call remove() on each one. This deletes the links and their contents from the rendered DOM. If you want to keep the text and only prevent navigation, remove or change the href attribute instead.

Remove all anchor elements

page.$$eval is a direct fit for a bulk edit: it selects all elements matching a selector, passes the resulting array to one callback in the page, and returns the callback’s result to Node.js. The callback below counts the matches before removing them, then returns that count.

const removedCount = await page.$$eval('a', anchors => {
  const count = anchors.length;
  anchors.forEach(anchor => anchor.remove());
  return count;
});

console.log(`Removed ${removedCount} links`);

The selector 'a' matches anchor elements regardless of whether their destination is internal or external, and whether they have an href attribute. The operation changes the live document in the browser tab; it does not edit the website’s files or server response.

Run it in a complete Puppeteer script

This CommonJS example launches a browser, opens a page, removes the anchors after navigation completes, prints the number matched, and closes the browser even if an error occurs. Replace the example URL with the page you control or are authorized to inspect.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();

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

    const removedCount = await page.$$eval('a', anchors => {
      const count = anchors.length;
      anchors.forEach(anchor => anchor.remove());
      return count;
    });

    console.log(`Removed ${removedCount} links`);
  } finally {
    await browser.close();
  }
})();

The removal runs after page.goto resolves. domcontentloaded waits for the document to be parsed, but page scripts may still add or replace content afterward. If the links are inserted later, wait for the relevant content or perform another removal after it appears. The script does not need to save or return the page’s modified HTML unless that is a separate requirement.

Keep the text but disable the links

anchor.remove() removes the element and its children, so linked words, images, or other nested content disappear too. When the goal is to make the page non-navigable while preserving what it displays, remove the href attributes instead:

const changedCount = await page.$$eval('a[href]', anchors => {
  const count = anchors.length;
  anchors.forEach(anchor => anchor.removeAttribute('href'));
  return count;
});

console.log(`Disabled ${changedCount} link destinations`);

This variant selects only anchors that currently have an href. Their text and child markup remain in place. It does not remove other link-related attributes or event handlers, so if the page has custom click behavior, inspect that behavior separately rather than assuming that removing href disables every possible action.

Choose the right Puppeteer API

Use page.$$eval for all matches

page.$$eval(selector, callback) passes the selector’s matching elements as an array to a single callback in the page context. It is the concise option for a bulk operation such as deleting all anchors or changing each matching element’s attribute.

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

Use page.evaluate for custom document logic

If selection depends on more than a single selector, query the document inside page.evaluate. The function runs in the page context, and Puppeteer returns its result to Node.js. For example, this removes anchors whose visible text matches a condition:

const removedCount = await page.evaluate(() => {
  const anchors = [...document.querySelectorAll('a')];
  const matches = anchors.filter(anchor => anchor.textContent?.trim() === 'Read more');

  matches.forEach(anchor => anchor.remove());
  return matches.length;
});

Keep the work that touches document inside the callback: browser-page objects and Node.js variables are not automatically available in that page context. Return plain values, such as a number or string, when the Node.js script needs a result.

Use page.$eval only when you intend to handle one match

page.$eval(selector, callback) applies its callback to the first matching element. It throws if nothing matches, so it is not a safe substitute for a bulk operation when the selector might find zero elements. If only one element is expected but may be absent, check for a match before using it, or use an API and control flow that explicitly handles the missing case.

Limit the removal to a specific part of the page

Removing every anchor can strip navigation, buttons implemented as links, and links in unrelated sections. When only one region should change, scope the selector to a stable container. For example, if the page has a container with the ID article-body:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const removedCount = await page.$$eval('#article-body a', anchors => {
  const count = anchors.length;
  anchors.forEach(anchor => anchor.remove());
  return count;
});

Use a selector that identifies the intended region, and check that it still matches the page’s markup before relying on it. If a site’s layout changes or uses a different container, the selector may match nothing or the wrong links. A count of zero is a useful signal to inspect the selector and the page state rather than treating the run as proof that the intended content was processed.

Handle pages that change after loading

The callback edits the DOM as it exists when the evaluation runs. It does not establish a permanent rule against later changes: scripts can insert new anchors, replace a modified section, or navigate to another document. If the page builds the target content asynchronously, wait for a meaningful selector or other known page condition before removing links, then verify that the target is present.

If new anchors appear after the first pass, run the same operation again after the page update. A repeated pass is often simpler than monitoring all mutations. An observer-based approach can be appropriate when content arrives continuously, but it needs a clearly defined lifetime and scope so it does not keep modifying unrelated content. The supplied removal callback itself does not watch for later mutations.

Frames and shadow roots need separate attention

A document-level 'a' query should not be assumed to include every link visible on screen. Content in an iframe belongs to a separate document context; select or evaluate in the relevant Puppeteer frame if those links are in scope. A simple document query also does not automatically traverse shadow roots. Puppeteer documents deep selector support, and open shadow roots can also be traversed deliberately when necessary. Closed or otherwise inaccessible content may require a different strategy.

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

Decide whether the task concerns only the top-level document or also specific embedded content. Then target those contexts explicitly and report counts per context if the result needs to be audited.

Troubleshoot common results

  • The visible text vanished too. That is expected from anchor.remove(), which removes the anchor and its child content. Use the removeAttribute('href') version if the content should remain.
  • The count is zero. Check that navigation has reached the page containing the links, that the selector matches its markup, and that the links are not inside a separate frame or shadow root. If scripts add them later, wait for the relevant content and run the operation afterward.
  • The links return after the callback. A page script may have inserted new anchors or rebuilt the modified area. Run the removal after that update, or use a deliberate observer strategy if repeated updates are part of the page behavior.
  • page.$eval throws. It requires a matching element. Use page.$$eval for zero-or-more matches, or explicitly check for the single element before applying a one-element callback.
  • The top page is clean but an embedded area is not. Inspect whether that area is an iframe or shadow-root content and query the appropriate context; the top-level query does not cover every such context automatically.
  • The page’s appearance changed unexpectedly. Removing anchors also removes their child nodes and can alter layout. Scope the selector more narrowly or preserve the elements and remove their destinations instead.

Performance, reliability, and scope

For a single bulk edit, one $$eval call avoids issuing a separate Node.js-to-page operation for every matching anchor. The returned number tells you how many elements the callback selected at that moment; it does not certify that later scripts will leave the document unchanged. If the operation is part of a larger automated workflow, log the page URL and count alongside the workflow’s own status so that a zero-match result can be distinguished from a successful edit.

DOM removal is temporary and local to the current rendered page. A reload or navigation creates a document from the website again, and the change does not persist on the site for other visitors. If you need a saved artifact, capture or serialize the modified page separately after making the change; this code alone only performs the in-memory DOM edit.

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

Or skip the browser setup

If your actual goal is a clean screenshot or PDF rather than changing a page’s DOM in an automation workflow, ScreenshotNeo can capture a URL with one GET request. It is not a replacement for Puppeteer code that must mutate the DOM or return the changed markup. Its optional clean-shot steps accept cookie or consent banners like a visitor and remove 60+ known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the page verdict and billing status. It also has an MCP server with take_screenshot, get_page_info, and capture_pdf tools for AI agents.

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

Example cURL call, using the documented API endpoint and parameters:

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

See the ScreenshotNeo API documentation for request options. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Sign up for the free plan to try it without a card.

Frequently Asked Questions

Does the returned count include links nested inside another anchor?

It counts every anchor matched in the initial selection before removal, including any nested matches present in the DOM. Nested anchor markup is non-conforming HTML, so do not use the count as a measure of distinct clickable areas.

Does this treat mail, telephone, and download links differently?

No. The selector matches anchors without classifying their destination. Narrow the selector or add a condition based on the element’s attributes if you want to leave particular kinds of links untouched.

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.

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 *

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.

Read next

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

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.