Schema-related post meta controls JSON-LD output for individual posts.
| Meta Key | Type | Description |
|---|---|---|
_seoforge_schema_type | string | Manual schema type override (e.g. 'Article', 'FAQ', 'Product'). When set, generates the corresponding JSON-LD automatically. |
_seoforge_manual_schema | string (JSON) | Full manual JSON-LD schema object as a JSON string. Bypasses all auto-generation. |
_seoforge_ai_schema | array (serialized) | AI-generated schema cache containing detected_types, json_ld, generated_at, and source keys. |
// ----- Reading schema meta -----
$post_id = 42;
// Read the manual schema type override
$schema_type = get_post_meta( $post_id, '_seoforge_schema_type', true );
// Read the full manual schema JSON
$manual_json = get_post_meta( $post_id, '_seoforge_manual_schema', true );
if ( $manual_json ) {
$schema_array = json_decode( $manual_json, true );
}
// Read AI-detected schema (returns unserialized array)
$ai_schema = get_post_meta( $post_id, '_seoforge_ai_schema', true );
if ( $ai_schema ) {
$types = $ai_schema['detected_types']; // e.g. ['Article', 'FAQ']
}// ----- Writing schema meta -----
$post_id = 42;
// Set a simple schema type override (SEO Forge generates the JSON-LD)
update_post_meta( $post_id, '_seoforge_schema_type', 'FAQ' );
// Set a complete manual schema (takes full control of JSON-LD output)
$faq_schema = json_encode( [
'@context' => 'https://schema.org',
'@type' => 'FAQPage',
'mainEntity' => [
[
'@type' => 'Question',
'name' => 'What is SEO Forge?',
'acceptedAnswer' => [
'@type' => 'Answer',
'text' => 'SEO Forge is a WordPress SEO plugin providing comprehensive on-page optimization.',
],
],
],
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
update_post_meta( $post_id, '_seoforge_manual_schema', $faq_schema );
// Clear AI schema cache (forces re-detection on next analysis)
delete_post_meta( $post_id, '_seoforge_ai_schema' );
// Remove manual schema type to fall back to auto-detection
delete_post_meta( $post_id, '_seoforge_schema_type' );
delete_post_meta( $post_id, '_seoforge_manual_schema' );Additive emit cascade (April 2026 / R3 F4)
SEOFORGE_Schema::output_schema() is additive, not priority-wins. Setting _seoforge_schema_type to 'FAQ' on a post does not suppress the post-type-default Article schema — instead the post emits [Article, FAQPage, BreadcrumbList]. The cascade is:
- Auto-detected default for the post type —
Articlefor posts,Productforproduct,WebPageeverywhere else (auto_detect_type()). Skipped when_seoforge_schema_typeresolves to the same@type, since step (3) would generate it anyway. - AI-cached blocks from
_seoforge_ai_schema['json_ld'](a multi-block array — each entry is emitted independently, deduplicated by@type). - Manual
_seoforge_schema_typeselection. If the type-specific generator returnsnull(e.g. HowTo without imperative steps — seeextract_howto_steps()filter), the block is silently dropped without affecting other layers. - Auto-FAQ — emitted when
generate_faq($post_id, $post, only_questions=true)finds 2+?-terminated H2/H3 + paragraph pairs. Skipped ifFAQPagewas already emitted by an earlier layer. BreadcrumbList(always) andOrganization(front page only).
The whole list is deduplicated by @type. Full manual JSON-LD via _seoforge_manual_schema is the only “exclusive” path — when set it owns the entire emit and only BreadcrumbList layers on top.
#### Filters and actions
// Opt out of auto-FAQ (returns null to suppress the FAQPage block).
add_filter( 'seoforge_auto_faq_schema', function( $faq, $post_id ) {
if ( get_post_type( $post_id ) === 'tutorial' ) {
return null; // tutorials shouldn't auto-trigger FAQPage
}
return $faq;
}, 10, 2 );
// Inspect the final list of emitted @types per request — useful for
// debug / analytics / extra dedup. Fires AFTER all schemas are echoed.
add_action( 'seoforge_schemas_emitted', function( $emitted_types, $post_id ) {
error_log( "Schemas on post {$post_id}: " . implode( ',', $emitted_types ) );
}, 10, 2 );The seoforge_schemas_emitted action is informational only — schemas are already on the page when it fires. To pre-empt or modify a block, use the per-type generator filters (e.g. seoforge_auto_faq_schema) or hook earlier in wp_head (priority < 5) and call remove_action( 'wp_head', [ SEOFORGE_Schema::instance(), 'output_schema' ] ) if you need to fully replace the emit.
—