# WordPress Plugin Reviews Scraper & Support Monitor (`coolinbex/wordpress-plugin-reviews-support-monitor`) Actor

Scrape WordPress.org plugin reviews and support forum topics. Export ratings, review text, replies, and resolution status. Monitor new reviews, unanswered questions, and topic changes across multiple plugins. No WordPress login or API key required.

- **URL**: https://apify.com/coolinbex/wordpress-plugin-reviews-support-monitor.md
- **Developed by:** [coolinbex](https://apify.com/coolinbex) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $8.00 / 1,000 review or support topics

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?

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

## WordPress Plugin Reviews Scraper & Support Monitor

Scrape **WordPress.org plugin reviews and support forum topics** into structured JSON, CSV, or Excel. Collect star ratings, review text, support questions, replies, and resolution status. Schedule the Actor to monitor new reviews and changes to previously observed topics.

Built for plugin developers, WordPress agencies, product researchers, and support teams. **No WordPress account, API key, AI service, or paid enrichment API is required.**

### What you can do

- Export reviews and support topics for up to 20 WordPress plugins in one run.
- Find 1-star and 2-star reviews, unresolved support threads, and topics with no replies.
- Filter by keywords, excluded phrases, and the last-activity date.
- Include reply text and publicly displayed Plugin Author/Plugin Support badges.
- Monitor newly observed topics, edited reviews, reply changes, and resolution changes.
- Export source links, absolute UTC timestamps, and predictable fields for automation.

### Quick start

Enter plugin slugs such as `woocommerce` or `contact-form-7`. Official plugin and support URLs also work. Select reviews, support topics, or both, then run the Actor.

```json
{
  "plugins": ["woocommerce", "contact-form-7"],
  "sources": "both",
  "maxTopicsPerSource": 25,
  "maxTopicsTotal": 100
}
```

Results appear in the default dataset. The **Run summary and coverage** output explains how much was scanned, which limits were reached, and whether monitoring state was saved.

### Monitor new reviews and support changes

Use a stable monitor name and schedule the same saved task:

```json
{
  "plugins": ["woocommerce"],
  "sources": "both",
  "mode": "monitor",
  "monitorKey": "woocommerce-feedback",
  "firstRun": "emit",
  "maxTopicsPerSource": 50,
  "maxTopicsTotal": 100
}
```

The first successful run establishes a baseline. With `firstRun: "emit"`, it also exports the existing matching topics. Set `firstRun: "silent"` to establish the baseline without exporting existing items.

Later successful runs emit only newly observed or changed topics that match your filters. An unchanged run can legitimately return zero rows. Scrape mode always returns matching current snapshots and does not update monitor state.

| `changeType` | Meaning |
| --- | --- |
| `snapshot` | Matching data from a scrape-mode run |
| `baseline` | Matching data from the first successful monitor run |
| `new` | First observed by this monitor; not necessarily newly published |
| `updated` | Previously observed topic with a changed tracked field |

Monitoring state persists in this Actor's own named Apify storage, within the account running it. Each monitor name has a separate lock. Coverage and filter settings are fingerprinted: changing those settings creates a separate baseline rather than silently mixing incompatible comparisons. Network tuning and the first-run display preference do not change that scope.

State tracks scanned topics even if they do not currently match your filters. For example, a 5-star review edited to 1 star can later enter your negative-review results as an update. Changes that no longer match the filters are not emitted.

### Input reference

| Input | Default | Behavior |
| --- | --- | --- |
| `plugins` | Required | 1–20 slugs or official WordPress.org plugin URLs |
| `sources` | `both` | `both`, `reviews`, or `support` |
| `mode` | `scrape` | `scrape` or `monitor` |
| `monitorKey` | `default` | Stable monitor name; letters, numbers, `_` and `-` |
| `firstRun` | `emit` | `emit` or `silent` baseline |
| `maxTopicsPerSource` | `50` | Topics examined per plugin and source, **before filters** |
| `maxTopicsTotal` | `500` | Total topics examined across the run, **before filters** |
| `maxListingPages` | `5` | Listing pages scanned per plugin/source |
| `includeReplies` | `true` | Include public reply bodies |
| `maxRepliesPerTopic` | `50` | Reply bodies retained per topic, in source order |
| `maxReplyPages` | `3` | Maximum reply pages, including the first topic page |
| `includePinnedTopics` | `false` | Include sticky announcements |
| `reviewStars` | `[]` | Selected ratings as strings, e.g. `["1", "2"]`; empty means all |
| `supportStatus` | `all` | `all`, `unresolved`, `resolved`, or `no_replies` |
| `keywords` | `[]` | Match any literal phrase in the original title/body |
| `excludeKeywords` | `[]` | Exclude any matching literal phrase in the original title/body |
| `since` | Empty | Filter by last activity; `YYYY-MM-DD` or ISO timestamp with timezone |
| `requestDelayMillis` | `750` | Global minimum spacing between request starts; minimum 500 ms |
| `requestTimeoutSecs` | `30` | Timeout for each HTTP attempt |
| `maxRetries` | `3` | Retry count for temporary failures |
| `maxRequests` | `5000` | Hard HTTP request budget, including retries and redirects |
| `proxyConfiguration` | Direct requests | Optional Apify/custom proxy settings |

Keyword matching is case-insensitive and uses the original review or question. Replies are excluded from keyword filtering. Review-specific filters do not remove support topics, and support-specific filters do not remove reviews.

#### Example: negative plugin reviews

```json
{
  "plugins": ["woocommerce"],
  "sources": "reviews",
  "reviewStars": ["1", "2"],
  "maxTopicsPerSource": 100
}
```

#### Example: support questions with no replies

```json
{
  "plugins": ["contact-form-7"],
  "sources": "support",
  "supportStatus": "no_replies",
  "maxTopicsPerSource": 100
}
```

### Output fields

Each dataset row represents a review or support topic. Replies are nested inside that row, not emitted as extra topic records.

| Fields | Content |
| --- | --- |
| `id`, `topicId`, `recordType` | Stable topic identity; type is `review` or `support` |
| `pluginSlug`, `pluginName`, `url` | Plugin identity and WordPress topic source |
| `title`, `body`, `author`, `authorUrl` | Original public post and author |
| `rating` | 1–5 for reviews; `null` for support topics |
| `createdAt`, `lastActivityAt`, `observedAt` | Absolute timestamps in UTC |
| `supportStatus` | `resolved`, `unresolved`, `unknown`, or `not_applicable` |
| `isClosed`, `isPinned` | Separate closed-thread and pinned-post signals |
| `replyCount`, `participantCount` | Counts reported by the topic detail page |
| `isNegativeReview` | A review rated 1 or 2 stars |
| `isUnansweredSupport` | A support topic with zero reported replies |
| `hasPluginReply` | `true` when a collected reply has a plugin badge; `false` only when complete reply coverage finds none; otherwise `null` |
| `replies` | Reply ID, author, public badge, timestamp, body, and truncation flag |
| `repliesTruncated`, `replyPagesFetched` | Reply coverage information |
| `contentTruncated` | Original post exceeded the 50,000-character output cap |
| `matchedKeywords` | Included phrases found in the original title/body |
| `changeType`, `changedFields` | Monitoring change category and changed field names |
| `contentFingerprint`, `eventId` | Content-version fingerprint and event identity |

Public badges are evidence of a plugin role on that specific post. An unbadged user can still be associated with a plugin, so this is not identity verification. Individual reply bodies are capped at 20,000 characters and have their own `contentTruncated` flag. HTML is converted to text; scripts and login-only site-link placeholders are removed.

### Coverage and failure behavior

**This is a bounded monitor of the configured listing window.** Increase listing/topic limits to cover more history. The Actor follows WordPress's listing order; it does not promise a full historical archive or detection of every old-topic edit. A topic outside the scanned window is not rechecked independently.

Limits apply before filters. Checking 100 topics with a 1-star filter may produce fewer than 100 results. Earlier plugins are processed first, with reviews before support in `both` mode. The run summary explicitly reports skipped or capped sources. Pinned topics are excluded by default.

Missing topics are never interpreted as deleted, and missing resolution markers remain `unknown`. A closed thread is not automatically resolved. A thread with a reply is not necessarily solved.

Network failures, blocking pages, missing topics, and source markup changes are reported as errors rather than converted into empty successful results. When a source fails, valid partial rows remain available, the run fails visibly, and the previous monitor baseline is preserved.

Overlapping cloud runs using the same monitor name are rejected while the existing run holds its server-side lock. Locks are renewed while working and released when finished. After a forced termination, allow up to 10 minutes for the cloud lock to expire.

Interrupted, failed, or spending-limited runs can leave partial results. Retrying can repeat those rows; downstream consumers should deduplicate using `id` and `contentFingerprint`. This Actor does not claim exactly-once delivery. It retains up to 25,000 topic fingerprints per configuration; choose a fresh monitor name when intentionally starting a new tracking history.

### Pricing and integrations

When the owner enables pay-per-event pricing, one `topic` event is charged for each emitted dataset row. Nested replies do not create additional `topic` events. Filtered-out and unchanged topics do not produce this event; platform/run charges can still apply according to the active pricing configuration. Check the Pricing tab before running.

Apify's dataset exports support JSON, CSV, and Excel. Use JSON when you need nested replies. Schedule a saved task or connect run results to your own workflow through Apify's API or integrations. The Actor itself does not send emails, Slack messages, or WordPress replies.

### FAQ

**Does it scrape reviews on individual WooCommerce stores?** No. It collects plugin feedback from the public WordPress.org directory and support forums.

**Does it support premium-only plugins, themes, or private tickets?** No. A plugin must have an accessible WordPress.org support/review area. Themes and private support systems are outside this Actor's scope.

**Do I need a proxy?** It is optional. Direct access is the default. WordPress may rate-limit or block particular runtime IPs; configure a proxy if needed. No fixed-IP or permanent-access guarantee is made.

**Why did my monitor export zero rows?** It may be a silent baseline, nothing may have changed, or no scanned topics may match. Inspect `SUMMARY` to distinguish these cases from errors.

**Can it identify recurring complaints using AI?** It provides searchable text and keyword filters. It does not invent sentiment, diagnoses, or AI summaries.

This is an independent community Actor and is not affiliated with or endorsed by WordPress.org.

### Development

The project uses Node.js 24, TypeScript, the Apify SDK, Cheerio, and Zod. See **DEPLOY.md** for exact local-run, deployment, pricing, and release steps. See **VALIDATION.md** for the checks actually performed and the remaining cloud validation.

# Actor input Schema

## `plugins` (type: `array`):

Plugin slugs or official WordPress.org plugin URLs. Up to 20 plugins; duplicates are removed.

## `sources` (type: `string`):

Scrape reviews, support topics, or both.

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

Scrape returns matching snapshots. Monitor compares against a saved baseline and returns new or changed matching topics.

## `monitorKey` (type: `string`):

Give each independent monitor a stable name. Keep this name and settings unchanged on scheduled runs. Used only in monitor mode.

## `firstRun` (type: `string`):

Emit baseline outputs existing matching topics on the first successful run. Silent baseline saves current topics without emitting them.

## `maxTopicsPerSource` (type: `integer`):

Maximum topics examined for each plugin/source, BEFORE filters. Both sources means up to twice this number per plugin. Results follow the source listing order.

## `maxTopicsTotal` (type: `integer`):

Maximum topics examined across the run, BEFORE filters. Earlier plugins run first; sources skipped by this cap are reported in SUMMARY.

## `maxListingPages` (type: `integer`):

Safety cap for listing pagination. SUMMARY reports when more pages exist. Changes outside the scanned window are not detected.

## `includeReplies` (type: `boolean`):

Include public replies and plugin-author/support badges. Disable for lower output volume; topic details are still fetched.

## `maxRepliesPerTopic` (type: `integer`):

Maximum reply bodies returned per topic, in source order. Zero disables reply bodies. repliesTruncated flags incomplete coverage.

## `maxReplyPages` (type: `integer`):

Maximum pages of replies fetched per topic, including its first page. Truncation is explicit.

## `includePinnedTopics` (type: `boolean`):

Include sticky announcements. Disabled by default so old pinned posts do not consume the topic limit.

## `reviewStars` (type: `array`):

Leave empty for all reviews. Select ratings from 1 to 5. Does not filter support topics.

## `supportStatus` (type: `string`):

Unresolved and resolved use the explicit WordPress status. No replies means zero replies, regardless of closed/resolved status. Reviews are unaffected.

## `keywords` (type: `array`):

Case-insensitive literal phrases. Match ANY phrase in the original title or body, excluding replies. Leave empty for all.

## `excludeKeywords` (type: `array`):

Exclude topics containing ANY of these literal phrases in the original title or body.

## `since` (type: `string`):

Optional YYYY-MM-DD or ISO timestamp with timezone. Filters by LAST ACTIVITY, not creation date. Empty means no date filter.

## `requestDelayMillis` (type: `integer`):

Global minimum spacing between WordPress requests. At most two topic requests run concurrently.

## `requestTimeoutSecs` (type: `integer`):

Timeout per HTTP attempt.

## `maxRetries` (type: `integer`):

Retries for network errors, temporary server errors and blocking, using backoff and bounded Retry-After handling.

## `maxRequests` (type: `integer`):

Hard request budget including retries and redirects. Exceeding this fails clearly and preserves the previous monitor baseline.

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

Direct requests work without a paid external API. If WordPress blocks your runtime IP, configure an Apify or custom proxy. Proxy usage may add platform costs.

## Actor input object example

```json
{
  "plugins": [
    "woocommerce"
  ],
  "sources": "both",
  "mode": "scrape",
  "monitorKey": "default",
  "firstRun": "emit",
  "maxTopicsPerSource": 50,
  "maxTopicsTotal": 500,
  "maxListingPages": 5,
  "includeReplies": true,
  "maxRepliesPerTopic": 50,
  "maxReplyPages": 3,
  "includePinnedTopics": false,
  "reviewStars": [],
  "supportStatus": "all",
  "keywords": [],
  "excludeKeywords": [],
  "since": "",
  "requestDelayMillis": 750,
  "requestTimeoutSecs": 30,
  "maxRetries": 3,
  "maxRequests": 5000,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

One row per matching topic snapshot or monitoring change, with nested replies and source links.

## `summary` (type: `string`):

Request counts, per-source scan limits, errors, and whether monitoring state was committed.

# 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 = {
    "plugins": [
        "woocommerce"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("coolinbex/wordpress-plugin-reviews-support-monitor").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 = { "plugins": ["woocommerce"] }

# Run the Actor and wait for it to finish
run = client.actor("coolinbex/wordpress-plugin-reviews-support-monitor").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 '{
  "plugins": [
    "woocommerce"
  ]
}' |
apify call coolinbex/wordpress-plugin-reviews-support-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,coolinbex/wordpress-plugin-reviews-support-monitor"
        }
    }
}
```

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/ya72WOFgcXHtGtsoO/builds/k0wrGp93WxZbI7U5Q/openapi.json
