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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Use the CSS attribute-negation selector :not([attribute]). For example, $('li:not([data-id])') selects list items where data-id is absent. If you already have a Cheerio selection, use .not('[data-id]') to remove elements that have the attribute. The important distinction: an empty attribute such as data-id="" is still present, so it does not match either absence check.

Select elements that lack one attribute

Cheerio uses CSS selectors to find elements in the HTML tree it has parsed. Put the attribute selector inside :not() to exclude elements where that attribute exists:

const $ = cheerio.load(`
  <ul>
    <li>No data-id</li>
    <li data-id="2">Has a data-id</li>
    <li data-id="">Has an empty data-id</li>
  </ul>
`);

const withoutId = $('li:not([data-id])');
console.log(withoutId.text()); // No data-id

The selector has two parts: li limits the match to list items, and :not([data-id]) excludes those with a data-id attribute. The square-bracket selector [data-id] tests for attribute presence, not for a nonempty value.

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

For any element type, use $(':not([data-id])'). That can match many elements, including document-level elements, so a type or other condition is usually more useful. Use * :not([data-id]) only when you mean elements that are descendants of another element and lack the attribute; the space adds a descendant relationship and changes the selection.

Use .not() when you already have a selection

Cheerio’s .not() method removes elements matching a selector from the current collection. It is often clearer when you first narrow the collection, then filter out the elements with the attribute:

const itemsWithoutTestId = $('.item').not('[data-test]');

That means “start with elements matching .item, then exclude those with data-test.” It is equivalent in intent to $('.item:not([data-test])'). The official Cheerio traversal guide describes not as similar to filter, but for selecting elements that do not match a selector.

Choose based on where the selection begins. A selector is compact when the full condition is easy to read in one place. .not() is convenient when you already have a collection or want to make the exclusion step explicit. Both operate on the parsed tree, not on a live browser page.

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

Require several attributes to be absent

Chain a separate :not() clause for each attribute that must be absent:

const linksWithoutHrefOrTarget = $('a:not([href]):not([target])');

Adjacent selector conditions are cumulative here: a match must be an a element, must not have href, and must not have target. For an existing collection, the corresponding exclusion can be written as:

const linksWithoutHrefOrTarget = $('a')
  .not('[href]')
  .not('[target]');

Do not use a comma when you mean that every listed attribute must be absent. A comma separates alternatives, so $('a:not([href]), a:not([target])') selects links missing href or links missing target. A link may still have the other attribute and match one of those alternatives.

Decide how empty values should be treated

Attribute presence and attribute value are separate questions. Both data-id="2" and data-id="" satisfy [data-id], so :not([data-id]) excludes both. Use that selector when you mean strictly “the attribute is absent.”

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

If your requirement is “absent or empty,” express those as alternatives:

const missingOrEmpty = $('[data-id]:not([data-id]), [data-id=""]');

That expression is needlessly hard to read because the first branch combines presence and absence. A clearer version for elements of a known type is:

const missingOrEmpty = $('li:not([data-id]), li[data-id=""]');

One branch matches list items without the attribute; the other matches list items whose value is exactly empty. If you need custom rules, such as treating whitespace-only values as empty, filter in JavaScript and state the normalization rule explicitly:

const missingOrBlank = $('li').filter((i, el) => {
  const value = $(el).attr('data-id');
  return value == null || value.trim() === '';
});

Here, a missing attribute produces undefined, while a present attribute produces its value. Trimming means a value made only of whitespace is treated as blank; omit trim() if whitespace should count as content. Apply the same rule consistently in the rest of your extraction code.

Free tools Windows power users keep installed

One-click scans. No signup required.

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

Make sure the selector is scoped to the right collection

Cheerio’s find() searches within the current selection. A selector used with find() therefore does not search the whole document unless the current selection is the whole document:

const section = $('section#results');
const rowsWithoutKey = section.find('li:not([data-key])');

If this returns no elements, first check that section#results matched anything. If it matched, check that the target list items are actually inside that section in the parsed markup. For nested extraction, verify each parent selection before applying a child selector; a correct selector used against the wrong root can return zero elements or a different set than expected.

When scope is already established, the collection method makes it visible:

const rowsWithoutKey = $('section#results li').not('[data-key]');

These approaches are useful in different places. find() makes a parent-child traversal explicit, while a combined selector can be concise. Keep the scope readable, especially when extracting data from repeated nested structures.

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

Run a small, complete Cheerio example

In a Node.js project with Cheerio available, this example parses a supplied HTML string, selects list items without data-id, and prints their text:

const cheerio = require('cheerio');

const html = `
  <ul>
    <li>No attribute</li>
    <li data-id="12">Has a value</li>
    <li data-id="">Present but empty</li>
  </ul>
`;

const $ = cheerio.load(html);
const matches = $('li:not([data-id])');

matches.each((i, element) => {
  console.log($(element).text());
});

Expected output is No attribute. The present-but-empty item is excluded because the attribute exists. To exclude from a selection instead, replace the selector with $('li').not('[data-id]').

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

Understand what Cheerio can and cannot select

Cheerio parses the HTML or XML tree supplied to it; it is not a web browser. It does not visually render a page, load external resources, or execute JavaScript. As a result, an attribute added by client-side code after a browser loads the page is not available to a Cheerio selector unless that attribute is already present in the markup you provide.

Cheerio also does not apply CSS. An element hidden by a stylesheet remains in the parsed tree and can still match :not([attribute]). If your code needs the browser’s post-script DOM or rendered visibility, obtain that browser-produced content by an appropriate browser workflow first; do not interpret Cheerio’s selection as a visual or runtime-DOM query.

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

Troubleshoot unexpected matches

  • An empty-valued element is missing from the result. The attribute exists, even when its value is empty. Use an explicit empty-value alternative or a JavaScript filter if blank values should count as missing.
  • Elements with another required attribute appear in the result. Check whether you used a comma. Commas mean alternative selectors; chain :not() clauses when every listed attribute must be absent.
  • The result is empty inside find(). Inspect the current parent collection before checking the child selector. find() is relative to that collection.
  • A page’s browser DOM appears to disagree. Compare against the exact HTML supplied to cheerio.load(). Cheerio does not run page scripts that may add or change attributes.
  • Hidden nodes appear in the selection. Cheerio does not apply CSS, so stylesheet visibility does not remove an element from the parsed tree.
  • Changing selector syntax has inconsistent results across installations. Selector compatibility can depend on the installed Cheerio and selector-engine versions. Check the versions used by the project and test the selector against a minimal input like the example above.

Or skip the browser setup

If your starting point is a URL and you need a clean screenshot rather than a Cheerio selection, ScreenshotNeo can capture the page with one GET request. It does not replace Cheerio for querying attributes in HTML; it is an option for obtaining a screenshot of a rendered page.

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

See the ScreenshotNeo API documentation for request options. Before capture, ScreenshotNeo accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and each response includes X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Sign up for ScreenshotNeo and get 1,000 free screenshots a month with no card.

Frequently Asked Questions

Does :not([data-id]) select elements whose data-id value is zero?

Yes. The selector tests whether the attribute exists, not whether its value is truthy or nonzero. A present value such as “0” is excluded.

Can I use the absence selector with a class or element selector?

Yes. Put the condition alongside the selector for the elements you intend to match, such as $(‘button.primary:not([disabled])’).

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

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