cheerio Documentation

The fast, flexible, and elegant library for parsing and manipulating HTML and XML.

30,516 stars TypeScript TypeScript 1 file Β· ~16,419 tokens #cheerio#dom#hacktoberfest#html#htmlparser#htmlparser2#jquery#parser
RAW Doc

Technical Documentation: cheeriojs/cheerio

ℹ️ Provenance: Multi-Tier Ingestion Β· CodeWiki Reference

Overview (`Readme.md`)

cheerio

The fast, flexible, and elegant library for parsing and manipulating HTML and XML.

δΈ­ζ–‡ζ–‡ζ‘£ (Chinese Readme)

js
import * as cheerio from 'cheerio';
const $ = cheerio.load('<h2 class="title">Hello world</h2>');

$('h2.title').text('Hello there!');
$('h2').addClass('welcome');

$.html();
//=> <html><head></head><body><h2 class="title welcome">Hello there!</h2></body></html>

Installation

Install Cheerio using a package manager like npm, yarn, bun, or Deno.

bash
npm install cheerio
# or
bun add cheerio
# or
deno install cheerio

Features

❤ Proven syntax: Cheerio implements a subset of core jQuery. Cheerio
removes all the DOM inconsistencies and browser cruft from the jQuery library,
revealing its truly gorgeous API.

ϟ Blazingly fast: Cheerio works with a very simple, consistent DOM
model. As a result parsing, manipulating, and rendering are incredibly
efficient.

❁ Incredibly flexible: Cheerio wraps around
parse5 for parsing HTML and can optionally
use the forgiving htmlparser2. Cheerio
can parse nearly any HTML or XML document. Cheerio works in both browser and
server environments.

API

Loading

First you need to load in the HTML. This step in jQuery is implicit, since
jQuery operates on the one, baked-in DOM. With Cheerio, we need to pass in the
HTML document.

js
// ESM or TypeScript:
import * as cheerio from 'cheerio';

// In other environments:
const cheerio = require('cheerio');

const $ = cheerio.load('<ul id="fruits">...</ul>');

$.html();
//=> <html><head></head><body><ul id="fruits">...</ul></body></html>

Selectors

Once you've loaded the HTML, you can use jQuery-style selectors to find elements
within the document.

\$( selector, [context], [root] )

selector searches within the context scope which searches within the root
scope. selector and context can be a string expression, DOM Element, array
of DOM elements, or cheerio object. root, if provided, is typically the HTML
document string.

This selector method is the starting point for traversing and manipulating the
document. Like in jQuery, it's the primary method for selecting elements in the
document.

js
$('.apple', '#fruits').text();
//=> Apple

$('ul .pear').attr('class');
//=> pear

$('li[class=orange]').html();
//=> Orange

Rendering

When you're ready to render the document, you can call the html method on the
"root" selection:

js
$.root().html();
//=>  <html>
//      <head></head>
//      <body>
//        <ul id="fruits">
//          <li class="apple">Apple</li>
//          <li class="orange">Orange</li>
//          <li class="pear">Pear</li>
//        </ul>
//      </body>
//    </html>

If you want to render the
outerHTML
of a selection, you can use the outerHTML prop:

js
$('.pear').prop('outerHTML');
//=> <li class="pear">Pear</li>

You may also render the text content of a Cheerio object using the text
method:

js
const $ = cheerio.load('This is <em>content</em>.');
$('body').text();
//=> This is content.

The "DOM Node" object

Cheerio collections are made up of objects that bear some resemblance to
browser-based DOM nodes.
You can expect them to define the following properties:

  • tagName
  • parentNode
  • previousSibling
  • nextSibling
  • nodeValue
  • firstChild
  • childNodes
  • lastChild

Screencasts

https://vimeo.com/31950192

This video tutorial is a follow-up to Nettut's "How to Scrape Web Pages with
Node.js and jQuery", using cheerio instead of JSDOM + jQuery. This video shows
how easy it is to use cheerio and how much faster cheerio is than JSDOM +
jQuery.

Cheerio in the real world

Are you using cheerio in production? Add it to the
wiki!

Sponsors

Does your company use Cheerio in production? Please consider
sponsoring this project! Your
help will allow maintainers to dedicate more time and resources to its
development and support.

Headlining Sponsors

text
</a>
text
</a>

Other Sponsors

text
</a>
text
</a>
text
</a>

Backers

Become a backer to show your
support for Cheerio and help us maintain and improve this open source project.

text
</a>

License

MIT


Overview (`website/README.md`)

Website

This website is built using Docusaurus 2, a modern
static website generator.

Installation

text
$ yarn

Local Development

text
$ yarn start

This command starts a local development server and opens up a browser window.
Most changes are reflected live without having to restart the server.

Build

text
$ yarn build

This command generates static content into the build directory and can be
served using any static contents hosting service.


Website Src Content Docs Intro (`website/src/content/docs/intro.md`)


Welcome to Cheerio!

Cheerio parses HTML and XML, and gives you a jQuery-like API for traversing and
manipulating the result. This page takes you from installation to your first
scrape in about five minutes.

:::note[Cheerio is not a web browser]

Cheerio parses markup and provides an API for working with the resulting data
structure. It does not interpret that markup the way a browser does:
there is no visual rendering, no CSS, no loading of external resources, and no
JavaScript execution. That is what makes Cheerio much faster than
browser-based tools β€” but it also means content that a single-page app renders
on the client won't be there.

If you need any of that, reach for browser automation such as
Puppeteer or
Playwright, or a DOM emulation
project like jsdom.

:::

Installation

Cheerio runs on Node.js 22.19 or later. Once you have
Node.js installed, add Cheerio to your
project:

bash
npm install cheerio

Then import it:

js
import * as cheerio from 'cheerio';

If you prefer CommonJS, require works too:

js
const cheerio = require('cheerio');

Loading a document

Every Cheerio session starts by loading a document. The simplest way is load,
which takes a string of markup:

js
const $ = cheerio.load('<h2 class="title">Hello world</h2>');

load returns a $ function that you use to query the document, just like
jQuery's $.

Cheerio can also load documents from buffers, streams, and URLs β€” see
loading documents for the full list.

Selecting elements

Call $ with a CSS selector to find elements. Here we
select the <h2> with the class title and read its text:

js
$('h2.title').text(); // "Hello world"

Traversing the DOM

$ returns a Cheerio object β€” a list of matched elements with methods for
moving around the document. For example, find searches within the current
selection:

js
const $ = cheerio.load(`
  <div class="post">
    <h2 class="title">Hello world</h2>
    <p class="subtitle">Nice to meet you</p>
  </div>
`);

$('.post').find('.subtitle').text(); // "Nice to meet you"

There are methods for moving up, down, and sideways through the tree, plus a
range of filters. See traversing the DOM.

Manipulating elements

The same methods that read values also write them. Passing an argument to
text() replaces the text content, and methods like after() insert new
markup:

js
$('h2.title').text('Hello there!');

$('h2').after('<h3>How are you?</h3>');

Call $.html() at any point to serialize the document back to a string. See
manipulating elements for the full set of methods.


Website Src Content Docs Advanced Configuring Cheerio (`website/src/content/docs/advanced/configuring-cheerio.md`)


Configuring Cheerio

Cheerio ships with two parsers and picks between them based on the options you
pass to .load():

  • parse5 β€” the default for HTML. It rigorously
    conforms to the HTML standard, so it produces the same tree a browser would.
  • htmlparser2 β€” the default for XML.
    It is faster, uses less memory, and is more forgiving of malformed markup.

This guide covers how to configure each of them.

Parsing HTML with parse5

parse5 is used automatically for HTML. To change how it parses, pass an
options object as the second argument to .load():

js
import * as cheerio from 'cheerio';

// With scripting disabled, the contents of <noscript> are parsed as HTML.
const $ = cheerio.load('<noscript><h1>Nested Tag!</h1></noscript>', {
  scriptingEnabled: false,
});

For a full list of options and their effects, have a look at
the API documentation.

Fragment Mode

By default, parse5 treats its input as a complete document and wraps the
content in <html>, <head>, and `` elements β€” exactly as a browser
would:

js
const $ = cheerio.load('<li>Apple</li><li>Banana</li>');

$.html(); // => '<html><head></head><body><li>Apple</li><li>Banana</li></body></html>'

Pass false as the third argument to parse the input as a fragment instead,
without adding the surrounding document structure:

js
const $ = cheerio.load('<li>Apple</li><li>Banana</li>', {}, false);

$.html(); // => '<li>Apple</li><li>Banana</li>'

This is usually what you want when the input is a snippet of a larger page β€” a
table row or a list item, say β€” rather than a page of its own.

Parsing XML with htmlparser2

To parse a document as XML, set the xml option. Cheerio then uses
htmlparser2, which β€” unlike parse5 β€” will not add HTML-specific structure or
apply HTML parsing rules:

js
const $ = cheerio.load('<ul id="fruits">...</ul>', {
  xml: true,
});

If you need to customize the parsing options for XML input, you may pass an
object as the xml option to .load(), with the options you want to change:

js
const $ = cheerio.load('<ul id="fruits">...</ul>', {
  xml: {
    withStartIndices: true,
  },
});

When xml is set, the default options are:

js
{
    xmlMode: true, // Enable htmlparser2's XML mode.
    decodeEntities: true, // Decode HTML entities.
    withStartIndices: false, // Add a `startIndex` property to nodes.
    withEndIndices: false, // Add an `endIndex` property to nodes.
}

These options are passed straight through to htmlparser2, so anything valid
there is valid here. For a full list, see
the API documentation.

Using `htmlparser2` for HTML

You may want htmlparser2 to parse HTML too β€” if you are upgrading from a
pre-1.0 release of Cheerio (which used it by default), if the browser parsing
rules parse5 applies get in your way (htmlparser2 is more forgiving[^1]), or
if you are in a performance-critical situation (it is faster and the resulting
DOM uses less memory).

[^1]:
"More forgiving" means htmlparser2 has error-correcting mechanisms that
aren't always a match for the standards observed by web browsers. This
behavior may be useful when parsing non-HTML content.

To do that, disable xmlMode inside the xml option:

js
const $ = cheerio.load('<ul id="fruits">...</ul>', {
  xml: {
    // Disable `xmlMode` to parse HTML with htmlparser2.
    xmlMode: false,
  },
});

:::tip[The slim export]

If you never need parse5, import Cheerio's slim export instead. It always
uses htmlparser2 and leaves parse5 out of the bundle entirely β€” worth doing
in browser environments:

js
import * as cheerio from 'cheerio/slim';

:::

.load() also accepts an already-parsed, htmlparser2-compatible document as
its first argument:

js
import * as htmlparser2 from 'htmlparser2';

const dom = htmlparser2.parseDocument('<ul id="fruits">...</ul>', {
  xmlMode: true,
});

const $ = cheerio.load(dom);

:::warning

This route still uses parse5's serializer, so $.html() returns HTML rather
than XML and ignores the options you passed to htmlparser2. Disabling
xmlMode as shown above is the recommended approach.

:::


Website Src Content Docs Advanced Extending Cheerio (`website/src/content/docs/advanced/extending-cheerio.md`)


Extending Cheerio

Cheerio has two extension points: you can teach its selector engine new
pseudo-classes, and you can add your own methods to the Cheerio prototype.

Adding Custom CSS Pseudo-Classes

The pseudos option maps pseudo-class names to either a string or a function:

  • A string is a selector the element must match to be selected.
  • A function is called with the element as its first argument and the
    pseudo-class's parameter as its second. Return true to select the element.
js
const $ = cheerio.load('<div class="foo"></div><div data-bar="boo"></div>', {
  pseudos: {
    // `:foo` is an alias for `div.foo`
    foo: 'div.foo',
    // `:bar(val)` is equivalent to `[data-bar=val s]`
    bar: (el, val) => el.attribs['data-bar'] === val,
  },
});

$(':foo').length; // 1
$('div:bar(boo)').length; // 1
$('div:bar(baz)').length; // 0

Writing Plugins for Cheerio

Every loaded document has its own Cheerio prototype, reachable as $.prototype
or its alias $.fn. Adding a method there makes it available on every selection
from that document β€” and only that one, so plugins don't leak between documents.

js
const $ = cheerio.load('<html><body>Hello, <b>world</b>!</body></html>');

$.fn.logHtml = function () {
  console.log(this.html());
};

$('body').logHtml(); // logs "Hello, <b>world</b>!" to the console

Inside the method, this is the current selection, so the full Cheerio API is
available to you.

If you're using TypeScript, declare the method so the compiler knows about it:

ts
declare module 'cheerio' {
  interface Cheerio<T> {
    logHtml(this: Cheerio<T>): void;
  }
}

Website Src Content Docs Advanced Security (`website/src/content/docs/advanced/security.md`)


Security

Cheerio is usually pointed at pages someone else controls, so it is worth being
explicit about what it does and does not protect you from.

Cheerio parses markup, lets you query and change the resulting tree, and
serializes it back to markup. It never executes scripts, evaluates
expressions, or touches the network
β€” the one exception being
fromURL, which makes the request you asked for.

Cheerio is not a sanitizer

This is the big one. Cheerio's output is markup, not safe markup. If you
load a page containing <script> or an onerror attribute, those survive
parsing and serialization intact β€” that is correct behaviour for a parser, and
it means the output of $.html() is exactly as trustworthy as the input.

js
const $ = cheerio.load('<img src=x onerror="alert(1)">');

$.html(); // The onerror attribute is still there.

If you are going to render scraped markup in a browser, run it through a
dedicated sanitizer such as
sanitize-html or
DOMPurify first.

Reaching for text() instead of html() narrows the problem but does not
remove it: text() strips the markup structure, yet the string it returns can
still contain <, >, and ". Dropping that into an HTML sink builds markup
right back up. Assign it somewhere that treats it as text β€” textContent in a
browser, .text() in Cheerio β€” or escape it for whatever context it lands in.

Manipulation methods take raw HTML

html(), append(), prepend(), before(), after(), replaceWith(), and
wrap() all parse their argument as markup. Passing user input to any of them
injects whatever tags it contains:

js
// If `name` is "<img src=x onerror=…>", you just injected an element.
$('#greeting').html(`Hello, ${name}`);

// `text()` escapes instead, so this is safe.
$('#greeting').text(`Hello, ${name}`);

Use text() when you mean text. Reach for the markup-parsing methods only with
content you construct or have sanitized.

Selectors built from user input

Treat a selector from an untrusted source the way you would treat a regular
expression from an untrusted source: don't. A crafted selector can make the
engine do a lot of work, and it can match things you didn't intend.

Quoting the value is not enough. A " in the value closes the attribute
selector, and everything after it is parsed as more selector syntax:

js
const id = 'x"], [data-id="secret';

// Becomes: [data-id="x"], [data-id="secret"] β€” two selectors, not one.
$(`[data-id="${id}"]`); // Matches the element the caller was never meant to see.

Cheerio has no CSS.escape, so the reliable fix is to keep the value out of
the selector entirely: match on a fixed selector, then compare the attribute as
data.

js
const matches = $('[data-id]').filter((_, el) => $(el).attr('data-id') === id);

Bound the input you accept

Parsing is proportional to input size, and Cheerio will happily try to parse a
500 MB response. If you accept markup from somewhere you don't control, cap the
size before handing it over β€” and when using fromURL, remember that the URL
itself decides what gets fetched, so validate it if it comes from a user
(otherwise you have an SSRF).

Reporting a vulnerability

See
SECURITY.md for
the reporting process, and
THREAT_MODEL.md
for the full statement of what is and isn't considered a vulnerability.


Website Src Content Docs Basics Extract (`website/src/content/docs/basics/extract.mdx`)


import { LiveCode } from '@/components/live-code';

Extracting Data with the `extract` Method

extract pulls several values out of a document in one call and returns them as
a plain object. Instead of writing a chain of selections and text() calls, you
describe the shape of the data you want and let Cheerio fill it in.

You pass extract a map: the keys become properties on the result, and the
values describe what to put in them.

The examples below all run against this document:

js
import * as cheerio from 'cheerio';

const $ = cheerio.load(`
  <ul>
    <li>One</li>
    <li>Two</li>
    <li class="blue sel">Three</li>
    <li class="red">Four</li>
  </ul>
  <ul>
    <li class="sel">Five</li>
    <li class="red">Six</li>
  </ul>
  <a href="/docs">Docs</a>
  <a href="/blog">Blog</a>
`);

A single value

The simplest descriptor is a selector string. It yields the text content of the
first matching element:

js
$.extract({ red: '.red' });
//=> { red: 'Four' }

Lists of values

Wrap the descriptor in an array to collect every match instead:

js
$.extract({ red: ['.red'] });
//=> { red: ['Four', 'Six'] }

That one bracket is the whole difference between "the first match" and "every
match":

js live
const $ = cheerio.load(`
  <ul class="fruit">
    <li class="sel">Apple</li>
    <li>Banana</li>
  </ul>
  <ul class="veg">
    <li class="sel">Carrot</li>
    <li>Daikon</li>
  </ul>
`);

console.log($.extract({ selected: '.sel' }));
console.log($.extract({ selected: ['.sel'] }));

Extracting something other than text

To extract an attribute or another property, replace the string with an object
that has a selector and a value:

js
$.extract({
  red: '.red',
  links: {
    selector: 'a',
    value: 'href',
  },
});
//=> { red: 'Four', links: '/docs' }

value is passed to Cheerio's
prop method, so anything prop understands
works here β€” outerHTML, innerHTML, tagName, innerText, or any attribute
name. It defaults to textContent, which is why a bare selector gives you text.

:::tip[Relative URLs]

href and src are resolved against the document's URL. This document doesn't
have one, which is why the example above returns the raw /docs.
fromURL sets a URL automatically; with the
other loaders, pass the
baseURI option to get absolute
URLs back.

:::

Descriptor objects can be wrapped in an array just like plain selectors:

js
$.extract({
  red: [{ selector: '.red', value: 'outerHTML' }],
});
//=> { red: ['<li class="red">Four</li>', '<li class="red">Six</li>'] }

Nested objects

Pass an object as value to extract a nested structure. Selectors inside it are
evaluated relative to the outer selection, which makes it easy to pull
repeated records out of a page:

js
$.extract({
  lists: [
    {
      selector: 'ul',
      value: {
        red: ['.red'],
        selected: '.sel',
      },
    },
  ],
});
//=> {
//     lists: [
//       { red: ['Four'], selected: 'Three' },
//       { red: ['Six'], selected: 'Five' },
//     ],
//   }

This is the pattern you'll use most when scraping: an outer selector picks the
repeating element, and the inner map describes one record.

js live
const $ = cheerio.load(`
  <ul class="fruit">
    <li class="sel">Apple</li>
    <li>Banana</li>
  </ul>
  <ul class="veg">
    <li class="sel">Carrot</li>
    <li>Daikon</li>
  </ul>
`);

const data = $.extract({
  lists: [
    {
      selector: 'ul',
      value: {
        items: ['li'],
        selected: '.sel',
      },
    },
  ],
});

console.log(JSON.stringify(data, null, 2));

Computed values

Finally, value can be a function. It's called with each selected element and
the key it's being extracted for, and whatever it returns is used as the value:

js
$.extract({
  links: [
    {
      selector: 'a',
      value: (el, key) => `${key}=${$(el).attr('href')}`,
    },
  ],
});
//=> { links: ['links=/docs', 'links=/blog'] }

Putting it all together

Here we fetch Cheerio's releases page and extract the name, date, and notes of
each release:

js
import * as cheerio from 'cheerio';

const $ = await cheerio.fromURL(
  'https://github.com/cheeriojs/cheerio/releases',
);

const data = $.extract({
  releases: [
    {
      // First, select the individual release sections.
      selector: 'section',
      // Then extract the release date, name, and notes from each one.
      value: {
        // Selectors here run within the context of the selected section.
        name: 'h2',
        date: {
          selector: 'relative-time',
          // The actual release date is stored in the `datetime` attribute.
          value: 'datetime',
        },
        notes: {
          selector: '.markdown-body',
          // We want the markup, not just the text.
          value: 'innerHTML',
        },
      },
    },
  ],
});

Website Src Content Docs Basics Loading (`website/src/content/docs/basics/loading.md`)


Loading Documents

Before you can query a document, Cheerio has to parse it. Which method you reach
for depends on where the markup comes from and whether you know its character
encoding.

:::tip

If you're coming from jQuery, this step is new. jQuery operates on the one
baked-in DOM of the page it runs on; with Cheerio you pass in the document
yourself.

:::

Method Input Use it when
load string You already have the markup as a string.
loadBuffer Buffer You have raw bytes and the encoding is unknown.
stringStream stream of decoded text You're streaming and already know the encoding.
decodeStream stream of raw bytes You're streaming and the encoding is unknown.
fromURL URL You want Cheerio to fetch the page for you.

The methods that accept bytes β€” loadBuffer and decodeStream β€” run the
HTML encoding sniffing algorithm,
so they will pick up a <meta charset> or a byte order mark. Prefer them
whenever you can't be sure the source is UTF-8.

:::danger[Browser environments]

Only load is available in the browser. loadBuffer, stringStream,
decodeStream, and fromURL rely on Node.js APIs and are not included in the
browser build.

:::

`load`

load takes a string containing the document and returns a $ function you can
use to traverse and manipulate it.

js
import * as cheerio from 'cheerio';

const $ = cheerio.load('<h1>Hello, world!</h1>');

console.log($('h1').text());
// Output: Hello, world!

:::tip[Parsing fragments]

Like a browser, load adds <html>, <head>, and `` elements if they
aren't already present. Pass false as the third argument to parse the input as
a fragment instead:

js
const $ = cheerio.load('<ul id="fruits">...</ul>', null, false);

$.html();
//=> '<ul id="fruits">...</ul>'

See Configuring Cheerio for
details.

:::

Learn more about the load method in the
API documentation.

`loadBuffer`

loadBuffer works like load, but takes a Buffer instead of a string. Cheerio
determines the encoding from the bytes themselves, which makes it the right
choice for files and network responses whose encoding you don't control.

js
import * as cheerio from 'cheerio';
import * as fs from 'node:fs';

// The file may be UTF-8, ISO-8859-1, … β€” Cheerio works it out.
const $ = cheerio.loadBuffer(fs.readFileSync('document.html'));

console.log($('title').text());

Learn more about the loadBuffer method in the
API documentation.

`stringStream`

stringStream returns a writable stream that parses the document as it arrives.
Use it when the encoding is already known, so the stream can be decoded to text
before Cheerio sees it.

js
import * as cheerio from 'cheerio';
import * as fs from 'node:fs';

const writeStream = cheerio.stringStream({}, (err, $) => {
  if (err) {
    // Handle error
    return;
  }

  console.log($('title').text());
});

fs.createReadStream('document.html', { encoding: 'utf8' }).pipe(writeStream);

The first argument holds Cheerio's options;
the second is a callback that receives the finished document.

Learn more about the stringStream method in the
API documentation.

`decodeStream`

decodeStream is the streaming counterpart to loadBuffer: it accepts raw bytes
and runs the encoding sniffing algorithm before parsing. Reach for it when
streaming a document of unknown encoding.

js
import * as cheerio from 'cheerio';
import * as fs from 'node:fs';

const writeStream = cheerio.decodeStream({}, (err, $) => {
  if (err) {
    // Handle error
    return;
  }

  console.log($('title').text());
});

// Note: no `encoding` option β€” the bytes are passed through as-is.
fs.createReadStream('document.html').pipe(writeStream);

Learn more about the decodeStream method in the
API documentation.

`fromURL`

fromURL is the one loader that fetches for you. It is asynchronous, so await
the result:

js
import * as cheerio from 'cheerio';

const $ = await cheerio.fromURL('https://example.com');

It does rather more than a bare fetch would, and the details are worth
knowing:

  • Redirects are followed, up to five of them.
  • Non-2xx responses reject with an undici ResponseError carrying the
    status code, rather than handing you an error page to parse.
  • Non-markup responses reject with a RangeError. If the Content-Type is
    neither HTML nor XML, fromURL refuses rather than parsing a PDF as HTML.
  • XML mode is chosen for you from that same Content-Type, so an
    application/xml response is parsed as XML without your asking.
  • The encoding comes from the charset parameter of the Content-Type when
    present, and from sniffing the bytes otherwise.
  • baseURI is set to the final URL β€” after redirects. That is what lets
    prop('href') and extract hand you absolute links.

Customizing the request

Pass requestOptions to control the request. These go to
undici's stream method:

js
const $ = await cheerio.fromURL('https://example.com', {
  requestOptions: {
    method: 'GET',
    headers: {
      'user-agent': 'my-scraper/1.0 (+https://example.com/bot)',
    },
  },
});

:::warning[requestOptions overrides, it doesn't merge]

Two things to watch for:

  • method is not defaulted. Supplying requestOptions without it fails
    with method must be a string, so always include it.
  • headers is all-or-nothing. Omit it and you keep Cheerio's default
    Accept header; supply it and yours replaces that default outright, rather
    than being added to it.

:::

Learn more about the fromURL method in the
API documentation, and see
Security before pointing it at a URL that came from a
user.


Website Src Content Docs Basics Manipulation (`website/src/content/docs/basics/manipulation.md`)


Manipulating the DOM

Once you've selected some elements, Cheerio lets you
change them: attributes, classes, text, HTML, and the structure of the tree
itself.

Most methods here do double duty: called without an argument they read,
called with an argument they write. Writes always apply to every element in
the selection and return the selection, so they chain. Reads usually look at
only the first element β€” the exceptions are called out below.

When you're done, $.html() serializes the document back to a string.

Modifying Element Attributes and Properties

attr() reads and writes HTML attributes β€”
the values literally present in the markup.
prop() reads and writes properties, the
values a browser would compute from those attributes. Both take a key, plus a
value when setting.

js
// Set the 'src' attribute of an image element
$('img').attr('src', 'https://example.com/image.jpg');

// Set the 'checked' property of a checkbox element
$('input[type="checkbox"]').prop('checked', true);

// Get the 'href' attribute of a link element
const href = $('a').attr('href');

// Get the 'disabled' property of a button element
const isDisabled = $('button').prop('disabled');

That distinction matters most for URLs and booleans: attr('href') gives you
the raw string from the markup, while prop('href') resolves it against the
document's URL. prop() also exposes computed values that don't exist as
attributes at all β€” style, tagName, innerHTML, outerHTML,
textContent, and innerText.

js
// Get the `style` object of an element
const style = $('div').prop('style');

// Get the resolved `src` URL of an image element
$('img').prop('src');

// Get the outerHTML of an element
const outerHTML = $('div').prop('outerHTML');

// Get the innerText of an element
const innerText = $('div').prop('innerText');

Adding and Removing Classes

To add or remove classes from an element, you can use the
addClass(),
removeClass(), and
toggleClass() methods. All three
methods take a class name or a space-separated list of class names as an
argument. They modify all elements in the selection.

js
// Add a class to an element
$('div').addClass('new-class');

// Add multiple classes to an element
$('div').addClass('new-class another-class');

// Remove a class from an element
$('div').removeClass('old-class');

// Remove multiple classes from an element
$('div').removeClass('old-class another-class');

// Toggle a class on an element (add if it doesn't exist, remove if it does)
$('div').toggleClass('active');

Modifying the Text Content of an Element

To query or modify the text content of an element, you can use the
text() method. Given a string as an
argument, it sets the text content of every element in the selection to the
given string. Without arguments, it returns the text content of every element
(including its descendants) in the selection, concatenated together.

js
// Set the text content of an element
$('h1').text('Hello, World!');

// Get the text content of an element
const text = $('p').text();

:::warning[text() includes script and style content]

text() returns the raw textContent, which means the source of any
<script> and <style> elements in the selection ends up in the result.
.prop('innerText') skips those two, which is usually what you want β€” but note
that it works from the tree alone. Cheerio applies no CSS, so content hidden by
display: none or a hidden attribute is still included.

:::

Modifying the HTML Content of an Element

To query or modify the HTML content of an element, you can use the
html() method. Given an HTML string as an
argument, it sets the inner HTML of every element in the selection to the given
string. Without arguments, it returns the inner HTML of the first element in
the selection.

js
// Set the inner HTML of an element
$('div').html('<p>Hello, World!</p>');

// Get the inner HTML of an element
const html = $('div').html();

Inserting New Elements

To insert new elements into a document, you can use the
append(),
prepend(),
before(), and
after() methods. These modify every element
in the selection.

js
// Append an element to the end of a parent element
$('ul').append('<li>Item</li>');

// Prepend an element to the beginning of a parent element
$('ul').prepend('<li>Item</li>');

// Insert an element before a target element
$('li').before('<li>Item</li>');

// Insert an element after a target element
$('li').after('<li>Item</li>');

Each of these has a mirrored counterpart β€”
appendTo(),
prependTo(),
insertBefore(), and
insertAfter() β€” which swap the roles
of the two sides: the selection is the content being inserted, and the argument
is the target. Use whichever reads better at the call site.

js
// These two lines do the same thing
$('ul').append('<li>Item</li>');
$('<li>Item</li>').appendTo('ul');

// …as do these
$('h1').after('<p>Inserted element</p>');
$('<p>Inserted element</p>').insertAfter('h1');

Wrapping and Unwrapping Elements

Sometimes you may want to wrap an element in another element, or remove the
element's parent element while keeping its children. To do this, you can use the
wrap(), wrapInner(), and unwrap() methods.

The wrap() method takes a string or a
Cheerio object as an argument and wraps the element in the given element.

js
// Wrap an element in a div
$('p').wrap('<div></div>');

The wrapInner() method works like
wrap(), but wraps the element's contents rather than the element itself.

js
// <div>text</div> becomes <div><span>text</span></div>
$('div').wrapInner('<span></span>');

The unwrap() method removes the element's
parent element, while keeping the element and its children.

js
// Unwrap an element
$('p').unwrap();

Replacing Elements

To replace an element with another element, you can use the
replaceWith() method. It takes a
string or a Cheerio object as an argument and replaces each element in the
selection with the given element.

js
// Replace an element with another element
$('li').replaceWith('<li>Item</li>');

Note that the replaceWith() method removes the element from the document and
replaces it with the given element or HTML string. If you want to keep the
element and modify its contents, you can use the html() or text() methods
instead.

Removing Elements

To remove an element from a document, you can use the
remove() method. It removes each element
in the selection, and all of their children, from the document.

js
// Remove an element from the document
$('li').remove();

To clear an element without removing it, use
empty(). It removes everything inside each
element in the selection β€” child elements, text, and comments alike β€” leaving
the element itself in place.

js
// <li><b>a</b> text</li> becomes <li></li>
$('li').empty();

Where to go next

To pull many values out of a document at once rather than method by method, see
the extract method. To add your own methods to the
Cheerio prototype, see
Extending Cheerio.


Website Src Content Docs Basics Selecting (`website/src/content/docs/basics/selecting.mdx`)


import { LiveCode } from '@/components/live-code';

Selecting Elements

Cheerio finds elements using
CSS selectors,
the same syntax you would use in a stylesheet or in document.querySelectorAll.
This guide covers the selectors you'll reach for most often, and where to look
for the rest.

Start by loading a document. The load call returns the
$ function, which is what you pass selectors to:

js
import * as cheerio from 'cheerio';

const $ = cheerio.load('<html>...</html>');

:::tip

By convention, variables holding a Cheerio object are prefixed with $. It's
not required, but it makes code much easier to scan.

:::

Basic selectors

Selector Matches
$('p') All `` elements.
$('.selected') All elements with the class selected.
$('#main') The element with the id main.
$('[data-selected=true]') All elements whose data-selected is true.
$('*') Every element in the document.

The examples on this page are live β€” edit them and press Run.

js live
const $ = cheerio.load(`
  <article id="post">
    <h1>Cheerio</h1>
    <p class="intro">Fast, flexible, and lean.</p>
    <p data-kind="note">Not a web browser.</p>
    <p>Hello from the last paragraph.</p>
  </article>
`);

console.log('By tag:', $('p').length);
console.log('By class:', $('.intro').text());
console.log('By id:', $('#post h1').text());
console.log('By attribute:', $('[data-kind=note]').text());

:::tip[XML namespaces]

Namespaced attributes can be selected, but
the CSS specification
requires the colon (:) to be escaped:

js
$('[xml\\:id="main"]');

:::

Combining selectors

Write selectors next to each other to require that an element match all of them:

js
// <p> elements that also have the class `selected`
const $selected = $('p.selected');

Separate selectors with a comma to match any of them:

js
// All <h1> and <h2> elements
const $headings = $('h1, h2');

Selecting by relationship

Combinators describe where an element sits relative to another:

Selector Matches
$('div p') elements anywhere inside a.
$('div > p') elements that are direct children of a.
$('h2 + p') The `` immediately following an <h2>.
$('h2 ~ p') All `` elements following an <h2> under the same parent.

Note the difference between the first two: div p reaches the nested inside the, while div > p stops at the direct children.

js live
const $ = cheerio.load(`
  <div>
    <h2>Fruit</h2>
    <p>Apple</p>
    <p>Banana</p>
    <ul><li><p>Cherry</p></li></ul>
  </div>
`);

console.log('div p:', $('div p').length); // 3 β€” includes the nested one
console.log('div > p:', $('div > p').length); // 2 β€” direct children only
console.log('h2 + p:', $('h2 + p').text());
console.log('h2 ~ p:', $('h2 ~ p').length);

Beyond standard CSS

Cheerio's selector engine,
css-select,
supports most standard pseudo-classes β€” see its README for the exact list β€”
plus :contains(), which matches on text. On top of that,
cheerio-select
adds jQuery's positional extensions β€” :first, :last, and :eq(n). These
are not valid CSS and won't work in a browser, but they're handy when scraping.

js live
const $ = cheerio.load(`
  <ul>
    <li>Apple</li>
    <li>Banana</li>
    <li>Cherry</li>
  </ul>
`);

console.log(':contains:', $('li:contains("an")').text());
console.log(':first:', $('li:first').text());
console.log(':last:', $('li:last').text());
console.log(':eq(1):', $('li:eq(1)').text()); // zero-based

You can also define your own pseudo-classes β€” see
Extending Cheerio.

Where to go next

Once you have a selection, use the
traversal methods to move around it, or the
manipulation methods to change it.

For more on selectors themselves, see
MDN and
jQuery's guide to
selecting elements.


Website Src Content Docs Basics Traversing (`website/src/content/docs/basics/traversing.mdx`)


import { LiveCode } from '@/components/live-code';

Traversing the DOM

A selector gets you a starting point; traversal methods get you from there to
whatever you actually want. Every method below returns a new selection β€”
the original is left untouched β€” so calls can be chained freely.

The examples on this page are live: edit them and hit Run to see the result.

Goal Methods
Move down find, children, contents
Move up parent, parents, parentsUntil, closest
Move sideways next, prev, nextAll, prevAll, siblings, nextUntil, prevUntil
Narrow the current set eq, filter, not, has, first, last

For the full signatures and edge cases, see the
Cheerio API reference.

Moving Down the DOM Tree

Cheerio provides several methods for moving down the DOM tree and selecting
elements that are children or descendants of the current selection.

`find`

The find method allows you to locate
specific elements within a selection. It takes a CSS selector as an argument and
returns a new selection containing all elements that match the selector within
the current selection.

Here's an example of using find to select all <li> elements within a ``
element:

js live
const $ = cheerio.load(
  `<ul>
    <li>Item 1</li>
    <li>Item 2</li>
  </ul>`,
);

const listItems = $('ul').find('li');
console.log(`List item count: ${listItems.length}`);

`children`

The children method allows you to select
the direct children of an element. It returns a new selection containing all
direct children of the current selection.

Here's an example of using children to select all <li> elements within a
`` element:

js live
const $ = cheerio.load(
  `<ul>
    <li>Item 1</li>
    <li>Item 2</li>
  </ul>`,
);

const listItems = $('ul').children('li');
console.log(`List item count: ${listItems.length}`);

`contents`

The contents method allows you to select
all children of an element, including text and comment nodes. It returns a new
selection containing all children of the current selection.

Here's an example of using contents to select all children of a ``
element:

js live
const $ = cheerio.load(
  `<div>
    Text <p>Paragraph</p>
  </div>`,
);

const contents = $('div').contents();
console.log(`Contents count: ${contents.length}`);

Moving Up the DOM Tree

Cheerio provides several methods for moving up the DOM tree and selecting
elements that are ancestors of the current selection.

`parent`

The parent method allows you to select the
parent element of a selection. It returns a new selection containing the parent
element of each element in the current selection.

Here's an example of using parent to select the parent `` element of a
<li> element:

js live
const $ = cheerio.load(
  `<ul>
    <li>Item 1</li>
  </ul>`,
);

const list = $('li').parent();
console.log(list.prop('tagName'));

`parents` and `parentsUntil`

The parents method allows you to select
all ancestor elements of a selection, up to the root element. It returns a new
selection containing all ancestor elements of the current selection.

The parentsUntil method is similar
to parents, but allows you to specify an ancestor element as a stop point. It
returns a new selection containing all ancestor elements of the current
selection up to (but not including) the specified ancestor.

Here's an example of using parents and parentsUntil to select ancestor
elements of a <li> element:

js live
const $ = cheerio.load(
  `<div>
    <ul>
      <li>Item 1</li>
    </ul>
  </div>`,
);

const ancestors = $('li').parents();
const ancestorsUntil = $('li').parentsUntil('div');

console.log(`Ancestor count (includes body and html): ${ancestors.length}`);
console.log(`Ancestor count (until div): ${ancestorsUntil.length}`);

`closest`

The closest method allows you to select
the closest ancestor matching a given selector. It returns a new selection
containing the closest ancestor element that matches the selector. If no
matching ancestor is found, the method returns an empty selection.

Here's an example of using closest to select the closest ancestor ``
element of a <li> element:

js live
const $ = cheerio.load(
  `<div>
    <ul>
      <li>Item 1</li>
    </ul>
  </div>`,
);

const list = $('li').closest('ul');
console.log(list.prop('tagName'));

Moving Sideways Within the DOM Tree

Cheerio provides several methods for moving sideways within the DOM tree and
selecting elements that are siblings of the current selection.

`next` and `prev`

The next method allows you to select the
next sibling element of a selection. It returns a new selection containing the
next sibling element (if there is one). If the given selection contains multiple
elements, next includes the next sibling for each one.

The prev method is similar to next, but
allows you to select the previous sibling element. It returns a new selection
containing the previous sibling element for each element in the given selection.

Here's an example of using next and prev to select sibling elements of a
<li> element:

js live
const $ = cheerio.load(
  `<ul>
    <li>Item 1</li>
    <li>Item 2</li>
  </ul>`,
);

const nextItem = $('li:first').next();
const prevItem = $('li:eq(1)').prev();

console.log(`Next: ${nextItem.text()}`);
console.log(`Prev: ${prevItem.text()}`);

`nextAll`, `prevAll`, and `siblings`

The nextAll method allows you to select
all siblings after the current element. It returns a new selection containing
all sibling elements after each element in the current selection.

The prevAll method is similar to nextAll,
but allows you to select all siblings before the current element. It returns a
new selection containing all sibling elements before each element in the current
selection.

The siblings method allows you to select
all siblings of a selection. It returns a new selection containing all sibling
elements of each element in the current selection.

Here's an example of using nextAll, prevAll, and siblings to select
sibling elements of a <li> element:

js live
const $ = cheerio.load(
  `<ul>
    <li>[1]</li>
    <li>[2]</li>
    <li>[3]</li>
  </ul>`,
);

const nextAll = $('li:first').nextAll();
const prevAll = $('li:last').prevAll();
const siblings = $('li:eq(1)').siblings();

console.log(`Next All: ${nextAll.text()}`);
console.log(`Prev All: ${prevAll.text()}`);
console.log(`Siblings: ${siblings.text()}`);

`nextUntil` and `prevUntil`

The nextUntil method allows you to
select all siblings after the current element up to a specified sibling. It
takes a selector or a sibling element as an argument and returns a new selection
containing all sibling elements after the current element up to (but not
including) the specified element.

The prevUntil method is similar to
nextUntil, but allows you to select all siblings before the current element up
to a specified sibling. It takes a selector or a sibling element as an argument
and returns a new selection containing all sibling elements before the current
element up to (but not including) the specified element.

Here's an example of using nextUntil and prevUntil to select sibling
elements of a <li> element:

js live
const $ = cheerio.load(
  `<ul>
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
  </ul>`,
);

const nextUntil = $('li:first').nextUntil('li:last-child');
const prevUntil = $('li:last').prevUntil('li:first-child');

console.log(`Next: ${nextUntil.text()}`);
console.log(`Prev: ${prevUntil.text()}`);

Filtering elements

Cheerio provides several methods for filtering elements within a selection.

:::tip

Most of these filters also exist as selectors. For example, the first method
is available as the :first selector. Users are encouraged to use the selector
syntax when possible, as it is more performant.

:::

`eq`

The eq method allows you to select an element
at a specified index within a selection. It takes an index as an argument and
returns a new selection containing the element at the specified index.

Here's an example of using eq to select the second <li> element within a
`` element:

js live
const $ = cheerio.load(
  `<ul>
    <li>Item 1</li>
    <li>Item 2</li>
  </ul>`,
);

const secondItem = $('li').eq(1);
console.log(secondItem.text());

`filter` and `not`

The filter method allows you to select
elements that match a given selector. It takes a selector as an argument and
returns a new selection containing only those elements that match the selector.

The not method is similar to filter, but
allows you to select elements that do not match a given selector. It takes a
selector as an argument and returns a new selection containing only those
elements that do not match the selector.

Here's an example of using filter and not to select <li> elements within a
`` element:

js live
const $ = cheerio.load(
  `<ul>
    <li class="item">Item 1</li>
    <li>Item 2</li>
  </ul>`,
);

const matchingItems = $('li').filter('.item');
const nonMatchingItems = $('li').not('.item');

console.log(`Matching: ${matchingItems.text()}`);
console.log(`Non-matching: ${nonMatchingItems.text()}`);

`has`

The has method allows you to select elements
that contain an element matching a given selector. It takes a selector as an
argument and returns a new selection containing only those elements that contain
an element matching the selector.

Here's an example of using has to select <li> elements within a element that contain a element:

js live
const $ = cheerio.load(
  `<ul>
    <li>Item 1</li>
    <li>
      <strong>Item 2</strong>
    </li>
  </ul>`,
);

const matchingItems = $('li').has('strong');
console.log(matchingItems.length);

`first` and `last`

The first method allows you to select the
first element in a selection. It returns a new selection containing the first
element.

The last method is similar to first, but
allows you to select the last element in a selection. It returns a new selection
containing the last element.

Here's an example of using first and last to select elements within a ``
element:

js live
const $ = cheerio.load(
  `<ul>
    <li>Item 1</li>
    <li>Item 2</li>
  </ul>`,
);

const firstItem = $('li').first();
const lastItem = $('li').last();

console.log(`First: ${firstItem.text()}`);
console.log(`Last: ${lastItem.text()}`);

Where to go next

With a selection in hand, head to
manipulating the DOM to change it, or
the extract method to pull structured data out of it
in one go.


Website Src Content Docs Basics Troubleshooting (`website/src/content/docs/basics/troubleshooting.mdx`)


import { LiveCode } from '@/components/live-code';

Troubleshooting

Most Cheerio surprises come from one of a handful of causes. This page collects
them, roughly in the order people hit them.

My selection is empty

$('…') never throws when nothing matches β€” it returns an empty selection, and
every method you chain onto it quietly returns an empty result. text() gives
you '', attr() gives you undefined. So the first thing to check is
length:

js live
const $ = cheerio.load('<div class="post"><h1>Title</h1></div>');

console.log($('h1').length); // 1 β€” found it
console.log($('h2').length); // 0 β€” no match
console.log($('h2').text()); // '' β€” not an error

Once you know the selection is empty, the usual causes are:

The content is rendered by JavaScript. Cheerio does not run scripts. If the
markup you're looking for is built client-side by React, Vue, or similar, it
isn't in the HTML Cheerio received. Check what you actually got:

js
console.log($.html().slice(0, 500));

If you see an empty `` and a bundle <script>, you need
Puppeteer or
Playwright rather than Cheerio.

The class or id is dynamic. Build-tool-generated names like
css-1x2y3z change on every deploy. Prefer stable anchors: data- attributes,
element structure, or text with :contains().

You're scoping to the wrong element. Inside find() and inside an
extract nested value, selectors run relative to the
current selection, not the document.

`text()` gives me JavaScript or CSS

text() returns the raw textContent, which includes the source of any
<script> and <style> elements inside the selection. Use
.prop('innerText'), which skips both. It works from the tree alone, though β€”
Cheerio applies no CSS, so display: none content still comes through:

js live
const $ = cheerio.load(`
  <div>
    Hello
    <style>p { color: red }</style>
    <script>var x = 1;</script>
  </div>
`);

console.log(JSON.stringify($('div').text()));
console.log(JSON.stringify($('div').prop('innerText')));

`text()` is full of whitespace

Cheerio preserves the document's whitespace exactly, including the newlines and
indentation between tags. Browsers collapse that when rendering; Cheerio does
not. Trim it yourself:

js live
const $ = cheerio.load(`
  <li>
    Apple
  </li>
`);

console.log(JSON.stringify($('li').text()));
console.log(JSON.stringify($('li').text().trim()));

Note that .prop('innerText') does not help here β€” it skips <script> and
<style> content, but it preserves whitespace just like text() does.

`text()` on many elements runs them together

Reading text() from a selection concatenates every match with no separator, so
$('li').text() on three list items gives you one run-on string. Map over the
selection when you want them separately:

js live
const $ = cheerio.load('<ul><li>One</li><li>Two</li><li>Three</li></ul>');

console.log($('li').text()); // 'OneTwoThree'
console.log($('li').map((i, el) => $(el).text()).get());

`$.html()` gives me `…`

load treats its input as a complete document and adds the missing structure,
just like a browser. Pass false as the third argument to parse a fragment
instead β€” see fragment mode.

`attr('href')` returns a relative path

attr() returns the literal attribute value. Use prop('href') to get it
resolved against the document's URL β€” and make sure the document has one, either
by loading it with fromURL or by passing the
baseURI option.

My changes don't show up

Cheerio methods that return a new selection β€” find, filter, parent, and
the rest of the traversal methods β€” never modify the
original. Manipulation methods do modify in place, but they modify the tree, not
any string you extracted earlier. Re-serialize with $.html() after making
changes.

Selectors that work in the browser fail here

Cheerio supports jQuery's positional extensions (:first, :last, :eq(n)),
which are not valid CSS. The reverse also holds: pseudo-classes that depend on
rendering or user state mean nothing in a document that is never rendered β€” but
they don't all fail the same way, which matters when you're debugging.

Some are recognised and simply match nothing:

js
$('a:hover').length; // 0
$('a:visited').length; // 0
$('a:active').length; // 0

Others aren't implemented at all and throw:

js
$('a:focus'); // Error: Unknown pseudo-class :focus
$('a:target'); // Error: Unknown pseudo-class :target
$('a:lang(en)'); // Error: Unknown pseudo-class :lang

So an Unknown pseudo-class error means the selector isn't supported, whereas an
empty result from :hover means it is supported and genuinely matched nothing.
See Selecting Elements.

Looking something up by a value from data

If a class name, id, or attribute value comes from data rather than being
hard-coded, it may contain characters the selector parser treats specially β€” a
., :, or space breaks an id selector, and a " breaks even a quoted
attribute selector. Rather than trying to escape it, match on a fixed selector
and compare the value as data:

js
// Fragile β€” `.`, `:`, or a space in `id` changes what this selects
$(`#${id}`);

// Still fragile β€” a `"` in `id` closes the attribute and injects new syntax
$(`[id="${id}"]`);

// Robust β€” the value is never part of the selector
$('[id]').filter((_, el) => $(el).attr('id') === id);

If the value comes from an untrusted source this is a
security issue, not
just a correctness one.

Still stuck?

Search the issue tracker, and
include a minimal document plus the selector you tried when opening a new one.


--- METRICS ---