My Notion database knows my SEO better than I do
I used to login to Search Console to check my search stats. Now a Notion Worker writes clicks, position and the main query onto every article row, and I can ask Notion AI what’s changed
Summary: Search data goes unused because it lives in Google Search Console, a tab you open when you remember, while the decisions about what to write and fix happen somewhere else. The number it leads with, average position, also pointed me at the wrong articles. A better signal is your position on the one search that shows each article the most, and you can check that by hand today. To make it automatic, I built a Notion Worker that writes clicks, impressions, position and that main search onto each article's row every day, and a formula labels each article: protect, refresh, watch or rework. Then I ask Notion AI what's new, which is the question Search Console can't answer. Claude wrote the code and the build took a few hours.
The Notion ambassadors' Slack channel had been full of chatter about Workers for weeks, and I felt like I should know what one was before I turned up to the summit. So, as homework, I built one the week before I flew out, and I picked the thing I was already doing badly. Producing Paradise has 171 published articles, and until last month I could not have told you which ten deserved my next free Thursday. The answer was in Search Console, which I open about four times a year, and even then it's a table of URLs that doesn't know which of my articles are half-finished or on my list to improve.
My content database in Notion knows all of that. It has a row for every article, with status, publish date and the live URL, so the fix was to bring the search numbers to the rows and let Notion's filters and formulas do the sorting.
Highlight this:
Search Console is a reporting tool until it sits next to your content. Then it becomes a to-do list.
Gina Ribando from In Full Detail, a fellow ambassador I met at the summit and now count as a friend, interviewed me about it there. If you'd rather hear me explain it with slightly less polish, in the Notion office, her summit recap is here, starting at my bit (from 10:46).
You can run this check by hand today
You don't need to build anything to get the first win, just a Search Console account for your site. The worker automates one check, and the check takes a few minutes per article.
In Search Console, open 'Performance', tick 'Average position' above the chart, and go to the 'Pages' tab.
Click one of your articles. Search Console now shows numbers for that page only.
Switch to the 'Queries' tab and sort by impressions, highest first.
The top row is your main query: the search that shows this article to the most people. Note your position on it.
Then read the position against these rules. I ignore anything under 500 impressions a month, because position means little at low volume.
| Label | When | What I do |
|---|---|---|
| Protect | Position better than 4 | Leave the title alone |
| Refresh | Position 4 to 6.9 | Rewrite the title and meta description towards the main query |
| Refresh (priority) | Same, with under 2% of viewers clicking | These first |
| Watch | Position 7 to 7.9 | Check again next month |
| Content gap | Position 8 or worse | Check the main query matches the article, then rework the content |
| Your rule | Your threshold | Your action |
ACTIVITY: Run the check on your five biggest articles and write your own rules before you copy mine. The 500 impressions and the 2% are my judgement calls for a site my size.
If SEO is new to you, Google's own starter guide is a good primer, and it backs the rules above. The title link and the snippet are the two parts of a search result you control, and Google suggests waiting a few weeks before judging whether a change worked. That's why the rewrite rules target the title and meta, and why 'Watch' waits a month.
Average position misled me
There's a reason the check above asks for your position on the main query, and not the average position Search Console puts in front of you. Search Console gives each page an average across every search it appeared in, and I built my first set of rules on that number, and it pointed me at the wrong articles. (The figures that follow are from the week I built the worker, and they'll have moved by the time you read this.)
My Gmail forwarding article averages position 9.3, which reads as a page in trouble. The worker's query lookup showed why. For 'gmail forward to multiple email addresses', the thing the article is about, it ranks around position 2, and the 9.3 comes from one enormous generic search, 'how to forward emails in gmail', where it sits ninth with 140,273 impressions and zero clicks. Those people want Google's own help page, no action needed.
The Siri article is the opposite case. One search, 'siri commands', accounts for nearly all of its 7.3 million impressions, at position 8.2. The intent matches the article exactly, so that's a gap worth closing, and the biggest opportunity on the site.
That's why the worker records a 'main query' for each article, the search with the most impressions, along with my position on it. It's also why I wasn't going to do this by hand for 171 articles every month.
A worker is a small program Notion runs for you
Notion Workers are new. You write a short program, send it to Notion with one command, and Notion runs it on a schedule. There's no server to rent and nothing running on your laptop. Matthias Frank's beginner's guide is a good primer if you want the long version!
Once a day, mine asks Search Console for the numbers on every page of my site, matches each page to a row in my articles database using the URL, and updates six properties on that row. It also keeps a second database of its own, called 'Article performance', with a row for every URL Google knows about, including tag pages and old broken links.
I made two decisions I'd recommend copying. The worker can only update rows that already exist, and the Notion connection it uses has no permission to create pages, so it can't litter my database with surprises. And it only writes when a number has changed.
You need four things before you start
| What | Why | Cost |
|---|---|---|
| Notion Business or Enterprise plan | Workers aren't on Free or Plus | Free during the beta. Once Workers move onto Notion credits, Notion's own example prices a daily sync at a few cents a month |
| Google Search Console for your site | It's the data source | Free |
| A Google Cloud project | It lets your worker log in to Search Console as you | Free |
| An articles database with a URL property | The URL is how each page finds its row | Free |
Plus a few hours, and you'll need to be okay with opening Terminal and pasting things into it. You don't need to be a developer, though. Claude wrote every line of my code, and my job was pasting commands and making the decisions.
ACTIVITY: Open your articles database and check the URL property. Every published row needs the full live address, exactly as it appears in your browser. Claude checked all 171 of mine during the build, and found one article sitting in the database twice.
Let Claude write the code
I built this in Claude's Cowork mode with my project folder connected, so it could read Notion's starter files and write code against them. Claude Code works the same way. The starter project ships with its own prompts and a '/sync' skill for coding agents.
Give Claude the prompt below and let it build yours from scratch. It's slower than copying someone else's worker, and you'll understand yours better at the end of it.
PROMPT: "I want to build a Notion Worker that syncs Google Search Console data into my Notion workspace. Read the Workers documentation at developers.notion.com/workers first, then read the scaffold in this folder before writing any code. Requirements: one daily sync in replace mode. Pull clicks, impressions and position per page for the last 28 days, the previous 28 days and the last 12 months. Write every URL to a managed database. Then, using the Notion API client, update matching pages in my existing articles database, matched on my URL property, writing only to number properties I name and only when a value has changed. Never create pages. For articles over 100 impressions, fetch the query with the most impressions and the top two queries by clicks. Add a write limit setting so I can test on my ten newest articles first. Use Google OAuth with the read-only Search Console scope. Tell me which properties to create myself, and give me every terminal command starting with the cd into the project folder."
Set it up in this order
Two of these steps fail quietly if they happen too early, so follow the order.
Install Notion's command line tool and create the project. Notion's quickstart has the three commands. Deploy the untouched starter once, which logs you in to the right workspace.
In Google Cloud, create a project and enable the 'Google Search Console API'.
Set up the OAuth consent screen, add the read-only Search Console scope, and set the publishing status to 'In production'. Left on 'Testing', Google expires your login every seven days and the sync stops without telling you.
Create an OAuth client of type 'Web application'. Run ntn workers oauth show-redirect-url and paste the result in as the authorised redirect URI.
In Notion, create an internal connection with read and update access only, and connect it to your articles database.
Add the properties to your database yourself: four numbers ('Clicks 28d', 'Impressions 28d', 'Position 28d', 'Clicks 12m'), one text ('Top queries 28d') and one more number ('Main query position').
Store your secrets, then deploy, then authorise Google:
cd path/to/your-worker
ntn workers env set GOOGLE_CLIENT_ID=... GOOGLE_CLIENT_SECRET=... NOTION_API_TOKEN=... GSC_SITE=sc-domain:yoursite.com
ntn workers deploy
ntn workers oauth start googleAuthSecrets go in before the deploy, because the Google login is tied to them at that moment.
Lesson: type your secrets into Terminal and nowhere else. I pasted my whole Terminal window into the chat to show Claude where I was up to, secrets included, then had to regenerate both. (So, you know, check what you're pasting.)
Test on ten articles first
My worker has a setting called 'ARTICLE_WRITE_LIMIT'. Left unset, it writes nothing to my articles database at all. At 10 it writes to the ten newest articles, and 'all' opens it up to every row.
So the first real run touched ten rows. Claude read those rows back and checked the totals against what the sync had pulled, and then I let it loose on the other 161. Compare one article against the Search Console screen yourself, for the same dates, before you trust yours.
cd path/to/your-worker
ntn workers sync trigger performanceSync --preview
ntn workers env set ARTICLE_WRITE_LIMIT=10
ntn workers deploy
ntn workers sync trigger performanceSyncThe preview command shows what the sync would write to its own database and writes nothing there. It still runs your code, which is why the write limit starts at zero.
The first run turned up one more thing. My site moved from WordPress to Squarespace, and Google still held a year of clicks against the old addresses. My Gmail forwarding article showed 2,191 clicks for the year. With the old address folded in, it's 4,478. If you've ever migrated, ask Claude to fold old paths into new ones.
A formula tells me what to do next
With the numbers on the row, a Notion formula can apply the rules from the manual check to every article at once. Mine uses the main query position where there is one and the average where there isn't, and stays blank under 500 impressions.
ACTIVITY: Build two views of your articles database: one filtered to labels containing 'Refresh', one to 'Content gap', both sorted by impressions, highest first.
Here's the formula, for a property called 'SEO action':
lets(
main, prop("Main query position"),
pos, if(empty(main), prop("Position 28d"), main),
basis, if(empty(main), " (average)", " on main query"),
imp, prop("Impressions 28d"),
ctr, if(imp > 0, prop("Clicks 28d") / imp, 0),
where, "Pos " + format(round(pos * 10) / 10) + basis + ". ",
if(empty(pos) or imp < 500, "",
if(pos < 4, "Protect: " + where + "Leave the title alone",
if(pos < 7,
if(ctr < 0.02,
"Refresh (priority): " + where + "Rewrite title and meta towards the main query",
"Refresh: " + where + "Test a sharper title and meta"),
if(pos < 8, "Watch: " + where + "Recheck next month",
"Content gap: " + where + "Check the main query matches this article first"))))
)The views and the formula kind of give me my actions, which matters because my backlog of article ideas is huge (I don't have a good concept of the finiteness of time), I want to write all of them, and the useful question has always been which one next.
Google Analytics is next
I’ve got the worker pulling data from Google Analytics but it’s miscounting, so that’s next on my list to fix! For the Claude custom instructions article it reports 661 sessions in a month when Search Console counts 3,965 clicks from Google alone.
My cookie banner asks visitors to opt in, and Google Analytics only counts the ones who say yes. Enough of my readers are in the UK and Europe, where that choice is theirs to make, so the banner stays. The sessions column is in the database, I know it's low, and fixing the tracking is the next job. If your site has an opt-in banner, you might notice the same gap.
Search Console can't answer questions
Having the numbers in Notion is only a bit faster than opening Search Console. I was already doing this, and the worker sped it up. What I couldn't do before is talk to the data.
Search Console has suggested insights, and they are bad. It's either "performance is up" (I knew that) or something completely irrelevant to my site. Inside Notion I can ask the AI directly, in the workspace where I'm planning: tell me what's new in the article performance database. Give me the cheat sheet. What should I be asking you about this? What's interesting here? It'll find something I wouldn't have got from reading the views myself, and that turns into either a tweak to an existing article or a new one.
PROMPT: "Look at the article performance database. Compare the last 28 days with the 28 before. Tell me which three articles moved the most in either direction, and for each one say whether the change looks like a title and meta problem (impressions up, clicks flat) or a content problem (impressions down)."
FOLLOW-UP PROMPTS:
"What should I be asking you about this data that I haven't?"
"Which articles have high impressions and low click-through? Draft two meta descriptions for the top one."
"Which topics are climbing that I haven't written a follow-up on?"
That last one is the backlog question. When something is climbing, that's the validation to go and write the next thing on that topic, and it's the closest I've got to a tie-breaker for a list that otherwise has no order. It's super nerdy. I love it.
Questions you might have
Do I need to know how to code?
No, but you do need to be comfortable pasting commands into Terminal and reading what comes back. Claude wrote the code and explained each error. Budget time for the Google Cloud setup, which is the fiddliest part and has nothing to do with Notion.
What does it cost?
Workers need a Notion Business or Enterprise plan. They've been free during the beta, and Notion has said they'll move onto Notion credits, with its own example pricing a daily sync at a few cents a month. Check Notion's credits help pages for the current state. Search Console and the Google Cloud project are free.
Could it damage my articles database?
Mine can't create or delete pages, because the Notion connection has read and update permission only, and the code writes to six named properties. Start with the write limit at ten. One side effect: every updated row gets a new 'Last edited time', so that column stops telling you when a person last touched the article.
Does this work if my site isn't on Squarespace?
Yes. Search Console works the same whatever your site is built on. The only thing the worker needs from your site is that each article's live URL is stored on its Notion row.
Why not use an automation tool like Zapier?
Every step here is known in advance: fetch, match, write. That's a job for a small script on a schedule. Paying per step for an AI agent makes sense when the next step can't be predicted, and every step here can.
Search numbers sorted? Come and see what I build next
This worker started as a question about what Notion's new developer tools could do for a small content site, and it turned into something I love.
Prefer something you can install today?
My free Notion custom agents, including Reality Check and Next Step Nudge, are on the Notion marketplace and need no code at all.