【WordPress】管理画面に独自メニューを追加する方法

【WordPress】管理画面に独自メニューを追加する方法 WordPress

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を短時間で構築できます。