べんりワザ・WordPressのカスタマイズの土台

エスケープ関数の使い分け(esc_html・esc_attr・esc_url)

テーマやプラグインを書くときに欠かせないエスケープ関数の使い分け。出力する場所ごとの正解、入力を洗うsanitize系、nonceと権限チェックまで、コード付きで整理しました。

テーマやプラグインを書くときの決まり事——出力する値は必ずエスケープする。理由は、投稿やフォームから入った文字列に<script>が混ざっていた場合に、そのまま実行されてしまうのを防ぐためです。

覚えるのは「どこに出すか」で関数を選ぶというルールだけです。

出力する場所ごとの正解

出す場所使う関数
画面のテキストesc_html()<p><?php echo esc_html($name); ?></p>
タグの属性値esc_attr()<img alt="<?php echo esc_attr($alt); ?>">
href・srcなどのURLesc_url()<a href="<?php echo esc_url($url); ?>">
textareaの中身esc_textarea()<textarea><?php echo esc_textarea($v); ?></textarea>
JavaScriptの文字列esc_js()インライン用(できればwp_localize_script
HTMLを保ちたい値wp_kses_post()リッチテキスト・投稿本文相当
<?php $img = get_field('photo'); ?>
<a href="<?php echo esc_url(get_permalink()); ?>" class="card">
  <img src="<?php echo esc_url($img['url']); ?>" alt="<?php echo esc_attr($img['alt']); ?>">
  <h3><?php echo esc_html(get_the_title()); ?></h3>
</a>

WordPress関数のエスケープ事情

「関数で出しているから安全」とは限りません。エスケープするかどうかは関数ごとに違います

関数扱い
the_title()基本的に安全に出力される
the_permalink()URLとして安全に出力される
the_content()投稿本文としてフィルタを通る(HTML可)
the_field()(ACF/SCF)エスケープしない——値をそのまま出す
get_〇〇()値を返すだけ——自分でエスケープする

get_で始まる関数は「値を返す」ので、echoするときは自分でエスケープが必要です。この一点を押さえるだけで、書き方が安定します。

べんりワザACFフィールド別・表示の書き方早見表ACFの値を安全に出す書き方はこちらの早見表で

入力は「洗ってから保存」

出力側のエスケープと対になるのが、保存前のサニタイズです。

用途関数
1行テキストsanitize_text_field()
複数行テキストsanitize_textarea_field()
メールアドレスsanitize_email()
URLesc_url_raw()(保存用)
整数absint() / (int)
スラッグsanitize_title()
キー名sanitize_key()
HTMLを許可する本文wp_kses_post()
$name  = sanitize_text_field($_POST['name'] ?? '');
$email = sanitize_email($_POST['email'] ?? '');
$count = absint($_POST['count'] ?? 0);

外から来た値($_POST $_GET $_COOKIE)は、必ず型と形を決めてから使う——これが基本姿勢です。

フォームの保存処理は3点セット

管理画面や公開側でデータを保存する処理では、nonce・権限・サニタイズの3つを必ず入れます。

// フォーム側
function my_form() {
  ?>
  <form method="post">
    <?php wp_nonce_field('my_save_action', 'my_nonce'); ?>
    <input type="text" name="nickname">
    <button type="submit">保存</button>
  </form>
  <?php
}

// 受け取り側
add_action('admin_post_my_save', function () {
  // ① nonceの確認(このサイトの画面から来たか)
  if (!isset($_POST['my_nonce']) || !wp_verify_nonce($_POST['my_nonce'], 'my_save_action')) {
    wp_die('不正なリクエストです');
  }
  // ② 権限の確認(この人に許された操作か)
  if (!current_user_can('edit_posts')) {
    wp_die('権限がありません');
  }
  // ③ 値を洗ってから保存
  update_option('my_nickname', sanitize_text_field($_POST['nickname'] ?? ''));

  wp_safe_redirect(admin_url('options-general.php'));
  exit;
});
  • nonce——その送信が自分のサイトの画面から来たかを確認する使い捨ての値
  • 権限——current_user_can()で「この人がやっていい操作か」を確認する
  • サニタイズ——値の形を決める

3つのうちどれが欠けても穴になります。特にnonceだけ確認して権限を見ないケースは多いので、セットで書く癖をつけます。

べんりワザget_post_metaでカスタムフィールドを扱う投稿の保存時にメタを更新する例はこちら

データベースを直接触るときはprepare

$wpdbで自分でSQLを書くときは、値を必ずprepare()経由にします。

global $wpdb;

// ❌ 危険(SQLインジェクションの原因)
$wpdb->get_results("SELECT * FROM {$wpdb->posts} WHERE post_title = '$title'");

// ✅ prepareでプレースホルダに渡す
$wpdb->get_results(
  $wpdb->prepare("SELECT * FROM {$wpdb->posts} WHERE post_title = %s", $title)
);

%s(文字列)・%d(整数)・%f(小数)を使い分けます。そもそもWP_Queryget_posts()で済むならそちらを使うのがいちばん安全です。

べんりワザループの外で投稿を取得する方法(WP_Query)WP_Queryで取得する書き方はこちら

リダイレクトはwp_safe_redirect

wp_safe_redirect(home_url('/thanks/'));
exit;

wp_safe_redirect()自サイト内のURLにしか飛ばないので、外部サイトへ誘導される「オープンリダイレクト」を防げます。exitを忘れると処理が続いてしまうので、必ずセットで書きます。

翻訳関数と組み合わせる

多言語対応するテーマでは、翻訳とエスケープを兼ねた関数があります。

<?php esc_html_e('お問い合わせ', 'my-theme'); ?>       <!-- 出力 -->
<?php echo esc_attr__('検索', 'my-theme'); ?>          <!-- 属性用 -->

__()(値を返す)と_e()(出力する)に、esc_htmlesc_attrが付いた形です。命名の規則が分かれば、必要なものを推測できます。

まとめ

  1. 出力する場所で関数を選ぶ——本文esc_html()/属性esc_attr()/URLesc_url()
  2. get_で始まる関数は値を返すだけ。echoするときは自分でエスケープthe_field()も対象)
  3. 保存処理はnonce・権限(current_user_can)・サニタイズの3点セット
  4. SQLを書くなら$wpdb->prepare()。リダイレクトはwp_safe_redirect()exit

エスケープは覚えることが少ないのに効果が大きい習慣です。手が勝手に書くようになれば、それだけで安全性が一段上がります。

よくある質問

esc_htmlとesc_attrはどう使い分けますか?
画面に表示する本文のテキストはesc_html()、HTMLタグの属性値(alt・class・valueなど)はesc_attr()です。属性値には引用符の扱いが含まれるため、専用の関数を使います。
the_field()やthe_title()はエスケープされていますか?
the_title()は基本的に安全に出力されますが、ACFのthe_field()はエスケープしません。自分でechoするときはesc_html()やesc_url()を通してください。
HTMLを含む値を表示したいときは?
リッチテキストのように意図的にHTMLを含む値はwp_kses_post()を使います。許可されたタグだけを残し、危険なスクリプトを取り除いてくれます。
nonceは何のために必要ですか?
そのフォーム送信が本当に自分のサイトの画面から行われたかを確認するための使い捨てトークンです。管理画面の保存処理では、nonceの検証と権限チェック(current_user_can)を必ず組み合わせます。