Local SEO skill for Codex
Win the map pack without breaking Google's rules. Codex keeps one source of truth for every location's name, address, phone and hours and generates location pages and JSON-LD from it. It audits Business Profiles field by field against Google's guidelines, flags keyword-stuffed names and doorway location pages, fixes inconsistent citations, and sets up review requests that avoid gating and incentives.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- local-seo Installs to ~/.agents/skills/local-seo/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install local-seo The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project local-seo Installs to .agents/skills/local-seo/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/local-seo.zipunzip -o local-seo.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) local-seo PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $local-seo, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Improves local search visibility for businesses with physical locations or service areas — Google Business Profile setup and audit (name, categories, hours, services, photos), review acquisition and responses within policy, NAP consistency across citations, location and service-area pages, LocalBusiness JSON-LD, and tracking GBP traffic. Use when a business wants to show in the map pack or "near me" searches, has multiple branches, sees wrong info on Google Maps, or needs location pages built for a website.
Use it when
- A business wants to appear in the map pack or near-me searches
- Building or fixing location or service-area pages
- Business details are wrong or inconsistent across Google, Apple and directories
- Setting up a review request and response process
Use something else when
- You need many template pages for non-local entities: use programmatic-seo
- Only the JSON-LD is in question: use schema-markup
How to check the result
Each location's NAP matches the source file on GBP, the site and top citations; location pages validate with zero schema errors; GBP clicks show up in analytics under the UTM campaign.
The mistake it prevents
Adding service keywords and city names to the Business Profile name, which violates guidelines and commonly ends in suspension.
Done well: each real location has one verified, complete, policy-compliant Business Profile; the website has a genuinely useful page per location that matches the profile’s name, address and phone exactly; reviews arrive steadily from real customers; and GBP traffic is measurable in analytics.
How local ranking works
Google states local results are based on relevance (how well the profile matches the search), distance (from the searcher or the location in the query) and prominence (reviews, links, mentions, overall web presence). Distance cannot be optimized; relevance and prominence can. Paying Google does not affect local organic ranking.
Workflow
- [ ] 1. Inventory locations and the source of truth for NAP + hours
- [ ] 2. Audit and fix each Business Profile
- [ ] 3. Build or fix location pages and LocalBusiness markup
- [ ] 4. Align citations (NAP consistency)
- [ ] 5. Set up a compliant review program
- [ ] 6. Track GBP traffic and calls
- [ ] 7. Monitor monthly
1. Inventory
Create one table as the source of truth and use it everywhere (site, GBP, citations):
location_id,business_name,street,city,region,postal_code,country,phone,url,primary_category,hours,lat,lng
nyc-01,Acme Dental,"120 W 23rd St, Suite 4",New York,NY,10011,US,+1-212-555-0142,https://acme.example/locations/chelsea,Dentist,"Mo-Fr 08:00-18:00; Sa 09:00-13:00",40.7433,-73.9940
In a repo, check AGENTS.md and look for existing location data (CMS collection, JSON,
database table) before creating a new one; location pages should be generated from it.
2. Business Profile audit
Edits to profiles happen in the Business Profile interface or API under the owner’s account; Codex prepares the changes and the user applies them (or approves API calls).
| Field | Rule |
|---|---|
| Business name | Exactly the real-world name on signage and documents. Adding keywords or cities (“Acme Dental - Best Dentist NYC”) violates guidelines and risks suspension |
| Primary category | The most specific category describing the core business; the strongest relevance lever. Check which categories top-ranking competitors use |
| Secondary categories | Only those genuinely offered; a few, not dozens |
| Address / service area | Storefront: real address customers visit. Service-area business with no customer visits: hide address, set service areas (regions or cities, not a giant radius). No virtual offices or PO boxes |
| Phone | Local number for the location; a tracking number is acceptable as primary if the main number is listed as additional |
| Website | The matching location page, not the homepage, for multi-location brands; add UTM parameters (step 6) |
| Hours | Accurate, plus special hours for holidays. Wrong hours generate negative reviews |
| Services/products | Complete, in the customer’s words, with descriptions |
| Photos | Real exterior (helps people find it), interior, team, work; refreshed periodically |
| Description | Plain description of what the business does; no links, no promotions, no keyword lists |
One profile per location; one profile per practitioner only where guidelines allow (e.g. individual doctors), never duplicates. Merge or remove duplicates through support.
3. Location pages
One page per real location, generated from the source of truth, with content a visitor needs:
- Name, full address, phone (clickable
tel:), hours, embedded map or directions link. - What is offered at this location specifically: services, staff, languages, parking, accessibility, nearby landmarks, local photos.
- Reviews or testimonials from this location’s customers (marked up only if shown).
- Unique copy; city-name swaps of one template are doorway pages.
- Linked from a store locator/index page with crawlable links, not only a JS map.
LocalBusiness JSON-LD on each page (use the most specific subtype):
{
"@context": "https://schema.org",
"@type": "Dentist",
"@id": "https://acme.example/locations/chelsea#location",
"name": "Acme Dental",
"url": "https://acme.example/locations/chelsea",
"telephone": "+1-212-555-0142",
"image": "https://acme.example/img/chelsea-front.jpg",
"address": { "@type": "PostalAddress", "streetAddress": "120 W 23rd St, Suite 4",
"addressLocality": "New York", "addressRegion": "NY", "postalCode": "10011", "addressCountry": "US" },
"geo": { "@type": "GeoCoordinates", "latitude": 40.7433, "longitude": -73.9940 },
"openingHoursSpecification": [
{ "@type": "OpeningHoursSpecification", "dayOfWeek": ["Monday","Tuesday","Wednesday","Thursday","Friday"], "opens": "08:00", "closes": "18:00" },
{ "@type": "OpeningHoursSpecification", "dayOfWeek": "Saturday", "opens": "09:00", "closes": "13:00" }
],
"parentOrganization": { "@id": "https://acme.example/#org" }
}
Do not add aggregateRating from your own reviews about yourself; self-serving review
markup is not eligible. Validate with schema-markup’s linter and the Rich Results Test.
Service-area businesses: one page per service the business offers, mentioning the areas served, plus a small number of area pages only where there is real area-specific content (local projects, regulations, photos, testimonials).
4. Citations and NAP consistency
- Match the source of truth character for character where possible (Suite vs Ste. is fine; a different phone number or old address is not).
- Priority order: Google Business Profile, Apple Business Connect, Bing Places, the main data aggregators in the country, Facebook, Yelp, and the top industry directories that rank for the business’s main queries.
- Find inconsistencies by searching the old address and old phone number; fix the sources, not just the copies.
- Moving or rebranding: update GBP first, then site, then citations; keep redirects.
5. Reviews within policy
- Ask every customer, through the same channel, with a direct review link (from the profile’s “Ask for reviews” share link).
- Never gate (asking only happy customers, or filtering via a satisfaction survey first), never offer incentives, never write or buy reviews, never have staff review. This violates Google’s policies and, in the US, the FTC rule on fake reviews.
- Respond to reviews, especially negative ones: specific, calm, no private details (no confirming someone was a patient), offer an offline resolution path.
- Steady flow beats bursts; sudden spikes can trigger filtering.
Drafting replies is fine; posting them on the user’s behalf requires their explicit confirmation of each reply.
6. Tracking
- Website link in GBP with UTM tags so it is not lumped into organic:
https://acme.example/locations/chelsea?utm_source=google&utm_medium=organic&utm_campaign=gbp-chelsea(keeputm_medium=organicif reporting must still count it as organic search; agree the convention with whoever owns analytics, seeutm-attribution). - GBP Performance shows searches, views, calls, direction requests and website clicks per profile; export monthly.
- Call tracking numbers: use as primary only with the real number as additional.
7. Monitor monthly
- Profile edits suggested by users or Google (“Google updated your profile”) — review and reject wrong ones.
- Rank checks for core queries from several points in the service area (grid-based rank trackers), not from one office desk.
- Review count, rating, response rate per location.
Gotchas
- Keyword-stuffed business names often rank short-term and then get suspended; competitor edits can expose them. Do not copy the tactic.
- A suspended profile is reinstated through the reinstatement form with proof (signage photos, utility bills, business licence); do not create a new profile.
- Changing many fields at once can trigger re-verification; batch carefully.
- Coworking spaces and virtual offices typically fail verification and violate policy.
- Store locators that render entirely in JS with no crawlable links hide every location page from crawlers.
Output
**Locations:** 12 (locations.csv as source of truth, used by /locations/[slug]).
**GBP fixes:** 3 names de-stuffed, primary category Dentist→Pediatric dentist for 2,
special hours added for Nov–Jan, website links now point to location pages with UTMs.
**Site:** 12 location pages with unique content blocks, Dentist JSON-LD (0 errors),
crawlable /locations index.
**Citations:** 9 listings with old Brooklyn phone found (list), fixed 6, 3 pending.
**Reviews:** request flow drafted (SMS + email, same link for all customers). What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Local SEO"
short_description: "Win the map pack without breaking Google's rules"
default_prompt: "$local-seo Audit our 12 dental clinics' Google Business Profiles and location pages" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.