Customizing Yoast SEO's structured data with schema API part 4
Tags: wordpress, yoast, schemaap
Published: 2022-06-04 15:30:34

Thus far we have looked into how [Yoast SEO](https://yoast.com/wordpress/plugins/seo/) implements structured data, how we can use schema API to alter properties of a piece / schema and how to make a new custom piece.

In the final part of this series we take a look a second way to customize a piece. This will be a more pratical example. I did something very similar on a site I recently build.

## The goal

Let's imagine we're building a little website for a plumber and we want to customize the structured data. By default Yoast SEO would give us the `Organization` piece. But there are more specific schemas for a plumber. Upon reading the documentation we find an actual <https://schema.org/Plumber> schema. ([LocalBusiness](https://schema.org/LocalBusiness) > [HomeAndConstructionBusiness](https://schema.org/HomeAndConstructionBusiness) > [Plumber](https://schema.org/Plumber)) So, of course we want to use this.

A `LocalBusiness` differs from an `Organization` in that a `LocalBusiness` gets these props: "currenciesAccepted", "openingHours", "paymentAccepted" and "priceRange". That's all. The `HomeAndConstructionBusiness` and `Plumber` schema's are identical to `LocalBusiness` except for their type.

Because `Plumber` and `Organization` are so similar we are going to make the `Plumber` class by extending the `Organization` class. This lets us benefit from all the properties and methods that `Organization` already has. We can then edit these props or add new ones if needed.

When building a custom piece, wherever possible, try to base it on an existing class. For example: make a `TeamMember` or a `Speaker` by extending `Person` or make a `CarReview` by extending `Article`.

Let's start building.

## Remove `Organization`

We already covered how to remove a piece in [part 2](https://dev.to/peterlidee/customizing-yoast-seos-structured-data-with-schema-api-part-2-591f).

```php
add_filter( 'wpseo_schema_graph_pieces', 'remove_organization_from_schema', 11, 2 );

function remove_organization_from_schema( $pieces, $context ) {
  return \array_filter( $pieces, function( $piece ) {
    return ! $piece instanceof \Yoast\WP\SEO\Generators\Schema\Organization;
  });
}
```

This will break the referral the chain but we will fix this later.

## Add `Plumber`

We are going to extend `Organization` so let's first look at what props `Organization` actually renders:

```json
{
  "@type": "Organization",
  "@id": "https://www.mycompany.com/#organization",
  "name": "My company",
  "url": "https://www.mycompany.com/",
  "sameAs": [],
  "logo": {
    "@type": "ImageObject",
    "@id": "https://www.mycompany.com/#/schema/logo/image/",
    "url": "https://www.mycompany.com/wp-content/uploads/2022/05/logo.jpg",
    "contentUrl": "https://www.mycompany.com/wp-content/uploads/2022/05/logo.jpg",
    "width": 500,
    "height": 200,
    "caption": "My company"
  },
  "image": {
    "@id": "https://www.mycompany.com/#/schema/logo/image/"
  }
},
```

A sidenote: Notice how the logo prop has an "id" that gets referenced by the "image" prop. Yoast kept the logo nested for some (probably good) reason.

As I said before, `Plumber` and `Organization` are quite similar. To convert above piece to a `Plumber` piece we would only have to change the type. The extra props like "currenciesAccepted" are "openingHours" are optional. They are not required for a valid `Plumber` schema. Let's make the `Plumber` class.

```php
// functions.php

use Yoast\WP\SEO\Generators\Schema\Organization;

class Plumber extends Organization {
  public function generate(){

    // get the fields from Organization
    $data = parent::generate();

    // we overwrite @type
    $data['@type'] = 'Plumber';

    return $data;
  }
}
```

As you can see, we only have the single method `generate`. This line `$data = parent::generate();` is where a lot of the magic happens. We call the `generate` method from the parent class `Organization`. The generate method returns a data object that we store in our $data property. In other words, the data variable now holds all the properties like "@type" and "id" from `Organization`. We need to change "@type" so we just overwrite it to 'Plumber'. We will talk about "id" in a bit. The result:

```json
{
  "@type": "Plumber",
  "@id": "https://www.mycompany.com/#organization",
  "name": "My company",
  "url": "https://www.mycompany.com/",
  "sameAs": [],
  "logo": {
    "@type": "ImageObject",
    "@id": "https://www.mycompany.com/#/schema/logo/image/",
    "url": "https://www.mycompany.com/wp-content/uploads/2022/05/logo.jpg",
    "contentUrl": "https://www.mycompany.com/wp-content/uploads/2022/05/logo.jpg",
    "width": 500,
    "height": 200,
    "caption": "My company"
  },
  "image": {
    "@id": "https://www.mycompany.com/#/schema/logo/image/"
  }
},
```

What about the other methods like `is_needed`? These are public methods and they are inherited from `Organization`. In our case, `Person` *is needed* in the same way `Organization` *is needed*. If you are writing something else with a different *need* you can simply declare the `in_needed` method and write your conditional.

A quick word about context. I'm not quite sure if and how context is inherited. As per the [schema API documentation](https://developer.yoast.com/features/schema/integration-guidelines/#an-example-use-case), you should declare the "$context" property and `_construct` method in your child class when you need context. Like we did in [part 3](https://dev.to/peterlidee/customizing-yoast-seos-structured-data-with-schema-api-part-3-1i7d).

## The "id" prop

The "id" prop links different pieces together. But, it holds no semantic meaning. If you were to use "ids" like "fluffybunny" or a hash like "dlg68sv98s", everything would still work. Yoast has a standardized approach to IDs that helps to make sense of this structured data mess.

Now, our `Plumber` class will inherit it's "id" from `Organization` and thus the id will be something like "url#organization". You might be tempted to go for "url#plumber". But this would unlink everything else. `Website` for example would then link to a non-existing `Organization` piece. So, you would have to make all other pieces refer to "url#plumber"! And I'm not sure how to do that. On top of that, in our case, we don't need to. "id" has no semantic meaning and if we keep using "url#organization" as "id" we don't have to update anything.

Hopefully this makes sense. We removed the `Organization` piece but all the other pieces still link to `Organization`. So, if we give our new piece `Plumber` the same "id" as `Organization`, everything is correctly linked.

What happens if you do need a different "id". That, you will have to figure out on your own. You may want to look into the [wpseo_schema_graph](https://developer.yoast.com/features/schema/api/#perform-complex-changes-to-the-whole-schema) filter.

## Register `Plumber`

Let's register this piece:

```php
// adds Schema pieces to our output.
add_filter( 'wpseo_schema_graph_pieces', 'yoast_add_graph_pieces', 11, 2 );
function yoast_add_graph_pieces( $pieces, $context ) {
  $pieces[] = new Plumber( $context );
  return $pieces;
}
```

This however leads to a strange result. As we talked about in [part 1](https://dev.to/peterlidee/customizing-yoast-seos-structured-data-with-schema-api-part-1-1p76), Yoast SEO always renders 3 pieces on every page: `Organization` (or a subtype), `Website` and `Webpage`, in that order. We removed `Organization` and just now added `Plumber`. But this led to this result: `Website`, `Webpage` and only then `Plumber`. So, the order is now different.

Is this a problem? I'm not sure. `Website` refers to `Plumber` but `Plumber` is only declared later? Problem or not? I don't know. To be on the safe side I decided to add the new `Plumber` piece to the beginning of the array instead of to the end of the array. This worked and placed `Plumber` as the first piece, in front of `Website`. The new registration:

```php
// adds Schema pieces to our output.
add_filter( 'wpseo_schema_graph_pieces', 'yoast_add_graph_pieces', 11, 2 );
function yoast_add_graph_pieces( $pieces, $context ) {
  // add to beginning of the array
  array_unshift( $pieces, new Plumber( $context ));
  return $pieces;
}
```

## Add some properties

While we're at it, let's add some props to our new `Plumber` class' generate method.

```php
// functions.php

use Yoast\WP\SEO\Generators\Schema\Organization;

class Plumber extends Organization {
  public function generate(){

    // get the fields from Organization
    $data = parent::generate();

    // we overwrite @type
    $data['@type'] = 'Plumber';

    // hardcoded
    $data['priceRange'] = '$$$$';

    // dynamic
    $data['email'] = get_option('company-email');

    return $data;
  }
}
```

Remember, if you need to, you can also use context to retieve data as we saw in [part 3](https://dev.to/peterlidee/customizing-yoast-seos-structured-data-with-schema-api-part-3-1i7d). Let's take a look at the result of the above code:

```json
{
  "@type": "Plumber",
  "@id": "https://www.mycompany.com/#organization",
  "name": "My company",
  "url": "https://www.mycompany.com/",
  "sameAs": [],
  "logo": {
    "@type": "ImageObject",
    "@id": "https://www.mycompany.com/#/schema/logo/image/",
    "url": "https://www.mycompany.com/wp-content/uploads/2022/05/logo.jpg",
    "contentUrl": "https://www.mycompany.com/wp-content/uploads/2022/05/logo.jpg",
    "width": 500,
    "height": 200,
    "caption": "My company"
  },
  "image": {
    "@id": "https://www.mycompany.com/#/schema/logo/image/"
  },
  "priceRange": "$$$$",
  "email": "email@company.com"
},
```

And that's it, we're done.

## Summary

In this final part we've shown how to create a custom piece by building on an existing piece class. This will save you time and a couple of headaches.

I hope this serie has given a solid introduction to Yoast SEO schema API. I will leave you with a few more tips. 

Make sure to test your json-ld. Use [google rich snippet test tool](https://search.google.com/test/rich-results) for this. It's a good idea to test before you actually start building.

Validate your data. Make sure you have actual values for your piece properties. If not, handle it.

Read the [Schema API docs](https://developer.yoast.com/features/schema/api/). They are not that long, have options I didn't cover here and if you made it this far, the docs will be easy to follow.

Good luck and good coding.