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.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchPC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11For 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.
#1 Best Overall
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.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →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.”
If your requirement is “absent or empty,” express those as alternatives:
Rank #3
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.
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.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Clear out junk files and repair common Windows errors3Fix the driver behind crashes, sound loss and screen glitchesRun 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:
Best Value
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]').
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.
Recommended Free Tools
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])’).
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Quick Recap
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.

