The Missing field 'hasCourseInstance' warning occurs when Rank Math outputs a Course schema generator on a custom post type (CPT) without populated course instance properties, such as execution mode, location, or start dates. According to Google’s Structured Data guidelines, a Course entity requires a CourseInstance (or hasCourseInstance array) detailing when and where the course takes place to be eligible for rich results. This typically happens when Rank Math auto-assigns Course Schema globally to a CPT, but custom metadata fields mapping to hasCourseInstance remain undefined or unmapped in the post editor.
Root Cause Diagnosis
When Rank Math generates structured data for a Course entity, it constructs an @type: Course JSON-LD payload. Google requires the hasCourseInstance property to contain nested CourseInstance details such as courseMode, instructor, or location. When Rank Math’s Schema Generator is applied to a Custom Post Type without these specific sub-fields mapped or filled out in the post’s metabox, the outputted JSON-LD skips the key entirely, triggering Schema validation and Google Search Console warnings.
Quick Fix
Force Rank Math to inject a default fallback CourseInstance array into the Course schema payload using the rank_math/snippet/rich_snippet_custom_description or rank_math/json_ld filter hook.
Add this single snippet to your theme’s functions.php or a custom MU-Plugin:
add_filter('rank_math/json_ld', function ($data, $jsonld) {
if (isset($data['richSnippet']) && $data['richSnippet']['@type'] === 'Course' && empty($data['richSnippet']['hasCourseInstance'])) {
$data['richSnippet']['hasCourseInstance'] = [
'@type' => 'CourseInstance',
'courseMode' => 'Online',
'courseWorkload' => 'P1W'
];
}
return $data;
}, 99, 2);
Step-by-Step Resolution
1. Disable Global Schema Auto-Assignment
Prevent Rank Math from automatically applying incomplete Course Schema across all entries of the CPT.
- Navigate to Rank Math SEO > Titles & Meta.
- Select your Custom Post Type under the left sidebar (e.g., Courses or Lessons).
- Scroll down to Schema Type.
- Set Schema Type to None or Article as the default fallback.
- Click Save Changes.
2. Programmatically Inject Complete hasCourseInstance Schema
Create an MU-plugin file at wp-content/mu-plugins/fix-rankmath-course-schema.php to dynamically attach the required hasCourseInstance properties for post types using Course schema.
<?php
/**
* Plugin Name: Rank Math CourseInstance Schema Fix
* Description: Dynamically appends missing hasCourseInstance fields to Rank Math JSON-LD output.
*/
add_filter('rank_math/json_ld', 'fix_rankmath_course_has_course_instance', 10, 2);
function fix_rankmath_course_has_course_instance($data, $jsonld) {
// Target only singular posts of your targeted custom post type
if (!is_singular('your_custom_post_type')) {
return $data;
}
// Check if Course schema is present in the rich snippet data
if (isset($data['richSnippet']) && $data['richSnippet']['@type'] === 'Course') {
// Retrieve custom post meta or ACF values if available
$post_id = get_the_ID();
$course_mode = get_post_meta($post_id, '_course_mode', true) ?: 'Online';
$start_date = get_post_meta($post_id, '_course_start_date', true) ?: date('Y-m-d');
// Construct the required CourseInstance entity
$data['richSnippet']['hasCourseInstance'] = [
'@type' => 'CourseInstance',
'courseMode' => $course_mode,
'startDate' => $start_date,
'location' => [
'@type' => 'VirtualLocation',
'url' => get_permalink($post_id)
]
];
}
return $data;
}
Replace 'your_custom_post_type' with your actual registered CPT slug (e.g., 'sfwd-courses', 'course', or 'academy').
3. Clear Caching Layers
Flush object cache and page cache to regenerate the JSON-LD script on the frontend.
Using WP-CLI:
wp cache flush
If using a persistent object cache like Redis:
redis-cli flushdb
4. Validate JSON-LD Output
Verify the corrected schema payload by testing the URL:
- Open Google Rich Results Test (
[https://search.google.com/test/rich-results](https://search.google.com/test/rich-results)). - Input the URL of a CPT item.
- Confirm that the detected
Courseitem contains thehasCourseInstanceblock without warnings:
"hasCourseInstance": {
"@type": "CourseInstance",
"courseMode": "Online",
"startDate": "2026-01-01",
"location": {
"@type": "VirtualLocation",
"url": "https://example.com/course/sample-course"
}
}
Why This Fails for Some Users
- Schema Template Overrides in Rank Math Pro: Users with Rank Math Pro Schema Templates may have custom display conditions that override filter hooks. The programmatic fix will not run if a custom Schema Template with manual fields is explicitly bound to the post ID.
- LMS Plugin Conflicts (LearnDash, LifterLMS, Sensei): LMS plugins often inject their own independent
Courseschema onto CPTs. If both Rank Math and an LMS plugin generate schema simultaneously, duplicate nodes occur where Rank Math’s node lackshasCourseInstancewhile the LMS node contains it. - ACF/Meta Field Key Mismatches: If custom fields feeding
startDateorcourseModereturn an invalid date format (non-ISO 8601), Google’s parser rejects the entireCourseInstanceblock, reproducing the missing field warning despite the key being present in JSON-LD. - Headless / REST API Implementations: When using decoupled WordPress frontends (Next.js/Gatsby), standard PHP filters modifying
$data['richSnippet']do not propagate if Rank Math’s Headless REST API extension endpoint is consumed directly without hooking intorank_math/head.