2026年5月20日

2026年5月20日

WordPressプラグイン開発の基礎:アクションフックとフィルターフックの使い方

WordPress のプラグインは、コアのファイルを書き換えずに「フック」と呼ばれる差し込み口へ処理を登録することで機能を追加します。フックには、決まったタイミングで処理を実行する「アクション」と、受け取った値を加工して返す「フィルター」の2種類があります。この記事では add_action() と add_filter() の使い方、優先度と引数の数の指定、remove_action() が効かないときのタイミングの考え方、そして do_action() と apply_filters() で自分のプラグインにフックを用意する方法までを解説します。

アクションとフィルターの違い

両者は内部的にはほぼ同じ仕組みで動いていますが、役割がはっきり分かれています。

  • アクション: 「ここに来たら何かをする」ための差し込み口です。記事の保存時、<head> の出力時などに処理を実行します。戻り値は使われません。
  • フィルター: 「この値を渡すので、加工して返して」という差し込み口です。本文、タイトル、クエリの条件などを受け取り、変更した値を返します。

フィルターでもっとも多い失敗は、値を返し忘れることです。return がないと null が返り、本文やタイトルが空になります。条件に合わないときも、受け取った値をそのまま返すように書きます。逆にフィルターの中で echo すると、値の加工ではなく予期しない場所への出力になるため避けます。

最小のプラグインを用意する

wp-content/plugins/my-hooks-sample/my-hooks-sample.php を作り、次のヘッダーを書くと、管理画面のプラグイン一覧に表示されて有効化できるようになります。

<?php
/**
 * Plugin Name: My Hooks Sample
 * Description: フックの使い方を確認するためのサンプルプラグイン。
 * Version: 1.0.0
 * Requires at least: 6.0
 * Requires PHP: 7.4
 * Text Domain: my-hooks-sample
 */

defined( 'ABSPATH' ) || exit;

defined( 'ABSPATH' ) || exit; は、ファイルを URL で直接開かれたときに処理を止めるための定型句です。関数名には myhs_ のようなプラグイン固有の接頭辞を付け、他のプラグインやテーマとの名前の衝突を防ぎます。動作確認はステージングやローカル環境で行ってください(PHP や WordPress の最低バージョンは 2026年10月現在の一般的な値の例です)。

add_action() で処理を追加する

アクションの登録は add_action( フック名, コールバック, 優先度, 受け取る引数の数 ) の形です。次の例は、投稿を更新したユーザーの ID を投稿メタに記録します。

add_action( 'save_post', 'myhs_record_last_editor', 10, 3 );

function myhs_record_last_editor( $post_id, $post, $update ) {
	// 自動保存とリビジョンの保存では何もしない
	if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
		return;
	}
	// 新規作成時と「投稿」以外は対象外
	if ( ! $update || 'post' !== $post->post_type ) {
		return;
	}
	// 編集権限のないユーザーによる保存は記録しない
	if ( ! current_user_can( 'edit_post', $post_id ) ) {
		return;
	}
	update_post_meta( $post_id, '_myhs_last_editor', get_current_user_id() );
}

save_post は投稿 ID、投稿オブジェクト、更新かどうか、の3つの値を渡してくれますが、第4引数を 3 にしないとコールバックには1つ目の値しか届きません。この例ではフォームから値を受け取っていないので nonce は使っていませんが、独自のメタボックスの入力値を保存する場合は、wp_verify_nonce() による確認と sanitize_text_field() などによる無害化を加えます。

save_post の中で wp_update_post() を呼ぶと、その更新で再び save_post が発火して無限ループになります。投稿本体を更新したいときは、処理の前に remove_action() で自分自身を外し、更新後に add_action() で戻します。update_post_meta() は save_post を発火させないので、この例では問題ありません。

add_filter() で値を加工する

フィルターの登録方法もアクションと同じ形です。次の例は、投稿の個別ページで本文の末尾に注意書きを加えます。

add_filter( 'the_content', 'myhs_append_notice' );

function myhs_append_notice( $content ) {
	if ( ! is_singular( 'post' ) || ! in_the_loop() || ! is_main_query() ) {
		return $content;
	}
	$notice = sprintf(
		'<p class="myhs-notice">%s</p>',
		esc_html__( 'この記事の情報は公開時点のものです。', 'my-hooks-sample' )
	);
	return $content . $notice;
}

the_content はメインの本文以外に、ウィジェットや抜粋の生成、他のプラグインの処理からも呼ばれることがあります。in_the_loop() と is_main_query() で条件を絞っているのは、サイドバーや関連記事の部分に注意書きが重複して出るのを防ぐためです。追加する文字列は esc_html__() で翻訳とエスケープを同時に行っています。

優先度と受け取る引数の数

第3引数の優先度は、同じフックに登録された処理の実行順を決めます。初期値は 10 で、数値が小さいほど先に実行されます。同じ優先度の場合は登録した順です。

  • 他のプラグインの処理より後で値を上書きしたい: 20 や 99 など大きい値
  • 他の処理より前に準備をしたい: 1 や 5 など小さい値

フィルターは前の処理が返した値を次の処理が受け取る「バケツリレー」なので、優先度によって最終結果が変わります。「自分のフィルターが効かない」と感じたら、後から別の処理が値を上書きしていないかを疑います。第4引数の受け取る引数の数は、フック側が渡す値のうち何個をコールバックで使うかを指定します。初期値は 1 です。

remove_action() が効かないときはタイミングを見る

他のプラグインやコアが登録した処理を外すには remove_action()・remove_filter() を使います。外すための条件は2つあります。

  1. 登録時と同じコールバックと優先度を指定する: 優先度 5 で登録されたものは、優先度 5 を指定しないと外れません。
  2. 登録された後、フックが実行される前に呼ぶ: まだ登録されていない処理は外せず、すでに実行されたフックから外しても意味がありません。

コアが登録している処理は、プラグインの読み込みより前に登録済みなので、プラグインのファイル内でそのまま外せます。

// <head> から WordPress のバージョン表記(generator)を出力しない
remove_action( 'wp_head', 'wp_generator' );

他のプラグインがクラスのメソッドを登録している場合は、そのプラグインのインスタンスを指定する必要があります。読み込み順に左右されないよう、すべてのプラグインが読み込まれた後の plugins_loaded で外します。

add_action( 'plugins_loaded', function () {
	// 例: 他プラグインが $GLOBALS['other_plugin'] にインスタンスを保持し、
	// add_action( 'wp_head', array( $this, 'print_meta' ), 5 ) で登録している場合
	if ( isset( $GLOBALS['other_plugin'] ) ) {
		remove_action( 'wp_head', array( $GLOBALS['other_plugin'], 'print_meta' ), 5 );
	}
} );

相手が init の中で登録しているなら、init の後(優先度を大きくした init や wp_loaded)で外します。無名関数で登録された処理は、同じ関数を指定する手段がないため基本的に外せません。自分のプラグインで無名関数を多用すると、他の開発者が挙動を変えにくくなる点も覚えておくとよいでしょう。

独自のフックを用意する

自分のプラグインにもフックを用意しておくと、利用者がプラグイン本体を書き換えずに挙動を調整できます。値を変えさせたい場所には apply_filters()、タイミングを知らせたい場所には do_action() を置きます。

function myhs_format_price( $price, $product_id ) {
	$label = number_format_i18n( $price ) . '円';
	// 表示用の価格ラベルを外部から変更できるようにする
	return apply_filters( 'myhs_price_label', $label, $price, $product_id );
}

function myhs_finish_import( $imported_count ) {
	// 取り込み完了を外部に知らせる
	do_action( 'myhs_after_import', $imported_count );
}

利用する側は、通常のフックと同じように登録します。

add_filter( 'myhs_price_label', function ( $label, $price ) {
	return $label . '(税込)';
}, 10, 2 );

フック名にはプラグインの接頭辞を付け、渡す引数の順番は公開後に変えないようにします。順番を変えると、利用者のコードが受け取る値がずれて動かなくなるからです。廃止したいフックには apply_filters_deprecated() や do_action_deprecated() を使うと、利用者に移行を促せます。

代表的なフックの実行順と確認方法

フロント側の表示では、おおむね plugins_loaded → after_setup_theme → init → wp_loaded → template_redirect → wp_head → wp_footer の順に実行されます。スクリプトやスタイルを読み込む wp_enqueue_scripts は wp_head の中で実行されます。テーマの functions.php はプラグインより後、after_setup_theme の直前に読み込まれるため、テーマで追加された処理をプラグインから外すなら after_setup_theme 以降で行います。

登録状況の確認には、has_action()・has_filter()(登録されていれば優先度を返す)、did_action()(そのアクションが何回実行されたか)、current_filter()(今どのフックの中にいるか)が使えます。開発中は Query Monitor などのデバッグ用プラグインを使うと、ページごとに実行されたフックと登録された処理の一覧を画面で確認できます。

まとめ

  • アクションは「決まったタイミングで処理を実行」、フィルターは「値を受け取って加工し、返す」ための仕組みです。フィルターでは値を返し忘れないようにします。
  • 優先度は小さいほど先に実行され、初期値は 10 です。複数の値を受け取るには第4引数で数を指定します。
  • remove_action() は同じコールバックと優先度で、登録後かつ実行前に呼びます。迷ったら plugins_loaded や init の後で外します。
  • 自作プラグインには接頭辞付きの apply_filters()・do_action() を用意し、引数の順番は公開後に変えないようにします。
  • 保存処理では権限チェック、nonce の確認、入力値の無害化を組み合わせます。

お気軽にご相談ください

お見積りへ お問い合わせへ
▲