---
title: "Add local business schema to WordPress without a plugin"
url: https://wpministry.co/blog/local-business-schema-wordpress/
description: "One PHP file in wp-content/mu-plugins prints a LocalBusiness block as JSON-LD. Google requires a name and an address and recommends hours, a phone and a URL."
lang: en-US
---

Skip to content

# How to add local business schema to WordPress without a plugin

Save one PHP file in wp-content/mu-plugins and WordPress prints a LocalBusiness block, as JSON-LD, on the page that shows your business details. Google requires a name and an address and recommends hours, a phone number, a URL, coordinates and a price range.

By

WP Ministry

Published

October 8, 2026

Tested on

WordPress 7.1.4, PHP 8.3.36

## In short

- Google's documentation requires two properties, a name and an address. Everything else it lists is recommended.
- Use the most specific schema.org type that fits, such as Dentist or Plumber, in place of LocalBusiness.
- Google says the markup can go on any page and makes more sense on one with information about the business. What it states has to be visible there.
- One file in wp-content/mu-plugins prints the block. Nothing has to be downloaded or installed.
- Leave out ratings and reviews of your own business. Google says such pages are ineligible for review stars.
- The Rich Results Test reads the block. Search Console lists no report for local business markup.

Local business schema is a block of structured data that states a business's name, address, hours and phone number in a form a search engine can read. On WordPress it takes one PHP file and no plugin from outside. Save the file below in `wp-content/mu-plugins/` and WordPress prints one `LocalBusiness` block, as JSON-LD, on the page you choose. Google's documentation requires two properties, a name and an address, and recommends a short list of others.

This page gives the properties as Google lists them today, the file, a way to read the block back, and what to leave out. Every statement about Google is from a page Google publishes, listed under Sources.

## What the markup is and what Google says it does

Google's page on local business structured data was last updated on September 8, 2026. It opens with two things a search can show: a knowledge panel with details about a business, and a carousel of businesses for a search about a kind of business. Then it says what the markup is for. With it, "you can tell Google about business hours, different departments within a business" and more.

Google is plain about the limit. Its general guidelines say structured data "enables a feature to be present, it does not guarantee that it will be present", and the local business page says the same in its troubleshooting section.

What the page leaves unsaid matters as much:

- It does not say the markup creates a knowledge panel or puts a business on the map.
- It does not mention Google Business Profile, or say how details in markup and details in a profile are weighed against each other.
- It says nothing about where a business appears. Google's help for Business Profile says local results are mainly based on relevance, distance and popularity, and that there is no way to request or pay for a better local ranking.

So treat the block as an accurate statement of what the page already shows. A business missing from the map is a matter for its profile, and a business not showing on Google Maps (https://wpministry.co/blog/business-not-showing-on-google-maps/) covers that.

## The properties Google lists

Google's page has one table for `LocalBusiness`. Two properties are required for the page to be eligible for display as a rich result. The rest are recommended.

| Property | Google says | What goes in it | Mistake to avoid |
| --- | --- | --- | --- |
| `name` | Required | The name of the business. | Any name other than the one the page and the storefront show. |
| `address` | Required | A `PostalAddress`: the physical location, with as many parts as possible. Google's example has the street, town, region, postal code and country. | Leaving parts out, or giving an address the page does not show. |
| `geo` | Recommended | `latitude` and `longitude`, as numbers. | Rounded coordinates. Google says the precision must be at least five decimal places. |
| `openingHoursSpecification` | Recommended | One entry or a list, each with `dayOfWeek`, `opens` and `closes`. `validFrom` and `validThrough` mark seasonal hours. | Times such as "9am". Google's examples use the 24-hour clock: `09:00`, `21:00`. |
| `telephone` | Recommended | The main number for customers. | A number in local form. Google asks for the country code and the area code. |
| `url` | Recommended | The full address of the page for this business location. Google says it must be a working link. | With several locations, giving each one the home page. |
| `priceRange` | Recommended | A range such as `$10-15`, or currency signs such as `$$$`. | A sentence about prices. At 100 characters or more, Google shows no price range. |
| `department` | Recommended | A nested business for each department that has its own hours or phone number. | A department name without the store's name in front, unless the department has a brand of its own. |
| `menu` | Recommended, for food establishments | The address of the menu. | A relative address such as `/menu/`. Google asks for the fully-qualified URL. |
| `servesCuisine` | Recommended | The type of cuisine a restaurant serves. | Using it for a business that is not a restaurant. |
| `aggregateRating`, `review` | Recommended only for sites that capture reviews about other local businesses | A rating or a review of a business, on a site that collects them about others. | Adding them to your own site about your own business. See "What to leave out". |

Three things about that table are easy to miss:

- **`image` is not in it.** Google's examples include an `image`, but the table does not list one. It is required only in a second table, for the restaurant carousel, which Google says is limited to a small set of restaurant providers.
- **The time format is stated two ways.** The table gives `hh:mm:ss`. Every example on the same page writes `09:00`. The file below follows the examples.
- **schema.org has many more properties.** These are the ones Google documents for this feature. Because `LocalBusiness` is a subtype of `Organization`, Google also recommends following its fields for `Organization`, such as `logo` and `sameAs`.

## Choose the type

Google's page says to use the most specific `LocalBusiness` subtype possible, and gives `Restaurant`, `DaySpa` and `HealthClub` as examples. schema.org lists the subtypes on its `LocalBusiness` page under "More specific Types", and several of those have subtypes of their own. A few examples:

- A dental practice: `Dentist`.
- A plumber, an electrician or a roofer: `Plumber`, `Electrician`, `RoofingContractor`. All three sit under `HomeAndConstructionBusiness`.
- A restaurant: `Restaurant`.
- A hair salon: `HairSalon`.
- A car repair shop: `AutoRepair`.
- A law firm: `LegalService`. schema.org marks `Attorney` as deprecated in its favor.

If none fits, use `LocalBusiness` itself. For a business that is two kinds at once, Google says to give the types as a list, which in the file below is `'@type' => array( 'Electrician', 'Plumber' )`, and that `additionalType` is not supported.

## Which page carries it

Google says neither "every page" nor "the home page only". Its local business page says the markup can be added to any page of a site, though it may make more sense on one that contains information about the business. Three other lines in its documentation narrow that down.

- The general guidelines say to put structured data on the page it describes.
- They also say not to mark up content that is not visible to readers of the page.
- The `Organization` page recommends the home page or a single page that describes the organization, and says the markup is not needed on every page.

For a business with one location, that points to one page: the home page or the contact page, whichever shows the name, address, phone number and hours as text. For a business with several, Google says to define each location as its own `LocalBusiness`. Each belongs on that location's page, with that page as its `url`. Location pages on WordPress (https://wpministry.co/blog/location-pages-wordpress/) covers those pages.

## Add the block

The commands below need `curl` and PHP. Run them over SSH on the server, from the folder WordPress is in, and replace `https://example.com/` with the address of your own page.

1. ### Step 1: See what the page prints now
   This prints the opening tag of every JSON-LD block on the page, or says there is none.
   If a block is already there, read "A second block from a theme or a plugin" below before adding another.
   bash Copy

   ```
   curl -sL https://example.com/ | grep -o '<script[^>]*application/ld+json[^>]*>' || echo "no JSON-LD block on this page"
   ```

2. ### Step 2: Create the file
   Save this as `local-business-markup.php` in `wp-content/mu-plugins/`. If `wp-content` has no `mu-plugins` folder, create one.
   wp-content/mu-plugins/local-business-markup.php Copy

   ```
   <?php
   /**
    * Plugin Name: Local business markup
    * Description: Prints LocalBusiness structured data on the front page.
    */
   if ( ! defined( 'ABSPATH' ) ) {
   	exit;
   }
   add_action(
   	'wp_head',
   	function () {
   		if ( ! is_front_page() ) {
   			return;
   		}
   		$business = array(
   			'@context' => 'https://schema.org',
   			'@type' => 'Dentist',
   			'name' => 'Example Dental Care',
   			'address' => array(
   				'@type' => 'PostalAddress',
   				'streetAddress' => '100 Example Street, Suite 2',
   				'addressLocality' => 'Exampleton',
   				'addressRegion' => 'CA',
   				'postalCode' => '00000',
   				'addressCountry' => 'US',
   			),
   			'geo' => array(
   				'@type' => 'GeoCoordinates',
   				'latitude' => 12.34567,
   				'longitude' => -123.45678,
   			),
   			'url' => 'https://example.com/',
   			'telephone' => '+15555550100',
   			'priceRange' => '$$',
   			'openingHoursSpecification' => array(
   				array(
   					'@type' => 'OpeningHoursSpecification',
   					'dayOfWeek' => array( 'Monday', 'Tuesday', 'Wednesday', 'Thursday' ),
   					'opens' => '08:00',
   					'closes' => '17:00',
   				),
   				array(
   					'@type' => 'OpeningHoursSpecification',
   					'dayOfWeek' => 'Friday',
   					'opens' => '08:00',
   					'closes' => '13:00',
   				),
   				// Closed all day: both times are 00:00.
   				array(
   					'@type' => 'OpeningHoursSpecification',
   					'dayOfWeek' => array( 'Saturday', 'Sunday' ),
   					'opens' => '00:00',
   					'closes' => '00:00',
   				),
   			),
   		);
   		echo '<script type="application/ld+json">' . wp_json_encode( $business ) . '</script>' . "\n";
   	}
   );
   ```

3. ### Step 3: Check its syntax
   It should answer `No syntax errors detected`. If it reports a parse error, fix the line it names before doing anything else.
   bash Copy

   ```
   php -l wp-content/mu-plugins/local-business-markup.php
   ```

4. ### Step 4: Read the block back
   This fetches the page and decodes every JSON-LD block on it. A block that parses is printed back with one property to a line. One that does not is reported with the reason.
   bash Copy

   ```
   curl -sL https://example.com/ | php -r '
   preg_match_all( "#<script[^>]*application/ld\+json[^>]*>(.*?)</script>#is", stream_get_contents( STDIN ), $found );
   foreach ( $found[1] as $i => $json ) {
   	$data = json_decode( $json, true );
   	echo "Block ", $i + 1, is_array( $data ) ? " parses:\n" . json_encode( $data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) : " does not parse: " . json_last_error_msg(), "\n";
   }
   echo count( $found[1] ), " JSON-LD block(s) on this page\n";
   '
   ```

5. ### Step 5: Compare it with what you typed
   With the file above, the output starts and ends like this. On a post or any other page, the same command should find no block of yours.
   text Copy

   ```
   Block 1 parses:
   {
       "@context": "https://schema.org",
       "@type": "Dentist",
       "name": "Example Dental Care",
       "address": {
           "@type": "PostalAddress",
           "streetAddress": "100 Example Street, Suite 2",
       ...
   }
   1 JSON-LD block(s) on this page
   ```

6. ### Step 6: Clear any page cache
   A caching plugin or a CDN that stores whole pages goes on serving the copy it made before the change. Clear it, then read the block again.

What each part of the file does:

- **The folder.** WordPress loads every PHP file directly inside `wp-content/mu-plugins/` with no activation. Its documentation says such a file is listed in a separate Must-Use section of the Plugins screen and cannot be switched off there. To remove the markup, delete the file.
- **`wp_head`** is the hook a theme fires inside the page's `<head>`. The block is printed there.
- **`is_front_page()`** is true for whatever is shown at the site's main address, a static page or the latest posts. To print the block on the contact page instead, write `is_page( 'contact' )` in its place, with that page's own slug.
- **`wp_json_encode()`** turns the array into JSON. In the page's source every slash is written `\/`, which is how PHP encodes one unless told otherwise. That is valid JSON, and the read-back command shows it as a plain slash.

### Hours, a closed day and late hours

Days with the same hours share one entry, as in Google's examples. Three cases have a form Google's page spells out.

- **Closed all day.** Set both `opens` and `closes` to `00:00`, as the file does for Saturday and Sunday.
- **Open all day.** Set `opens` to `00:00` and `closes` to `23:59`.
- **Open past midnight.** Use one entry whose `closes` is the earlier time. Google's example is Saturday from `18:00` to `03:00`.

### A business with no public address

Google's local business page does not cover a business that works at its customers' premises and shows no street address: the words "service area" do not occur on it. It lists `address` as required and describes it as the physical location of the business. Separately, Google's guidelines for Business Profile tell a service-area business to hide its address from customers on the profile. Neither page refers to the other. What Google's documents do support:

- Do not put an address in the markup that the page does not show.
- `Organization` markup has no required properties, and Google describes its `address` as physical or mailing, "if applicable". A business with no address to show can state its name, phone number and `url` that way.
- schema.org defines `areaServed`. Google's local business page does not list it, so nothing documented says what Google does with it.

Google does not say whether part of an address, such as the town, region and country, meets the requirement. The Rich Results Test, below, shows what it reads.

## What to leave out

### Ratings and reviews of your own business

Google's review snippet guidelines give the reason for the last row of the table above. Where a business controls the reviews about itself, its pages that use `LocalBusiness` or any other `Organization` markup "are ineligible for star review feature". That covers reviews written into the markup and reviews shown through an embedded widget alike. Why review stars do not show for a local business (https://wpministry.co/blog/review-stars-not-showing-local-business/) has the rest.

### Anything the page does not show

The general guidelines say not to mark up content that is not visible to readers, and that structured data must be a true representation of the page. Hours, a phone number or an address that appear only in the block break that rule, so put them on the page first.

### A second block from a theme or a plugin

An SEO plugin or a theme can print structured data of its own. Yoast SEO's specification, for one, describes one block on every page that covers the page, the site and the organization or person behind it. The first command on this page shows whether your site prints one, and the read-back command shows what is in it.

Google's guidelines ask for up-to-date information that represents the page. They do not say which of two blocks Google would use if the two disagree. Keep one source for the business's details: either fill in the plugin's own settings and delete this file, or switch that part of the plugin off and keep the file.

## Check it with Google's own tools

**The Rich Results Test** takes a page's address or pasted code. Its help page lists "Local business" among the types it supports and says the result shows which rich result types were found, with any errors or suggestions. The page has to be reachable from the internet without a login. A clean result is not a promise: the general guidelines say Google does not guarantee that structured data will show up even when the test finds it correct.

**Search Console** says less about this markup than about most.

- The URL Inspection tool tests whether Google can reach and index the page, and lets you request indexing. Google's local business page says it may take several days after publishing for Google to find and crawl a page.
- Search Console's list of rich result reports has no report for local business markup. The types the URL Inspection tool shows do not include it either, and its help page says a type it does not support can be present and valid without appearing there. So seeing nothing about the block in Search Console is not a fault.
- The Unparsable structured data report appears only if Google found markup it could not parse. If it names this page, run the read-back command.

## Keep it true

The block is one more place the business's details live, and the general guidelines ask for it to be kept up to date. When the hours, the phone number or the address change, change the page, the file and the Business Profile together. Google's guidelines for Business Profile say a business's name should reflect its real-world name, as used consistently on its storefront, website and stationery. The name in the block should be that name. Name, address and phone number on a WordPress site (https://wpministry.co/blog/business-name-address-phone-wordpress/) keeps the details in one file that feeds the footer and prints a short block of its own. Use that file's markup or this one's, not both.

If you would rather not edit the array by hand, the local business schema generator (https://wpministry.co/tools/local-business-schema-generator/) writes this file from a form. If you would rather hand over the whole job, WP Ministry sets this up as part of local SEO for WordPress (https://wpministry.co/services/local-seo/).

## Common questions

Do I need a plugin to add local business schema to WordPress?

No. The file on this page is a must-use plugin you write yourself, and nothing is downloaded or installed. A schema or SEO plugin does the same job with a form. Use one or the other, not both.

Does local business schema change where my business appears on Google?

Google's documentation for this markup does not say so. It describes what the markup tells Google and says it does not guarantee that any feature will show. Google's help for Business Profile says local results are mainly based on relevance, distance and popularity.

Should the block be on every page?

Google's documentation does not ask for that. It says the markup can go on any page and makes more sense on one with information about the business, and its `Organization` page says the markup is not needed on every page.

Can I add my star rating to it?

Not for your own business on your own site. Google's review snippet guidelines say such pages are ineligible for the star review feature, whether the reviews are in the markup or in an embedded widget.

How soon does Google read it?

When it next crawls the page. Google's local business page says that may take several days after publishing, and that you can ask for a recrawl. Reading the block is not the same as showing anything from it.

## More on this subject

- Local SEO for a WordPress site: https://wpministry.co/topics/local-seo/
- WordPress SEO: https://wpministry.co/topics/wordpress-seo/
- Resource Local SEO checklist for a WordPress site: the profile, the website, and what to leave out: https://wpministry.co/resources/local-seo-checklist-wordpress/
- Guide NAP consistency: what Google says about name, address and phone, and one place to keep them in WordPress: https://wpministry.co/blog/business-name-address-phone-wordpress/
- Guide Review stars not showing in Google for a local business: Google's rule, and what to do instead: https://wpministry.co/blog/review-stars-not-showing-local-business/
- Guide Service area business SEO: Google's rules for the profile, and a website with no address to show: https://wpministry.co/blog/service-area-business-website/
- Guide Business not showing on Google Maps: what to check, in order: https://wpministry.co/blog/business-not-showing-on-google-maps/
- Guide Google Business Profile suspended: what it means, why it happens and how to appeal: https://wpministry.co/blog/google-business-profile-suspended/

## SEO audit, done for you

SEO audit is $249. A written audit, each finding with its source, and up to 2 hours of fixes done and tested again.

More about this work: WordPress SEO audit (https://wpministry.co/services/wordpress-seo-audit/)

May we measure how this site is used?

A yes notes the pages of this visit and loads Google Analytics. A no keeps and loads nothing. See the cookies page (https://wpministry.co/legal/cookies/).

If you say yes, we note the pages you look at during this visit and load Google Analytics. If you say no, nothing is kept and nothing is loaded. The site works the same either way. The cookies page (https://wpministry.co/legal/cookies/) has the details.

## Structured data

```json
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "Organization",
      "@id": "https://wpministry.co/#organization",
      "name": "WP Ministry",
      "url": "https://wpministry.co/",
      "logo": "https://wpministry.co/brand/logo.png",
      "address": {
        "@type": "PostalAddress",
        "streetAddress": "1309 Coffeen Ave STE 1200",
        "addressLocality": "Sheridan",
        "addressRegion": "WY",
        "postalCode": "82801",
        "addressCountry": "US"
      },
      "sameAs": [
        "https://www.linkedin.com/company/wp-ministry/",
        "https://x.com/wpministryco",
        "https://www.instagram.com/wpministryco/",
        "https://www.facebook.com/wpministryco",
        "https://www.tiktok.com/@wpministryco",
        "https://www.pinterest.com/wpministryco/",
        "https://www.youtube.com/channel/UCOZRuAukk5QSTRPG6BaHl3Q"
      ]
    },
    {
      "@type": "WebSite",
      "@id": "https://wpministry.co/#website",
      "name": "WP Ministry",
      "url": "https://wpministry.co/",
      "inLanguage": "en-US",
      "publisher": {
        "@id": "https://wpministry.co/#organization"
      }
    },
    {
      "@type": "Article",
      "@id": "https://wpministry.co/blog/local-business-schema-wordpress/#article",
      "headline": "How to add local business schema to WordPress without a plugin",
      "description": "One PHP file in wp-content/mu-plugins prints a LocalBusiness block as JSON-LD. Google requires a name and an address and recommends hours, a phone and a URL.",
      "url": "https://wpministry.co/blog/local-business-schema-wordpress/",
      "mainEntityOfPage": "https://wpministry.co/blog/local-business-schema-wordpress/",
      "inLanguage": "en-US",
      "image": "https://wpministry.co/og/blog/local-business-schema-wordpress.png",
      "author": {
        "@id": "https://wpministry.co/#organization"
      },
      "publisher": {
        "@id": "https://wpministry.co/#organization"
      },
      "datePublished": "2026-10-08T00:00:00.000Z",
      "dateModified": "2026-10-08T00:00:00.000Z"
    },
    {
      "@type": "BreadcrumbList",
      "itemListElement": [
        {
          "@type": "ListItem",
          "position": 1,
          "name": "Home",
          "item": "https://wpministry.co/"
        },
        {
          "@type": "ListItem",
          "position": 2,
          "name": "Guides",
          "item": "https://wpministry.co/blog/"
        },
        {
          "@type": "ListItem",
          "position": 3,
          "name": "How to add local business schema to WordPress without a plugin"
        }
      ]
    }
  ]
}
```