# Chrome Web Store Scraper — Permissions, Users & Category Rank (`mochiboo/chrome-web-store-extension-scraper`) Actor

Scrape Chrome Web Store extensions with their PARSED manifest permissions, host permissions and manifest version, plus install count, rating, version, last-updated date, size and rank within a category. For extension risk review, competitor tracking and browser-extension governance.

- **URL**: https://apify.com/mochiboo/chrome-web-store-extension-scraper.md
- **Developed by:** [mochi](https://apify.com/mochiboo) (community)
- **Categories:** Developer tools, Automation, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Chrome Web Store Scraper — Permissions, Users & Category Rank

Extract Chrome Web Store extensions with **their parsed manifest permissions** — plus install count,
rating, version, last-updated date, package size and position within a category.

Most extension scrapers hand you the marketing fields. This one also opens the extension's own
`manifest.json`, which the store ships inline with every listing, and gives you `permissions`,
`host_permissions`, `content_scripts` match patterns and `manifest_version` as **structured arrays you
can filter on** — the fields an extension-review or browser-governance policy is actually written
against.

***

### What you get per extension

| Field | Description |
|---|---|
| `extensionId` · `name` · `url` | Identity. `url` is rebuilt from the ID, so it is always canonical |
| `publisher` · `publisherWebsite` | Who ships it, and their site |
| `users` | Google's published install count |
| `rating` · `ratingCount` | Star average and number of ratings |
| `rankInCategory` | Position on the category page — the store's own ranking (category mode) |
| `categorySlug` · `category` · `subCategory` | e.g. `productivity/workflow` |
| `version` · `lastUpdated` · `firstPublished` | Release cadence at a glance |
| `sizeText` · `sizeBytes` | Package size, as published and as a number |
| `minimumChromeVersion` · `languages` | Compatibility and locale coverage |
| **`permissions`** | API permissions from the manifest, e.g. `["storage","scripting","cookies"]` |
| **`hostPermissions`** | Site-access patterns, normalised across Manifest V2 and V3 |
| **`contentScriptMatches`** | Where its content scripts inject |
| **`optionalPermissions`** | What it can ask for later |
| **`requestsAllUrls`** | `true` if it asks for `<all_urls>` or `*://*/*` |
| `permissionCount` · `hostPermissionCount` · `manifestVersion` · `manifestParsed` | Quick filters |
| `shortDescription` · `description` | Listing text |
| `supportUrl` · `privacyPolicyUrl` · `iconUrl` | Links |
| `detailFetched` · `matchedCategory` · `storefrontCountry` · `source` · `scrapedAt` | Provenance |

```json
{
  "extensionId": "nkbihfbeogaeaoehlefnkodbefgpgknn",
  "name": "MetaMask",
  "publisher": "metamask",
  "users": 12000000,
  "rating": 2.67,
  "ratingCount": 5902,
  "rankInCategory": 4,
  "categorySlug": "productivity/workflow",
  "version": "13.42.0",
  "lastUpdated": "2026-08-04T00:00:00.000Z",
  "sizeText": "21.13MiB",
  "sizeBytes": 22156411,
  "manifestVersion": 3,
  "permissions": ["activeTab", "alarms", "clipboardWrite", "notifications", "scripting", "storage"],
  "hostPermissions": ["https://*/*", "http://*/*"],
  "requestsAllUrls": true,
  "minimumChromeVersion": "123"
}
```

***

### Three ways to find extensions

| Mode | What it does |
|---|---|
| **`category`** (default) | Browses a store category **in the store's ranked order** and fills `rankInCategory`. Give it several categories and results are round-robined, so one busy category cannot eat your whole result budget |
| **`extensions`** | Looks up exactly the extensions you list, by ID or store URL. This is the monitoring mode — point it at your watchlist on a schedule |
| **`discover`** | Walks the store's own published sitemap for catalogue-wide sampling |

#### There is no keyword search, on purpose

The Chrome Web Store's `robots.txt` contains `Disallow: /search`, so this actor has no keyword-search
mode and never requests that path. The same applies to the `/reviews`, `/privacy`, `/support`,
`/related` and `/report` sub-pages. This is enforced in code — a disallowed URL throws before any
request is made — not just promised in documentation. Discovery therefore runs through **categories**
and the **sitemap that the store publishes for crawlers**.

If you need keyword search, this is not the actor for you, and we would rather say so here than have
you find out after a run.

***

### The catalogue is region-specific — read this before you schedule anything

**The Chrome Web Store shows a different catalogue in different countries, and every response is
`HTTP 200` either way.** Measured 2026-08-05: `Capital One Shopping` returns a full record through a
US exit and an empty page through Germany or Singapore; `SwagButton` resolves from US and Germany but
not Singapore. Six extensions listed on a single category page were unreadable from a Singapore exit
and all six resolved normally from the US.

So this actor **pins the storefront country** (`country`, default `US`) instead of letting whichever
proxy IP it happened to draw decide your results, stamps `storefrontCountry` on every row, and reports
`unavailableInRegion` in `RUN_SUMMARY`. Extensions that your chosen country cannot see are still
returned — with `detailFetched: false` and whatever the category page carried — rather than silently
disappearing from your row count.

If you are comparing runs over time, keep `country` fixed. If a competitor's extension suddenly looks
"gone", check `unavailableInRegion` before concluding it was pulled.

### Coverage — measured, not assumed

From a live sample of 8 captured detail pages and a full 32-extension category page (2026-08-05):

| Field | Populated | Note |
|---|---|---|
| `permissions` / manifest fields | **100%** | the manifest parsed on every extension tested, on both page types |
| `users` | **31 of 32** | Google publishes **no install count for some first-party extensions**. Fetching the detail page does **not** recover it — verified. `users` is `null` there, never `0` |
| `publisher`, `version`, `lastUpdated`, `size`, `languages` | detail pages only | absent from category tiles; `detailFetched: false` tells you which rows those are |
| `publisherWebsite`, `privacyPolicyUrl` | varies | many publishers list neither; `null` means they did not, not that we missed it |

**`fetchDetails` (default ON) costs one extra request per extension.** Turning it OFF is dramatically
faster and still returns the ID, name, users, rating, category, rank, description **and the full
permission set** — because the category page carries the same core record the detail page does. Turn it
ON when you need version, publisher, last-updated or size.

***

### Privacy

Chrome Web Store listings carry a **developer contact email**, and publisher-written descriptions
frequently contain support addresses too. This actor emits **no email addresses at all**: the contact
field is dropped, free-text fields are redacted to `[email removed]`, and a `mailto:` link in a URL
field becomes `null`. Company/organisation-level data only.

One thing to know: `publisher` is the publisher-of-record shown on the public listing. For large
extensions that is a company; for long-tail extensions it is sometimes an individual developer's
chosen handle, exactly as the store displays it.

***

### Reliability

- **A partial run is never reported as a complete one.** `RUN_SUMMARY` records `pagesFetched`,
  `detailFetched`, `detailFailed`, `shellResponses`, `unavailableInRegion`, `rejectedInputs`,
  `errors` and `complete`.
- **Bad input is reported, not swallowed.** An ID or URL we cannot parse comes back in
  `RUN_SUMMARY.rejectedInputs` instead of vanishing from your row count.
- **An extension the store will not serve you is distinguished from a broken parser.** The store answers
  `HTTP 200` even for an extension it will not show you, so status codes alone cannot tell the two
  apart. That case has a measurably different payload shape and is counted as `unavailableInRegion`;
  a genuinely unreadable page is counted as an error and logged loudly.
- **Retries draw a fresh proxy IP each attempt**, which is what keeps rate-limiting from turning into
  silent data loss.

***

### Example — extensions that can read every site you visit

```json
{
  "mode": "category",
  "categories": ["productivity/workflow", "productivity/tools"],
  "maxResults": 200,
  "onlyRequestsAllUrls": true,
  "minUsers": 100000
}
```

Returns every widely-installed extension in those categories whose manifest requests `<all_urls>` or
`*://*/*` — the broad-access permission most extension-review policies flag first — with its full
permission list, publisher, version and last-updated date.

### Example — weekly watchlist monitoring

```json
{
  "mode": "extensions",
  "extensions": [
    "nkbihfbeogaeaoehlefnkodbefgpgknn",
    "https://chromewebstore.google.com/detail/grammarly-ai-writing-assi/kbfnbcaeplbcioakkpcpgfkobkghlhen"
  ],
  "maxResults": 50
}
```

Run it on a schedule and diff the results: a jump in `permissionCount`, a new `hostPermissions` entry,
a change of `publisher`, or a `version` bump after a long quiet period are all things extension-risk
reviewers watch for.

***

**Source:** `chromewebstore.google.com` — public listing pages only, within what its `robots.txt`
allows.

# Actor input Schema

## `mode` (type: `string`):

How to find extensions. 'category' browses a store category in its ranked order. 'extensions' looks up the exact extensions you list. 'discover' walks the store's published sitemap. There is no keyword-search mode — the Chrome Web Store's robots.txt disallows /search, so this actor does not use it.

## `categories` (type: `array`):

Category slugs to browse, e.g. "productivity/workflow", "productivity/tools", "lifestyle/shopping", "make\_chrome\_yours/privacy". Results are round-robined across categories so one busy category cannot use up your whole result budget.

## `extensions` (type: `array`):

Used when mode = 'extensions'. Accepts a 32-character extension ID or a full chromewebstore.google.com/detail/... URL. Anything else is reported back in RUN\_SUMMARY.rejectedInputs rather than silently skipped.

## `maxResults` (type: `integer`):

Stop after this many extensions.

## `fetchDetails` (type: `boolean`):

ON (default) adds publisher, version, last-updated date, package size, minimum Chrome version, languages, full description, support and privacy-policy URLs — one extra request per extension. OFF returns only what the category page itself carries (still includes permissions, users, rating and rank) and is much faster.

## `country` (type: `string`):

Two-letter country code for the storefront to read. This CHANGES YOUR RESULTS: the Chrome Web Store publishes a different catalogue per country, and an extension that is not published in your country renders an empty page (measured — six extensions visible from US were empty from Singapore). Defaults to US, the largest catalogue. The chosen country is stamped on every row as `storefrontCountry`.

## `minUsers` (type: `integer`):

Only return extensions with at least this many users. 0 = no filter.

## `onlyRequestsAllUrls` (type: `boolean`):

Return only extensions whose manifest asks for \<all\_urls> or *://*/\* — the broad-access permission most extension-review policies flag first.

## `proxyConfiguration` (type: `object`):

Apify Proxy settings. Residential is recommended; a fresh IP is drawn on every retry.

## Actor input object example

```json
{
  "mode": "category",
  "categories": [
    "productivity/workflow"
  ],
  "extensions": [
    "nkbihfbeogaeaoehlefnkodbefgpgknn"
  ],
  "maxResults": 20,
  "fetchDetails": true,
  "country": "US",
  "minUsers": 0,
  "onlyRequestsAllUrls": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "mode": "category",
    "categories": [
        "productivity/workflow"
    ],
    "extensions": [
        "nkbihfbeogaeaoehlefnkodbefgpgknn"
    ],
    "maxResults": 20,
    "fetchDetails": true,
    "country": "US",
    "minUsers": 0,
    "onlyRequestsAllUrls": false,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("mochiboo/chrome-web-store-extension-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "mode": "category",
    "categories": ["productivity/workflow"],
    "extensions": ["nkbihfbeogaeaoehlefnkodbefgpgknn"],
    "maxResults": 20,
    "fetchDetails": True,
    "country": "US",
    "minUsers": 0,
    "onlyRequestsAllUrls": False,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("mochiboo/chrome-web-store-extension-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "mode": "category",
  "categories": [
    "productivity/workflow"
  ],
  "extensions": [
    "nkbihfbeogaeaoehlefnkodbefgpgknn"
  ],
  "maxResults": 20,
  "fetchDetails": true,
  "country": "US",
  "minUsers": 0,
  "onlyRequestsAllUrls": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call mochiboo/chrome-web-store-extension-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mochiboo/chrome-web-store-extension-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/FLgJshg8QvaOzuWJp/builds/RIQ99JI24RWi1Q63M/openapi.json
