WordPressで記事を投稿していると、カテゴリーを複数選べてしまうため、運用がぶれやすくなることがあります。特に、サイト構造を整理したい場合や、SEOを意識して設計したい場合は、1記事につきカテゴリーは1つに統一したい場面が少なくありません。共有された内容でも、最初はチェックボックスをjQueryでラジオボタンに置き換える方法を試したものの、ブロックエディターでは反映されなかった、という流れでした。この記事では、なぜカテゴリーを1つに絞ると運用しやすいのか、なぜ従来のjQueryコードでは動かないのか、そしてブロックエディターで実際に単一選択を実現する方法まで、まとめて解説します。
なぜカテゴリーは1つにした方がよいのか
カテゴリーは、記事を大きく分類するための軸です。ここが複数にまたがると、サイト全体の構造が曖昧になりやすくなります。

共有テキストでも、カテゴリーは大きなフォルダ分け、タグは横断的な関連付けという役割分担にした方が整理しやすい、という考え方が示されていました。
1記事1カテゴリーにしておくと、まずサイトの階層が明確になります。パンくずリストや内部リンクの整理もしやすくなり、読者が「いまどの分類の記事を見ているのか」を理解しやすくなります。

さらに、運営メンバーが複数いる場合でも、カテゴリー付けのルールがぶれにくくなります。タグを併用すれば、細かな関連性は十分に表現できます。
なぜ今までのコードでは反映されなかったのか
以前のコードは、管理画面上のチェックボックスをjQueryでラジオボタンに置き換える方法でした。
このやり方は、クラシックエディターや古い管理画面構造では動く可能性がありますが、現在の標準であるブロックエディターでは安定しません。共有されたテキストでも、ブロックエディターはReactベースで動作しているため、単純なDOMの置換では対応が難しいと整理されていました。
理由は主に2つあります。
描画タイミングの問題
1つ目は、描画タイミングの問題です。ブロックエディターでは、画面表示後にReactがUIを構築するため、jQueryで対象要素を探した時点では、まだカテゴリーの入力欄が十分に出そろっていないことがあります。これにより、セレクタが空振りし、何も変更されません。
見た目と内部状態が別管理になっている
2つ目は、見た目と内部状態が別管理になっていることです。仮に画面上で無理やりチェックボックスをラジオボタンに変えられたとしても、ブロックエディター内部では投稿データが別の状態管理で保持されています。
そのため、見た目だけ変わっても、保存時のカテゴリー情報が正しく連動しない可能性があります。
つまり、ブロックエディターでは、画面のHTMLを無理に書き換えるのではなく、エディターのデータそのものを制御する必要があります。
ブロックエディターで正しくラジオボタン化する考え方
ブロックエディターでカテゴリーを1つだけにしたい場合は、標準のカテゴリーパネルをそのまま触るのではなく、独自の単一選択UIを追加し、選択値をWordPressの投稿データへ直接反映させる形が安全です。
実装の流れは次の通りです。
まず、エディター専用のJavaScriptを読み込みます。次に、投稿画面のサイドバーに独自のカテゴリーパネルを追加します。
そのパネルでは、カテゴリー一覧をラジオボタンとして表示します。最後に、ラジオボタンで選ばれたカテゴリーIDだけを categories に配列で保存します。これで見た目も保存内容も一致します。
実装コード
以下は、ブロックエディターでカテゴリーを1つだけ選べるようにするプラグインコードです。functions.php に書くより、独立したプラグインとして入れる方が管理しやすく、安全です。
保存先は次の通りです。
wp-content/plugins/single-category-radio/single-category-radio.php
<?php
/**
* Plugin Name: Single Category Radio
* Description: ブロックエディターのカテゴリー選択をラジオボタン化し、1つだけ選べるようにします。
* Version: 1.0.0
* Author: Custom
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
add_action( 'enqueue_block_editor_assets', function () {
$handle = 'single-category-radio-editor';
wp_register_script(
$handle,
false,
array( 'wp-plugins', 'wp-element', 'wp-components', 'wp-data', 'wp-edit-post' ),
'1.0.0',
true
);
wp_enqueue_script( $handle );
$script = <<<'JS'
(function (wp) {
if (!wp || !wp.plugins || !wp.element || !wp.components || !wp.data) {
return;
}
const registerPlugin = wp.plugins.registerPlugin;
const PluginDocumentSettingPanel =
(wp.editor && wp.editor.PluginDocumentSettingPanel) ||
(wp.editPost && wp.editPost.PluginDocumentSettingPanel);
if (!PluginDocumentSettingPanel) {
return;
}
const el = wp.element.createElement;
const withSelect = wp.data.withSelect;
const withDispatch = wp.data.withDispatch;
const RadioControl = wp.components.RadioControl;
const Spinner = wp.components.Spinner;
const PANEL_NAME = 'taxonomy-panel-category';
let lastSelected = null;
const CategoryRadioPanel = withSelect(function (select) {
const core = select('core');
const editor = select('core/editor');
const terms = core.getEntityRecords('taxonomy', 'category', {
per_page: 100,
hide_empty: false,
orderby: 'name',
order: 'asc'
});
const selected = editor.getEditedPostAttribute('categories') || [];
return {
terms: terms,
selected: selected
};
})(
withDispatch(function (dispatch) {
return {
onChange: function (value) {
dispatch('core/editor').editPost({
categories: value ? [Number(value)] : []
});
}
};
})(function (props) {
if (props.terms === null) {
return el(
PluginDocumentSettingPanel,
{
name: 'single-category-radio-panel',
title: 'カテゴリー'
},
el(Spinner)
);
}
const options = (props.terms || []).map(function (term) {
return {
label: term.name,
value: String(term.id)
};
});
const selectedValue =
props.selected && props.selected.length
? String(props.selected[0])
: '';
return el(
PluginDocumentSettingPanel,
{
name: 'single-category-radio-panel',
title: 'カテゴリー'
},
el(RadioControl, {
label: 'カテゴリーを1つ選択',
selected: selectedValue,
options: options,
onChange: props.onChange
})
);
})
);
function hideCoreCategoryPanel() {
try {
wp.data.dispatch('core/editor').removeEditorPanel(PANEL_NAME);
} catch (e) {
}
}
function enforceSingleCategory() {
const editor = wp.data.select('core/editor');
if (!editor) {
return;
}
const categories = editor.getEditedPostAttribute('categories') || [];
if (categories.length <= 1) {
if (categories.length === 1) {
lastSelected = categories[0];
}
return;
}
const next = lastSelected && categories.includes(lastSelected)
? [lastSelected]
: [categories[categories.length - 1]];
lastSelected = next[0];
wp.data.dispatch('core/editor').editPost({
categories: next
});
}
if (wp.domReady) {
wp.domReady(hideCoreCategoryPanel);
} else {
document.addEventListener('DOMContentLoaded', hideCoreCategoryPanel);
}
wp.data.subscribe(enforceSingleCategory);
registerPlugin('single-category-radio-plugin', {
render: CategoryRadioPanel
});
})(window.wp);
JS;
wp_add_inline_script( $handle, $script );
}, 20 );
導入手順
このコードを使う場合は、まず single-category-radio という名前のフォルダを作成し、その中に single-category-radio.php を保存します。
次に、WordPress管理画面の「プラグイン」から有効化します。投稿編集画面を開き直すと、カテゴリーがラジオボタン形式で表示されるようになります。
この方法の利点は、見た目だけを変えるのではなく、投稿データの categories を直接制御している点です。つまり、ブロックエディターの仕組みに合わせた実装になっているため、保存時の不整合が起きにくくなります。
実装時の注意点
まず、カテゴリー数が非常に多いサイトでは調整が必要です。コード内では per_page: 100 でカテゴリーを取得しているため、100件を超える場合は別途拡張が必要になります。
また、標準カテゴリーではなく、カスタムタクソノミーを使っている場合は、その分類名に合わせて取得対象を変更する必要があります。共有内容でも、クラシック側の例として #categorychecklist をカスタムタクソノミー名に読み替える話が出ていましたが、ブロックエディター側でも同じく、対象タクソノミーを明示して実装する必要があります。
さらに、テーマの functions.php に直接混ぜ込むと、管理が煩雑になります。将来の修正や停止を考えると、独立プラグイン化しておく方が運用しやすくなります。
プラグインを使うという選択肢もある
コード管理を増やしたくない場合は、専用プラグインを使う方が簡単です。共有された内容でも、ブロックエディターでは専用プラグインの導入が安全で確実という整理になっていました。
ただし、外部プラグインに依存すると、将来の更新状況や互換性確認が必要になります。細かい挙動を自分で管理したい場合は、今回のように独自プラグインとして実装しておく方が、制御性は高くなります。
まとめ
WordPressでカテゴリーを1つだけ選べるようにしたい場合、古いjQueryの置換コードでは、現在のブロックエディターには対応しきれません。原因は、Reactによる動的な描画と、エディター内部の状態管理にあります。
そのため、ブロックエディターでは、独自のラジオボタンUIを追加し、投稿データの categories を直接制御する方法が適切です。これにより、見た目だけでなく保存内容まで一貫した状態で管理できます。
カテゴリーを1つに統一すると、サイト構造が明確になり、運用ルールも整いやすくなります。WordPressを長く運用するなら、早い段階でこの設計にしておく価値は大きいです。


