Skip to the tool
URLExtractor
Code and automation

How to Extract Webpage Links with JavaScript

Two lines in the browser console give you every link on the page — including the ones built by JavaScript that a crawler cannot see. Plus how to get anchor text, rel values and a downloadable file.

By URL Extractor EditorialPublished

The browser console is the fastest link extractor there is, and it has one ability nothing else has: it sees the page as rendered, including links that JavaScript created after load.

Open the console — F12, or Ctrl/Cmd + Shift + J — and paste one of these.

Every link on the page

Browser console — absolute addresses, one per line
javascript
[...document.querySelectorAll('a[href]')]
  .map(a => a.href)
  .join('\n')

Reading the `href` *property* (not the attribute) returns the address already resolved against the document, so relative links come back absolute.

That distinction is the single most useful thing to know here:

| Expression | <a href="/about"> on https://example.com/blog/ |
| --- | --- |
| a.href (property) | https://example.com/about — resolved |
| a.getAttribute('href') (attribute) | /about — as written |

The property is what you want almost always.

Unique, sorted, same-site only

Browser console — de-duplicated internal links
javascript
[...new Set(
   [...document.querySelectorAll('a[href]')]
     .map(a => a.href)
     .filter(href => new URL(href).hostname === location.hostname)
 )].sort().join('\n')

With anchor text and rel values

Browser console — a table you can read
javascript
console.table(
  [...document.querySelectorAll('a[href]')].map(a => ({
    url: a.href,
    written: a.getAttribute('href'),
    text: a.textContent.trim().replace(/\s+/g, ' ').slice(0, 60),
    rel: a.rel || '',
    target: a.target || '',
    scope: new URL(a.href).hostname === location.hostname ? 'internal' : 'external',
  }))
)

Straight to the clipboard

copy() is a console utility, not part of JavaScript — it only exists in the browser's developer tools, and it puts its argument on your clipboard:

javascript
copy([...document.querySelectorAll('a[href]')].map(a => a.href).join('\n'))

Download it as CSV

For a long list, a file is easier than the clipboard. This builds one in memory and triggers a download, with the quoting and formula-injection guard that a CSV needs:

Browser console — download links.csv
javascript
(() => {
  const cell = (value) => {
    let v = String(value ?? '');
    // A leading =, +, - or @ makes a spreadsheet treat the cell as a formula.
    if (/^[=+\-@\t\r]/.test(v)) v = "'" + v;
    return '"' + v.replace(/"/g, '""') + '"';
  };

  const rows = [...document.querySelectorAll('a[href]')].map(a => [
    a.href,
    a.textContent.trim().replace(/\s+/g, ' '),
    a.rel || '',
    new URL(a.href).hostname === location.hostname ? 'internal' : 'external',
  ]);

  const csv = '\ufeff' + [['URL', 'Anchor text', 'rel', 'Scope'], ...rows]
    .map(r => r.map(cell).join(','))
    .join('\r\n');

  const url = URL.createObjectURL(new Blob([csv], { type: 'text/csv' }));
  const link = Object.assign(document.createElement('a'), {
    href: url,
    download: 'links.csv',
  });
  link.click();
  URL.revokeObjectURL(url);
})();

The leading \ufeff is a UTF-8 byte-order mark, which Excel needs to read non-ASCII characters correctly from a .csv. The apostrophe prefix is the standard mitigation for CSV formula injection.

Other useful selectors

javascript
// Images, including srcset candidates
[...document.querySelectorAll('img')].flatMap(img => [
  img.currentSrc || img.src,
  ...(img.srcset || '').split(',').map(s => s.trim().split(/\s+/)[0]).filter(Boolean),
])

// Links that open in a new tab
[...document.querySelectorAll('a[target="_blank"]')].map(a => a.href)

// Links marked nofollow
[...document.querySelectorAll('a[rel~="nofollow"]')].map(a => a.href)

// Links inside one region only
[...document.querySelector('main').querySelectorAll('a[href]')].map(a => a.href)

When to use this instead of a crawler

The console's advantage is that it sees the rendered page. A server-side crawler — including ours — reads the HTML the server sends and does not run scripts, so links created in the browser are invisible to it.

So: if a crawl came back suspiciously empty on a site that clearly has navigation, open the page, run the snippet above, and you will usually find the links were never in the HTML at all.

The reverse is also true: the console works on one page at a time, so for anything across many pages a crawler or a sitemap is the right tool.

Frequently asked questions

Why do some results look different from the HTML?

`a.href` is resolved against the document, so `/about` becomes a full address, and the browser normalises encoding along the way. Use `a.getAttribute("href")` when you need the exact string that was written.

copy() is not defined.

It is a devtools console utility, so it only exists in the console — not in a page script, a bookmarklet or Node. Use the CSV download snippet instead in those contexts.

Can I run this on several pages at once?

Not from the console — each tab is separate. For a set of pages, use the crawl mode of the website tool, or the site’s sitemap.

Does this get links inside an iframe?

Only if the iframe is same-origin, and then you need to reach into `iframe.contentDocument` explicitly. Cross-origin frames are inaccessible by design.

Sources

HTML URL ExtractorPaste the rendered HTML here to get the same data with filters and export.

Tools used in this guide

Related guides

Share this pageWhatsAppXLinkedInFacebook