Yes, programmatic SEO still works, on one condition: every page has to be built on data that only that page has. Programmatic SEO means building many pages from one template and a dataset, such as one page per city, per integration, or per product pair. AI makes it fast to write those pages. It also makes it fast to publish 500 pages Google will never index. The difference comes down to the data, not the model. This article covers what Google's scaled content abuse policy actually says, the test I use before building anything, how to build the dataset, where AI helps and where it must not, the exact prompt and quality checks I use, the launch order, the things that do nothing, and the questions people ask most.
What Google's policy actually says
In March 2024 Google added scaled content abuse to its spam policies. It describes producing many pages mainly to manipulate search rankings rather than to help people, and it applies whether the pages were written by a person, generated by a model, or both. Two points in that policy matter for anyone planning programmatic pages.
- Google does not object to templates or to AI. Plenty of large, useful sites are built from templates, such as currency converter pages, app integration directories, and travel listings.
- Google objects to pages that exist only to catch a keyword and add nothing a searcher could not get elsewhere. Swapping a city name into the same paragraph 300 times is the textbook case.
So the question for every programmatic project is not whether to use AI. It is whether each page would still be worth reading if it were the only page of its kind on the site.
The test I run before building anything
Take two pages your template would produce, for example the Austin page and the Dallas page. Remove the city name from both. Would the two pages still be different? If not, you are about to build near-duplicates, and the project will end in the Crawled, currently not indexed report. If yes, list exactly which fields make them different. Those fields are your dataset.
Good programmatic pages are built on facts that change per page:
- Location pages: real service areas, local prices, response times, jobs done in that area, reviews from customers there, and the local details customers mention.
- Integration pages: what syncs, in which direction, how often, the setup steps, the limits, and screenshots of the actual screens.
- Comparison pages: pricing, features and limits taken from each vendor's own pages, dated, with a clear choose this one if verdict.
- Glossary and how-to pages: an answer specific to your product or industry, not a dictionary definition anyone could write.
Check the demand before you build
A dataset can be perfect and still produce pages nobody searches for. Before building, confirm the pattern has demand in three places. Type the pattern into Google with a few real values, such as emergency plumber round rock, and read the autocomplete suggestions and the results. If the results are full of directory pages and local businesses, the searches are real. Check Search Console for queries that already match the pattern, because those are searches your site is close to winning. Then check a keyword tool for volume on a sample of 20 values, not just the biggest one. If only the top three cities have any searches, build three strong pages by hand instead of three hundred thin ones.
Step 1: Build the dataset first
The dataset is the product. The template and the prose come later. Build it in a spreadsheet with one row per page and one column per fact. This is an example for a plumbing company's city pages. The company and figures are invented to show the shape:
slug,city,service_area_zips,avg_response_min,jobs_last_12m,common_issue,starting_price_usd,review_quote,review_author
emergency-plumber-austin,Austin,"78701;78702;78703",42,318,slab leaks,149,"Fixed our slab leak the same night.",Maria G.
emergency-plumber-round-rock,Round Rock,"78664;78665",55,121,water heater failure,149,"Replaced the heater before work.",Dan P.Every column is a fact a page can state. If a cell is empty, the page says nothing about that point. It never fills the gap with a guess. Where the data comes from matters too: your own job records, your booking system, your product database, public data you can cite. If the only source for a column is a model's imagination, delete the column.
Step 2: Write one template and ten pages by hand
Design one template with fixed sections, then write the first ten pages yourself or with close editing. Publish them, link to them from a hub page, and wait two to three weeks. Check in Search Console whether they are indexed and whether they pull impressions. If ten careful pages do not get indexed, 500 generated pages will not either, and you have saved yourself a cleanup.
Step 3: Where AI helps, and where it must not
I use Claude and ChatGPT to turn structured rows into readable sections, to vary the phrasing across hundreds of pages so they do not read like a mail merge, and to draft FAQs from real customer questions. I do not let a model invent facts. If the dataset has no price for a city, the page does not state one. Every number on the page should trace back to a cell in the data.
This is the prompt pattern I use. The model receives one row at a time and a strict instruction about what it may say:
You are writing one section of a service page for {business_name}.
Use ONLY the facts in the JSON row below. Do not add numbers, names,
prices, places or claims that are not in the row. If a field is empty,
do not mention that topic.
Write:
1. A 2-sentence intro that answers "emergency plumber in {city}" directly.
2. A paragraph on {common_issue} in {city}, using jobs_last_12m and
avg_response_min exactly as given.
3. One customer quote, verbatim, with the author's name.
Plain language, no superlatives, no em dashes.
ROW:
{row_json}Step 4: Check the batch before it goes live
Two automated checks catch most problems before Google does. The first measures how similar the generated pages are to each other. The second confirms every number in the text exists in the row it came from. This short Python script handles the first one:
from itertools import combinations
import re, glob
def shingles(text, n=5):
words = re.findall(r"\w+", text.lower())
return {" ".join(words[i:i + n]) for i in range(len(words) - n + 1)}
pages = {p: shingles(open(p, encoding="utf-8").read()) for p in glob.glob("out/*.txt")}
for a, b in combinations(pages, 2):
j = len(pages[a] & pages[b]) / max(1, len(pages[a] | pages[b]))
if j > 0.5:
print(f"{j:.2f} {a} {b}")Any pair above about 0.5 shares half its five-word phrases, which usually means the data for those two pages is too thin to tell them apart. Fix the data or merge the pages. For the second check, extract every number from each page and confirm it appears in that page's row. A number with no source is a made-up fact, and it is the most damaging mistake in this whole process.
Step 5: Launch in batches and link everything
- Publish in batches of 50 to 100, not all at once, so Google judges the set by its best pages rather than its weakest.
- Give every page a unique title, meta description, H1 and canonical, plus structured data that matches the page type.
- Link the pages into the site: a hub page, breadcrumbs, and related-page links between neighbors, not only the sitemap.
- Add each batch to the sitemap with a lastmod date.
- Watch Indexing > Pages in Search Console. If a batch lands in Crawled, currently not indexed, stop and improve the template before you publish more.
- After three to six months, review pages with no impressions. Merge them, improve them, or remove them.
Structured data for every generated page
Each page type should carry structured data that matches what the page is, built from the same row as the text so the two never disagree. For a city service page, that is usually a Service with an areaServed and a provider. Generate it from the row, not by hand:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Service",
"name": "Emergency plumbing in {city}",
"serviceType": "Emergency plumbing",
"areaServed": { "@type": "City", "name": "{city}" },
"provider": {
"@type": "Plumber",
"name": "{business_name}",
"telephone": "{phone}"
},
"url": "https://yourdomain.com/{slug}"
}
</script>Leave out any property the row cannot fill. A Service with an invented price or rating is worse than one with fewer fields, because it is a false statement in a format machines trust.
How to measure a programmatic set in Search Console
Judge the set as a group, not page by page. In Search Console, open Performance, click Add filter, choose Page, switch the match type to Custom (regex), and enter a pattern that matches only the generated URLs:
^https://yourdomain\.com/emergency-plumber-[a-z-]+$Now the clicks, impressions and positions shown are for the programmatic pages only. Check two numbers every month. The share of generated pages that have at least one impression tells you whether Google is giving the set a chance. The count in Indexing > Pages for the same URL pattern tells you whether it is keeping them. If impressions are spread across most pages, scale up. If a small group earns everything and the rest sit at zero, the data behind the rest is too thin, and that is where the next round of work goes.
What does nothing
- Publishing thousands of pages on day one and hoping some stick.
- Swapping only the city name in otherwise identical copy, however well the sentences are varied.
- Letting AI invent local details, reviews or statistics. It reads fine and it is false.
- Adding more words to thin pages. A longer page built on the same empty row is still empty.
- Hiding generated pages from navigation and relying on the sitemap to get them indexed.
Questions people also ask
Is programmatic SEO dead?
No. Template-driven sites with real data, such as currency converters, integration directories and travel listings, still rank. What stopped working is publishing thousands of near-identical pages built only to catch keywords, which Google now names as scaled content abuse.
Is programmatic SEO considered spam?
Not by itself. Google's spam policy targets pages produced at scale mainly to manipulate rankings. Programmatic pages that each answer a real search with facts specific to that page are within the rules.
What does scaled content abuse mean?
It is Google's term, added to its spam policies in March 2024, for generating many pages mainly to manipulate search rankings rather than to help people. It applies to content written by people, by AI, or by both.
What are some programmatic SEO examples?
Common examples are currency conversion pages for each currency pair, app integration pages for each pair of tools, travel pages for each destination, and service pages for each city a business really serves. Each one works because the data changes from page to page.
Can an AI agent do programmatic SEO for me?
An agent can generate and publish pages quickly, which is exactly why it needs limits. Give it a verified dataset, a strict prompt that forbids invented facts, automated similarity and number checks, and a person who reviews a sample of each batch before it goes live.
Planning a programmatic build?
Send me the search pattern you want to target and the data you have. I will tell you whether it can support unique pages and how many to launch first, and you can hire me for the build on Upwork.
Related reading: what crawled, currently not indexed means, how to get your business cited by ChatGPT and AI Overviews, and the full programmatic SEO with AI service.



