WordPress投稿のSEOカスタムフィールドとは、各記事ごとのSEOタイトル・メタディスクリプションを投稿編集画面で個別入力・保存し、wp_head で出力する仕組みです。AIOSEOやYoast SEOといった専用プラグインを使わず、WordPress標準の postmeta API とフックだけで実装できます。
この記事では、前回のWordPressのSEOを自作するメリット・デメリットで挙げた「必須実装要素①: title/meta管理」を深掘りし、メタボックスの追加から保存・出力・REST API連携まで、WordPress公式APIだけで完結する最小実装をステップごとに解説します。コード例はすべて公式の汎用形式で、コピペで動く構成にしています。
WordPress投稿のSEOカスタムフィールドとは(定義と仕組み)
SEOカスタムフィールドは、postmeta テーブルに「SEOタイトル」「メタディスクリプション」専用のキーを用意し、記事ごとに個別の値を保存する仕組みです。WordPress標準ではこの2項目は post_title と post_excerpt しか持っていないため、SEO最適化のために別フィールドを追加します。
代表的な実装パターンは次の3層に分かれます。
- 入力UI:投稿編集画面に
add_meta_boxでメタボックスを追加 - 保存処理:
save_postアクションでupdate_post_metaを呼び、postmetaに格納 - 出力処理:
wp_headでget_post_metaから読み出し、<title>と<meta name="description">として出力
この3層を自分のテーマ(または機能プラグイン)に実装すれば、SEOプラグインを入れずにページ単位のSEO最適化が可能になります。
カスタムフィールド自作 vs SEOプラグイン vs ACFの比較
SEOメタの管理方法には主に3つの選択肢があります。それぞれの特徴を整理した上で判断するのが安全です。
| 項目 | 自作(postmeta) | SEOプラグイン | ACF |
|---|---|---|---|
| 導入コスト | 実装が必要 | インストールのみ | プラグイン+設定 |
| 表示速度への影響 | 最小(必要な機能だけ) | 不要機能も読み込まれがち | 中程度 |
| カスタマイズ自由度 | 高い | プラグインの設計に依存 | UIは強いが出力は自作 |
| UI管理画面 | 自前で実装 | 充実したUI標準装備 | 強力なフィールドUI |
| メンテナンス | WP本体APIのみ追随 | プラグイン更新追随 | プラグイン+自作出力 |
| 向いている人 | 個人開発者・自社運用 | 編集者多数の組織サイト | カスタムフィールド多用サイト |
SEOメタ用途だけを自作する場合、必要な関数は4つ程度(add_meta_box / update_post_meta / get_post_meta / register_meta)で、合計100行ほどの実装に収まります。プラグインを増やしたくない個人開発者・自社メディア運営者に最も向いています。
実装の全体像(4ステップ)
SEOカスタムフィールドの実装は次の4ステップで完成します。
- ステップ1:投稿編集画面にメタボックスを追加する(入力欄を表示)
- ステップ2:入力された値を postmeta に保存する(nonce検証・サニタイズ込み)
- ステップ3:
wp_headでフロントの <title> と <meta name=”description”> に出力する - ステップ4:(任意)
register_metaでREST APIに公開し、Gutenberg・外部ツールから参照可能にする
ステップ1〜3が必須、ステップ4は任意です。以下、各ステップを WordPress 公式APIの最小サンプルで順に見ていきます。コードは functions.php またはテーマ内の任意のPHPファイルにそのまま貼って動作確認できます。
ステップ1:投稿編集画面にメタボックスを追加する
add_meta_box を add_meta_boxes アクションで呼ぶと、投稿編集画面の任意の位置にカスタム入力欄を追加できます。
<?php
add_action( 'add_meta_boxes', 'my_seo_add_meta_box' );
function my_seo_add_meta_box() {
add_meta_box(
'my_seo_meta_box', // メタボックスのID
'SEO設定', // タイトル
'my_seo_render_meta_box', // コールバック関数名
'post', // 表示する投稿タイプ
'side', // 表示位置(normal/side/advanced)
'high' // 表示優先度(high/core/default/low)
);
}
function my_seo_render_meta_box( $post ) {
// nonceフィールドを出力(保存時の検証に使う)
wp_nonce_field( 'my_seo_save_meta', 'my_seo_meta_nonce' );
$seo_title = get_post_meta( $post->ID, '_my_seo_title', true );
$seo_desc = get_post_meta( $post->ID, '_my_seo_description', true );
?>
<p>
<label for="my_seo_title"><strong>SEOタイトル</strong></label>
<input type="text" id="my_seo_title" name="my_seo_title"
value="<?php echo esc_attr( $seo_title ); ?>"
style="width:100%;" />
</p>
<p>
<label for="my_seo_description"><strong>メタディスクリプション</strong></label>
<textarea id="my_seo_description" name="my_seo_description"
rows="4" style="width:100%;"><?php echo esc_textarea( $seo_desc ); ?></textarea>
</p>
<?php
}
ポイントは メタキーの先頭にアンダースコアを付けること(例:_my_seo_title)。アンダースコア始まりのメタキーは「保護されたメタ」として扱われ、投稿編集画面の標準カスタムフィールドUIに表示されなくなります。SEO専用フィールドが標準UIに二重表示されるのを防ぐ目的で、SEOプラグイン各種もこの命名規則を採用しています。
ステップ2:入力値を postmeta に保存する
save_post アクションで投稿が保存されたタイミングをフックし、update_post_meta でpostmetaテーブルに値を格納します。ここで nonce検証・自動保存除外・権限チェック・サニタイズ の4点セットを必ず実装します。
<?php
add_action( 'save_post', 'my_seo_save_meta' );
function my_seo_save_meta( $post_id ) {
// 1. nonce検証
if ( ! isset( $_POST['my_seo_meta_nonce'] )
|| ! wp_verify_nonce( $_POST['my_seo_meta_nonce'], 'my_seo_save_meta' ) ) {
return;
}
// 2. 自動保存はスキップ
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
// 3. 編集権限チェック
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
// 4. サニタイズして保存
if ( isset( $_POST['my_seo_title'] ) ) {
update_post_meta(
$post_id,
'_my_seo_title',
sanitize_text_field( wp_unslash( $_POST['my_seo_title'] ) )
);
}
if ( isset( $_POST['my_seo_description'] ) ) {
update_post_meta(
$post_id,
'_my_seo_description',
sanitize_textarea_field( wp_unslash( $_POST['my_seo_description'] ) )
);
}
}
wp_unslash は WordPress が自動付与するエスケープを除去する関数で、サニタイズ前に必ず通します。タイトルは sanitize_text_field、複数行のメタディスクリプションは sanitize_textarea_field を使い分けるのがWordPress公式の作法です。
4つのチェックのうち nonce検証を省略するとCSRF攻撃で他ユーザーの投稿メタを書き換えられる可能性があり、権限チェックを省略すると低権限ユーザーが管理者投稿を編集できる脆弱性になります。どちらも公開サイトでは必須です。
ステップ3:wp_headでフロントに出力する(フォールバック設計)
保存した値を <title> タグと <meta name="description"> として出力します。ここで肝心なのは postmetaが空のときのフォールバックです。全記事に毎回SEOタイトルを書くのは現実的ではないため、空のときは post_title や post_excerpt に自動で切り替える設計が必須になります。
<title>タグの上書き
WordPress 4.4以降、テーマが add_theme_support( "title-tag" ) を宣言していると、<title> は pre_get_document_title フィルタで上書きできます。
<?php
add_filter( 'pre_get_document_title', 'my_seo_filter_title' );
function my_seo_filter_title( $title ) {
if ( ! is_singular( 'post' ) ) {
return $title;
}
$post_id = get_queried_object_id();
$seo_title = get_post_meta( $post_id, '_my_seo_title', true );
if ( ! empty( $seo_title ) ) {
return $seo_title;
}
// フォールバック:post_title をそのまま使う
return $title;
}
meta description の出力
メタディスクリプションは wp_head アクションでHTMLを直接出力します。こちらも空欄時は get_the_excerpt でフォールバックさせます。
<?php
add_action( 'wp_head', 'my_seo_output_meta_description' );
function my_seo_output_meta_description() {
if ( ! is_singular( 'post' ) ) {
return;
}
$post_id = get_queried_object_id();
$seo_desc = get_post_meta( $post_id, '_my_seo_description', true );
if ( empty( $seo_desc ) ) {
// フォールバック:抜粋から自動生成
$seo_desc = get_the_excerpt( $post_id );
$seo_desc = wp_strip_all_tags( $seo_desc );
$seo_desc = mb_substr( $seo_desc, 0, 160 );
}
if ( ! empty( $seo_desc ) ) {
printf(
'<meta name="description" content="%s" />' . "\n",
esc_attr( $seo_desc )
);
}
}
出力時には esc_attr または esc_html を必ず通すのがWordPressの作法です。これを省くとメタ情報経由でHTMLが注入され、XSSの原因になります。フォールバック側でも wp_strip_all_tags でHTMLタグを除去してから文字数を切ることで、安全な出力になります。
ステップ4:register_metaでREST API公開(Gutenberg/AI連携)
ここまでで postmeta への保存と出力は完成していますが、register_meta を追加するとそのメタキーが REST API経由で取得・更新可能になります。Gutenbergのサイドバーから操作したり、外部AIツール・記事一括管理スクリプトから読み書きしたい場合に有効です。
<?php
add_action( 'init', 'my_seo_register_meta' );
function my_seo_register_meta() {
register_post_meta( 'post', '_my_seo_title', array(
'show_in_rest' => true,
'single' => true,
'type' => 'string',
'auth_callback' => function() {
return current_user_can( 'edit_posts' );
},
) );
register_post_meta( 'post', '_my_seo_description', array(
'show_in_rest' => true,
'single' => true,
'type' => 'string',
'auth_callback' => function() {
return current_user_can( 'edit_posts' );
},
) );
}
auth_callback を省略すると、アンダースコア始まりの保護メタは REST API経由で書き込めません。編集権限を持つユーザーだけが更新できるよう、上記のように current_user_can( "edit_posts" ) を返すようにします。
登録後は GET /wp-json/wp/v2/posts/{id} のレスポンス内 meta オブジェクトに _my_seo_title と _my_seo_description が含まれるようになります。AI記事一括メンテや、Gutenbergサイドバーパネルへの拡張に応用できます。
よくある実装ミス5つと回避策
自作実装でよく見かける典型的なミスと、その回避方法を5つ挙げます。公開前のチェックリストとして使ってください。
| ミス | 症状 | 回避策 |
|---|---|---|
| nonce検証を省略 | CSRFで他ユーザーが投稿メタを改変可能 | wp_nonce_fieldとwp_verify_nonceを必ずセットで使う |
| 権限チェックを省略 | 低権限ユーザーが管理者投稿のメタを編集可能 | current_user_can("edit_post", $post_id)を入れる |
| 出力時のエスケープ忘れ | メタ経由でHTML/JS注入(XSS)が成立 | 必ずesc_attrまたはesc_htmlを通す |
| フォールバック未設計 | postmeta未入力ページが空titleで配信される | 空のときpost_title/get_the_excerptに切り替え |
| 自動保存除外を忘れる | 編集中の意図しないタイミングで上書きが走る | DOING_AUTOSAVE定数で早期return |
特に「フォールバック未設計」は古い投稿で空タイトル配信される事故の主因です。実装直後は問題が見えづらいため、SEOスコア無料診断ツールなどで本番URLを実際にチェックし、<title>と<meta name="description">が両方出力されているか確認してください。
SEOプラグインから移行する場合の注意点
既にAIOSEOやYoast SEOを使っているサイトが自作実装に切り替える場合、旧プラグインが残したpostmetaを引き継ぐか・破棄するかを最初に決める必要があります。代表的なメタキーは以下です。
| プラグイン | SEOタイトルのメタキー | メタディスクリプションのメタキー |
|---|---|---|
| Yoast SEO | _yoast_wpseo_title | _yoast_wpseo_metadesc |
| All in One SEO(AIOSEO) | 専用テーブル wp_aioseo_posts に格納 | 同左 |
| Rank Math | rank_math_title | rank_math_description |
既存値を活かしたい場合は、get_post_meta で旧キーから読み、update_post_meta で自作キー(例:_my_seo_title)に書き写すマイグレーションスクリプトを書きます。AIOSEOのように専用テーブルを持つプラグインは、$wpdb 経由で別途SELECT文を組む必要があります。
マイグレーションが終わったらSEOプラグインを停止・削除し、自作実装のフォールバック動作と <title> / <meta name="description"> の出力をフロントで再確認します。
SEO設定が正しく出力されているか診断する
自作実装が完成したら、本番URLで実際の出力を確認するのが最重要です。WordPress公式ディレクトリで公開している無料プラグイン ORECTIC SEO CHECK は、ページ単位でSEOタイトル・メタディスクリプション・OGP・構造化データの設定状況を一覧表示します。プラグイン本体に管理画面UIだけ任せて、出力は自作のままにしておく「ハイブリッド運用」にも使えます。
よくある質問(FAQ)
Q. SEOプラグインを使う場合と比較してメリットは何ですか?
不要機能のロードを避けられるため表示速度(特にCore Web Vitals)が改善されやすく、UIも自分の運用に合わせて最小化できます。一方で実装・保守の責任は自分で持つ必要があり、編集者が複数いるサイトでは標準UIが充実したSEOプラグインの方が運用コストは低くなります。
Q. nonce検証は必須ですか?省略するとどうなりますか?
必須です。省略すると外部サイトに仕込まれたフォームから管理画面のセッションを利用して投稿メタを書き換えるCSRF攻撃が成立します。wp_nonce_fieldとwp_verify_nonceは必ずセットで使ってください。
Q. register_metaは使わなくても動きますか?
入力UIと出力だけなら不要です。register_metaはREST API経由でメタを読み書きしたい場合(Gutenbergサイドバーパネルの自作・AI記事一括メンテ・外部スクリプトとの連携など)にのみ必要になります。
Q. ACFやSCFと併用できますか?
可能です。ACFで作ったフィールドのメタキーを get_post_meta で読み出して同じく wp_head で出力すれば、入力UIはACFに任せて出力だけ自作という構成にできます。
Q. postmetaが未入力の場合、フォールバックはどう設計すべきですか?
<title>はWordPress標準のpost_titleに、<meta name="description">はget_the_excerpt+wp_strip_all_tags+mb_substrで抜粋から自動生成するのが基本パターンです。空文字を出力するくらいなら自動生成の方がSEO上ずっと安全です。
Q. 既存記事のSEOタイトルを一括で初期化したい場合は?
WP-CLIのwp post meta updateか、register_metaでREST APIに公開した上でPythonやNode.jsの管理スクリプトから一括PUTする方法があります。記事数が多い場合はWAFや負荷を考慮してバッチ実行してください。
まとめ:次は「タクソノミー編」へ
WordPress標準のpostmeta APIだけでSEOカスタムフィールドは実装できます。ポイントは「nonce・権限・サニタイズ・エスケープ・フォールバック」の5点セットを必ず守ることと、register_metaでREST APIに公開しておくと後の拡張が楽になることです。
投稿側のSEOメタが整ったら、次はカテゴリページ・タグページのSEOメタです。termmeta APIを使えば同じ考え方でタクソノミー側にもSEOタイトル・メタディスクリプションを追加できます。実装パターンは次の記事で解説予定です。
また、自作するかSEOプラグインを選ぶかの判断軸そのものを整理した記事は、WordPressのSEOを自作するメリット・デメリットにまとめています。導入判断段階で迷っている場合はこちらを先に確認してください。
