Programmatic SEO: how to build pages at scale without thin content (2026 guide)
Programmatic SEO explained: when it works, what Google's scaled content abuse policy rules out, and how to build it safely in Laravel or Next.js, with code.
Programmatic SEO is the practice of generating many search-targeted pages from a database and a template, one page per item in the data: every integration your software supports, every currency pair, every city where you actually operate. Done well, it answers thousands of specific searches that you could never write pages for by hand. Done badly, it is exactly what Google's scaled content abuse policy describes, and the pages never get indexed or get the whole site demoted. The difference is not the technique. It is whether each page carries real, distinct information that a searcher wants.
Quick answer. Programmatic SEO works when two things are true: you have real data that makes each page different and useful, and people actually search for the pattern your pages target. Validate demand first, set a minimum data threshold per page, keep pages below it out of the index (or do not create them), build hub pages and related links so every page is reachable, split sitemaps by pattern, and roll out in stages while watching the Page indexing report. If swapping the keyword in the template is the only thing that makes pages differ, do not build it.
What programmatic SEO is (and is not)
A programmatic page has three ingredients:
- A search pattern: a head term plus a modifier that changes, such as "[app] integration", "[city] coworking spaces", "[currency] to [currency]" or "[tool] alternatives".
- A dataset: one row per modifier, with fields that make each page genuinely different: specifications, prices, capabilities, locations, ratings, steps, images.
- A template: a page layout that turns each row into a useful page, with sections that appear only when the data supports them.
What programmatic SEO is not: taking a list of keywords and asking an AI model to write a page for each one. That has no dataset behind it. Every page is a rewording of the same general knowledge, and it is the first example Google lists in its scaled content abuse policy.
Programmatic SEO examples that work
These are the patterns where programmatic pages tend to earn their place, because the data per page is real and the searcher wants exactly that data.
| Pattern | Example query | What makes each page unique | Main risk |
|---|---|---|---|
| Integrations | "Slack Trello integration" | Supported triggers and actions, setup steps, screenshots, limits | Pages for integrations that barely work |
| Conversions and tools | "USD to INR" | Live rate, a working converter, history chart | Copying data others already show without adding a tool |
| Locations | "coworking space in Bopal" | Real listings, prices, photos, reviews for that area | Doorway pages for areas you do not serve |
| Directories and listings | "React developer jobs remote" | Current listings that change daily | Empty or expired listing pages (soft 404s) |
| Comparisons | "Notion vs Coda" | Feature-by-feature data, pricing, verdict per use case | Generic comparisons with no real testing |
| Templates and examples | "invoice template for freelancers" | The actual downloadable template, preview, usage notes | Near-identical templates with different titles |
| Glossaries | "what is a webhook" | Definition plus examples specific to your product | Thin one-paragraph definitions |
Well-known sites built on these patterns include Zapier's app integration pages, Wise's currency converter pages and the city-level listing pages of travel and property sites. Each has something a general article cannot offer: product data, live rates, current inventory.
When programmatic SEO works and when it fails
Before building anything, a pattern has to pass two tests.
Test 1: real data per page. Write down the fields each page will show. If more than a handful of them are filled with the same text on every page, or if the only thing that changes is the keyword, the pages will be near-duplicates. A useful rule of thumb I use: if you swap the modifier on a page (put "Trello" where it says "Asana") and the page still reads as true, the page has no real content of its own.
Test 2: real search demand per pattern. Check that people search the pattern, not just the head term. Look up a sample of modifiers in a keyword tool, check Google's autocomplete, and look at the queries your own site already gets in Search Console. A pattern where most modifiers have no searches will produce pages that nobody needs, which is also a signal Google picks up on.
It tends to fail when:
- The data is scraped from a source everyone else also scrapes, with nothing added.
- The pages target locations or categories you do not actually serve.
- The page count is set by the number of keywords, not the number of real items.
- Every page is long, but the long part is the same boilerplate.
What Google's scaled content abuse policy says
Google's spam policies (opens in a new tab) define it directly: "Scaled content abuse is when many pages are generated for the primary purpose of manipulating search rankings and not helping users." The policy adds that it is "typically focused on creating large amounts of unoriginal content that provides little to no value to users, no matter how it's created."
Its examples include:
- "Using generative AI tools or other similar tools to generate many pages without adding value for users"
- "Scraping feeds, search results, or other content to generate many pages (including through automated transformations like synonymizing, translating, or other obfuscation techniques) where little value is provided to users"
- "Stitching or combining content from different web pages without adding value"
- "Creating multiple sites with the intent of hiding the scaled nature of the content"
- "Creating many pages where the content makes little or no sense to a reader but contains search keywords"
The same page defines doorway abuse as "when sites or pages are created to rank for specific, similar search queries," with examples that include "Having multiple domain names or pages targeted at specific regions or cities that funnel users to one page" and "Creating substantially similar pages that are closer to search results than a clearly defined, browseable hierarchy."
Read those carefully and the line is clear. The policy does not ban templates, databases or automation. It targets pages whose main purpose is ranking and that add little value. A page generated from a database that shows a searcher exactly the data they wanted is fine. A page generated to catch a keyword variation is not. Google's guidance on generative AI content (opens in a new tab) makes the same point about AI: it is useful for research and structure, but using it "to generate many pages without adding value for users may violate" that policy.
Programmatic SEO without thin content
This is where most projects succeed or fail. The practical safeguards:
- Set a data threshold. Define the minimum data a page needs to be worth indexing: say, at least five filled fields, a written description and two related items. Pages below the threshold either do not get created, or get created with
noindexuntil the data fills in. - Compute something. Derived data adds value that raw data does not: averages, rankings within a category, comparisons with similar items, price history, "works best with". A page that says "this integration supports 12 triggers, more than any other tool in its category" tells the reader something new.
- Use conditional sections. If an item has no pricing data, drop the pricing section rather than showing "Pricing: not available". Empty sections make every page look the same.
- Add human input where it matters most. A short, hand-written note on your top 50 pages (an expert tip, a known limitation, a use case) makes those pages clearly different from a competitor with the same data.
- Keep boilerplate small. Shared text (how the product works, the sign-up pitch) should be a small share of the page, not the bulk of it.
- Return a real 404 for empty items. A page for an item with no data that returns 200 is a soft 404 in waiting.
Programmatic SEO for SaaS
SaaS companies are in an unusually good position for programmatic SEO because they already own the most valuable thing: product data nobody else has. The patterns that tend to fit:
- Integration pages: one per connected app, with real triggers, actions, setup steps and screenshots from your product.
- Use-case pages: "[product] for [industry or role]", each with the features, templates and workflows that industry actually uses. Only build the ones where the product genuinely differs per use case.
- Template galleries: every template users can start from, with a preview and instructions.
- Comparison and alternatives pages: built from a maintained feature matrix, honest about where the competitor is better.
The data usually already exists in the app's database or admin panel; the SEO work is deciding which of it to expose and how. That is the kind of planning I do in SEO for SaaS startups.
How to build programmatic SEO safely, step by step
- Validate the pattern. Pick one pattern, check search demand for a sample of modifiers, and check what ranks today. If the results are dominated by pages with far richer data than yours, pick another pattern. My guide to keyword research for small business covers the method.
- Model the data. Design the table with every field a page will use, mark which fields are required, and store a computed quality flag on each row.
- Design the template around the data. Write the page for one real item by hand first, then turn it into a template. If the hand-written version is not something you would be proud to publish, the template will not be either.
- Control indexing. Index only rows that pass the quality threshold. Everything else gets
noindexor no page at all, and never appears in the sitemap. - Build internal links. Create hub pages (all integrations, integrations by category), breadcrumbs, and "related" blocks that link items to each other. A page that exists only in a sitemap is likely to land in "Crawled - currently not indexed"; I explain why in my guide to crawled currently not indexed.
- Split sitemaps by pattern. One sitemap per pattern (or per batch) lets you filter the Page indexing report and see the indexing rate of exactly those pages.
- Roll out in stages. Publish your best 50 to 100 pages first. Watch indexing, impressions and engagement for several weeks. Expand only when the first batch is mostly indexed and getting impressions. If it is not, fix the template before adding more pages.
Programmatic SEO in Laravel: an implementation sketch
Here is a minimal version of an integrations pattern in Laravel 12 or 13. The key idea is that the quality decision is stored on the row, so the page, the robots meta tag and the sitemap all agree.
The model computes an is_indexable flag every time a row is saved:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Integration extends Model
{
protected $fillable = [
'name', 'slug', 'category', 'summary', 'description',
'triggers', 'actions', 'setup_steps', 'published_at',
];
protected function casts(): array
{
return [
'triggers' => 'array',
'actions' => 'array',
'setup_steps' => 'array',
'published_at' => 'datetime',
'is_indexable' => 'boolean',
];
}
public function getRouteKeyName(): string
{
return 'slug';
}
protected static function booted(): void
{
static::saving(function (Integration $integration) {
$integration->is_indexable = $integration->meetsQualityBar();
});
}
public function meetsQualityBar(): bool
{
$capabilities = count($this->triggers ?? []) + count($this->actions ?? []);
return mb_strlen(strip_tags((string) $this->description)) >= 400
&& $capabilities >= 3
&& count($this->setup_steps ?? []) >= 2;
}
}
The migration needs an is_indexable boolean column (default false, indexed) alongside the JSON columns. Then the routes and controller:
<?php
// routes/web.php
use App\Http\Controllers\IntegrationPageController;
use Illuminate\Support\Facades\Route;
Route::get('/integrations/{integration}', [IntegrationPageController::class, 'show'])
->name('integrations.show');
Route::get('/sitemaps/integrations.xml', [IntegrationPageController::class, 'sitemap'])
->name('sitemaps.integrations');
<?php
namespace App\Http\Controllers;
use App\Models\Integration;
class IntegrationPageController extends Controller
{
public function show(Integration $integration)
{
// Unpublished items do not exist publicly: a real 404, not a thin page.
abort_if($integration->published_at === null, 404);
$related = Integration::query()
->where('category', $integration->category)
->whereKeyNot($integration->getKey())
->where('is_indexable', true)
->whereNotNull('published_at')
->orderBy('name')
->limit(6)
->get(['name', 'slug']);
return view('integrations.show', compact('integration', 'related'));
}
public function sitemap()
{
$items = Integration::query()
->where('is_indexable', true)
->whereNotNull('published_at')
->orderBy('id')
->get(['slug', 'updated_at']);
return response()
->view('sitemaps.integrations', compact('items'))
->header('Content-Type', 'application/xml');
}
}
In the page template, the canonical and robots tags come from the same flag. This assumes your layout has @stack('head') inside <head>:
@push('head')
<link rel="canonical" href="{{ route('integrations.show', $integration) }}">
@unless ($integration->is_indexable)
<meta name="robots" content="noindex, follow">
@endunless
@endpush
And the sitemap view lists only indexable rows:
{!! '<?xml version="1.0" encoding="UTF-8"?>' !!}
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
@foreach ($items as $item)
<url>
<loc>{{ route('integrations.show', $item) }}</loc>
<lastmod>{{ $item->updated_at->toAtomString() }}</lastmod>
</url>
@endforeach
</urlset>
Add the sitemap to a sitemap index or robots.txt, and submit it in Search Console. Above 50,000 URLs, split it into several files; my Laravel SEO guide shows a sitemap index with spatie/laravel-sitemap. One caution on lastmod: updated_at changes on any save, so if you run bulk scripts that touch every row, track a separate content_updated_at column and use that instead. If you want this built into an existing app, it is a typical Laravel development job.
Programmatic SEO in Next.js
The same pattern in the Next.js App Router (this was checked against Next.js 16, where params is a Promise):
// app/integrations/[slug]/page.tsx
import type { Metadata } from "next";
import { notFound } from "next/navigation";
import { getIntegration, getPublishedIntegrations } from "@/lib/integrations";
type Props = { params: Promise<{ slug: string }> };
// Slugs not returned by generateStaticParams return a 404.
export const dynamicParams = false;
export async function generateStaticParams() {
const items = await getPublishedIntegrations();
return items.map((item) => ({ slug: item.slug }));
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params;
const item = await getIntegration(slug);
if (!item) return {};
return {
title: `${item.name} integration: triggers, actions and setup`,
description: item.summary,
alternates: { canonical: `/integrations/${item.slug}` },
robots: item.isIndexable ? undefined : { index: false, follow: true },
};
}
export default async function Page({ params }: Props) {
const { slug } = await params;
const item = await getIntegration(slug);
if (!item) notFound();
return (
<article>
<h1>{item.name} integration</h1>
{/* Render only the sections this item has data for. */}
</article>
);
}
// app/integrations/sitemap.ts
import type { MetadataRoute } from "next";
import { getPublishedIntegrations } from "@/lib/integrations";
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const items = await getPublishedIntegrations();
return items
.filter((item) => item.isIndexable)
.map((item) => ({
url: `https://www.example.com/integrations/${item.slug}`,
lastModified: item.contentUpdatedAt,
}));
}
Two notes. The relative canonical needs metadataBase set in your root layout. And dynamicParams is not available when Cache Components is enabled, so on that setup handle unknown slugs with notFound() alone. For more than 50,000 URLs, Next.js offers generateSitemaps to split the file.
Measuring programmatic SEO
Measure each pattern as a group, not page by page.
- Indexing rate: in the Page indexing report, filter by the pattern's sitemap. A healthy pattern gets most of its submitted pages indexed within a few weeks. A pattern stuck with most pages in "Crawled - currently not indexed" is telling you the template is not good enough.
- Impressions per page: in the Performance report, filter pages with a custom regex such as
/integrations/and compare how many pages get impressions at all. Pages with no impressions after a few months need better data, merging or removal. - Business results: track sign-ups or enquiries by landing page in GA4. A pattern that brings traffic but no conversions may be matching the wrong intent.
- Prune regularly. Remove or merge items that never earn impressions, and redirect them where a close equivalent exists.
AI answers add one more reason to get this right. AI Overviews and assistants tend to cite pages with specific facts that others lack, and a programmatic page built on unique data is exactly that. I cover the wider picture in my guide on how to get cited in AI Overviews.
Frequently asked questions
Is programmatic SEO spam?
Not by definition. Google's spam policy targets pages created mainly to manipulate rankings that add little value, "no matter how it's created." Programmatic pages built on real, distinct data that searchers want are a legitimate way to publish at scale. Pages that differ only by the keyword are not.
Can I use AI for programmatic SEO?
Yes, for parts of the work: drafting descriptions from structured data, summarizing specifications, writing first drafts that a person then checks. Google's guidance describes generative AI as useful for research and for adding structure to original content, but warns that generating many pages "without adding value for users" may violate its scaled content abuse policy. AI should turn your data into readable text, not stand in for data you do not have.
How many pages should I launch with?
Start with the best 50 to 100 items in one pattern, the ones with the most complete data and clearest search demand. Expand once most of that batch is indexed and getting impressions. Launching thousands of pages at once makes it much harder to tell whether the template works.
Does programmatic SEO still work in 2026?
Yes, where the pages carry information people cannot get from a general answer: live data, product specifics, current listings, working tools. Thin pattern pages built only to catch keyword variations work far less well than they once did, and they carry real risk under Google's scaled content abuse and doorway policies.
If you are planning a programmatic build and want a second opinion on the pattern, the data or the indexing setup before you publish thousands of pages, a technical SEO audit is the cheapest point to catch problems.