【WordPress】カスタム投稿とタクソノミーで作る「よくある質問(FAQ)」実装方法

WordPressでよくある質問を実装する方法

WordPressでサイトを制作する際、固定ページに直接FAQ(よくある質問)をベタ書きすると、運用フェーズで「管理しづらい」「他のページで使い回せない」という問題が発生しがちです。

プラグインで対応する前にまずは自分で構造化データまで確実に出力できるものを作ろうと思い、自サイトで試作しました。

今回は、管理画面から誰でも簡単に質問と回答を追加でき、カテゴリごとの出し分けやテンプレートパーツ化にも対応したカスタム投稿によるFAQの実装方法を、備忘録を兼ねて解説します。

FAQを作る代表的な方法

作り方を紹介する前に、FAQを作る方法として代表的な方法を紹介しておきます。

WordPressでFAQを作成する方法はいくつかありますが、それぞれメリット・デメリットがあるため、サイトの規模や運用方法に合わせて選ぶのがおすすめです。

1. 手動HTMLで作成

メリット

  • 自由にデザインできる
  • プラグイン不要で動作が軽い
  • シンプルなFAQならすぐ作れる

デメリット

  • 質問を追加・修正するたびにコードを編集する必要がある
  • FAQが増えると管理しにくい
  • 他のページへの使い回しは大変

2. プラグインで作成

例:Ultimate FAQ、Easy Accordion、JetMonstar FAQ、Heroic FAQsなど

メリット

  • 専門知識がなくても導入しやすい
  • 並び替えや検索機能など便利な機能が揃っている
  • 短時間で実装できる

デメリット

  • 不要な機能まで読み込まれることがある
  • デザインの自由度に制限がある場合も
  • プラグインの更新や将来的な互換性に依存する

3. ブロックエディタ(Gutenberg)で作成

テーマ依存のQ&Aパーツなら簡単に追加できて便利です。

メリット

  • コードを書かずに作成できる
  • パソコンの操作に慣れていない人でも使いやすい
  • 小規模なFAQに向いている

デメリット

  • FAQが多くなると管理しづらい
  • 同じFAQを複数ページで使い回しにくい
  • カテゴリーごとの表示切り替えなどには向かない

カスタム投稿で作成(今回の方法)

メリット

  • 管理画面から簡単に追加・編集できる
  • カテゴリーごとに表示を切り替えられる
  • テンプレートパーツとして使い回せる
  • FAQが増えても管理しやすい
  • 独自機能(並び替え・JSON-LD自動生成など)も実装しやすい

デメリット

  • 初期構築に手間がかかる
  • FAQが多くなると管理しづらい
  • WordPressのカスタマイズ知識が必要

シンプルなQ&AならYoast SEOのFAQブロックもおすすめ

WordPressのSEOプラグインとして有名な『Yoast SEO』には、標準で「Yoast FAQブロック」という機能がついています。これを使えば、プラグインなしで構造化データ付きのFAQをページ内に作ることができます。

Q&Aを1ページのみで使っていて、SEO対策でYoast SEOを使用している場合は、Yoast FAQブロックで問題ありません。

それぞれのメリット・デメリットを比較してみます。

比較項目Yoast FAQブロック今回のカスタム投稿+パーツ化
データの管理方法固定ページごとのベタ書き専用メニューでの一括管理
複数ページでの使い回しできない(ページごとに作る必要あり)できる(1箇所直せば全ページ連動)
プランやカテゴリごとの出し分け手動で選んで配置自動化できる(タクソノミーで制御)
デザインの自由度プラグインの仕様に縛られやすい完全自由(自作のCSS/JSがそのまま活きる)

どちらを選ぶべきか迷った場合

Yoast FAQブロックが向いているケース

「会社概要ページに、そのページ専用のFAQを3つだけ載せたい」など、他のページで使い回す予定がない場合は、プラグインの手軽さを活かせるのでおすすめです。

今回のカスタム投稿ルートが向いているケース(コーポレート・サービスサイト向け)

「料金に関するFAQを、トップページとサービス詳細ページとLPの3箇所に載せたい」「プランごとに表示するFAQを自動で切り替えたい」という場合は、今回のカスタム投稿+パーツ化の構成がおすすめです。

今回作成するFAQの仕様

今回実装するFAQシステムは、以下のような要件を満たすように設計します。

  • 管理画面の専用メニューから「タイトル=質問(Q)」「本文=回答(A)」として登録する。
  • カスタムタクソノミー(カテゴリ)を紐付け、親カテゴリ(例:ホームページ制作)や子カテゴリ(例:Starterプラン)ごとに表示を切り替えられる。
  • 表示順を番号(menu_order)で管理し、管理画面から任意の順番に並び替えられる。 
  • テンプレートパーツとしてFAQセクションを共通化し、任意のページ(サービス詳細ページなど)から1行で呼び出せる。
  • 検索結果にFAQがリッチリザルトとして表示されるよう、構造化データを自動出力する。

【STEP1】カスタム投稿タイプとタクソノミーの登録

まずは functions.php に以下のコードを追加し、カスタム投稿タイプ faq と、専用のタクソノミー(カテゴリ) faq_category を登録します。 

管理画面から各FAQに「表示順(番号)」を設定できるよう、supports パラメータに ‘page-attributes’(ページ属性) を指定しているのがポイントです。

function register_faq_custom_post() {
    // 1. カスタム投稿タイプ「FAQ」の登録
    register_post_type('faq', [
        'labels' => [
            'name'          => 'FAQ(よくある質問)',
            'singular_name' => 'FAQ',
            'menu_name'     => 'FAQ',
            'add_new_item'  => '新しいFAQを追加',
            'edit_item'     => 'FAQを編集',
        ],
        'public'       => true,
        'has_archive'  => false, // アーカイブページ(一覧)が不要な場合はfalse
        'menu_icon'    => 'dashicons-editor-help', // 管理画面のアイコン
        
        // 【重要】'page-attributes' を追加することで、管理画面で「順序」が入力可能になります
        'supports'     => ['title', 'editor', 'page-attributes'], 
        
        'show_in_rest' => true, // ブロックエディタ(Gutenberg)を有効化
    ]);

    // 2. カスタムタクソノミー「FAQカテゴリ」の登録
    register_taxonomy('faq_category', 'faq', [
        'labels' => [
            'name'          => 'FAQカテゴリ',
            'singular_name' => 'FAQカテゴリ',
            'search_items'  => 'FAQカテゴリを検索',
            'all_items'     => 'すべてのFAQカテゴリ',
            'parent_item'   => '親カテゴリ',
            'edit_item'     => 'FAQカテゴリを編集',
            'update_item'   => 'FAQカテゴリを更新',
            'add_new_item'  => '新規FAQカテゴリを追加',
        ],
        'hierarchical' => true, // 階層構造を持たせる(投稿のカテゴリと同じ仕様)
        'show_in_rest' => true,
    ]);
}
add_action('init', 'register_faq_custom_post');

コードを保存したら、WordPress管理画面の「設定」>「パーマリンク」に移動し、何も変更せずに「変更を保存」ボタンを押してください(これをしないと、カスタム投稿に関連するURLが404エラーになることがあります) 。

【STEP2】テンプレートパーツの作成

テーマの template-parts ディレクトリ内に faq.php を作成します。 このパーツを呼び出す際、引数($args)でスラッグを指定することで、特定のカテゴリに絞り込んだFAQを出力できるようにします。

同時に、SEO対策(検索エンジンにFAQであることを正しく伝えるため)として必須の構造化データ(JSON-LD)も自動生成する仕組みを組み込みます。

template-parts/faq.php 内に以下を記入

<?php
/**
 * FAQ表示用テンプレートパーツ
 *
 * 呼び出し例:
 * get_template_part('template-parts/faq', null, ['category' => 'starter-plan']);
 */

// 呼び出し元から渡されたカテゴリスラッグを取得
// 指定がない場合は全FAQを表示
$cat_slug = $args['category'] ??  '';

// FAQを取得するためのクエリ設定
$query_args = [
    'post_type'      => 'faq',          // FAQのカスタム投稿
    'posts_per_page' => -1,             // 全件取得
    'orderby'        => 'menu_order',   // 管理画面で設定した表示順
    'order'          => 'ASC',          // 昇順(小さい番号から表示)
];

// カテゴリが指定されている場合は、そのカテゴリのみ取得
if (!empty($cat_slug)) {
    $query_args['tax_query'] = [
        [
            'taxonomy' => 'faq_category',
            'field'    => 'slug',
            'terms'    => $cat_slug,
        ]
    ];
}

// FAQを取得
$faq_query = new WP_Query($query_args);

// JSON-LD(構造化データ)を格納する配列
// ループ内で質問・回答を追加していく
$faq_schema = [
    '@context'   => 'https://schema.org',
    '@type'      => 'FAQPage',
    'mainEntity' => [],
];

if ($faq_query->have_posts()) : ?>

    <div class="faq-container">
        <dl class="faq-list">

            <?php while ($faq_query->have_posts()) : $faq_query->the_post();

                // タイトルを質問、本文を回答として取得
                $question = get_the_title();
                $answer   = get_the_content();

                // JSON-LD用のデータを追加
                // HTMLタグは不要なのでプレーンテキストに変換
                $faq_schema['mainEntity'][] = [
                    '@type' => 'Question',
                    'name' => $question,
                    'acceptedAnswer' => [
                        '@type' => 'Answer',
                        'text' => wp_strip_all_tags($answer),
                    ],
                ];
            ?>

                <div class="faq-item">
                    <!-- 質問 -->
                    <dt class="faq-question">
                        <span class="faq-icon-q">Q</span>
                        <?php echo esc_html($question); ?>
                        <span class="faq-toggle-icon"></span>
                    </dt>
                    <!-- 回答 -->
                    <dd class="faq-answer">
                        <div class="faq-answer-inner">
                            <span class="faq-icon-a">A</span>
                            <div class="faq-answer-content">
                                <?php
                                // 本文はHTMLとして表示
                                // wpautop()で改行を<p>タグへ変換
                                echo wp_kses_post(wpautop($answer));
                                ?>
                            </div>
                        </div>
                    </dd>
                </div>

            <?php endwhile; ?>

        </dl>
    </div>

    <!--
        FAQPageの構造化データ(JSON-LD)を出力
        FAQを追加・編集すると、このJSONも自動で更新される
    -->
    <script type="application/ld+json">
        <?php
        echo wp_json_encode(
            $faq_schema,
            JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT
        );
        ?>
    </script>

<?php
endif;

// グローバル$postを元に戻す
wp_reset_postdata();
?>

以下コードの解説なので必要のない方は読み飛ばしてもらって大丈夫です。

構造化データ(JSON-LD)の実装

FAQを表示するだけでなく、検索エンジンにも「このページはFAQである」と正しく伝えるために、構造化データ(JSON-LD)も出力します。

今回は、FAQを取得するループの中でHTMLの表示とJSON-LDの生成を同時に行っています。

同じループで生成する理由

FAQを表示するためにWP_Queryで投稿を取得しているので、そのデータをそのまま利用してJSON-LDも生成します。

もしHTML表示用とJSON-LD生成用で別々にWP_Queryを実行すると、同じデータを二度取得することになり、処理が無駄になってしまいます。

そのため、1回のループで

  • 画面にFAQを表示する
  • 構造化データ用の配列を作成する

という2つの処理を同時に行っています。

HTML表示用とJSON-LD用でデータを分ける

回答本文は用途によって扱い方が異なります。

画面に表示する回答は、改行や段落を反映させるためにwpautop()を使用してHTMLとして出力します。

一方、JSON-LDはプレーンテキストで出力する必要があるため、wp_strip_all_tags()でHTMLタグを除去しています。

このように表示用と構造化データ用で処理を分けることで、見た目を保ちながら正しいJSON-LDを生成できます。

wp_json_encode()を使用する

JSON-LDを出力する際はPHP標準のjson_encode()ではなく、WordPressが用意しているwp_json_encode()を使用します。

wp_json_encode()はWordPress環境に合わせたJSONの生成を行うため、文字コードなどの問題が起こりにくく、WordPress公式の開発者向けドキュメントでも推奨されている関数です。

参考ページはこちら

Googleのリッチリザルトについて

現在は一般的な企業サイトでFAQリッチリザルトが表示されるケースは以前より少なくなっています。

しかし、構造化データを実装することで検索エンジンがページ内容を理解しやすくなるため、SEOの基本施策として実装しておくことをおすすめします。

【STEP3】各ページで呼びす

作成したテンプレートパーツは、固定ページ(例:page-starter.php など)の表示したい箇所で以下のように呼び出します。 

全てのFAQを一括表示したい場合

引数を渡さずに呼び出せば、登録されているすべてのFAQが出力されます。

<?php get_template_part('template-parts/faq'); ?>

「Starterプラン」のFAQを表示したい場合 

  ‘category’ => ‘starter-plan’を入力してください。

starter-planなどのカテゴリ名はご自分で登録した名前を使用してください。

<?php 
get_template_part('template-parts/faq', null, [
    'category' => 'starter-plan' // タクソノミーのタームスラッグを指定
]); 
?>

【STEP4】CSSとJavaScriptでアコーディオンを実装

見た目を整え、JavaScriptでスムーズに開閉するアニメーション(アコーディオン機能)を実装します。

アクセシビリティと滑らかな挙動を担保するため、dd(回答部分)は height: 0; overflow: hidden; で隠し、JSで高さを制御します。

.faq-container {
    max-width: 800px;
    margin: 0 auto;
    padding: 20px;
}
.faq-item {
    border-bottom: 1px solid #e0e0e0;
    margin-bottom: 15px;
}
.faq-question {
    display: flex;
    align-items: center;
    font-weight: bold;
    padding: 15px 0;
    cursor: pointer;
    position: relative;
}
.faq-icon-q {
    color: #ff5722;
    font-size: 1.2rem;
    margin-right: 10px;
}
.faq-toggle-icon {
    margin-left: auto;
    width: 12px;
    height: 12px;
    border-right: 2px solid #333;
    border-bottom: 2px solid #333;
    transform: rotate(45deg);
    transition: transform 0.3s ease;
}
/* 開いた時の矢印の動き */
.faq-question.is-active .faq-toggle-icon {
    transform: rotate(-135deg);
}

.faq-answer {
    height: 0;
    overflow: hidden;
    transition: height 0.3s ease-in-out;
    margin: 0;
}
.faq-answer-inner {
    display: flex;
    padding-bottom: 20px;
    color: #555;
    line-height: 1.6;
}
.faq-icon-a {
    color: #009688;
    font-weight: bold;
    font-size: 1.2rem;
    margin-right: 10px;
}

動的アニメーション追加(JavaScript)

window.getComputedStyle や scrollHeight を活用し、CSSの height 変化だけでは難しい「コンテンツに応じた可変高さのスムーズなアニメーション」を実現します。 

document.addEventListener('DOMContentLoaded', () => {
    const faqQuestions = document.querySelectorAll('.faq-question');

    faqQuestions.forEach(question => {
        question.addEventListener('click', () => {
            const answer = question.nextElementSibling;
            
            // アクティブクラスの切り替え
            question.classList.toggle('is-active');

            if (question.classList.contains('is-active')) {
                // 開く処理(中身の高さに合わせて動的に指定)
                answer.style.height = answer.scrollHeight + 'px';
            } else {
                // 閉じる処理
                answer.style.height = '0px';
            }
        });
    });
});

管理画面での実際の並び替え手順

‘page-attributes’ を有効にすると、各FAQの編集画面や一覧画面から並び順(menu_order)を変更できるようになります。WordPressの標準機能だけで順番をコントロールする具体的な手順は以下の通りです。

方法1:各FAQの編集画面から設定する

順番を変えたいFAQの編集画面を開きます。

FAQの編集画面の三点リーダの場所

画面右側のサイドバー(設定パネル)にある三点リーダの場所にマウスカーソルをあてます。

FAQの編集画面の三点リーダを押し、順序を押す

「順序」をクリックすると以下の表示になります。

順序をクリックしたあとの画像

入力欄に表示したい順に数値を入力します。

デフォルトは 0。一番上に表示したいFAQには1、二番目には2を入力。

「保存」を押して順序の表示を閉じ、ページの「保存」を押して更新します。

方法2:一覧画面から「クイック編集」で変更する

FAQの一覧画面から変更することも可能です。

まずはFAQの一覧画面を開き、順番を変えたいFAQのタイトルにマウスを合わせ、表示されるメニューから「クイック編集」をクリックします。

FAQのクイック編集の場所

次に、右側に表示される「順序」の欄の数値を書き換え、左下の「更新」ボタンをクリックします。

デフォルトは 0なので、一番上に表示したいFAQには1、二番目には2を入力してください。

FAQのクイック編集を押した後、順序がある場所の図

方法3:「Intuitive Custom Post Order」を導入する

標準機能でも問題なく並び替えは可能ですが、FAQの数が数十個に増えてくると「どれに何番を割り振ったか」を管理するのが大変になります。

そのため、もし直感的にドラッグ&ドロップで並び替えたい!という場合は、無料プラグイン の「Intuitive Custom Post Order」を導入するのがおすすめです。 

プラグインの導入と設定手順

  1. プラグインのインストール
    WordPress管理画面の「プラグイン」>「新規追加」から「Intuitive Custom Post Order」を検索し、インストールして有効化します。
  2. FAQの並び替えを有効にする
    管理画面の「設定」>「並び替え設定」に移動します。
  3. 対象のカスタム投稿にチェックを入れる
    「カスタム投稿タイプ」の項目にある 「FAQ(faq)」 にチェックを入れます。必要に応じて、カテゴリの順番もドラッグ&ドロップしたい場合は「カスタムタクソノミー」の 「FAQカテゴリ(faq_category)」 にもチェックを入れ、更新」をクリックします。

これで設定は完了です!

「FAQ」の一覧画面に戻ると、投稿をマウスで掴んで上下に入れ替えられるようになっています。

ちなみに、プラグインを導入しても、STEP 1で書いた functions.php や、STEP 2の faq.php のコードを変更する必要はありません。

プラグインが裏側で自動的に「順序(menu_order)」の数値を更新してくれるため、手動で数字を入力する手間が省けます。

【STEP5】リッチリザルトテストで構造化データを確認する

構造化データを実装したら、Googleの「リッチリザルトテスト」で正しく認識されているか確認しましょう。

確認方法はとても簡単です。

  1. Googleのリッチリザルトテストを開く
  2. 確認したいページのURLを入力する
  3. 「URLをテスト」をクリックする
  4. 解析が完了するまで待つ

解析が終わると、Googleが検出した構造化データの種類や、エラー・警告の有無が表示されます。

正常に認識された場合

FAQの構造化データが正しく実装されていると、「FAQ」が検出されたことが表示されます。

また、

  • エラー:0件
  • 警告:0件

となっていれば、構造化データとして問題なく認識されています。

エラーが表示された場合

構造化データに必須項目が不足していたり、JSONの記述ミスがあるとエラーが表示されます。

例えば、

  • acceptedAnswer がない
  • text が空になっている
  • JSONの記述ミス
  • HTMLタグの閉じ忘れ

などが原因になることがあります。

エラー内容をクリックすると、どの項目に問題があるのか確認できるため、修正後に再度テストを実行しましょう。

リッチリザルトとして表示されるとは限らない

リッチリザルトテストでエラーがなくても、必ず検索結果にFAQリッチリザルトが表示されるわけではありません。

現在はGoogleの仕様変更により、一般的な企業サイトではFAQリッチリザルトが表示されるケースは少なくなっています。

そのため、このテストは「リッチリザルトが表示されるか」を確認するものではなく、「構造化データが正しく実装されているか」を確認するためのツールとして利用するとよいでしょう。

まとめ:カスタム投稿FAQで運用の効率化とSEO対策を両立させよう

今回は、WordPressのカスタム投稿とタクソノミーを組み合わせた、実践的な「よくある質問(FAQ)」の構築方法を解説しました。

最後に、今回紹介した実装のポイントを振り返ってみましょう。

  • カスタム投稿(faq)化: 管理画面が独立し、ブログ感覚でQ&Aを追加・編集できる
  • ページ属性(menu_order)の活用: 手動での数値入力やプラグインでのドラッグ&ドロップで、並び替えが自由自在
  • テンプレートパーツ化: 引数(category)を渡すだけで、サービスごとの出し分けや複数ページでの使い回しが1行で可能
  • JSON-LDの自動出力: 検索結果にリッチリザルトを表示させ、SEOのクリック率(CTR)を高める

この方法は最初にコードを書く手間は少しだけかかりますが、一度このテンプレートを作ってしまえば、今後のサイト運用がかなり楽になります。

複数カテゴリをそれぞれのページで表示・非表示を選ぶたいサイトや、将来的にFAQが増える予定のあるコーポレートサイトにおすすめです。

ぜひ今回のコードを実際のテーマ開発で活用してみてください。

それでは、よきWordPress開発ライフを!

お問い合わせ

CONTACT

小さなお悩みや「何から始めればいいかわからない」といった段階でも大歓迎です。
まずはお気軽にご相談ください。