WP Manifestindependent plugin directory
manifest / seo / wp-local-seo-pages

Local SEO City Pages

WordPress plugin: Schema.org JSON-LD, automatic interlinking and SEO helpers for local-business city landing pages

★ 1stars

Install

Download source zip No release zip yet

This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/aohl1986-ohlcenter/wp-local-seo-pages/archive/refs/heads/main.zip
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

A WordPress plugin for local businesses that run city landing pages (/cities/springfield/, /cities/shelbyville/, …). It adds Schema.org JSON-LD markup, automatic internal linking between city pages, and keyword linking — fully configurable, with no business data hardcoded.

Extracted and generalized from a production plugin that powers 170+ city landing pages for a German moving company.

Features

  • Schema.org JSON-LD on every city page: a business node with configurable @type (LocalBusiness, MovingCompany, Plumber, Dentist, … or any custom type via filter), address, geo coordinates, opening hours, served area (city + postal codes), optional aggregate rating
  • Breadcrumb JSON-LD (BreadcrumbList) for city pages
  • FAQ JSON-LD (FAQPage) — content supplied entirely through a filter, nothing hardcoded
  • Automatic city interlinking: plain city names in list items (<li>Shelbyville</li>) become links to the matching city page; existing links are never touched, and pages never link to themselves
  • Regional links block: "More cities in {region}" cross-links between city pages of the same region
  • Keyword linking: link the first occurrence of configured keywords to target pages (internal link equity funneling)
  • Shortcodes: [lscp_city_links], [lscp_regional_links], [lscp_schema]
  • Secure cache clearing via admin_post action with nonce and capability check
  • Clean uninstall (removes its single option)

Requirements

  • WordPress 6.0+
  • PHP 7.4+
  • City pages organized as child pages of one parent page (e.g. a page with slug cities and one child page per city)

Installation

  1. Copy the plugin folder to wp-content/plugins/local-seo-city-pages/ (or install the ZIP via Plugins → Add New → Upload).
  2. Activate Local SEO City Pages.
  3. Configure it under Settings → Local SEO City Pages.

Configuration

1. Business data

Fill in your business name, Schema.org type, contact data, address, and geo coordinates. Only non-empty fields are emitted in the JSON-LD.

Opening hours use one rule per line:

Mo,Tu,We,Th,Fr|08:00|18:00
Sa|09:00|14:00

Aggregate rating: the rating fields are optional. Only use values that reflect real, verifiable customer reviews — fabricated ratings violate Google's structured-data guidelines and can lead to manual actions.

2. Cities

Set the parent page slug (default cities) and list your cities, one per line:

Springfield | springfield | Illinois | 62701,62702
Shelbyville | | Illinois
Capital City

Format: Name | slug | Region | postal codes — slug (auto-generated from the name), region and postal codes are optional. Lines starting with # are ignored.

Alternatively, provide cities from code:

add_filter( 'lscp_cities', function ( $cities ) {
    $cities['springfield'] = array(
        'name'         => 'Springfield',
        'slug'         => 'springfield',
        'region'       => 'Illinois',
        'postal_codes' => array( '62701', '62702' ),
    );
    return $cities;
} );

3. Keyword links

One per line: Keyword | /target-url/. Each keyword is linked at most once per page, existing links are skipped, and self-links are prevented. Also filterable via lscp_keyword_links.

4. FAQ markup

Supply FAQ items per city through the lscp_faq_items filter:

add_filter( 'lscp_faq_items', function ( $items, $city ) {
    $items[] = array(
        'question' => sprintf( 'Do you serve %s?', $city['name'] ),
        'answer'   => sprintf( 'Yes, %s is part of our regular service area.', $city['name'] ),
    );
    return $items;
}, 10, 2 );

Shortcodes

Shortcode Purpose
[lscp_city_links cities="Springfield,Shelbyville"] Linked city list (all registered cities when the attribute is omitted)
[lscp_regional_links] / [lscp_regional_links city="Springfield"] "More cities in this region" block
[lscp_schema city="Springfield" postal_codes="62701"] Business JSON-LD anywhere in content

Example JSON-LD output

For a city page at /cities/springfield/ with a plumbing business configured:

{
  "@context": "https://schema.org",
  "@type": "Plumber",
  "@id": "https://example.com/cities/springfield/#business",
  "name": "Acme Plumbing Springfield",
  "url": "https://example.com/cities/springfield/",
  "description": "Licensed plumbing service in Springfield — repairs, installations and 24/7 emergency service.",
  "telephone": "+15551234567",
  "email": "info@example.com",
  "priceRange": "$$",
  "address": {
    "@type": "PostalAddress",
    "streetAddress": "1 Main Street",
    "addressLocality": "Capital City",
    "postalCode": "62700",
    "addressRegion": "Illinois",
    "addressCountry": "US"
  },
  "geo": {
    "@type": "GeoCoordinates",
    "latitude": "39.7817",
    "longitude": "-89.6501"
  },
  "areaServed": [
    { "@type": "City", "name": "Springfield" },
    { "@type": "PostalAddress", "postalCode": "62701", "addressCountry": "US" },
    { "@type": "PostalAddress", "postalCode": "62702", "addressCountry": "US" }
  ],
  "openingHoursSpecification": [
    {
      "@type": "OpeningHoursSpecification",
      "dayOfWeek": ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
      "opens": "08:00",
      "closes": "18:00"
    }
  ]
}

Validate your output with the Rich Results Test or the Schema Markup Validator.

Hooks reference

Hook Type Purpose
lscp_settings filter Override any setting from code
lscp_cities filter Provide/modify the city registry
lscp_schema_types filter Extend the @type dropdown
lscp_business_schema filter Modify the business JSON-LD node
lscp_breadcrumb_schema filter Modify the breadcrumb JSON-LD node
lscp_faq_items filter Supply FAQ items per city
lscp_keyword_links filter Provide/modify keyword links
lscp_regional_links_max filter Cap the regional links count (default 12)
lscp_regional_links_html filter Modify the rendered regional block
lscp_cleared_cache action Hook additional cache backends into "Clear caches"

License

GPL-2.0-or-later — see LICENSE.

Author

Alexander Ohl / Pragma Code