WordPressの管理画面に独自メニューを追加すると、設定ページやツールページを一箇所にまとめられ、運用やカスタマイズの効率が大幅に向上します。ここではadd_menu_page / add_submenu_pageの基本から、保存処理(nonce検証・OPTIONS API / Settings API)、権限管理、アイコン設定、管理画面用のCSS/JS読込まで実務で使える最小~実用構成を紹介します。
最小構成:トップレベルメニューを追加する
独自メニューはadmin_menuフックで登録します。以下は「サイト設定」というトップレベルメニューと、その表示コールバックを定義する最小例です。
<?php
// functions.php もしくは独自プラグインで
add_action('admin_menu', function () {
add_menu_page(
'サイト設定', // ページタイトル(<title>)
'サイト設定', // メニューに表示されるラベル
'manage_options', // 必要な権限(例:管理者)
'cls-site-settings', // スラッグ(ユニーク)
'cls_site_settings_page',// コールバック関数(ページ出力)
'dashicons-admin-generic', // アイコン(Dashiconsまたはbase64 SVG)
58 // メニュー位置(数値ほど上位に表示)
);
});
// 管理画面:ページの中身
function cls_site_settings_page() {
if (!current_user_can('manage_options')) {
wp_die('このページにアクセスする権限がありません。');
}
echo '<div class="wrap">';
echo '<h1>サイト設定</h1>';
echo '<p>ここに独自の設定UIを配置します。</p>';
echo '</div>';
}
サブメニューを追加する
add_submenu_pageでトップレベル配下に第二階層の項目を追加できます。スラッグは親と重複しないようにします。
<?php
add_action('admin_menu', function () {
add_submenu_page(
'cls-site-settings', // 親メニューのスラッグ
'表示設定', // 子ページのタイトル
'表示設定', // サブメニューに表示されるラベル
'manage_options', // 権限
'cls-display-settings', // 子ページのスラッグ
'cls_display_settings_page' // 表示コールバック
);
});
function cls_display_settings_page() {
if (!current_user_can('manage_options')) {
wp_die('権限が不足しています。');
}
echo '<div class="wrap"><h1>表示設定</h1>ここにUI…</div>';
}
安全な保存処理(nonce検証とOptions API)
設定値の保存はadmin-post.php経由にすると安全で分かりやすくなります。nonceでCSRF対策を行い、sanitize_*系で入力を無害化してからupdate_optionします。
<?php
// 1) 画面のフォーム:保存先は admin-post.php?action=cls_save_settings
function cls_site_settings_page() {
if (!current_user_can('manage_options')) wp_die('権限なし');
$color = get_option('cls_brand_color', '#02A1CD');
echo '<div class="wrap">';
echo '<h1>サイト設定</h1>';
echo '<form method="post" action="' . esc_url(admin_url('admin-post.php')) . '">';
wp_nonce_field('cls_save_settings_nonce', 'cls_nonce');
echo '<input type="hidden" name="action" value="cls_save_settings">';
echo '<table class="form-table"><tr><th>ブランドカラー</th><td>';
echo '<input type="text" name="brand_color" value="' . esc_attr($color) . '" class="regular-text" placeholder="#02A1CD">';
echo '</td></tr></table>';
submit_button('変更を保存');
echo '</form></div>';
}
// 2) 保存ハンドラ:nonce検証→サニタイズ→保存→リダイレクト
add_action('admin_post_cls_save_settings', function () {
if (!current_user_can('manage_options')) wp_die('権限なし');
check_admin_referer('cls_save_settings_nonce', 'cls_nonce');
$color = isset($_POST['brand_color']) ? sanitize_hex_color($_POST['brand_color']) : '';
if (!$color) { $color = '#02A1CD'; } // フォールバック
update_option('cls_brand_color', $color);
wp_safe_redirect(add_query_arg(['page' => 'cls-site-settings', 'updated' => 'true'], admin_url('admin.php')));
exit;
});
Settings APIでフォームを自動構築する
項目が増える場合はSettings APIを使うとバリデーションや画面構築を半自動化できます。以下は基本パターンです。
<?php
add_action('admin_init', function () {
register_setting('cls_settings_group', 'cls_options', [
'type' => 'array',
'sanitize_callback' => function ($input) {
return [
'brand_color' => isset($input['brand_color']) ? sanitize_hex_color($input['brand_color']) : '#02A1CD',
'show_banner' => !empty($input['show_banner']) ? 1 : 0,
];
},
'default' => ['brand_color' => '#02A1CD', 'show_banner' => 1],
]);
add_settings_section('cls_main_section', '基本設定', function () {
echo '<p>サイトの基本動作を設定します。</p>';
}, 'cls-settings');
add_settings_field('cls_brand_color', 'ブランドカラー', function () {
$opts = get_option('cls_options');
echo '<input name="cls_options[brand_color]" type="text" value="' . esc_attr($opts['brand_color'] ?? '#02A1CD') . '" class="regular-text">';
}, 'cls-settings', 'cls_main_section');
add_settings_field('cls_show_banner', 'バナー表示', function () {
$opts = get_option('cls_options');
$checked = !empty($opts['show_banner']) ? 'checked' : '';
echo '<label><input name="cls_options[show_banner]" type="checkbox" value="1" ' . $checked . '> 表示する</label>';
}, 'cls-settings', 'cls_main_section');
});
// Settings APIページの描画
function cls_site_settings_page() {
if (!current_user_can('manage_options')) wp_die('権限なし');
echo '<div class="wrap"><h1>サイト設定</h1>';
echo '<form method="post" action="options.php">';
settings_fields('cls_settings_group'); // nonceやhidden出力
do_settings_sections('cls-settings'); // セクション・フィールド出力
submit_button('保存');
echo '</form></div>';
}
アイコン・メニュー位置の指定とベストプラクティス
アイコンはDashicons名(例:dashicons-admin-generic)を指定するか、base64エンコードしたSVGを渡すこともできます。位置は数値で細かく制御でき、58付近は外観・プラグインより下の見やすい帯域です。管理者以外に見せたくない場合はcapabilityを絞り、条件付きでadd_menu_pageを呼ぶのではなく、常に登録してcurrent_user_canで中身を分岐するかメニュー自体のcapability設定で制御するとメンテが容易です。
管理画面専用のCSS/JSを読み込む
admin_enqueue_scriptsで対象ページのときだけ読み込むと軽量です。$hookやget_current_screen()で判定します。
<?php
add_action('admin_enqueue_scripts', function ($hook) {
// 例: admin.php?page=cls-site-settings のときのみ読み込み
if ($hook === 'toplevel_page_cls-site-settings') {
$rel_css = 'assets/admin/settings.css';
$rel_js = 'assets/admin/settings.js';
wp_enqueue_style('cls-admin-settings', get_template_directory_uri() . '/' . $rel_css, [], filemtime(get_template_directory() . '/' . $rel_css));
wp_enqueue_script('cls-admin-settings', get_template_directory_uri() . '/' . $rel_js, ['jquery'], filemtime(get_template_directory() . '/' . $rel_js), true);
}
});
メニューの並び替えや削除が必要な場合
不要メニューはremove_menu_page / remove_submenu_pageで非表示にできます。実務上はプラグイン競合もあるため、admin_menuのpriorityを高めに設定して実行順序を後ろにすると安定します。
<?php
add_action('admin_menu', function () {
// 例: ツールの「エクスポート」を隠す(自己責任で)
// remove_submenu_page('tools.php', 'export.php');
}, 999);
よくあるエラーと対処
コールバック未定義で「valid callback」エラーが出る場合は関数名のスペルや読み込み順を確認します。スラッグが他と衝突してページが開かないケースはユニークな接頭辞を付けて回避します。保存できない場合はnonce名の不一致、actionやsettings_fieldsのグループ名の誤りが多いため見直します。権限不足でページが見えない場合はcapabilityをmanage_optionsから編集者向けに変更するなど要件に合わせて調整します。
まとめ
add_menu_page / add_submenu_pageで管理画面に独自メニューを追加し、nonce付きフォームとOptions API(またはSettings API)で安全に保存処理を実装すれば、運用しやすい設定ページが完成します。権限・アイコン・表示位置・アセット読込の最小構成を押さえることで、チームでも迷わない実用的な管理UIを短時間で構築できます。