# Google Trends Content Planner — Evidence-backed Topic Ideas (`joshuayan/google-trends-content-planner`) Actor

Turn seed keywords into prioritized content opportunities. Discover related searches, validate candidates with a second Trends lookup, and export evidence-backed titles, angles, trend metrics, regions and topics. Incomplete plans are clearly labeled; insufficient data incurs no plan event fee.

- **URL**: https://apify.com/joshuayan/google-trends-content-planner.md
- **Developed by:** [joshua yan](https://apify.com/joshuayan) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $25.00 / 1,000 evidence-backed content plans

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Google Trends Content Planner — Evidence-backed Topic Ideas

Turn seed keywords into a prioritized content plan instead of a folder of raw Trends exports. The Actor discovers related searches, validates the best candidates with a second lookup, and returns deterministic content cards with reproducible evidence.

### Workflow

1. Fetch Trends data for 1–5 seed keywords.
2. Merge related Top and Rising queries across seeds, retaining every source.
3. Spend the remaining `maxQueries` budget validating each selected candidate.
4. Score discovery signal (up to 35), support across seeds (20), within-query momentum (30), and goal-specific keyword intent (15).
5. Return ranked titles, angles, target queries, supporting evidence, and caveats.

No LLM or additional data provider is used. Titles and explanations come from transparent templates. Google Trends values are normalized, sampled relative interest—not absolute search volume. Scores are ranking heuristics, not traffic forecasts. Independent 0–100 curves are never concatenated into a single time series.

Candidates must contain all word anchors from at least one source seed before using a validation query. Matching ignores case, Unicode width and punctuation; CJK anchors may occur inside a longer CJK phrase. Candidates longer than 100 Unicode characters are excluded as an application limit. This conservative rule can omit useful synonyms and translations; it is not semantic relevance verification. Original candidates remain in the exclusions with `seed_mismatch` or `query_too_long`. Breakout priority applies only after these checks.

### Input

```json
{
  "seedKeywords": ["AI video", "video marketing"],
  "contentGoal": "educate",
  "searchType": "web",
  "timeRange": "today 3-m",
  "geo": "US",
  "category": "",
  "maxOpportunities": 5,
  "maxQueries": 12
}
```

| Field | Default | Meaning |
| --- | --- | --- |
| `seedKeywords` | required | 1–5 non-empty topics; case/whitespace duplicates are removed |
| `contentGoal` | `traffic` | `traffic`, `educate`, or `convert`; changes intent scoring and templates |
| `searchType` | `web` | `web`, `news`, `youtube`, `images`, or `shopping` |
| `timeRange` | past 12 months | Standard Google Trends preset |
| `customTimeRange` | empty | `YYYY-MM-DD YYYY-MM-DD`; overrides `timeRange` |
| `geo` | worldwide | Data region such as `US` or `US-CA` |
| `category` | all | Non-negative Trends category ID |
| `maxOpportunities` | `10` | 1–20 returned content cards |
| `maxQueries` | `12` | 2–30 total discovery + validation queries; must cover all unique seeds |
| `maxConcurrency` | `10` | Concurrent query workers |
| `maxRequestRetries` | `7` | Full-query retries; `0` disables retries |
| `pageLoadTimeoutSecs` | `180` | Browser navigation limit; component requests use at most 10 seconds |
| `viewedFrom` | `US` on Apify | Residential proxy country, independent of `geo` |

### Output

The default Dataset contains one row per opportunity:

- Identity and decision: `opportunityId`, `rank`, `keyword`, `score`, `confidence`, `contentGoal`.
- Actionable brief: `suggestedFormat`, `suggestedTitle`, `contentAngle`, `whyNow`, `targetQueries`.
- Evidence: `supportingTopics`, `recommendedRegions`, `metrics`, and `evidence` (query ID, source URL, source seeds, filters, retrieval time).
- Quality: `caveats` and `missingSections`.

Nested arrays/objects remain JSON structures. CSV export serializes those cells rather than creating one row per evidence item.

The key-value store also contains:

- `CONTENT_PLAN`: complete JSON plan, algorithm version, filters, exclusions, and data-quality counts.
- `CONTENT_PLAN.md`: readable editorial checklist with evidence links and caveats.
- `OUTPUT`: `succeeded`, `partial`, or `insufficient_data`, query/opportunity counts, missing components, charging, and stop reason.

A candidate passes validation only when its own original timeline has at least four valid buckets in each chronological half and at least 50% valid buckets overall. Valid means non-partial, `hasData: true`, and a finite interest value from 0 through 100. This fixed coverage rule prevents a few isolated points from becoming a normal ranked card; it is not a statistical-significance test or an absolute-demand estimate.

Momentum is calculated from the valid values in the original first and second halves, not from a compressed array. Metrics include `originalPointCount`, `validPointCount`, `coverageRatio`, `previousValidPointCount`, `recentValidPointCount`, and `latestValidTime`. Momentum needs at least eight valid points; a zero baseline followed by positive interest leaves momentum unknown. When the previous average is above zero but below 5, the raw percentage is retained with a low-baseline caveat, but momentum score and momentum confidence are both zero. Corroboration counts only additional independent seeds: one seed scores 0, two score 10, and three or more score 20.

Timeline is required. Related queries, regions, and related topics are optional enrichment. Each missing enrichment component reduces confidence by 0.10 without changing the ranking score. Candidate-owned related queries must also retain every source-seed anchor and add up to four secondary `targetQueries` (five including the candidate); unrelated secondary queries are removed rather than presented as editorial targets. Supporting topics retain at least one seed anchor, and topics/regions are each capped at five entries for readability. Seed evidence is never substituted for candidate evidence. Google may omit `hasData` from ranked rows, especially Rising rows. Such rows remain evidence when present in a successfully retrieved component; explicit `hasData: false` is excluded and the original flag is preserved. Each evidence query retains its original `retrievedAt`; the card timestamp is the latest of its own contributing queries. Confidence is a heuristic, not a calibrated probability.

#### Coverage and exclusions

`CONTENT_PLAN.coverage.validationAttempts` counts attempted candidate lookups, including failed ones; `validationPassed` counts candidates that passed all evidence requirements before `maxOpportunities` truncation. `failedQueries` and the JSON `failures` list expose seed and candidate failures with query ID, input, and reason. `OUTPUT.queriesUsed` and `CONTENT_PLAN.budget` count logical seed and candidate queries. Retries and component HTTP requests consume compute/proxy resources but do not increase this logical query count; `maxQueries` is not a dollar cap.

Each discovered candidate omitted from the Dataset appears in `CONTENT_PLAN.exclusions` and the Markdown plan. Coverage exclusions include the original/valid counts, ratio, per-half counts, and query ID:

| Reason | Meaning |
| --- | --- |
| `seed_mismatch` | Candidate does not retain all anchors of any source seed |
| `query_too_long` | Candidate exceeds the 100-character application limit |
| `query_budget` | Not selected within the remaining query budget |
| `validation_unavailable` | Selected but no candidate lookup result is available |
| `validation_failed` | Candidate lookup failed |
| `required_sections_missing` | Required candidate timeline is unavailable |
| `no_valid_timeline` | No complete usable candidate-specific interest points |
| `insufficient_coverage` | Some valid points exist, but a half has fewer than four or overall coverage is below 50% |
| `rank_limit` | Evidence passed, but the candidate falls below `maxOpportunities` |

A budget, rank, or coverage cutoff does not imply low demand. Use `maxQueries` greater than the unique seed count to leave room for validation. If no candidate passes, the plan is `insufficient_data`. A plan with usable cards and failed/unusable queries is `partial`; budget/rank cutoffs alone do not make it partial.

### Charging and recovery

A single `content-plan` event is emitted after an evidence-backed plan is persisted, including a partial plan with at least one usable card. Invalid input, insufficient data, and runs with no passing candidate emit no event. Apify compute and proxy usage are separate. The event price is configured when the Actor is published.

Completed query evidence, retrieval timestamps, Dataset delivery, and charging are journaled. Resuming the same run with unchanged input avoids repeating completed queries, delivered rows, or the plan charge. Changed input requires fresh storage.

Stored evidence is checked for valid data structure, status, query identity, and matching input before it is reused. Corrupted or mismatched records stop the run with a fresh-storage error. Completed failed lookups are also retained; start a new run to collect new evidence.

The current algorithm version is `10`. An older algorithm's storage cannot be resumed with this version; use fresh storage to avoid mixing evidence or delivery records.

`OUTPUT.stoppedBy` is `chargeLimit` when the plan event could not be charged within the event budget, or `chargeUncertain` when the charging ledger cannot confirm its outcome. Uncertain charges are not blindly repeated.

Use the Dataset API, Apify Schedules, Zapier, or Make to hand the ranked cards to an editorial workflow. This Actor does not publish to a CMS or generate unsupported claims with an AI model.

### Local development

From the repository root, use `pnpm dev:content-planner` to run with local INPUT storage, `pnpm vitest run actors/google-trends-content-planner` for offline tests, `pnpm verify` for repository checks, and `pnpm bundle:content-planner` to create the deployment bundle. A normal development run contacts Google; tests use local fixtures. Deployment and cloud validation are separate steps.

Related data is checked against its query keyword, geography, time-range parameter, category and search property. A nonempty ordinary Google widget response can recover an unavailable embedded component; an empty fallback remains missing. Optional topics use the shared HTTP session pool and ordinary-widget fallback rather than a serial browser reader, so optional enrichment does not block other query components. Failed queries retain the configured total request retry budget and backoff. A partial result with only optional enrichment missing is accepted without repeating the whole query, preventing optional-topic quota limits from multiplying traffic and cost. These controls reduce unnecessary traffic but cannot guarantee that Google will not rate-limit a run.

### Development status

This is an independent Actor and does not call or modify `google-trends-scraper`. Private cloud validation precedes any public release. Local fixture, recovery, bundled-entry, and small cloud samples do not constitute long-term reliability, performance, or commercial guarantees. Check the current Store page for deployment status and pricing.

# Actor input Schema

## `seedKeywords` (type: `array`):

One to five topics to expand and validate. Duplicates ignoring case and repeated whitespace are removed.

## `contentGoal` (type: `string`):

Changes transparent keyword-intent scoring and deterministic content templates.

## `searchType` (type: `string`):

Google Trends search property used for both discovery and candidate validation.

## `timeRange` (type: `string`):

Predefined Trends time range. customTimeRange takes precedence.

## `customTimeRange` (type: `string`):

Optional fixed range in YYYY-MM-DD YYYY-MM-DD format.

## `geo` (type: `string`):

Uppercase country or region code such as US or US-CA. Empty means worldwide; this is independent of proxy location.

## `category` (type: `string`):

Non-negative Google Trends category ID. Empty means all categories.

## `maxOpportunities` (type: `integer`):

Maximum number of ranked content cards returned.

## `maxQueries` (type: `integer`):

Maximum logical seed and candidate queries, excluding retries and component HTTP requests. Must cover all unique seeds; leave additional queries for validation.

## `maxConcurrency` (type: `integer`):

Maximum concurrent Trends queries.

## `maxRequestRetries` (type: `integer`):

Full-query retry budget, not a retry multiplier for every component.

## `pageLoadTimeoutSecs` (type: `integer`):

Browser navigation timeout; data requests use at most 10 seconds.

## `viewedFrom` (type: `string`):

Optional two-letter residential proxy country, default US on Apify. Independent of data geo.

## Actor input object example

```json
{
  "seedKeywords": [
    "AI video",
    "video marketing"
  ],
  "contentGoal": "traffic",
  "searchType": "web",
  "timeRange": "",
  "customTimeRange": "2026-01-01 2026-03-31",
  "geo": "",
  "category": "",
  "maxOpportunities": 10,
  "maxQueries": 12,
  "maxConcurrency": 10,
  "maxRequestRetries": 7,
  "pageLoadTimeoutSecs": 180,
  "viewedFrom": "US"
}
```

# Actor output Schema

## `opportunities` (type: `string`):

Evidence-backed content cards in the default dataset.

## `plan` (type: `string`):

No description

## `markdown` (type: `string`):

No description

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

No description

# 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 = {
    "seedKeywords": [
        "AI video"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("joshuayan/google-trends-content-planner").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 = { "seedKeywords": ["AI video"] }

# Run the Actor and wait for it to finish
run = client.actor("joshuayan/google-trends-content-planner").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 '{
  "seedKeywords": [
    "AI video"
  ]
}' |
apify call joshuayan/google-trends-content-planner --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,joshuayan/google-trends-content-planner"
        }
    }
}
```

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/15N48HmdbhDzwqlqy/builds/mj5QPQnfO8bOpSv3M/openapi.json
