# Technical Documentation: cheeriojs/cheerio

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/cheeriojs/cheerio)

### Overview (`Readme.md`)

<h1 align="center">cheerio</h1>

<h5 align="center">The fast, flexible, and elegant library for parsing and manipulating HTML and XML.</h5>

<div align="center">
  <a href="https://github.com/cheeriojs/cheerio/actions/workflows/ci.yml">
    <img src="https://github.com/cheeriojs/cheerio/actions/workflows/ci.yml/badge.svg" alt="Build Status">
  </a>
  <a href="https://coveralls.io/github/cheeriojs/cheerio">
    <img src="https://img.shields.io/coveralls/github/cheeriojs/cheerio/main" alt="Coverage">
  </a>
  <a href="#backers">
    <img src="https://img.shields.io/opencollective/backers/cheerio" alt="OpenCollective backers">
  </a>
  <a href="#sponsors">
    <img src="https://img.shields.io/opencollective/sponsors/cheerio" alt="OpenCollective sponsors">
  </a>
  <a href="https://scorecard.dev/viewer/?uri=github.com/cheeriojs/cheerio">
    <img src="https://api.scorecard.dev/projects/github.com/cheeriojs/cheerio/badge" alt="OpenSSF Scorecard">
  </a>
</div>

<br>

[中文文档 (Chinese Readme)](https://github.com/cheeriojs/cheerio/wiki/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

**&#10084; 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.

**&#991; Blazingly fast:** Cheerio works with a very simple, consistent DOM
model. As a result parsing, manipulating, and rendering are incredibly
efficient.

**&#10049; Incredibly flexible:** Cheerio wraps around
[parse5](https://github.com/inikulin/parse5) for parsing HTML and can optionally
use the forgiving [htmlparser2](https://github.com/fb55/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`](https://developer.mozilla.org/en-US/docs/Web/API/Element/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](https://developer.mozilla.org/en-US/docs/Web/API/Node).
You can expect them to define the following properties:

- `tagName`
- `parentNode`
- `previousSibling`
- `nextSibling`
- `nodeValue`
- `firstChild`
- `childNodes`
- `lastChild`

## Screencasts

[https://vimeo.com/31950192](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](https://github.com/cheeriojs/cheerio/wiki/Cheerio-in-Production)!

## Sponsors

Does your company use Cheerio in production? Please consider
[sponsoring this project](https://github.com/cheeriojs/cheerio?sponsor=1)! Your
help will allow maintainers to dedicate more time and resources to its
development and support.

**Headlining Sponsors**



<a href="https://github.com/" target="_blank" rel="noopener noreferrer">
            <img height="128px" width="128px" src="https://humble.imgix.net/https%3A%2F%2Favatars.githubusercontent.com%2Fgithub?ixlib=js-3.8.0&w=128&h=128&fit=fillmax&fill=solid&s=65172918690f124c0adebece30c66471" title="Github" alt="Github"></img>
          </a>
<a href="https://www.airbnb.com/" target="_blank" rel="noopener noreferrer">
            <img height="128px" width="128px" src="https://humble.imgix.net/https%3A%2F%2Favatars.githubusercontent.com%2Fairbnb?ixlib=js-3.8.0&w=128&h=128&fit=fillmax&fill=solid&s=e679125f79ca84598b8f8420b581dea5" title="AirBnB" alt="AirBnB"></img>
          </a>



**Other Sponsors**



<a href="https://onlinecasinosspelen.com" target="_blank" rel="noopener noreferrer">
            <img height="64px" width="64px" src="https://humble.imgix.net/https%3A%2F%2Fimages.opencollective.com%2Fonlinecasinosspelen%2F99ac6a2%2Flogo.png?ixlib=js-3.8.0&w=64&h=64&fit=fillmax&fill=solid&s=8ec1ec058845b823858f22205485be02" title="OnlineCasinosSpelen" alt="OnlineCasinosSpelen"></img>
          </a>
<a href="https://Nieuwe-Casinos.net" target="_blank" rel="noopener noreferrer">
            <img height="64px" width="64px" src="https://humble.imgix.net/https%3A%2F%2Fimages.opencollective.com%2Fnieuwecasinos%2Fc67d423%2Flogo.png?ixlib=js-3.8.0&w=64&h=64&fit=fillmax&fill=solid&s=ed55d86b80b1aa8cf89b033020521945" title="Nieuwe-Casinos.net" alt="Nieuwe-Casinos.net"></img>
          </a>
<a href="https://www.rapidproxy.io/?ref=cheerio" target="_blank" rel="noopener noreferrer">
            <img height="64px" width="64px" src="https://humble.imgix.net/https%3A%2F%2Fwww.rapidproxy.io%2Fstatic%2Frapidproxy%2Fimages%2Fhd_ft_public%2Frapidproxy_logo.webp?ixlib=js-3.8.0&w=64&h=64&fit=fillmax&fill=solid&rect=0%2C0%2C92%2C92&s=7317259362cd11f1050c21616be6d02f" title="Rapidproxy" alt="Rapidproxy"></img>
          </a>



## Backers

[Become a backer](https://github.com/cheeriojs/cheerio?sponsor=1) to show your
support for Cheerio and help us maintain and improve this open source project.



<a href="https://kafidoff.com" target="_blank" rel="noopener noreferrer">
            <img height="64px" width="64px" src="https://humble.imgix.net/https%3A%2F%2Fimages.opencollective.com%2Fkafidoff-vasy%2Fd7ff85c%2Favatar.png?ixlib=js-3.8.0&w=64&h=64&fit=fillmax&fill=solid&s=a41c66c2f9b1d3a7a241e425e7aa2d09" title="Vasy Kafidoff" alt="Vasy Kafidoff"></img>
          </a>



## License

MIT

---

### Overview (`website/README.md`)

# Website

This website is built using [Docusaurus 2](https://docusaurus.io/), a modern
static website generator.

### Installation

```
$ yarn
```

### Local Development

```
$ 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

```
$ 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`)

---
sidebar_position: 1
sidebar_label: Introduction
---

# 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](https://github.com/puppeteer/puppeteer) or
[Playwright](https://github.com/microsoft/playwright), or a DOM emulation
project like [jsdom](https://github.com/jsdom/jsdom).

:::

## Installation

Cheerio runs on Node.js 22.19 or later. Once you have
[Node.js](https://nodejs.org/en/download/) 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](/docs/basics/loading) for the full list.

## Selecting elements

Call `$` with a [CSS selector](/docs/basics/selecting) 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](/docs/basics/traversing).

## 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](/docs/basics/manipulation) for the full set of methods.

---

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

---
sidebar_position: 2
description: Configure Cheerio to work with different documents.
---

# Configuring Cheerio

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

- [`parse5`](https://parse5.js.org/) — the default for HTML. It rigorously
  conforms to the HTML standard, so it produces the same tree a browser would.
- [`htmlparser2`](https://github.com/fb55/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](/docs/api/interfaces/CheerioOptions).

### Fragment Mode

By default, `parse5` treats its input as a complete document and wraps the
content in `<html>`, `<head>`, and `<body>` 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](/docs/api/interfaces/HTMLParser2Options).

### 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`)

---
sidebar_position: 999
description: Create custom pseudo-classes and plugins.
---

# 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`)

---
sidebar_position: 3
description: Handling untrusted markup safely.
---

# 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`](/docs/basics/loading#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](https://www.npmjs.com/package/sanitize-html) or
[DOMPurify](https://github.com/cure53/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](https://github.com/cheeriojs/cheerio/blob/main/SECURITY.md) for
the reporting process, and
[THREAT_MODEL.md](https://github.com/cheeriojs/cheerio/blob/main/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`)

---
sidebar_label: Extracting Data
sidebar_position: 6
description: Extract multiple values at once.
---

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](/docs/api/classes/Cheerio#prop), 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`](/docs/api/functions/fromURL) sets a URL automatically; with the
other loaders, pass the
[`baseURI` option](/docs/api/interfaces/CheerioOptions#baseuri) 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`)

---
sidebar_position: 2
description: A walkthrough of different loading methods.
---

# 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`](#load)                 | string                 | You already have the markup as a string.        |
| [`loadBuffer`](#loadbuffer)     | `Buffer`               | You have raw bytes and the encoding is unknown. |
| [`stringStream`](#stringstream) | stream of decoded text | You're streaming and already know the encoding. |
| [`decodeStream`](#decodestream) | stream of raw bytes    | You're streaming and the encoding is unknown.   |
| [`fromURL`](#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](https://html.spec.whatwg.org/multipage/parsing.html#determining-the-character-encoding),
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 `<body>` 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](/docs/advanced/configuring-cheerio#fragment-mode) for
details.

:::

Learn more about the `load` method in the
[API documentation](/docs/api/variables/load).

## `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](/docs/api/functions/loadBuffer).

## `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](/docs/api/interfaces/CheerioOptions);
the second is a callback that receives the finished document.

Learn more about the `stringStream` method in the
[API documentation](/docs/api/functions/stringStream).

## `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](/docs/api/functions/decodeStream).

## `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`](/docs/basics/extract) hand you absolute links.

### Customizing the request

Pass `requestOptions` to control the request. These go to
[`undici`'s `stream` method](https://undici.nodejs.org/#/docs/api/Dispatcher?id=dispatcherstreamoptions-factory-callback):

```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](/docs/api/functions/fromURL), and see
[Security](/docs/advanced/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`)

---
sidebar_position: 5
description: Methods to manipulate elements within a document.
---

# Manipulating the DOM

Once you've [selected](/docs/basics/selecting) 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()`](/docs/api/classes/Cheerio#attr) reads and writes HTML attributes —
the values literally present in the markup.
[`prop()`](/docs/api/classes/Cheerio#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()`](/docs/api/classes/Cheerio#addclass),
[`removeClass()`](/docs/api/classes/Cheerio#removeclass), and
[`toggleClass()`](/docs/api/classes/Cheerio#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()`](/docs/api/classes/Cheerio#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()`](/docs/api/classes/Cheerio#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()`](/docs/api/classes/Cheerio#append),
[`prepend()`](/docs/api/classes/Cheerio#prepend),
[`before()`](/docs/api/classes/Cheerio#before), and
[`after()`](/docs/api/classes/Cheerio#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()`](/docs/api/classes/Cheerio#appendto),
[`prependTo()`](/docs/api/classes/Cheerio#prependto),
[`insertBefore()`](/docs/api/classes/Cheerio#insertbefore), and
[`insertAfter()`](/docs/api/classes/Cheerio#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()`](/docs/api/classes/Cheerio#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()`](/docs/api/classes/Cheerio#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()`](/docs/api/classes/Cheerio#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()`](/docs/api/classes/Cheerio#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()`](/docs/api/classes/Cheerio#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()`](/docs/api/classes/Cheerio#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](/docs/basics/extract). To add your own methods to the
`Cheerio` prototype, see
[Extending Cheerio](/docs/advanced/extending-cheerio#writing-plugins-for-cheerio).

---

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

---
sidebar_position: 3
description: An introduction to CSS selectors.
---

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

# Selecting Elements

Cheerio finds elements using
[CSS selectors](https://developer.mozilla.org/en-US/docs/Glossary/CSS_Selector),
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](/docs/basics/loading). 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 `<p>` 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](https://www.w3.org/TR/2011/REC-css3-selectors-20110929/#attribute-selectors)
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')`   | `<p>` elements anywhere inside a `<div>`.                     |
| `$('div > p')` | `<p>` elements that are direct children of a `<div>`.         |
| `$('h2 + p')`  | The `<p>` immediately following an `<h2>`.                    |
| `$('h2 ~ p')`  | All `<p>` elements following an `<h2>` under the same parent. |

Note the difference between the first two: `div p` reaches the `<p>` nested
inside the `<ul>`, 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`](https://github.com/fb55/css-select/blob/master/README.md#supported-selectors),
supports most standard pseudo-classes — see its README for the exact list —
plus `:contains()`, which matches on text. On top of that,
[`cheerio-select`](https://github.com/cheeriojs/cheerio-select/blob/master/README.md)
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](/docs/advanced/extending-cheerio#adding-custom-css-pseudo-classes).

## Where to go next

Once you have a selection, use the
[traversal methods](/docs/basics/traversing) to move around it, or the
[manipulation methods](/docs/basics/manipulation) to change it.

For more on selectors themselves, see
[MDN](https://developer.mozilla.org/en-US/docs/Glossary/CSS_Selector) and
jQuery's guide to
[selecting elements](https://learn.jquery.com/using-jquery-core/selecting-elements/).

---

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

---
sidebar_position: 4
description: Traverse the DOM tree and filter elements.
---

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](#moving-down-the-dom-tree)              | `find`, `children`, `contents`                                            |
| [Move up](#moving-up-the-dom-tree)                  | `parent`, `parents`, `parentsUntil`, `closest`                            |
| [Move sideways](#moving-sideways-within-the-dom-tree) | `next`, `prev`, `nextAll`, `prevAll`, `siblings`, `nextUntil`, `prevUntil` |
| [Narrow the current set](#filtering-elements)       | `eq`, `filter`, `not`, `has`, `first`, `last`                             |

For the full signatures and edge cases, see the
[`Cheerio` API reference](/docs/api/classes/Cheerio).

## 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](/docs/api/classes/Cheerio#find) 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 `<ul>`
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](/docs/api/classes/Cheerio#children) 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
`<ul>` 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](/docs/api/classes/Cheerio#contents) 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 `<div>`
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](/docs/api/classes/Cheerio#parent) 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 `<ul>` 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](/docs/api/classes/Cheerio#parents) 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](/docs/api/classes/Cheerio#parentsuntil) 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](/docs/api/classes/Cheerio#closest) 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 `<ul>`
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](/docs/api/classes/Cheerio#next) 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](/docs/api/classes/Cheerio#prev) 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](/docs/api/classes/Cheerio#nextall) 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](/docs/api/classes/Cheerio#prevall) 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](/docs/api/classes/Cheerio#siblings) 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](/docs/api/classes/Cheerio#nextuntil) 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](/docs/api/classes/Cheerio#prevuntil) 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](/docs/api/classes/Cheerio#eq) 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
`<ul>` 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](/docs/api/classes/Cheerio#filter) 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](/docs/api/classes/Cheerio#not) 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
`<ul>` 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](/docs/api/classes/Cheerio#has) 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 `<ul>`
element that contain a `<strong>` 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](/docs/api/classes/Cheerio#first) allows you to select the
first element in a selection. It returns a new selection containing the first
element.

The [`last` method](/docs/api/classes/Cheerio#last) 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 `<ul>`
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](/docs/basics/manipulation) to change it, or
[the `extract` method](/docs/basics/extract) to pull structured data out of it
in one go.

---

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

---
sidebar_position: 7
description: Why a selection is empty, and other common surprises.
---

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 `<div id="root">` and a bundle `<script>`, you need
[Puppeteer](https://github.com/puppeteer/puppeteer) or
[Playwright](https://github.com/microsoft/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`](/docs/basics/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 `<html><head></head><body>…`

`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](/docs/advanced/configuring-cheerio#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`](/docs/basics/loading#fromurl) or by passing the
[`baseURI` option](/docs/api/interfaces/CheerioOptions#baseuri).

## My changes don't show up

Cheerio methods that return a new selection — `find`, `filter`, `parent`, and
the rest of the [traversal methods](/docs/basics/traversing) — 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](/docs/basics/selecting#beyond-standard-css).

## 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](/docs/advanced/security#selectors-built-from-user-input), not
just a correctness one.

## Still stuck?

Search the [issue tracker](https://github.com/cheeriojs/cheerio/issues), and
include a minimal document plus the selector you tried when opening a new one.

---

--- METRICS ---
- Files Extracted: 12
- Estimated Token Budget: ~16339 tokens
- Canonical Reference: https://codewiki.google/github.com/cheeriojs/cheerio
