Location pages for multiple cities in WordPress: where Google's spam policy puts the limit
A real branch deserves a page of its own. A page for each town that differs only in the town's name is what Google's spam policies describe as doorway abuse. In WordPress, make each branch a child page of one Locations page, and compare any two pages with the place names removed.
- By
- WP Ministry
- Published
- Tested on
- WordPress 7.1.3, PHP 8.3.35
In short
- Google's spam policies name pages aimed at regions or cities that pass visitors on to one page as an example of doorway abuse.
- A branch with its own address, staff and hours has things to say that are true of it alone, and Google's guidelines allow it a Business Profile of its own.
- A business that travels to its customers gets one profile and up to 20 service areas. It has no branch to describe, so one service-area page is the usual answer.
- The test for any page about a place: take the place's name out. If nothing is left that is true of this page only, do not publish it.
- In WordPress, a Locations page with a child page for each branch needs no code. On the install tested, every one was in the sitemap and in the header menu.
- A command on this page compares two pages with the place names removed and prints the words only one of them has.
A business with real branches should give each branch a page of its own. The branch has an address, hours and people that no other page on the site can describe. A business at one address that travels to its customers is in a different position: a page for each town it serves has nothing of its own to say unless the business puts something true there. A set of pages that differ only in the town's name, each sending the visitor to the same place, is what Google's spam policies describe under doorway abuse.
Below: the policy, the two cases, a way to build branch pages in WordPress with no plugin, and a command that compares two pages with the place names taken out.
What Google's spam policies say
Google's spam policies define doorway abuse as making sites or pages to be found for particular, similar searches, which then lead the visitor to in-between pages that are less useful than where they end up. The policy lists four examples. One of them is about places: having several domain names or "pages targeted at specific regions or cities that funnel users to one page". The other three are:
- several websites whose addresses and home pages vary slightly, made to cover more of one search;
- pages generated to pass visitors on to the part of a site that is actually of use;
- pages much like one another that sit nearer to search results than to a clear hierarchy a visitor can browse.
The plain reading: a page for each town, alike except for the name, each handing the visitor on to the same contact form or service page, is the example about places as written. The policy sets no number of pages and does not forbid a page about a place. What it describes is a page made to be found and then passed through.
A second policy on the same page, scaled content abuse, covers many pages generated mainly to affect search rankings and not to help anyone, and it says the method of making them does not matter.
The same page says what may follow. Sites that break the policies may be placed lower in results or left out of them, and Google finds violations with automated systems and, where needed, human review that can end in a manual action. What happens to any one site is Google's to decide, and nothing here predicts it.
Which of the two cases you are
| Real branches | One base, customers visited | |
|---|---|---|
| What exists in each place | An address customers can visit, with staff and hours | Nothing. Staff travel from the base |
| Google Business Profile | One for each location | One, for the whole area served |
| Pages on the site | One for each branch | One service-area page. A page for a town only where something true of that town goes on it |
The middle row is from Google's guidelines for Business Profiles and its help page on service areas, both covered below.
Real branches: a page for each
Google's guidelines ask for no more than one profile for each location of a business, and they hold a location to a real-world address: a rented mailing address the business does not operate from is not eligible. A branch that passes that has plenty a page can say that is true of it alone:
- its address, phone number and hours;
- the people who work there;
- what is offered there and not at the other branches;
- photos of that building, inside and out;
- directions, parking and the entrance.
Three things belong with the page.
Its own markup. Google's documentation for local business structured data says to define each business location as a LocalBusiness item of its own, and describes the item's url as the full address of that specific location, which must be a working link. So each branch page carries the markup for its branch. Adding LocalBusiness markup in WordPress shows how.
The profile's website link. The guidelines ask for a phone number that connects to the individual location, or a website that represents it, and they rule out addresses that send people on to landing pages other than the business's own. They do not say which page of a site to use. On a site with a page for each branch, the page that represents one location is that branch's page. That is a reading of the guideline, not an instruction of Google's.
The same details everywhere they are printed. Keeping a business's name, address and phone number in one place in WordPress covers that. If a branch is missing from the map altogether, start with what to check when a business is not showing on Google Maps.
One base, customers visited: one page, unless a town has something of its own
Google calls this a service-area business. Its rules for the profile are these:
- One profile for the central location, with a service area set on it. The help page on service areas says a service-area business can have only one profile for the whole area it serves.
- Up to 20 service areas, each a city, a postal code or another kind of area. A radius around the business cannot be used.
- The edge of the whole area should be no more than about two hours' driving time from where the business is based. The guidelines add that a larger area may suit some businesses.
- A second profile only where there is a second location with its own service area and its own staff.
On the website there is no branch to describe. What can be true of one town and not the next is narrower: work done there, shown with the customer's agreement; a permit that town requires; the water, the soil or the age of the buildings, where it changes the work; a call-out time the business really keeps.
A test for any page about a place: take the place's name out and read what is left. Is any of it true of this page only? If not, the page is the one the policy describes, and one good service-area page that names the towns served is the honest answer. Name them once, in a sentence or a plain list a customer can read. Google's policy on keyword stuffing gives blocks of text listing the cities and regions a page wants to be found for as an example, and describes stuffed words as often sitting in a list or a group where they read as out of place.
If the aim of a town page is the map results, read Google's help page on them first. It says local results rest mainly on relevance, distance and popularity, defines distance as how far the business is from the person searching, and says a better local position cannot be requested or paid for. A page about a town moves neither the business nor the searcher.
What not to do
| Practice | What the policy says of it |
|---|---|
| Generating a page for each town from a list | Doorway abuse lists pages generated to pass visitors on. Scaled content abuse covers many pages made mainly for rankings, however they are made |
| Pages for towns the business does not serve | The page exists to be found and leads to a business that cannot help the visitor: the doorway pattern, and untrue besides |
| A block of town names hidden in the footer | Hidden text abuse is text placed for search engines and not easily seen by visitors. Left visible, it is the keyword stuffing example above |
| A separate domain for each town | The doorway example about places begins with domain names, and another example is several websites that vary slightly |
Build branch pages in WordPress
Two ways that need no plugin from outside were tried on a new install with the Twenty Twenty-Five theme and "Post name" permalinks: child pages of one Locations page, and a location post type. This is what each gave.
| Child pages | A location post type | |
|---|---|---|
| Code | None | One file, and the rewrite rules flushed once |
/locations/ | An ordinary page: your words, then the list | An archive headed "Archives: Locations", with no words of yours |
/locations/ in wp-sitemap.xml | Yes | No. Only the branch entries are |
| Each branch in the sitemap | Yes, in wp-sitemap-posts-page-1.xml | Yes, in wp-sitemap-posts-location-1.xml |
| Canonical link | On every page, the list included | On each branch, none on the archive |
| The theme's header menu | Listed by itself, branches under Locations | Not listed |
| The branch's template | The page template | The single post template, with an author line and "More posts" from the blog |
The header and the templates depend on the theme. For a handful of branches, child pages came out ahead, and the steps below build them for an invented dentist with two offices. They need WP-CLI and permalinks other than Plain.
Step 1: Create the Locations page and a page for each branch
The first command keeps the new page's ID, and the next two name it as the parent. In the editor the same thing is a page's Parent setting.
WordPress's documentation says a child page's permalink reflects the hierarchy, so the two offices answer at
/locations/alderbrook/and/locations/pinemont/.bashparent=$(wp post create --post_type=page --post_status=publish --post_title='Locations' --post_name=locations --porcelain) wp post create --post_type=page --post_status=publish --post_parent="$parent" \ --post_title='Alderbrook office' --post_name=alderbrook \ --post_content='<p>120 Harbor Street, Alderbrook. Phone 555-0142.</p><p>Open Monday to Friday, 8 a.m. to 5 p.m. Parking is behind the building, entered from Quay Lane. Orthodontics is offered at this office only.</p>' wp post create --post_type=page --post_status=publish --post_parent="$parent" \ --post_title='Pinemont office' --post_name=pinemont \ --post_content='<p>48 Mill Road, Pinemont. Phone 555-0177.</p><p>Open Tuesday to Saturday, 9 a.m. to 6 p.m. Parking is on Mill Road, and the entrance has a ramp. Saturday appointments are at this office only.</p>'Step 2: List the branches on the Locations page
The Page List block has a Parent setting that limits it to one page's children. A branch added later joins the list by itself.
bashparent=$(wp post list --post_type=page --name=locations --field=ID) wp post update "$parent" --post_content="<p>Example Dental has two offices. Each page below gives that office's address, hours and phone number.</p><!-- wp:page-list {\"parentPageID\":$parent} /-->"Step 3: Confirm the structure
This lists the children of the Locations page. It should print a row for each branch, with its slug and
publish.bashwp post list --post_type=page --post_parent="$(wp post list --post_type=page --name=locations --field=ID)" --fields=post_title,post_name,post_status --orderby=title --order=ascStep 4: Confirm the sitemap lists them
Put your own domain in. On the test site this printed three addresses:
/locations/,/locations/alderbrook/and/locations/pinemont/.bashcurl -s "https://example.com/wp-sitemap-posts-page-1.xml" | grep -o '<loc>[^<]*/locations/[^<]*</loc>'
If the command prints nothing, the guide to wp-sitemap.xml covers why that file can be missing.
When a post type fits better
With dozens of branches, or staff who add them often, a post type gives branches their own menu in the dashboard and one template for all of them. This file registers it. In wp-content/mu-plugins/ it is a must-use plugin: it stays on through a change of theme and cannot be switched off from the dashboard.
<?php
/**
* Plugin Name: Locations
* Description: One entry for each branch, listed at /locations/.
*/
add_action( 'init', function () {
register_post_type( 'location', array(
'labels' => array( 'name' => 'Locations', 'singular_name' => 'Location' ),
'public' => true,
'has_archive' => 'locations',
'rewrite' => array( 'slug' => 'locations', 'with_front' => false ),
'show_in_rest' => true,
'supports' => array( 'title', 'editor', 'excerpt', 'thumbnail', 'revisions' ),
) );
} );On the test site /locations/ and every branch under it answered 404 until the rewrite rules were flushed.
wp rewrite flushThen deal with the right-hand column of the table. Add /locations/ to the menu by hand, and link to it from a page that is in the sitemap, since the archive is not. Give the theme templates for the type, which the template hierarchy looks for as archive-location and single-location, so the archive has a heading of yours and a branch does not end in blog posts.
Check two pages for a name swap
This command takes two pages, removes the tags and the place names, and prints the words that only one of the two has. A page's ID is the number after post= in the address bar while the page is open in the editor.
first=12
second=13
places='Alderbrook|Pinemont'
for id in $first $second; do
{ wp post get "$id" --field=post_title; wp post get "$id" --field=post_content; } |
sed -E 's/<[^>]*>/ /g' | sed -E "s/($places)//g" | tr -s '[:space:]' '\n' | sort -u > "/tmp/words-$id.txt"
done
only=$(comm -3 "/tmp/words-$first.txt" "/tmp/words-$second.txt" | tr -d '\t')
if [ -z "$only" ]; then
echo "Nothing differs once the place names are removed."
else
echo "$(echo "$only" | wc -l | tr -d ' ') words are on one page only:"
echo $only
fiPut your own two IDs on the first lines and every place name the pages use, split by |, on the third. On the two offices above it printed this:
33 words are on one page only:
120 48 5 555-0142. 555-0177. 6 8 9 Friday, Harbor Lane. Mill Monday Orthodontics Quay Road, Saturday Saturday, Street, Tuesday a and appointments are behind building, entered entrance from has offered on ramp.On two drafts written from one template for an invented plumber, the same paragraph with the town changed, it printed this:
Nothing differs once the place names are removed.The check finds an exact swap and no more. Pages reworded so that the words differ and the content does not pass it and still fail the test above. Google's scaled content policy counts automated rewording, such as swapping in synonyms, among its examples.
Link to them where a visitor can find them
The last doorway example sets pages near search results against a hierarchy a visitor can browse. Build the second.
- Put the Locations page in the main menu. WordPress's documentation says a Navigation block with no menu yet starts as a Page List, which shows published pages and adds new ones. On the theme tested that put Locations in the header with both offices under it. The same documentation says that once the menu is edited by hand, new pages stop being added, so add a new branch yourself.
- Link to each branch where its address is printed: the contact page and the footer.
- Hide nothing. No list of towns in text the color of its background, off the screen or at a size of zero. Those are the hidden text policy's own examples.
The local SEO checklist for a WordPress site has the rest of the website's side in order. If you would rather hand that side to someone, it is the work of WP Ministry's local SEO service.
Common questions
How many location pages can a site have before they count as doorway pages?
Google's policy gives no number. It describes what the pages are for: being found for similar searches and passing the visitor on. Two pages can fit that description. A page for each of two hundred real branches need not.
Can a business that travels to its customers have a page for each town?
Yes, where each page holds something true of that town only: work done there, a rule or a condition that changes the job, a call-out time the business keeps. Where the name is the only difference, write one service-area page that names the towns.
Should a branch's Business Profile link to the home page or to the branch's page?
Google's guidelines ask for a website that represents the individual location and name no page. On a site with a page for each branch, that page is the one that represents it.
Is it safe to use a plugin that generates a page for every town?
The policy is about the pages, not the tool. Its scaled content section says the method of making the pages does not matter. Run the test on what the tool produced: with the town's name removed, is anything left that is true of this page only?
What should I do with town pages that are already published and fail the test?
Give each one something true of its town, or take it down. For a page taken down, redirect its address to the service-area page, so that a visitor who follows an old link lands on the page that names the town.
- ResourceLocal SEO checklist for a WordPress site: the profile, the website, and what to leave out
- GuideGoogle Business Profile suspended: what it means, why it happens and how to appeal
- GuideHow to add local business schema to WordPress without a plugin
- GuideHow to request ownership of a Google Business Profile that someone else controls
- GuideNAP consistency: what Google says about name, address and phone, and one place to keep them in WordPress
- GuideReview stars not showing in Google for a local business: Google's rule, and what to do instead

