Ten wpis jest dostępny tylko po angielsku.
Content Gap Analysis from Search Console with Claude Code
Content gap analysis means finding the topics your site already gets impressions for, or should rank for, but has no good page for. I built a Claude Code plugin, seo-content, that does this from your own Google Search Console data: it ranks the opportunities, writes one brief per new post, and turns a brief into a draft that a human reviews before anything goes live. The Search Console and GA4 parts are free, and the paid parts are optional and cost cents.
Below: what it finds, how the keys are set up, what happened when I ran it on this site, and what it costs.
The short answer
The plugin has two skills. /seo-gaps pulls your Search Console data, finds opportunities, and writes a dated report plus briefs. /blog-draft takes a brief and writes a draft in your site's language, voice and frontmatter shape. Nothing is published, committed or pushed automatically.
Install it from my public marketplace:
/plugin marketplace add kkuklewski/claude-plugins
/plugin install seo-content@kkuklewski
It needs Node 22 or newer and has no npm dependencies. The marketplace is covered in a separate post.
What it finds in Search Console data
Search Console only knows queries your site already appears for, over the last 16 months. That is a real limit, and I come back to it below. Within it, /seo-gaps looks for five things:
- Striking distance: queries where you rank at positions 4.5 to 20. A small push can move them onto page one.
- Low CTR: a good position but a weak snippet, so the title and meta description need rewriting.
- Cannibalisation: several of your pages competing for the same query.
- Content gaps: queries with impressions but no page of yours in the top 20. Question-shaped queries are flagged and come first.
- Decay: pages that lost at least 30% of their clicks compared with the previous period.
Each signal maps to exactly one action. A content gap becomes a new post. Striking distance on a matching page becomes a refresh: missing subtopics, an FAQ, internal links. Low CTR becomes a snippet rewrite with two variants. Cannibalisation becomes a consolidation: pick the canonical page, merge and redirect. Decay becomes a refresh, and the tool shows which queries were lost.
Similar queries (plurals, word order, synonyms) are clustered into one opportunity before ranking. Opportunities are ranked by potential clicks times business fit. If you connect GA4, pages that convert weigh more, and refresh work on pages nobody engages with is pushed down. The click estimate uses a CTR curve that only ranks things, so treat it as an estimate.
Setup: your keys stay yours
The design rule is that every person brings their own credentials and nothing is shared through the plugin or the repo.
- Google service-account key, one per client. A JSON file on your disk, outside any repo, for example
~/.config/<client>/sa.json, withchmod 600. The repo's.seo-content.jsonstores only its path ingoogleKeyFile, never the contents. - DataForSEO login and password (optional). Set through
/plugin configure seo-content@kkuklewski. The password is marked sensitive and goes to the OS keychain. - Site settings such as the Search Console property, brand terms and tone of voice live in
.seo-content.json. It is committed, shared by the team, and contains no secrets.
The steps: enable the Google Search Console API in your Google Cloud project, create a service account (no roles needed), download a JSON key, then add the service account's client_email as a user on your Search Console property. Restricted access is enough. Then run /seo-gaps --setup in the repo. It looks for an existing key, checks every data source, tells you what is missing, and writes .seo-content.json. If the property is not visible to the key, setup_check says so and tells you to add the service account under Search Console, Settings, Users and permissions.
Credentials only reach the bundled MCP server's environment. They are never put into prompts or skill text, and the skill is instructed never to print a key or key-file contents.
My first run: it could not read my data
I ran it on kuklewski.com and it could not pull any Search Console data. The service account did not have access to that property yet. I had created the key but had not added the service account as a user in Search Console.
This is the failure the setup_check tool exists for. It checks each source and returns ok, missing or error together with the exact fix. For Search Console it told me to add the service account's email as a user on the property, and it lists the properties the account can see, so you can also pick a different one. Because the key path lives in .seo-content.json as googleKeyFile, the fix is a setting in Search Console, not a change in the repo or the plugin.
So this post has no "I found 12 gaps on my own site" section, and I will not make one up. Once the access is fixed I will rerun it and write the results into a follow-up. For now it shows the setup flow and the honest failure mode, which is that you find out in the first minute and you get told what to fix.
Running /seo-gaps
With access working, the run goes like this:
- Preflight.
setup_checkruns every time, using the per-client key. - Pull and rank.
gsc_opportunitiesreads the last 90 days by default (--dayschanges it). On a small site with under 1,000 impressions, or empty lists, it retries once with a lower minimum of 10 impressions and says so. - Check what exists. It reads your sitemap so it never proposes a post that duplicates one you already have, and reads an existing page's headings before choosing refresh versus new post.
- Write the output. A dated report with one table per action type, plus briefs for the top opportunities. The default is 5 briefs.
--briefs 0gives the report only, and--refresh-onlyskips new posts.
From brief to draft
A brief is a markdown file with frontmatter and fixed sections: why this post (the evidence), reader and goal, what ranks now, term plan, our angle, outline, internal links and facts to verify. /blog-draft reads it, then reads two or three existing posts from your repo and copies their frontmatter shape exactly, so the draft drops in without edits.
The draft rules are strict on purpose:
- The first two or three sentences answer the searcher's question directly.
- Anything factual that is not common knowledge or in the brief stays in but is marked
[VERIFY: what to check]. Invented statistics, quotes and prices are not allowed. - The frontmatter gets two title variants (up to 60 characters) and two description variants (up to 155).
- A review checklist is appended for the editor to delete before publishing.
The draft is for a human to correct. I read every [VERIFY] marker, fix or cut it, and choose the title myself.
What the optional paid calls cost
Search Console and GA4 are free. For keyword demand and a look at the top 10, the plugin can call DataForSEO, which is pay-as-you-go and optional. You add your own login. The plugin states the estimated cost before a paid call. These are real figures from my own runs on 2026-09-30:
| Call | What it did | Cost |
|---|---|---|
keyword_volume | 14 keywords | $0.0132 |
term_plan | first plan | $0.0211 |
term_plan | second plan | $0.0313 |
The volume call is where this post's numbers come from: "content gap analysis" shows US monthly volume 170 and difficulty 20, and "claude code seo" shows 90 and 36. Difficulty is DataForSEO's own score, so I treat it as a relative guide between keywords, not an absolute measure.
A term plan takes the live Google top 10 for a keyword and returns what those pages share: target length, heading counts, common terms with a use range, and questions. /blog-draft can score a draft against it. It is a coverage checklist from word statistics, not semantic analysis, and I did not run one for this post. It is on the review checklist.
Limits
- Search Console shows only queries you already appear for. Brand-new topics need
competitor_gap(paid) or your own ideas. - There is no backlink data.
- Drafts are drafts. Facts are marked for checking and nothing publishes itself.
If you are also tidying the technical side of a site, my audit skill is the natural companion, described in my website audit checklist post. And if you want the bigger picture of where this sits in AI-assisted work, there is a separate post on that.
The site this runs on is self-hosted, as described in my Payload and Coolify guide. If you want this set up on your own site, get in touch.
FAQ
Do I need a paid SEO tool? No. Search Console and GA4 cost nothing. DataForSEO is optional and adds competitor gaps, search volume, SERP snapshots and term plans at cents per call.
Where does my Google key live?
In a JSON file on your disk outside any repo. The repo holds only the path in .seo-content.json, which is committed and contains no secrets.
Will it publish posts for me?
No. It writes a report, briefs and drafts into your docs/seo folder. Review and publishing are yours.
What if Search Console access is not set up?
setup_check reports which source is missing and gives the exact fix, such as adding the service-account email as a user on the property.
Does it work for clients?
Yes. Each client repo has its own .seo-content.json with its own googleKeyFile, so keys and properties never mix.