# Plugin & Extension Marketplace Radar (`plusonets/plugin-marketplace-radar`) Actor

Track WordPress.org plugins, VS Code extensions, and Atlassian Marketplace apps. Each run snapshots public listings, diffs the last good snapshot, and emits rank, rating, review, install, staleness, and version signals. Unofficial.

- **URL**: https://apify.com/plusonets/plugin-marketplace-radar.md
- **Developed by:** [Olalekan Johnson](https://apify.com/plusonets) (community)
- **Categories:** Developer tools, SEO tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 signal detecteds

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/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Plugin & Extension Marketplace Radar

See which plugins and extensions are rising, slipping, going stale, or shipping a new version — across WordPress.org, the VS Code Marketplace, and the Atlassian Marketplace, in one schema.

Built for indie makers benchmarking a competitor, agencies deciding what to replace, and anyone who wants a scheduled watch instead of a one-off scrape. Each run stores a snapshot. The next run compares against that history and emits signals. A failed fetch never overwrites the last good snapshot.

Unofficial, not affiliated with WordPress, Automattic, Microsoft, Visual Studio Code, or Atlassian.

### What you get

- A flat listing row per plugin or extension, with rank, rating, reviews, installs, version, and a source URL.
- History fields (`previousRating`, `rankDelta`, `reviewVelocityPerDay`, and the rest) once a snapshot exists.
- Signals: `rank-risers`, `rating-decliners`, `review-velocity`, `install-velocity`, `stale-updates`, `new-entrants`, `changelog-diffs`.
- One digest row per run with counts and short highlights.
- Alert-only mode for scheduled webhooks: unchanged listings stay in the snapshot and out of the dataset.
- The first run is still useful. Rows are flagged `isBaseline: true` with current ratings, installs, and stale/new-entrant signals. Deltas are null until the next good run.

You're only charged for rows that pass validation.

### Default run

The default input queries the WordPress.org tag `seo` and the VS Code Marketplace search `markdown`, 15 listings each. A live run of that input returned 30 listing rows plus a digest in about two seconds, with fill rate 1.0 and five real stale-update signals (VS Code extensions with no release in more than a year).

```json
{
    "marketplaces": ["wordpress", "vscode"],
    "wordpressCategory": "seo",
    "vscodeSearch": "markdown",
    "maxItemsPerMarketplace": 15,
    "signalFilter": "all",
    "windowDays": 7,
    "staleMonths": 12,
    "alertOnlyOnChange": false,
    "emitWeeklyDigest": false,
    "snapshotName": "plugin-marketplace-radar"
}
```

An empty input `{}` uses those same defaults.

### Example output

Baseline listing from the WordPress.org plugin API on 23 October 2026. History fields are null because this was the first snapshot. The full row is flat and includes every column in the dataset schema.

```json
{
    "recordType": "listing",
    "status": "baseline",
    "coverage": "complete",
    "isBaseline": true,
    "marketplace": "wordpress",
    "listingId": "wordpress-seo",
    "name": "Yoast SEO – Advanced SEO with real-time guidance and built-in AI",
    "publisher": "Yoast",
    "version": "28.7",
    "rating": 4.8,
    "ratingRaw": 96,
    "ratingScale": "0-100",
    "reviewCount": 27821,
    "installCount": 10000000,
    "installCountSource": "active_installs",
    "downloadCount": 1013620826,
    "rank": 1,
    "rankScope": "query-result",
    "lastUpdated": "2026-10-13T05:27:00Z",
    "publishedAt": "2010-10-11T00:00:00Z",
    "daysSinceUpdate": 10,
    "stale": false,
    "signals": [],
    "signalsCsv": "",
    "sourceUrl": "https://wordpress.org/plugins/wordpress-seo/",
    "observedAt": "2026-10-23T14:08:17Z",
    "confidence": 0.93,
    "contentHash": "f132e9de133b32cdd74e3daf3780d354f05c976da9058d5c4c80241e2f303407"
}
```

The same default run flagged current-state stale extensions. This one had not shipped a release in 592 days:

```json
{
    "recordType": "listing",
    "status": "baseline",
    "marketplace": "vscode",
    "listingId": "yzhang.markdown-all-in-one",
    "name": "Markdown All in One",
    "version": "3.6.3",
    "rating": 4.686,
    "reviewCount": 172,
    "installCount": 14814461,
    "installCountSource": "install_statistic",
    "daysSinceUpdate": 592,
    "stale": true,
    "signals": ["stale-updates"],
    "signalsCsv": "stale-updates",
    "signalBilling": "charged",
    "sourceUrl": "https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one"
}
```

The digest row (`recordType: "digest"`) from that run: 30 listings observed, 30 emitted, 5 signals, fill rate 1.0, coverage `complete`, status `baseline`. Highlights included `vscode · Markdown All in One · no release for 592 days`.

A later run against a snapshot that still had an older version string for Yoast SEO produced a real changelog excerpt (HTML removed) and a version diff. The previous version below is whatever the snapshot had stored; the excerpt is the public 28.7 release notes:

```json
{
    "isBaseline": false,
    "previousVersion": "0.0.1",
    "version": "28.7",
    "versionChanged": true,
    "versionDiff": "0.0.1 -> 28.7",
    "changelogExcerpt": "28.7 Release date: 2026-10-13 Yoast SEO 28.7 brings more enhancements and bugfixes. Find more information about our software releases and updates here. Enhancements Allows agents to update titles and descriptions via the respective Yoast Ability. …",
    "signals": ["changelog-diffs"],
    "signalBilling": "charged",
    "rankComparable": true,
    "previousRank": 1,
    "rankDelta": 0
}
```

A repeat of the same stale extension does not bill `signal-detected` again. The row keeps `stale: true`, `activeConditionsCsv: "stale-updates"`, `signals: []`, and `signalBilling: "already-reported"`.

If every marketplace fails, the dataset gets one unbilled status row instead of a fake success:

```json
{
    "recordType": "status",
    "status": "blocked",
    "coverage": "blocked",
    "reason": "wordpress: the source returned HTTP 403",
    "marketplace": "all"
}
```

The key-value record `OUTPUT` repeats the counts: `rowsCharged`, `rowsDropped`, `fillRate`, `chargedUnits`, and error counters (`proxy`, `rate-limit`, `blocked`, `network`, `other`).

### Pricing

This Actor uses pay-per-event pricing. There is no per-dataset-item charge. You are never charged for a failed or empty capture, a status row, or the same signal state twice.

| Event | Price | When it fires |
| --- | --- | --- |
| `listing-snapshot` | $0.005 | One validated listing row written to the dataset |
| `signal-detected` | $0.02 | One listing that has at least one newly detected signal |
| `weekly-digest` | $1.00 | Once per run, only if `emitWeeklyDigest` is true |
| Start event | $0.005 per GB | Charged by the platform. This actor defaults to 512 MB |

1,000 listing snapshots ≈ **$5**. 1,000 signal-detected events ≈ **$20**.

| Run | Events | Event cost |
| --- | --- | --- |
| Default check: 30 listings, 5 new stale signals, no digest | 30 × $0.005 + 5 × $0.02 | **$0.25** |
| Quiet daily rerun of those 30, no new signals | 30 × $0.005 | **$0.15** |
| Alert-only rerun with nothing new | digest row only, no listing charges | **$0** |
| Weekly watch: 200 listings, 25 new signals, digest on | 200 × $0.005 + 25 × $0.02 + $1 | **$2.50** |

The run stops cleanly when the max total charge is reached. Signals that were detected but not billed are kept and retried next run, so a cap does not silently drop them. Listing rows that fail the schema or a 60% core-field fill rate are dropped and not billed (`rowsDropped` in `OUTPUT`).

### Input

| Field | Default | What it does |
| --- | --- | --- |
| `marketplaces` | wordpress, vscode | `wordpress`, `vscode`, `atlassian` |
| `wordpressCategory` | `seo` | Browse preset (`popular`, `new`, `updated`, `top-rated`, `featured`) or a plugin tag. Prefix `search:` to force a keyword search |
| `vscodeSearch` | `markdown` | Search text. Results are ordered by install count |
| `atlassianQuery` | `time tracking` | Text search. Prefix `category:` to filter by category name |
| `watchlist` | empty | Always-included ids. Use a prefix when more than one marketplace is selected: `wordpress:akismet`, `vscode:yzhang.markdown-all-in-one`, `atlassian:com.soldevelo.apps.worklogs` |
| `signalFilter` | `all` | Limit which signals are emitted and billed. Metrics are still stored |
| `windowDays` | 7 | Rank, rating, review, and install deltas use the oldest snapshot inside this window |
| `staleMonths` | 12 | No public release in this many months counts as stale (30.44 days per month) |
| `maxItemsPerMarketplace` | 15 | Search page size, 1–100. Watchlist items are extra |
| `alertOnlyOnChange` | false | Skip listing rows with no new signal. The first run for a marketplace still returns every current row |
| `emitWeeklyDigest` | false | Charge one `weekly-digest` event. The digest row is written either way |
| `snapshotName` | `plugin-marketplace-radar` | Named key-value store for this watch. Use a different name for a different query |

No API key is required. The actor calls public read endpoints only.

### How a repeat run works

1. Fetch the official API for each selected marketplace.
2. Load the named snapshot store. Rank and rating deltas compare to the oldest snapshot still inside `windowDays`. Version changes compare to the most recent snapshot, so a release alerts once.
3. Write listing rows (unless alert-only mode suppresses them) and one digest row.
4. Save the new snapshot and the fingerprints of signals that were actually billed.

History keeps the last 40 successful snapshots. A blocked, empty, or crashed fetch does not replace the previous good one.

Signal rules, all visible on the row:

- **Rank risers.** Moved up at least 3 places in this query. Rank is the position in the fetched page, not a global store rank. Compared only when the query is unchanged.
- **Rating decliners.** Normalised 0–5 rating fell by at least 0.1. WordPress.org’s 0–100 score is divided by 20.
- **Review velocity.** At least one new rating since the comparison snapshot. `reviewVelocityPerDay` divides by the gap, with a one-hour minimum so a quick rerun does not explode the rate.
- **Install velocity.** Install count increased. WordPress.org active installs are coarse public buckets, so this fires when the bucket changes. Cumulative downloads are on the row as `downloadCount` and do not, by themselves, bill a signal.
- **Stale updates.** Latest public release is older than `staleMonths`. Available on the first run.
- **New entrants.** Published inside the window, or absent from the previous snapshot of the same query. A first run does not mark the whole result page as new.
- **Changelog diffs.** Version string changed. The new release notes are fetched as plain text (HTML stripped, length capped). Atlassian uses the vendor’s short release summary.

`confidence` is 0–1 based on how complete the official payload was. `coverage` is `complete`, `partial`, `empty`, `blocked`, or `unsupported`.

### Scope and limits

In this version:

- WordPress.org plugin directory API (`api.wordpress.org`)
- Visual Studio Marketplace public extension query for VS Code
- Atlassian Marketplace REST API (`/rest/2/addons`)

Not in this version: Chrome Web Store, Shopify App Store, Slack, and Zapier. Those need a different access path and are intentionally out of scope.

Other limits:

- Rank is the position inside your query, capped by `maxItemsPerMarketplace`.
- WordPress.org active-install figures are rounded buckets. Use `downloadDelta` if you need a finer public series, and do not treat either as audited sales.
- Atlassian’s latest-version call is one extra request per app. Release notes are often a one-line summary; marketing HTML from the version page is not copied through.
- VS Code and WordPress.org changelogs are fetched only after a version change, and only from allowlisted HTTPS hosts.
- Reviewer names and review text are not collected. Counts and averages only.
- Descriptions are stored as a short plain-text snippet. Marketplace copy is untrusted text: tags are stripped and fields are length-capped.
- The watchlist accepts slugs and ids, not URLs. Private hosts, `file://`, and raw IP addresses are rejected.
- Same-day reruns compare correctly, but velocity over a few minutes is not a trend. Schedule daily or weekly.
- A crash after a charge and before the snapshot is saved can bill a signal again on the next run. The idempotency key covers retries inside a few minutes.

### Integrations

Schedule the actor in the Apify Console (daily for a 7-day window, weekly if you only want the digest). Point a webhook at the default dataset.

- **n8n / Make:** run the actor, then keep items where `recordType` is `listing` and `signalsCsv` is not empty. The digest row is `recordType` `digest`.
- **Google Sheets:** download the dataset as CSV. `signalsCsv`, `categoriesCsv`, and `highlightsCsv` are single columns.
- **Slack or email:** use alert-only mode so a quiet day sends the digest and nothing else.
- **Agents:** every input has a default, nothing is both required and defaulted, and a run with no usable rows returns an explicit status row instead of an empty success.

### Run locally

Python 3.12.

```bash
pip install -r requirements.txt
mkdir -p storage/key_value_stores/default
echo '{}' > storage/key_value_stores/default/INPUT.json
python -m src
```

Dataset items land in `storage/datasets/default/`. The run report is `storage/key_value_stores/default/OUTPUT`. Snapshots land in `storage/key_value_stores/plugin-marketplace-radar/`.

```bash
pip install -r requirements-dev.txt
pytest
```

Set `ACTOR_TEST_PAY_PER_EVENT=true` to log charge events locally. Nothing is billed off the platform. On the platform, create the three events above and leave the automatic dataset-item event off.

The actor asks for 512 MB and does not use standby mode.

### Sources

Catalog metadata comes from the public WordPress.org plugin info API, the public Visual Studio Marketplace gallery query, and the Atlassian Marketplace REST API. Those providers keep their own terms. This actor sends a identifiable User-Agent, throttles requests, and retries with backoff.

Questions: plusonecares@gmail.com.

# Actor input Schema

## `marketplaces` (type: `array`):

Official marketplace APIs to query. wordpress is the WordPress.org plugin directory, vscode is the Visual Studio Code Marketplace, and atlassian is the Atlassian Marketplace REST API.

## `wordpressCategory` (type: `string`):

A browse preset (popular, new, updated, top-rated, featured) or a plugin tag such as seo. Prefix with search: to force a keyword search, for example search:backup.

## `vscodeSearch` (type: `string`):

Search text sent to the public VS Code Marketplace extension query API. Results are ordered by install count.

## `atlassianQuery` (type: `string`):

Text query for the Atlassian Marketplace REST API, for example time tracking. Prefix with category: to filter by a category name.

## `watchlist` (type: `array`):

Listing slugs or IDs that are always fetched, even if they fall outside the search page. Use a marketplace prefix when more than one marketplace is selected: wordpress:akismet, vscode:yzhang.markdown-all-in-one, atlassian:is.origo.jira.tempo-plugin.

## `signalFilter` (type: `string`):

Which change signals to emit and bill. all keeps every signal type. Other values keep the matching type only. Metrics are still stored either way.

## `windowDays` (type: `integer`):

Rank, rating, review, and install deltas are measured against the oldest successful snapshot still inside this window. New-entrant detection on a first run uses the publish date against this same window.

## `staleMonths` (type: `integer`):

A listing whose latest public release is older than this many months is marked stale. About 30.44 days are counted per month.

## `maxItemsPerMarketplace` (type: `integer`):

Maximum listings fetched from each marketplace search this run, not counting the watchlist. The default stays small so a scheduled run finishes quickly.

## `alertOnlyOnChange` (type: `boolean`):

When true, listing rows are written only when a new signal is detected. The digest row is always written. The first run for a marketplace is a baseline and still returns current-state rows.

## `emitWeeklyDigest` (type: `boolean`):

When true, the run charges one weekly-digest event for the summary row. Leave false for cheap scheduled checks. The summary row is written either way and is not charged unless this is true.

## `snapshotName` (type: `string`):

Named key-value store that keeps successful snapshots and signal fingerprints. Use separate names for separate watches. A failed or blocked run does not replace the last good snapshot.

## Actor input object example

```json
{
  "marketplaces": [
    "wordpress",
    "vscode"
  ],
  "wordpressCategory": "seo",
  "vscodeSearch": "markdown",
  "atlassianQuery": "time tracking",
  "watchlist": [],
  "signalFilter": "all",
  "windowDays": 7,
  "staleMonths": 12,
  "maxItemsPerMarketplace": 15,
  "alertOnlyOnChange": false,
  "emitWeeklyDigest": false,
  "snapshotName": "plugin-marketplace-radar"
}
```

# Actor output Schema

## `listings` (type: `string`):

Normalised listing rows. Filter recordType to listing. The first run is flagged isBaseline.

## `signals` (type: `string`):

Signal columns: rank, rating, review velocity, install velocity, stale updates, new entrants, and version diffs.

## `digest` (type: `string`):

Run summary row (recordType digest) plus the listing rows in the same dataset.

## `report` (type: `string`):

Key-value OUTPUT record with coverage, error counters, fill rate, and charged units.

# 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 = {
    "marketplaces": [
        "wordpress",
        "vscode"
    ],
    "wordpressCategory": "seo",
    "vscodeSearch": "markdown",
    "atlassianQuery": "time tracking",
    "watchlist": [],
    "signalFilter": "all",
    "windowDays": 7,
    "staleMonths": 12,
    "maxItemsPerMarketplace": 15,
    "alertOnlyOnChange": false,
    "emitWeeklyDigest": false,
    "snapshotName": "plugin-marketplace-radar"
};

// Run the Actor and wait for it to finish
const run = await client.actor("plusonets/plugin-marketplace-radar").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 = {
    "marketplaces": [
        "wordpress",
        "vscode",
    ],
    "wordpressCategory": "seo",
    "vscodeSearch": "markdown",
    "atlassianQuery": "time tracking",
    "watchlist": [],
    "signalFilter": "all",
    "windowDays": 7,
    "staleMonths": 12,
    "maxItemsPerMarketplace": 15,
    "alertOnlyOnChange": False,
    "emitWeeklyDigest": False,
    "snapshotName": "plugin-marketplace-radar",
}

# Run the Actor and wait for it to finish
run = client.actor("plusonets/plugin-marketplace-radar").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 '{
  "marketplaces": [
    "wordpress",
    "vscode"
  ],
  "wordpressCategory": "seo",
  "vscodeSearch": "markdown",
  "atlassianQuery": "time tracking",
  "watchlist": [],
  "signalFilter": "all",
  "windowDays": 7,
  "staleMonths": 12,
  "maxItemsPerMarketplace": 15,
  "alertOnlyOnChange": false,
  "emitWeeklyDigest": false,
  "snapshotName": "plugin-marketplace-radar"
}' |
apify call plusonets/plugin-marketplace-radar --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,plusonets/plugin-marketplace-radar"
        }
    }
}
```

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/PGN4ctjYCMbcW5Q9O/builds/R99J66l0l8f2MK2Gj/openapi.json
