Subscribe to Our Mailing List and Stay Up-to-Date!
Subscribe
Developer Guides

How to Create Custom Schema Types in WordPress (Advanced Tutorial)

While Nexus Pro provides seven essential schema types out of the box, some projects require custom schema implementations—industry-specific types, additional properties, or nested schema structures. Creating custom schema types in WordPress requires understanding Schema.org vocabulary, JSON-LD format, and WordPress hooks for proper implementation.

This advanced guide shows you how to create custom schema types, extend existing schemas, implement complex nested structures, and validate your structured data for Google compatibility.

Understanding Schema.org Structure

Foundation knowledge for custom implementations.

Schema.org Hierarchy

Core Concept: Schema.org uses hierarchical types where specific types inherit properties from parent types.

Example Hierarchy:

Thing
└── CreativeWork
    └── Article
        └── NewsArticle
        └── BlogPosting
        └── TechArticle

Inheritance: NewsArticle inherits all properties from Article, CreativeWork, and Thing.

JSON-LD Format

Structure:

{
  "@context": "https://schema.org",
  "@type": "TypeName",
  "property1": "value1",
  "property2": "value2",
  "nestedObject": {
    "@type": "NestedType",
    "nestedProperty": "value"
  }
}

Key Elements:

  • @context: Schema.org vocabulary
  • @type: Schema type
  • Properties: Type-specific attributes
  • Values: Strings, numbers, objects, arrays

Common Schema Types

Available on Schema.org:

Organizations:

  • Organization
  • LocalBusiness
  • Corporation
  • EducationalOrganization

Creative Works:

  • Article
  • BlogPosting
  • Book
  • Movie
  • MusicAlbum

Events:

  • Event
  • BusinessEvent
  • SocialEvent

Products:

  • Product
  • SoftwareApplication
  • Vehicle

People:

  • Person

200+ types available: Check Schema.org for complete list.

Creating Basic Custom Schema

Implement custom schema from scratch.

Step 1: Choose Schema Type

Determine Type: Browse Schema.org to find appropriate type for your content.

Example: Creating SoftwareApplication schema for plugin documentation.

Step 2: Define Schema Structure

Plan Properties:

/**
 * SoftwareApplication schema properties:
 * - name: Application name
 * - applicationCategory: Category (e.g., "DeveloperApplication")
 * - operatingSystem: "WordPress"
 * - offers: Price/availability
 * - aggregateRating: User ratings
 */

Step 3: Create Schema Function

Implementation:

function custom_software_schema($post_id) {
    // Get post data
    $post = get_post($post_id);

    // Build schema array
    $schema = [
        '@context' => 'https://schema.org',
        '@type' => 'SoftwareApplication',
        'name' => get_the_title($post_id),
        'description' => get_the_excerpt($post_id),
        'applicationCategory' => 'DeveloperApplication',
        'operatingSystem' => 'WordPress',
        'offers' => [
            '@type' => 'Offer',
            'price' => get_post_meta($post_id, 'price', true),
            'priceCurrency' => 'USD',
            'availability' => 'https://schema.org/InStock'
        ],
        'aggregateRating' => [
            '@type' => 'AggregateRating',
            'ratingValue' => get_post_meta($post_id, 'rating', true),
            'reviewCount' => get_post_meta($post_id, 'review_count', true)
        ]
    ];

    return $schema;
}

Step 4: Output Schema

Add to WordPress:

function output_software_schema() {
    if (is_singular('plugin')) {
        $schema = custom_software_schema(get_the_ID());
        echo '<script type="application/ld+json">' .
             wp_json_encode($schema, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) .
             '</script>';
    }
}
add_action('wp_head', 'output_software_schema');

Output:

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "SoftwareApplication",
  "name": "Nexus Pro",
  ...
}
</script>

Extending Existing Schema

Add properties to Nexus Pro schemas.

Filter Nexus Pro Schema

Hook Into Schema Output:

// Add custom properties to Article schema
add_filter('nexus_pro_article_schema', 'extend_article_schema', 10, 2);

function extend_article_schema($schema, $post_id) {
    // Add wordCount property
    $content = get_post_field('post_content', $post_id);
    $word_count = str_word_count(strip_tags($content));
    $schema['wordCount'] = $word_count;

    // Add timeRequired for reading
    $minutes = ceil($word_count / 200); // 200 words per minute
    $schema['timeRequired'] = 'PT' . $minutes . 'M'; // ISO 8601 duration

    // Add inLanguage
    $schema['inLanguage'] = get_locale();

    // Add audience
    $difficulty = get_post_meta($post_id, 'difficulty_level', true);
    if ($difficulty) {
        $schema['audience'] = [
            '@type' => 'EducationalAudience',
            'educationalRole' => $difficulty // Beginner, Intermediate, Advanced
        ];
    }

    return $schema;
}

Add Nested Schemas

Complex Structures:

add_filter('nexus_pro_article_schema', 'add_citation_schema', 10, 2);

function add_citation_schema($schema, $post_id) {
    // Get cited sources from custom field
    $citations = get_post_meta($post_id, 'citations', true);

    if (!empty($citations)) {
        $schema['citation'] = [];

        foreach ($citations as $citation) {
            $schema['citation'][] = [
                '@type' => 'CreativeWork',
                'name' => $citation['title'],
                'url' => $citation['url'],
                'author' => [
                    '@type' => 'Person',
                    'name' => $citation['author']
                ]
            ];
        }
    }

    return $schema;
}

Advanced Schema Patterns

Complex implementations.

Navigation Structure:

function generate_breadcrumb_schema() {
    if (!is_singular()) {
        return;
    }

    $breadcrumbs = [];
    $position = 1;

    // Home
    $breadcrumbs[] = [
        '@type' => 'ListItem',
        'position' => $position++,
        'name' => 'Home',
        'item' => home_url('/')
    ];

    // Categories
    $categories = get_the_category();
    if ($categories) {
        $category = $categories[0];
        $breadcrumbs[] = [
            '@type' => 'ListItem',
            'position' => $position++,
            'name' => $category->name,
            'item' => get_category_link($category->term_id)
        ];
    }

    // Current post
    $breadcrumbs[] = [
        '@type' => 'ListItem',
        'position' => $position,
        'name' => get_the_title(),
        'item' => get_permalink()
    ];

    $schema = [
        '@context' => 'https://schema.org',
        '@type' => 'BreadcrumbList',
        'itemListElement' => $breadcrumbs
    ];

    return $schema;
}

Recipe Schema

Detailed Cooking Instructions:

function create_recipe_schema($post_id) {
    $schema = [
        '@context' => 'https://schema.org',
        '@type' => 'Recipe',
        'name' => get_the_title($post_id),
        'image' => get_the_post_thumbnail_url($post_id, 'large'),
        'author' => [
            '@type' => 'Person',
            'name' => get_the_author_meta('display_name')
        ],
        'datePublished' => get_the_date('c', $post_id),
        'description' => get_the_excerpt($post_id),
        'prepTime' => 'PT' . get_post_meta($post_id, 'prep_time', true) . 'M',
        'cookTime' => 'PT' . get_post_meta($post_id, 'cook_time', true) . 'M',
        'totalTime' => 'PT' . get_post_meta($post_id, 'total_time', true) . 'M',
        'recipeYield' => get_post_meta($post_id, 'servings', true),
        'recipeCategory' => get_post_meta($post_id, 'category', true),
        'recipeCuisine' => get_post_meta($post_id, 'cuisine', true),
        'nutrition' => [
            '@type' => 'NutritionInformation',
            'calories' => get_post_meta($post_id, 'calories', true) . ' calories'
        ],
        'recipeIngredient' => get_post_meta($post_id, 'ingredients', true), // Array
        'recipeInstructions' => []
    ];

    // Build instructions
    $steps = get_post_meta($post_id, 'instructions', true);
    foreach ($steps as $index => $step) {
        $schema['recipeInstructions'][] = [
            '@type' => 'HowToStep',
            'name' => 'Step ' . ($index + 1),
            'text' => $step,
            'url' => get_permalink($post_id) . '#step-' . ($index + 1)
        ];
    }

    // Aggregate rating
    $rating = get_post_meta($post_id, 'rating', true);
    if ($rating) {
        $schema['aggregateRating'] = [
            '@type' => 'AggregateRating',
            'ratingValue' => $rating,
            'reviewCount' => get_post_meta($post_id, 'review_count', true)
        ];
    }

    return $schema;
}

Video Object Schema

YouTube/Vimeo Integration:

function create_video_schema($post_id) {
    $video_url = get_post_meta($post_id, 'video_url', true);

    if (empty($video_url)) {
        return null;
    }

    $schema = [
        '@context' => 'https://schema.org',
        '@type' => 'VideoObject',
        'name' => get_the_title($post_id),
        'description' => get_the_excerpt($post_id),
        'thumbnailUrl' => get_the_post_thumbnail_url($post_id, 'large'),
        'uploadDate' => get_the_date('c', $post_id),
        'duration' => 'PT' . get_post_meta($post_id, 'duration', true) . 'S', // seconds
        'contentUrl' => $video_url,
        'embedUrl' => $video_url,
        'publisher' => [
            '@type' => 'Organization',
            'name' => get_bloginfo('name'),
            'logo' => [
                '@type' => 'ImageObject',
                'url' => get_site_icon_url()
            ]
        ]
    ];

    return $schema;
}

Multiple Schemas Per Page

Implement graph structure.

Schema Graph

Multiple Related Schemas:

function output_schema_graph() {
    $graph = [
        '@context' => 'https://schema.org',
        '@graph' => []
    ];

    // Add Organization schema
    $graph['@graph'][] = [
        '@type' => 'Organization',
        '@id' => home_url('/#organization'),
        'name' => get_bloginfo('name'),
        'url' => home_url('/'),
        'logo' => get_site_icon_url()
    ];

    // Add WebSite schema
    $graph['@graph'][] = [
        '@type' => 'WebSite',
        '@id' => home_url('/#website'),
        'url' => home_url('/'),
        'name' => get_bloginfo('name'),
        'publisher' => [
            '@id' => home_url('/#organization')
        ],
        'potentialAction' => [
            '@type' => 'SearchAction',
            'target' => home_url('/?s={search_term_string}'),
            'query-input' => 'required name=search_term_string'
        ]
    ];

    // Add Article schema (if single post)
    if (is_singular('post')) {
        $graph['@graph'][] = create_article_schema(get_the_ID());
    }

    // Add Breadcrumb schema
    $graph['@graph'][] = generate_breadcrumb_schema();

    // Output
    echo '<script type="application/ld+json">' .
         wp_json_encode($graph, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) .
         '</script>';
}
add_action('wp_head', 'output_schema_graph');

Benefits:

  • All schemas in one script tag
  • Clear entity relationships
  • Efficient for Google parsing

Custom Meta Boxes for Schema

Admin interface for schema data.

Register Meta Box

function register_schema_meta_box() {
    add_meta_box(
        'custom_schema_meta',
        'Schema Markup Data',
        'render_schema_meta_box',
        'post',
        'side',
        'default'
    );
}
add_action('add_meta_boxes', 'register_schema_meta_box');

Render Fields

function render_schema_meta_box($post) {
    wp_nonce_field('save_schema_meta', 'schema_meta_nonce');

    $rating = get_post_meta($post->ID, 'rating', true);
    $review_count = get_post_meta($post->ID, 'review_count', true);
    $price = get_post_meta($post->ID, 'price', true);
    ?>
    <p>
        <label>Rating (1-5):</label><br>
        <input type="number" name="rating" value="<?php echo esc_attr($rating); ?>"
               min="1" max="5" step="0.1">
    </p>
    <p>
        <label>Review Count:</label><br>
        <input type="number" name="review_count" value="<?php echo esc_attr($review_count); ?>">
    </p>
    <p>
        <label>Price ($):</label><br>
        <input type="number" name="price" value="<?php echo esc_attr($price); ?>"
               min="0" step="0.01">
    </p>
    <?php
}

Save Meta Data

function save_schema_meta($post_id) {
    // Security checks
    if (!isset($_POST['schema_meta_nonce']) ||
        !wp_verify_nonce($_POST['schema_meta_nonce'], 'save_schema_meta')) {
        return;
    }

    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
        return;
    }

    if (!current_user_can('edit_post', $post_id)) {
        return;
    }

    // Save fields
    if (isset($_POST['rating'])) {
        update_post_meta($post_id, 'rating', sanitize_text_field($_POST['rating']));
    }

    if (isset($_POST['review_count'])) {
        update_post_meta($post_id, 'review_count', absint($_POST['review_count']));
    }

    if (isset($_POST['price'])) {
        update_post_meta($post_id, 'price', sanitize_text_field($_POST['price']));
    }
}
add_action('save_post', 'save_schema_meta');

Validation and Testing

Ensure schema correctness.

Google Rich Results Test

Test URL: https://search.google.com/test/rich-results

Process:

  1. Enter your URL or paste schema code
  2. Click “Test URL” or “Test Code”
  3. Review validation results
  4. Fix any errors or warnings

Schema Markup Validator

Official Tool: https://validator.schema.org/

Features:

  • Validates JSON-LD syntax
  • Checks property compatibility
  • Identifies schema errors
  • Suggests improvements

Common Validation Errors

Missing Required Properties:

// ERROR - missing required 'name'
$schema = [
    '@type' => 'Person'
    // Missing 'name' property
];

// CORRECT
$schema = [
    '@type' => 'Person',
    'name' => 'John Doe' // Required
];

Incorrect Data Types:

// ERROR - price should be number
'price' => '$99'

// CORRECT
'price' => '99.00',
'priceCurrency' => 'USD'

Invalid URLs:

// ERROR - not a valid URL
'url' => 'example.com'

// CORRECT
'url' => 'https://example.com'

Best Practices

Professional schema implementation.

Use Constants for @context

const SCHEMA_CONTEXT = 'https://schema.org';

$schema = [
    '@context' => SCHEMA_CONTEXT,
    '@type' => 'Article'
];

Sanitize All Data

$schema = [
    '@type' => 'Article',
    'headline' => esc_html(get_the_title()),
    'description' => esc_html(get_the_excerpt()),
    'url' => esc_url(get_permalink())
];

Check for Empty Values

$rating = get_post_meta($post_id, 'rating', true);

if (!empty($rating)) {
    $schema['aggregateRating'] = [
        '@type' => 'AggregateRating',
        'ratingValue' => $rating
    ];
}

Use ISO 8601 for Dates

// Correct format
'datePublished' => get_the_date('c', $post_id), // 2025-01-15T10:30:00+00:00

// Wrong
'datePublished' => get_the_date('F j, Y') // January 15, 2025

Document Custom Schemas

/**
 * Generate SoftwareApplication schema for plugin posts.
 *
 * @param int $post_id Post ID.
 * @return array Schema markup array.
 */
function custom_software_schema($post_id) {
    // Implementation
}

Conclusion

Creating custom schema types in WordPress extends structured data beyond standard implementations. By understanding Schema.org vocabulary, JSON-LD format, and WordPress development practices, you can implement any schema type for your specific needs.

Key Implementation Steps:

  1. Choose appropriate Schema.org type
  2. Define required and recommended properties
  3. Create PHP function to build schema array
  4. Output JSON-LD in wp_head
  5. Create admin interface for data input
  6. Validate with Google Rich Results Test
  7. Test with Schema.org validator
  8. Monitor Search Console for enhancements

Development Checklist:

  • ✓ Use proper @context and @type
  • ✓ Include all required properties
  • ✓ Sanitize and escape data
  • ✓ Use correct data types
  • ✓ Validate before deployment
  • ✓ Check for empty values
  • ✓ Use ISO 8601 for dates
  • ✓ Test in multiple validators

Start by extending Nexus Pro’s existing schemas with custom properties, then progress to creating completely custom schema types for specialized content. Always validate implementations before deployment to ensure Google compatibility.


Related Articles:

Leave a Reply

Your email address will not be published. Required fields are marked *