\n $question ) {\n if ( ! empty( $question ) && ! empty( $answers[ $i ] ) ) {\n $faqs[] = [\n 'question' => $question,\n 'answer' => $answers[ $i ],\n ];\n }\n }\n\n update_post_meta( $post_id, '_faq_schema_data', $faqs );\n}\nadd_action( 'save_post', 'faq_schema_save_meta' );\n\n/**\n * Output FAQ Schema JSON-LD in \n */\nfunction faq_schema_output_jsonld() {\n if ( ! is_singular() ) {\n return;\n }\n\n global $post;\n $faqs = get_post_meta( $post->ID, '_faq_schema_data', true );\n\n if ( empty( $faqs ) || ! is_array( $faqs ) ) {\n return;\n }\n\n $schema = [\n '@context' => 'https://schema.org',\n '@type' => 'FAQPage',\n 'mainEntity' => [],\n ];\n\n foreach ( $faqs as $faq ) {\n $schema['mainEntity'][] = [\n '@type' => 'Question',\n 'name' => $faq['question'],\n 'acceptedAnswer' => [\n '@type' => 'Answer',\n 'text' => $faq['answer'],\n ],\n ];\n }\n\n echo '' . \"\\n\";\n}\nadd_action( 'wp_head', 'faq_schema_output_jsonld' );","programmingLanguage":"PHP","codeRepository":"https://fysalyaqoob.com/snippets/faq-schema-wordpress","url":"https://fysalyaqoob.com/snippets/faq-schema-wordpress","datePublished":"2025-12-18","dateModified":"2025-12-18","keywords":"wordpress, faq, schema, seo, structured-data, rich-snippets, json-ld, google","about":{"@type":"Thing","name":"wordpress","description":"wordpress development"},"educationalLevel":"beginner","isAccessibleForFree":true,"license":"https://opensource.org/licenses/MIT"}
PHPwordpressbeginner

FAQ Schema Markup for WordPress Without Plugins

Add FAQPage structured data to WordPress posts and pages for Google rich results without any plugins

Faisal Yaqoob
#wordpress#faq#schema#seo#structured-data#rich-snippets#json-ld#google
Share this snippet:

Code

php
1/**
2 * Register FAQ Meta Box for Posts and Pages
3 */
4function faq_schema_add_meta_box() {
5 $screens = [ 'post', 'page' ];
6 foreach ( $screens as $screen ) {
7 add_meta_box(
8 'faq_schema_box',
9 'FAQ Schema Markup',
10 'faq_schema_meta_box_html',
11 $screen,
12 'normal',
13 'high'
14 );
15 }
16}
17add_action( 'add_meta_boxes', 'faq_schema_add_meta_box' );
18
19function faq_schema_meta_box_html( $post ) {
20 $faqs = get_post_meta( $post->ID, '_faq_schema_data', true );
21 $faqs = is_array( $faqs ) ? $faqs : [];
22 wp_nonce_field( 'faq_schema_nonce', 'faq_schema_nonce_field' );
23 ?>
24 <div id="faq-schema-fields">
25 <?php foreach ( $faqs as $i => $faq ) : ?>
26 <div class="faq-item" style="margin-bottom:15px;padding:10px;border:1px solid #ddd;">
27 <p><strong>Question <?php echo $i + 1; ?>:</strong></p>
28 <input type="text" name="faq_question[]" value="<?php echo esc_attr( $faq['question'] ); ?>" style="width:100%;" />
29 <p><strong>Answer:</strong></p>
30 <textarea name="faq_answer[]" rows="3" style="width:100%;"><?php echo esc_textarea( $faq['answer'] ); ?></textarea>
31 </div>
32 <?php endforeach; ?>
33 </div>
34 <button type="button" onclick="addFaqField()" class="button">+ Add FAQ</button>
35 <script>
36 function addFaqField() {
37 var container = document.getElementById('faq-schema-fields');
38 var count = container.querySelectorAll('.faq-item').length + 1;
39 var html = '<div class="faq-item" style="margin-bottom:15px;padding:10px;border:1px solid #ddd;">' +
40 '<p><strong>Question ' + count + ':</strong></p>' +
41 '<input type="text" name="faq_question[]" value="" style="width:100%;" />' +
42 '<p><strong>Answer:</strong></p>' +
43 '<textarea name="faq_answer[]" rows="3" style="width:100%;"></textarea></div>';
44 container.insertAdjacentHTML('beforeend', html);
45 }
46 </script>
47 <?php
48}
49
50/**
51 * Save FAQ Meta Data
52 */
53function faq_schema_save_meta( $post_id ) {
54 if ( ! isset( $_POST['faq_schema_nonce_field'] ) ||
55 ! wp_verify_nonce( $_POST['faq_schema_nonce_field'], 'faq_schema_nonce' ) ) {
56 return;
57 }
58
59 if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
60 return;
61 }
62
63 if ( ! current_user_can( 'edit_post', $post_id ) ) {
64 return;
65 }
66
67 $questions = isset( $_POST['faq_question'] ) ? array_map( 'sanitize_text_field', $_POST['faq_question'] ) : [];
68 $answers = isset( $_POST['faq_answer'] ) ? array_map( 'sanitize_textarea_field', $_POST['faq_answer'] ) : [];
69
70 $faqs = [];
71 foreach ( $questions as $i => $question ) {
72 if ( ! empty( $question ) && ! empty( $answers[ $i ] ) ) {
73 $faqs[] = [
74 'question' => $question,
75 'answer' => $answers[ $i ],
76 ];
77 }
78 }
79
80 update_post_meta( $post_id, '_faq_schema_data', $faqs );
81}
82add_action( 'save_post', 'faq_schema_save_meta' );
83
84/**
85 * Output FAQ Schema JSON-LD in <head>
86 */
87function faq_schema_output_jsonld() {
88 if ( ! is_singular() ) {
89 return;
90 }
91
92 global $post;
93 $faqs = get_post_meta( $post->ID, '_faq_schema_data', true );
94
95 if ( empty( $faqs ) || ! is_array( $faqs ) ) {
96 return;
97 }
98
99 $schema = [
100 '@context' => 'https://schema.org',
101 '@type' => 'FAQPage',
102 'mainEntity' => [],
103 ];
104
105 foreach ( $faqs as $faq ) {
106 $schema['mainEntity'][] = [
107 '@type' => 'Question',
108 'name' => $faq['question'],
109 'acceptedAnswer' => [
110 '@type' => 'Answer',
111 'text' => $faq['answer'],
112 ],
113 ];
114 }
115
116 echo '<script type="application/ld+json">' . wp_json_encode( $schema, JSON_UNESCAPED_SLASHES ) . '</script>' . "\n";
117}
118add_action( 'wp_head', 'faq_schema_output_jsonld' );

FAQ Schema Markup for WordPress

Add Google-eligible FAQ rich results to your WordPress site without any SEO plugins. FAQ schema can significantly increase your SERP real estate and click-through rate by displaying expandable Q&A directly in search results.

Method 1: Custom Meta Box for FAQ Schema

/**
 * Register FAQ Meta Box for Posts and Pages
 */
function faq_schema_add_meta_box() {
    $screens = [ 'post', 'page' ];
    foreach ( $screens as $screen ) {
        add_meta_box(
            'faq_schema_box',
            'FAQ Schema Markup',
            'faq_schema_meta_box_html',
            $screen,
            'normal',
            'high'
        );
    }
}
add_action( 'add_meta_boxes', 'faq_schema_add_meta_box' );

function faq_schema_meta_box_html( $post ) {
    $faqs = get_post_meta( $post->ID, '_faq_schema_data', true );
    $faqs = is_array( $faqs ) ? $faqs : [];
    wp_nonce_field( 'faq_schema_nonce', 'faq_schema_nonce_field' );
    ?>
    <div id="faq-schema-fields">
        <?php foreach ( $faqs as $i => $faq ) : ?>
        <div class="faq-item" style="margin-bottom:15px;padding:10px;border:1px solid #ddd;">
            <p><strong>Question <?php echo $i + 1; ?>:</strong></p>
            <input type="text" name="faq_question[]" value="<?php echo esc_attr( $faq['question'] ); ?>" style="width:100%;" />
            <p><strong>Answer:</strong></p>
            <textarea name="faq_answer[]" rows="3" style="width:100%;"><?php echo esc_textarea( $faq['answer'] ); ?></textarea>
        </div>
        <?php endforeach; ?>
    </div>
    <button type="button" onclick="addFaqField()" class="button">+ Add FAQ</button>
    <script>
    function addFaqField() {
        var container = document.getElementById('faq-schema-fields');
        var count = container.querySelectorAll('.faq-item').length + 1;
        var html = '<div class="faq-item" style="margin-bottom:15px;padding:10px;border:1px solid #ddd;">' +
            '<p><strong>Question ' + count + ':</strong></p>' +
            '<input type="text" name="faq_question[]" value="" style="width:100%;" />' +
            '<p><strong>Answer:</strong></p>' +
            '<textarea name="faq_answer[]" rows="3" style="width:100%;"></textarea></div>';
        container.insertAdjacentHTML('beforeend', html);
    }
    </script>
    <?php
}

/**
 * Save FAQ Meta Data
 */
function faq_schema_save_meta( $post_id ) {
    if ( ! isset( $_POST['faq_schema_nonce_field'] ) ||
         ! wp_verify_nonce( $_POST['faq_schema_nonce_field'], 'faq_schema_nonce' ) ) {
        return;
    }

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

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

    $questions = isset( $_POST['faq_question'] ) ? array_map( 'sanitize_text_field', $_POST['faq_question'] ) : [];
    $answers   = isset( $_POST['faq_answer'] ) ? array_map( 'sanitize_textarea_field', $_POST['faq_answer'] ) : [];

    $faqs = [];
    foreach ( $questions as $i => $question ) {
        if ( ! empty( $question ) && ! empty( $answers[ $i ] ) ) {
            $faqs[] = [
                'question' => $question,
                'answer'   => $answers[ $i ],
            ];
        }
    }

    update_post_meta( $post_id, '_faq_schema_data', $faqs );
}
add_action( 'save_post', 'faq_schema_save_meta' );

/**
 * Output FAQ Schema JSON-LD in <head>
 */
function faq_schema_output_jsonld() {
    if ( ! is_singular() ) {
        return;
    }

    global $post;
    $faqs = get_post_meta( $post->ID, '_faq_schema_data', true );

    if ( empty( $faqs ) || ! is_array( $faqs ) ) {
        return;
    }

    $schema = [
        '@context'   => 'https://schema.org',
        '@type'      => 'FAQPage',
        'mainEntity' => [],
    ];

    foreach ( $faqs as $faq ) {
        $schema['mainEntity'][] = [
            '@type'          => 'Question',
            'name'           => $faq['question'],
            'acceptedAnswer' => [
                '@type' => 'Answer',
                'text'  => $faq['answer'],
            ],
        ];
    }

    echo '<script type="application/ld+json">' . wp_json_encode( $schema, JSON_UNESCAPED_SLASHES ) . '</script>' . "\n";
}
add_action( 'wp_head', 'faq_schema_output_jsonld' );

Method 2: Shortcode-Based FAQ Schema

/**
 * FAQ Schema via Shortcode
 * Usage: [faq question="Your question here?"]Your answer here.[/faq]
 */
function faq_schema_shortcode_collector( $atts, $content = null ) {
    global $faq_schema_items;

    if ( ! isset( $faq_schema_items ) ) {
        $faq_schema_items = [];
    }

    $atts = shortcode_atts( [ 'question' => '' ], $atts );

    if ( ! empty( $atts['question'] ) && ! empty( $content ) ) {
        $faq_schema_items[] = [
            'question' => sanitize_text_field( $atts['question'] ),
            'answer'   => wp_kses_post( $content ),
        ];
    }

    // Render visible FAQ on the page
    $output  = '<div class="faq-item" itemscope itemprop="mainEntity" itemtype="https://schema.org/Question">';
    $output .= '<h3 class="faq-question" itemprop="name">' . esc_html( $atts['question'] ) . '</h3>';
    $output .= '<div class="faq-answer" itemscope itemprop="acceptedAnswer" itemtype="https://schema.org/Answer">';
    $output .= '<div itemprop="text">' . wp_kses_post( $content ) . '</div>';
    $output .= '</div></div>';

    return $output;
}
add_shortcode( 'faq', 'faq_schema_shortcode_collector' );

/**
 * Output collected FAQ schema in footer
 */
function faq_schema_shortcode_output() {
    global $faq_schema_items;

    if ( empty( $faq_schema_items ) ) {
        return;
    }

    $schema = [
        '@context'   => 'https://schema.org',
        '@type'      => 'FAQPage',
        'mainEntity' => array_map( function( $faq ) {
            return [
                '@type'          => 'Question',
                'name'           => $faq['question'],
                'acceptedAnswer' => [
                    '@type' => 'Answer',
                    'text'  => $faq['answer'],
                ],
            ];
        }, $faq_schema_items ),
    ];

    echo '<script type="application/ld+json">' . wp_json_encode( $schema, JSON_UNESCAPED_SLASHES ) . '</script>' . "\n";
}
add_action( 'wp_footer', 'faq_schema_shortcode_output' );

Shortcode Usage in Post Editor

[faq question="What is FAQ Schema?"]
FAQ Schema is structured data markup that tells Google your page
contains frequently asked questions. Google may display these as
expandable rich results in search.
[/faq]

[faq question="Does FAQ Schema improve SEO?"]
Yes. FAQ rich results increase your SERP visibility, improve
click-through rates, and can help you appear for voice search
queries. Pages with FAQ schema often take up more space in results.
[/faq]

[faq question="How many FAQs should I add?"]
Google recommends adding between 3-10 FAQ items per page. Focus
on genuine questions your audience asks. Avoid keyword stuffing.
[/faq]

Best Practices

  • Use real questions your audience actually asks — check Google Search Console queries
  • Keep answers concise but comprehensive (50-300 words per answer)
  • Match visible content — FAQ schema must reflect what users see on the page
  • Limit to 3-10 FAQs per page for optimal display
  • Avoid promotional content in FAQ answers — Google may penalize sales-heavy FAQs
  • Validate with Google Rich Results Test before publishing

Features

  • No Plugin Required: Pure PHP implementation
  • Two Methods: Meta box for editors, shortcode for content creators
  • Google Rich Results: Eligible for FAQ rich snippets
  • Voice Search Ready: FAQ schema powers Google Assistant answers
  • Visible + Structured: Renders FAQ on page AND outputs JSON-LD
  • WordPress Native: Uses WordPress APIs for security and compatibility

Related Snippets