環境
- WordPress 6.5.x(クラシックテーマ / 自作)
- PHP 8.2
- カスタム投稿タイプ
work(制作実績) - カスタムタクソノミー
work_cat - 一覧カードのバッジは
post_metaのwork_display_categoryを参照 - ACFは使わず
update_post_meta()を直接利用 - 公開操作は管理画面の「公開」ボタン(REST API経由)
何が起きたか
制作実績一覧サイトで、下書きを「公開」した直後、一覧カードのバッジが想定と別カテゴリで表示されました。具体的には「Web制作」で出すべきカードが「その他」になっていました。
- 記事編集画面のタクソノミー(
work_cat)は正しく「Web制作」が選択済み - しかし一覧カードは「その他」
- 個別ページにはカテゴリ名が出ていないので、一覧を見るまで誰も気づけない
- 公開から数日間、誤った分類が載ったままになった
つまり DB上は記事が正しいのに、表示だけが別カテゴリ という状態です。これが一番厄介で、編集画面をいくら見ても原因が見えません。
原因:post_meta は publish しても自動で入らない
バッジの描画は get_post_meta() で work_display_category を取得し、空だったら else で既定値にフォールバック する実装でした。
<?php
// 一覧カード用テンプレート(改修前)
$args = [
'post_type' => 'work',
'posts_per_page' => 12,
'post_status' => 'publish',
];
$q = new WP_Query($args);
?>
<ul class="work-list">
<?php while ($q->have_posts()) : $q->the_post(); ?>
<?php
$cat = get_post_meta(get_the_ID(), 'work_display_category', true);
if ($cat !== '') {
$label = $cat;
} else {
$label = 'その他'; // ← ここが「既定の別カテゴリ」として表示される
}
?>
<li class="card">
<span class="badge"><?php echo esc_html($label); ?></span>
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
</li>
<?php endwhile; wp_reset_postdata(); ?>
</ul>
ポイントは次の2つです。
-
publishはあくまでpost_statusをpublishに変えるだけ。post_metaは 誰も入れない限り空のまま - テンプレート側が「空 = その他」というフォールバックを持っているため、エラーではなく正常な見た目で誤分類が表示される
タクソノミー(work_cat)と表示用メタ(work_display_category)を分けていたのも遠因です。「タクソノミーを選んだのだから表示も追従するはず」という思い込みが、メタ投入工程の抜けを隠していました。
調査手順
まず「どの公開記事にメタが無いか」をSQLで洗い出します。
SELECT p.ID, p.post_title, p.post_status
FROM wp_posts AS p
LEFT JOIN wp_postmeta AS m
ON m.post_id = p.ID AND m.meta_key = 'work_display_category'
WHERE p.post_type = 'work'
AND p.post_status = 'publish'
AND m.meta_id IS NULL;
WP-CLI が使える環境ならこちらが速いです。
# 個別に確認
wp post meta get 123 work_display_category
# 公開済みの実績を一覧
wp post list --post_type=work --post_status=publish --format=table
wp post meta get が空を返したら、その記事はフォールバック表示になっています。つまり 「メタが空の公開記事 = 誤分類カード」 と同義です。
対策1:公開時にメタを自動投入する
save_post ではなく transition_post_status を使うのがミソです。下書き→公開の遷移を1回だけ拾えるので、編集のたびに走る save_post より意図が明確になります。
add_action('transition_post_status', function ($new_status, $old_status, $post) {
if ($post->post_type !== 'work') {
return;
}
if ($new_status !== 'publish') {
return;
}
// 既に値があれば上書きしない(編集時の意図を壊さない)
if (get_post_meta($post->ID, 'work_display_category', true) !== '') {
return;
}
$terms = wp_get_post_terms($post->ID, 'work_cat', ['fields' => 'names']);
$label = !empty($terms) ? $terms[0] : '未分類';
update_post_meta($post->ID, 'work_display_category', $label);
}, 10, 3);
$old_status を見て draft 以外からの遷移を弾く厳密化もできますが、まずは「空なら入れる」だけで事故はかなり減ります。
対策2:既存データを一括シードする
フックを後から足しても、過去の公開記事は空のままです。WP-CLI コマンドを用意して一括投入します。
if (defined('WP_CLI') && WP_CLI) {
WP_CLI::add_command('work seed-category', function () {
$q = new WP_Query([
'post_type' => 'work',
'post_status' => 'publish',
'posts_per_page' => -1,
'meta_query' => [[
'key' => 'work_display_category',
'compare' => 'NOT EXISTS',
]],
]);
foreach ($q->posts as $p) {
$terms = wp_get_post_terms($p->ID, 'work_cat', ['fields' => 'names']);
$label = !empty($terms) ? $terms[0] : '未分類';
update_post_meta($p->ID, 'work_display_category', $label);
WP_CLI::log("seeded: {$p->ID} => {$label}");
}
WP_CLI::success('done');
});
}
実行は wp work seed-category。冪等なので何度流しても安全です。
対策3:テンプレートのフォールバックを「安全側」に倒す
根本原因はメタ投入漏れですが、漏れたときに別カテゴリへ化ける のが本当の損害です。フォールバックは「未分類」など明らかに異常と分かる値にし、可能なら一覧から除外します。
<?php
// メタ未設定の記事は一覧に出さない(空バッジで公開してしまう事故を防ぐ)
$q = new WP_Query([
'post_type' => 'work',
'posts_per_page' => 12,
'post_status' => 'publish',
'meta_query' => [[
'key' => 'work_display_category',
'value' => '',
'compare' => '!=',
]],
]);
「表示されない」という症状は気づきやすいので、誤分類より遥かにマシです。
対策の比較
| 対策 | 実装コスト | 漏れ耐性 | 既存データ対応 |
|---|---|---|---|
transition_post_status で自動投入 |
小 | 高(新規公開のみ) | 不可 |
| WP-CLI で一括シード | 小 | 中(実行忘れ) | 可 |
| フォールバックを安全側に変更 | 極小 | 高 | 可 |
| 公開フローに手動チェックを追加 | 極小 | 低(属人化) | 可 |
結論は 自動投入+一括シード+安全側フォールバックの3点セット です。どれか1つでは穴が残ります。
FAQ
Q. save_post ではダメですか?
A. ダメではありませんが、記事を編集するたびに発火します。「公開時だけ」を表現したいなら transition_post_status が素直です。両方使うなら重複防止のガードを入れてください。
Q. ACFを使えば自動で入りますか?
A. フィールドに値が入っていなければ同じです。空のフィールドは get_post_meta() で空文字が返るため、フォールバックが働きます。
Q. get_post_meta() の第3引数は?
A. true で単一値、false で配列を返します。カード表示のように単一値を期待する箇所で false のままだと配列になり、esc_html() で警告が出ることがあります。
Q. ブロックエディタやREST API経由の公開でも同じですか?
A. 同じです。post_status が publish になるだけでメタは自動投入されません。
公開フローのチェックリスト
- 表示用メタが空の公開記事をSQLで洗い出す
-
transition_post_statusで自動投入を実装 - WP-CLIの一括シードを用意し、既存記事に適用
- フォールバック値を「未分類」など異常と分かる値に変更
- READMEに「publishは公開作業の開始点」と明記
まとめ
publish ボタンは 公開作業の完了点ではなく開始点 です。メタ投入まで含めて完了条件に数える。これを運用ルールとして言語化しておくと、同じ事故はまず起きません。テンプレートの else は便利ですが、「空を別の意味に翻訳する」フォールバックは誤りを正常表示に変えてしまう という点を忘れないようにしたいところです。
この記事を書いた人
BENTEN Web Works — 業務自動化・システム開発のフリーランスエンジニアです。
GAS / Python / RPA を使った業務自動化や、Web制作・システム開発のご相談を承っています。
「こんなこと自動化できる?」というご質問だけでもお気軽にどうぞ。
👉 BENTEN Web Works — 詳細・お問い合わせはこちら
🐦 X(旧Twitter) — 日々の知見を発信中