eBextractor Extension Config
Remote eBay/Amazon selectors for the eBextractor Chrome extension. Static files on Cloudflare Pages — no Workers, no code, data only.
What the extension reads
| File | Purpose |
|---|---|
version.json | Which release is live (rev, id) · no-cache |
live/selectors.json | The live selectors · no-cache |
r/<id>/selectors.json | Every release exactly as published, to check and compare · cached 1 year |
Releases
| Release | Rev | Note | |
|---|---|---|---|
2026-10-01-1110 live |
1 | First release: search page, item page, purchase history selectors | json |
Structure
ebextractor-extension-config/ (source repo) ├─ selectors.json working copy — edit this ├─ releases/ every release (<id>.json + index.json), committed ├─ SELECTORS.md what every key targets (this page is built from it) └─ scripts/ build · release/rollback/diff · devtools · page dist/ what's served here (built by Cloudflare on push) ├─ index.html · _headers · version.json ├─ live/selectors.json └─ r/<id>/selectors.json + index.html
How the extension uses it
- eBay pages read a copy saved in the browser — never this site, so it adds no page-load time.
- In the background, at most every 5 minutes while the user is on eBay, the extension fetches
version.json(~150 bytes). It downloadslive/selectors.jsononly whenrevis higher than what it has. - If a page parses to nothing, it checks right away (max once a minute), retries with the new file, then falls back to the PythonAnywhere API.
- A malformed file, a different schema or a lower
revis ignored; the last good copy stays. The extension also ships a bundled copy for first install / offline.
Fix a breakage
- Find the broken key:
pnpm devtools, pastedevtools-check.jsinto the DevTools console on the broken page. - Edit
selectors.json— new selector first, keep the old ones. pnpm release "what changed"(numbers the release, records it, builds), then commit and push. Users pick it up within ~5 minutes of browsing eBay.- Bad release?
pnpm rollback <id>re-publishes an earlier release as a new one.
What each key in selectors.json targets, where to find that HTML, and what breaks when it stops matching. Not published — the build only uploads the JSON, but it fails if a key in the JSON isn't documented here.
When something breaks
- Open the page type below in Chrome (signed in to eBay).
- Run
pnpm devtools, paste the printed file's contents into the DevTools console. It lists every key for that page with how many elements each selector matches;✖= nothing matched. - For a
✖key: right-click the element on the page → Inspect, find a stable class/attribute, test it withdocument.querySelectorAll('…').length. - Add the new selector to
selectors.json(first in the list for priority keys; anywhere for any keys), keep the old ones, thenpnpm release "what changed", commit and push.
Match modes (fixed in the extension code):
- text — not CSS: header texts matched case-insensitively against table headings (
historyColumnsonly). - any — all selectors are combined into one query; order doesn't matter.
- priority — tried in list order, first one that matches wins; put the newest selector first.
Pages
| Section | Page | Example URL |
|---|---|---|
search | Search results | https://www.ebay.com/sch/i.html?_nkw=shoes (also sold: &LH_Sold=1&LH_Complete=1) |
itemPage | Listing page | https://www.ebay.com/itm/<item id> |
purchaseHistory, historyColumns | Sold / offer history (needs sign-in) | https://www.ebay.com/bin/purchaseHistory?item=<item id> |
amazon | Amazon search / product page | https://www.amazon.com/s?k=shoes, https://www.amazon.com/dp/<ASIN> |
listing | eBay create-listing form | https://www.ebay.com/lstng?… (Sell → List an item) |
Check list and grid view (&_dmd=2) and at least one non-US site (ebay.de, ebay.pl) — eBay rolls markup out per site. Per-site lists go under "sites": { "www.ebay.de": { "search": { … } } }.
search — search results page
The search page has two consumers: the parser (reads a copy of the page HTML to build the price analysis sidebar) and the live page features (hide items, per-card buttons) that work on the real DOM.
search.resultsContainer
ul.srp-results.srp-list.clearfixul.srp-results.srp-grid.clearfixul.su-grid.su-grid--is-listul.su-grid- Targets: the
<ul>that holds every result card. - Mode: any (first in document order).
- Used by: parser (
src/lib/utils/getListings.ts). - If broken: sidebar shows 0 listings; the extension retries with a newer config, then falls back to the PythonAnywhere API.
- Look for: the list right under "Results" — currently
ul.srp-resultsorul.su-grid.
search.card
li.s-item.s-item__dsa-on-bottomli.s-item.s-item__pl-on-bottomli.s-item.s-item__before-answer.s-item__pl-on-bottomli.s-card.s-card--horizontalli.s-card.s-card--verticalli.su-grid__item- Targets: each listing
<li>inside the results container. - Mode: any (all matches).
- Used by: parser.
- If broken: 0 listings (same fallback as above), or some listings missing from the analysis.
- Look for: the repeating
<li>per listing — currentlyli.s-card,li.su-grid__item,li.s-item.
search.skipCard
li.srp-river-answer.srp-river-answer--SPECTRUM_OF_VALUE_CAROUSELli>div.srp-river-answer--START_LISTING_BANNERli>div.srp-river-answer--LIVE_EVENTS_CAROUSELli>div.srp-river-answer--NAVIGATION_ANSWER_COLLAPSIBLE_CAROUSELli>div.srp-river-answer--RIGHT_ALIGNED_MESSAGEli>div.srp-river-answer--BASIC_PAGINATION_V2li>div.srp-river-answer--REWRITE_STARTli>script- Targets: an element inside a card that marks it as not a real listing (banners, carousels, pagination, "start listing" ads).
- Mode: any.
- Used by: parser — a card containing any match is ignored.
- If broken: junk rows in the analysis (wrong prices/keywords), "Link element not found" errors in the console.
- Look for:
srp-river-answer--*classes on promo blocks between listings.
search.link
a.s-item__linka.su-linka.s-card__linka.su-link.su-item-card__title- Targets: the listing's
<a>to/itm/<id>inside a card. - Mode: any.
- Used by: parser — required: a card without it is skipped; also used to de-duplicate and build the image URL.
- If broken: listings missing or 0 listings.
- Look for: the title link — currently
a.su-link,a.s-card__link,a.s-item__link.
search.price
span.s-item__pricespan.s-card__pricespan.su-item-card__price- Targets: the price text element inside a card (e.g.
$12.99or$7.99 to $8.99). - Mode: any.
- Used by: parser — price stats, price frequencies, CSV.
- If broken: prices show
N/A/—; listings are excluded from price stats. - Look for:
span.su-item-card__price,span.s-card__price,span.s-item__price.
search.imageContainer
div.s-item__imagediv.su-media__imagediv.su-image- Targets: the element wrapping the listing image inside a card.
- Mode: any.
- Used by: parser — its
<img alt>is the 2nd fallback for the title. - If broken: only matters when
search.titlealso fails.
search.title
div.s-card__titlediv>a.su-item-card__titlediv>a- Targets: the element whose text is the listing title, inside a card.
- Mode: any.
- Used by: parser — titles, top keywords, CSV. Fallbacks: image
alt, then link text. - If broken: titles become "Opens in a new window…"/
Unknown, keyword counts get noisy.
search.category
li.srp-refine__category__item- Targets: category items in the left filter column.
- Mode: any (all matches).
- Used by: parser — "Categories" section of the CSV only.
- If broken: CSV categories empty; nothing else.
search.pageCards
ul.srp-results>li.s-item__pl-on-bottom:not(.srp-river-answer--ITEMS_CAROUSEL_WITH_COLOR)ul.srp-results>li.s-card.s-card--horizontal:not(.srp-river-answer--ITEMS_CAROUSEL_WITH_COLOR)ul.srp-results>li.s-card.s-card--vertical:not(.srp-river-answer--ITEMS_CAROUSEL_WITH_COLOR)ul.su-grid>li.su-grid__item:not(.srp-river-answer--ITEMS_CAROUSEL_WITH_COLOR)- Targets: the listing
<li>cards on the live page. Each must carrydata-listingid(on the<li>or a childdiv). - Mode: priority (all matches of the first selector that matches anything). Keep
:not(.srp-river-answer--ITEMS_CAROUSEL_WITH_COLOR)to skip carousels. - Used by: hide/unhide items (
src/lib/utils/itemVisibility.ts), re-hiding saved hidden items on load (src/features/exclude-checker/), the per-card buttons (src/features/ebay-search/components/CreateSoldHistoryAndOtherFeatures.tsx). - If broken: no Sold History / Quick Actions buttons under listings; hidden items reappear and are counted again.
search.cardActionsMount
div.s-item__info.clearfixdiv.su-card-container__content- Targets: the element inside a live card where the Sold History / Quick Actions buttons are appended.
- Mode: priority.
- If broken: buttons don't appear (cards found, nowhere to put them).
- Look for: the card's text/info column — currently
div.su-card-container__content,div.s-item__info.
search.cardTitle
.s-card__title>span.primary.default.s-item__title.s-card__title.su-item-card__header>a- Targets: the title text inside a live card.
- Mode: priority.
- Used by: per-card actions (product analysis, "check on other sites", hidden-item names).
- If broken: those actions get an empty title.
search.cardImage
.s-card__imagediv.su-image img- Targets: the listing
<img>inside a live card. - Mode: priority.
- Used by: per-card "save image" / search by image, hidden-item thumbnails.
- If broken: missing thumbnails, image actions do nothing.
itemPage — listing page
itemPage.quantityAvailability
#qtyAvailabilitydiv.x-quantity__availability- Targets: the block with "N available · M sold" on
/itm/<id>. - Mode: priority. The code reads the 2nd
<span>inside it and takes its first word as the sold count. - Used by: the sold count shown in the per-card Sold History panel on the search page (it fetches the item page in the background).
- If broken: sold count shows blank/
Error. - Look for: the quantity row near the Buy button — currently
#qtyAvailability,div.x-quantity__availability.
itemPage.titleMount
- Targets: the title block on
/itm/<id>; the button row (Sold History · Analyze · Search By Image · Download Images) is appended inside it. - Mode: priority.
- Used by:
src/features/ebay-item/components/CreateSoldHistoryButton.tsx. - If broken: the button row doesn't appear on item pages.
itemPage.title
- Targets: the element whose text is the listing title.
- Mode: priority.
- Used by: item panel title, compare links (Amazon/Walmart/Google/Facebook), image download file names.
- If broken: compare links search the browser tab title instead.
itemPage.itemId
- Targets: the "eBay item number: 123…" row in the item specifics; the text after
:is the ID. - Mode: priority. Fallback when broken: the ID is read from the URL.
- Used by: item panel (which listing's sold history to fetch).
- If broken: usually nothing (URL fallback); wrong history only on unusual URLs.
itemPage.imagePanel
- Targets: the image gallery container (
#PicturePanel). - Mode: priority.
- Used by: Download Images, Search By Image (
src/features/ebay-item/utils/getItemImages.ts). - If broken: "no images found" in those tools.
itemPage.imageItems
- Targets: each image tile inside the gallery; must carry
data-idxand contain an<img>(data-zoom-src/data-src/src). - Mode: priority (all matches of the first selector that matches).
- If broken: same as above.
purchaseHistory — /bin/purchaseHistory?item=<id>
The extension fetches this page with the user's own eBay session. Signed out → eBay redirects to sign-in and the extension asks the user to sign in.
purchaseHistory.soldTable
div.app-table.fixed-price table.app-table__table- Targets: the
<table>of purchases (buyer, price, quantity, date). First row =<th>headers, other rows =<td>. - Mode: priority.
- Used by: sold history (
src/lib/utils/getItemHistoryById.ts), on the item page and in the per-card panel. - If broken: "0 sold" / empty sold history while eBay shows sales — the top user complaint. If neither table matches, the extension retries with a newer config, then falls back to the PythonAnywhere API.
- Also check: the header texts — see
historyColumnsbelow. A renamed column breaks dates, prices and quantities even when the table is found.
purchaseHistory.offerTable
div.app-table.offer table.app-table__table- Targets: the
<table>of Best Offer history (same row layout). - Mode: priority.
- If broken: offer history empty.
purchaseHistory.itemDetails
dl.app-item-card__details- Targets: the
<dl>of item details at the top (<dt>label /<dd>value pairs). - Mode: priority.
- If broken: item details missing in the history view; tables still work.
historyColumns — purchase history table headings
Not CSS. Each key lists the heading text of one column in every language; a column matches if its <th> text equals any entry, ignoring case. Add the new wording when eBay renames a column (keep the old one). If these break, sold history may still list rows but dates, prices, quantities read as empty: period filters show 0, revenue "—", units counted as 1 each.
Check: open /bin/purchaseHistory?item=<id>, run the DevTools checker — it prints each table's headings and which ones matched no column.
historyColumns.date_of_purchase
- Column: purchase date of a sale ("Date of purchase", "Kaufdatum", …).
- Used by: sold table dates and every period filter (7/30/60/90 days).
historyColumns.date_of_offer
- Column: date of a Best Offer.
- Used by: offer table dates and offer period filters.
historyColumns.buy_it_now_price
- Column: sale price per unit ("Buy It Now price"). Empty for items sold as a special offer — that's eBay hiding it, not a broken key.
- Used by: revenue, average price, the sold table's price column.
historyColumns.offer_price
- Column: offered price (often hidden by eBay).
- Used by: offer table price column.
historyColumns.offer_status
- Column: offer outcome (accepted, declined, …).
- Used by: offer table status column.
historyColumns.quantity
- Column: units in the purchase/offer.
- Used by: units sold (the headline number) and revenue (price × quantity).
- If broken: every row counts as 1 unit — units sold is too low on multi-quantity listings.
historyColumns.variation
- Column: size/colour variation bought, on multi-variation listings.
- Used by: the sold table's Variation column (only shown when present).
historyColumns.user_id
- Column: buyer (masked by eBay, e.g.
a***b). - Used by: nothing displayed yet; kept so the row is complete.
amazon — Amazon search and product pages
The "Compare prices / Search on eBextractor" buttons. The content script only runs on /s…, /dp/… and /gp/product/… (allowlist in the extension's manifest) — these keys only decide where on those pages the buttons go.
amazon.searchResult
- Targets: each organic result card on
/s; cards withoutdata-asinare skipped. - Mode: priority.
- If broken: no buttons on search results.
amazon.cardSlot
- Targets: where inside a result card the buttons are appended (the card body, so they sit under "Add to cart").
- Mode: priority; falls back to the card itself.
- If broken: buttons still appear, at the end of the card.
amazon.cardImage
- Targets: the product image in a card; its
altis the title used for searches, itssrcis sent for image search. - Mode: priority.
amazon.cardTitle
- Targets: the title in a card — fallback when the image has no
alt. - Mode: priority.
- If broken (with
cardImage): cards without a title get no buttons.
amazon.productMount
- Targets: the title block on a product page; buttons are appended inside it.
- Mode: priority.
- If broken: no buttons on product pages.
amazon.productTitle
- Targets: the product title text on a product page.
- Mode: priority.
- If broken: no buttons on product pages (nothing to search for).
amazon.productImage
- Targets: the main product image (sent with "Compare prices").
- Mode: priority.
- If broken: comparisons run without the image.
listing — eBay create-listing form
listing.titleInput
- Targets: the listing title field the seller types into.
- Mode: priority. Inputs are matched by event delegation, so eBay re-rendering the form doesn't detach it.
- Used by: the Listing assistant panel (title checks, sold-price research, keyword suggestions).
- If broken: the assistant shows "Type a title…" and all its tools stay empty.
- Look for: the title
<input>— prefer[name="title"]-style attributes over generated IDs likes0-0-0-24-8-…, which change between form versions.
page — any eBay page
page.header
- Targets: eBay's global header (logo, search bar, My eBay, cart).
- Mode: priority. Only its bottom edge is used.
- Used by: the default position of the floating panel — just below the header, on the right — until the user drags it (
src/lib/ui/lib/components/FloatingPanel.tsx). - If broken: the panel opens at a fixed offset from the top instead; nothing else breaks.