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
Tested on
WordPress 7.1.3, PHP 8.3.35

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 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.

PropertyGoogle saysWhat goes in itMistake to avoid
nameRequiredThe name of the business.Any name other than the one the page and the storefront show.
addressRequiredA 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.
geoRecommendedlatitude and longitude, as numbers.Rounded coordinates. Google says the precision must be at least five decimal places.
openingHoursSpecificationRecommendedOne 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.
telephoneRecommendedThe main number for customers.A number in local form. Google asks for the country code and the area code.
urlRecommendedThe 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.
priceRangeRecommendedA range such as $10-15, or currency signs such as $$$.A sentence about prices. At 100 characters or more, Google shows no price range.
departmentRecommendedA 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.
menuRecommended, for food establishmentsThe address of the menu.A relative address such as /menu/. Google asks for the fully-qualified URL.
servesCuisineRecommendedThe type of cuisine a restaurant serves.Using it for a business that is not a restaurant.
aggregateRating, reviewRecommended only for sites that capture reviews about other local businessesA 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 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
    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
    <?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
    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
    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
    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 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 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 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.

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

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. It starts with a free diagnosis.