WordPress で「ちょっとした処理」を追加したいとき、書ける場所が3つあります。

テーマの functions.php
通常のプラグイン
mu-plugins

どれに書いても動くことが多いので、なんとなく functions.php に書き続けているという方は少なくないはずです。筆者もそうでした。

この記事では、この3つをどう使い分けるかを整理します。結論から言うと、判断の軸は 「寿命」 と 「読み込み順」 の2つです。

入門編で解説
mu-plugins を初めて聞いた方へ

管理画面に表示されない特殊なフォルダです。基本と確認方法は入門編で解説しています。

管理画面に出てこないプラグインがある?

先に結論:判断フロー

迷ったら、上から順に答えていけば決まります。

1
プラグインの読み込みに割り込む? 早い段階で定数を定義したい?

YES ならmu-plugins 一択(他では技術的に不可能)

2
テーマを変えても残るべき処理か?

NO なら functions.php(見た目・テーマ構造に関わるもの)

3
止められると事故になる?

YES なら mu-plugins / NO なら 通常プラグイン

以下、それぞれの根拠を説明します。

mu-plugins にしかできないこと

先に、いちばん誤解されやすい点をはっきりさせておきます。

「mu-plugins でなければできないこと」は、実はほとんどありません。

「管理画面から止められない」という特徴は有名ですが、それは運用上の都合であって、技術的な制約ではありません。多くの処理は通常のプラグインでも書けます。

ただし、原理的に mu-plugins にしかできないことが1つだけあります。読み込み順です。

WordPress の起動順を見てみる

WordPress のコアファイル wp-settings.php を読むと、順番がはっきり書かれています。

1 mu-plugins を読み込む 498行
2 muplugins_loaded フック 540行
3 通常のプラグインを読み込む 574行
4 plugins_loaded フック 622行
5 テーマの functions.php を読み込む 739行
6 after_setup_theme フック 749行
7 init フック 771行

mu-plugins は、通常のプラグインが1つも読み込まれていない時点で動きます。

この順番はプラグインの優先度設定では変えられません。「通常プラグインの中でいちばん先に動かす」ことはできても、「通常プラグインが読み込まれる前に動く」ことはできないからです。

具体例:プラグインの読み込み自体を止める

「このプラグインを、管理画面以外では読み込ませたくない」——表示速度のために、こうしたいことがあります。

<?php
/**
 * Plugin Name: 条件付きプラグイン読み込み
 */

add_filter('option_active_plugins', function ($plugins) {
    // 管理画面以外では重いプラグインを読み込まない
    if (!is_admin()) {
        $target = 'heavy-plugin/heavy-plugin.php';
        $key = array_search($target, $plugins, true);
        if ($key !== false) {
            unset($plugins[$key]);
        }
    }
    return $plugins;
});

この処理は 通常のプラグインにも functions.php にも書けません。「どのプラグインを読み込むか」を決める処理そのものに割り込む必要があるため、その判断が終わったあとに動くコードでは手遅れなのです。

同じ理由で、次のようなケースも mu-plugins の出番です。

  • 早い段階で定数を定義したいWP_CACHE など、後から定義しても効かないもの)
  • 他のプラグインより先にフックを登録しておきたい
  • 他のプラグインの動作そのものを制御したい

配布プラグインを mu-plugins に入れられるか

ここで多くの方が一度考えることに触れておきます。

「公開されているプラグインを mu-plugins に入れて、止められないようにできないか?」

結論から言うと、技術的には可能ですが、推奨できません。

まず構造の問題

配布プラグインは plugins/akismet/akismet.php のようにフォルダに入っています。mu-plugins が自動実行するのは直下の PHP だけなので、フォルダごと置いても読み込まれません。

これはローダーを1枚置けば回避できます。

<?php
// mu-plugins/load-example.php
require WPMU_PLUGIN_DIR . '/example-plugin/example-plugin.php';

……が、本当の問題はここからです。

動かない・危険な3つの理由

理由 1
有効化フックが実行されない

公式ドキュメントに明記されています。

Activation hooks are not executed in plugins added to the must-use plugins folder.

出典: https://developer.wordpress.org/advanced-administration/plugins/mu-plugins/

多くのプラグインは register_activation_hook() で初期設定をします——データベースのテーブル作成、初期オプションの登録など。これが走らないため、テーブルが無いまま動いてエラーになります。

独自テーブルを持つプラグイン(Contact Form 7、WooCommerce など)は、まず動きません。

理由 2
更新されないまま放置される

更新通知が出ず、自動更新もされません。セキュリティ修正が公開されても気づけないということです。

WordPress の脆弱性は91%がプラグイン起因で、公開から平均5時間で攻撃が始まります。わざわざ更新の届かない場所に置くのは、リスクを自分から抱え込む行為になります。

理由 3
読み込み順が変わって壊れる

mu-plugins は通常プラグインより先に読まれます。他のプラグインの関数を前提にしているプラグインだと、まだ存在しない関数を呼んで致命的エラーになります。

出典: https://developer.wordpress.org/advanced-administration/plugins/mu-plugins/

例外的に成立するケース

ひとつだけ、本来の設計意図に沿う用途があります。マルチサイトで全サイトに強制適用したい場合です。

マルチサイトでは各サイトの管理者がプラグインを停止できますが、mu-plugins に置けば停止という選択肢自体がなくなりますmu が本来 “multi-user”(現在の multisite)の略だったことからも、ここが出発点だと分かります。

「止められたくない」だけなら、こう書く

単一サイトで「顧客に停止されたくない」という目的なら、プラグインは通常の場所に置いたまま、停止リンクだけを消すほうが安全です。

<?php
/**
 * Plugin Name: プラグイン停止の防止
 */

add_filter('plugin_action_links', function ($actions, $plugin_file) {
    $protected = ['contact-form-7/wp-contact-form-7.php'];
    if (in_array($plugin_file, $protected, true)) {
        unset($actions['deactivate']);
    }
    return $actions;
}, 10, 2);

これなら更新は普通に届き、有効化フックも正常に動きます。目的だけを達成できます。

そしてこれは、mu-plugins の正しい使い方の好例でもあります。自作の小さなコードを置いて、他のプラグインの挙動を制御する——先ほどの「通常プラグインより先に動く」性質が活きる場面です。

つまり mu-plugins は「自作コード置き場」

ここまで見ると、性格がはっきりします。

mu-plugins のデメリット(更新されない・有効化フックが動かない)は、そのほとんどが配布プラグイン固有の問題です。自作の小さなコードなら、

  • 更新 → 自分で管理しているので通知は不要
  • 有効化フック → テーブル作成などしていないので使っていない

どちらも無害になります。

だから mu-plugins は「配布プラグインを入れる場所」ではなく、「自作の制御コードを置く場所」として使うのが本筋です。次に挙げる例がすべて自作の小さなコードなのは、偶然ではありません。

「できるが、mu-plugins のほうが確実」なこと

実務では、こちらが大半を占めます。技術的には他でも書けますが、止められると困るから mu-plugins に置く、という判断です。

例 1
テスト環境でだけメール送信を止める

テスト環境から顧客に誤ってメールが飛ぶ事故を防ぐ用途です。

<?php
/**
 * Plugin Name: 環境別メール制御
 */

// ステージング環境ではメールを送信しない
add_filter('pre_wp_mail', function ($return) {
    if (defined('WP_ENVIRONMENT_TYPE') && WP_ENVIRONMENT_TYPE === 'staging') {
        return false; // 送信をキャンセル
    }
    return $return;
});

これは誰かがうっかり停止した瞬間に事故が起きるタイプの処理です。管理画面から止められない場所に置く意味があります。

例 2
ログイン失敗の記録

セキュリティプラグインを入れるほどではないが、記録は残したいというとき。

<?php
/**
 * Plugin Name: ログイン失敗の記録
 */

add_action('wp_login_failed', function ($username) {
    $log = sprintf(
        "[%s] ログイン失敗: %s / IP: %s\n",
        current_time('Y-m-d H:i:s'),
        $username,
        $_SERVER['REMOTE_ADDR'] ?? 'unknown'
    );
    error_log($log, 3, WP_CONTENT_DIR . '/login-failed.log');
});

記録する仕組み自体を止められては意味がないので、mu-plugins に置く合理性があります。

例 3
管理者以外に管理バーを見せない
<?php
/**
 * Plugin Name: 管理バー制御
 */

add_action('after_setup_theme', function () {
    if (!current_user_can('administrator')) {
        show_admin_bar(false);
    }
});

——ただし、この例3を見て「これ、functions.php でもよくないですか?」と思われた方。そのとおりです。

ここから本題に入ります。

判断の軸は「寿命」

3つの置き場所は、処理が生き続ける条件が違います。

置き場所 生きている条件 テーマを変えたら
テーマの functions.php そのテーマが有効な間だけ 消える
通常のプラグイン 有効化されている間 残る
mu-plugins ファイルがある限り 残る

functions.php はテーマの一部です。テーマを切り替えた瞬間、そこに書いた処理はすべて止まります。ここから、判断の軸が導けます。

その処理は、テーマを変えても残るべきか?

  • 残るべき → プラグイン領域(通常 or mu-plugins)
  • テーマと運命を共にしてよい → functions.php

先ほどの例に当てはめてみる

ログイン失敗の記録(例2) — テーマを変えたら記録が止まってよいでしょうか。よくないですよね。セキュリティの記録がデザイン変更で消えるのは筋が通りません。→ プラグイン領域

環境別のメール停止(例1) — テーマ変更で事故防止の仕組みが外れたら危険です。→ mu-plugins

管理バーの制御(例3) — これは微妙です。「管理バーの見た目」はテーマ寄りとも言えますが、実際は権限管理の話。どちらもあり得ますが、権限に関わるならプラグイン領域が素直です。

プラグインの読み込み制御functions.php ではそもそも動きません。読み込み順が理由です。

functions.php はいちばん最後に読まれる

先ほどの起動順を思い出してください。

1. mu-plugins          (498行)
2. 通常のプラグイン     (574行)
3. functions.php       (739行)  ← いちばん最後
4. init                (771行)

functions.php は最後です。プラグインより後に読まれるので、「プラグインの読み込みに割り込む」ような処理は書けません。間に合わないのです。

逆に言えば、functions.php はすべてが揃った状態で動きます。プラグインが提供する関数を安心して使えるので、テーマの表示に関わる処理を書くには最も自然な場所です。

functions.php が向いているもの

そのテーマの見た目・構造に紐づくものです。

  • アイキャッチ画像のサイズ指定(add_image_size
  • メニューやウィジェットの登録
  • テーマ専用のショートコード
  • CSS / JavaScript の読み込み

これらは「テーマが変われば意味を失う」処理なので、テーマと一緒に消えるのが正しい挙動です。プラグインに書くと、テーマを変えたときに不要な処理が残り続けてしまいます。

早見表

こういう処理 置き場所
通常プラグインの読み込みに割り込む mu-plugins 一択
早い段階で定数を定義したい mu-plugins 一択
止められると事故になる(メール停止など) mu-plugins
記録・監査など、常に動くべきもの mu-plugins
機能のオン・オフを切り替えたい 通常プラグイン
他人に配布する・複数サイトで使い回す 通常プラグイン
アイキャッチサイズ、メニュー登録 functions.php
テーマ専用のショートコード、CSS 読み込み functions.php

それぞれの「代償」

どれを選んでも、引き換えに失うものがあります。ここを知らずに選ぶと後で困ります。

mu-plugins の代償
  • 更新通知が出ない — 公式ドキュメントに “Plugins in the must-use directory will not appear in the update notifications” とあります。手動で管理する前提です
  • 有効化フックが動かない“Activation hooks are not executed in plugins added to the must-use plugins folder” と明記されています。初期設定の処理が走らないため、そういう処理を含むプラグインをそのまま置くと正しく動きません
  • 不具合が起きても止められない — 「止められない」は利点であり、同時に欠点でもあります。切り分けが難しくなります
  • 引き継ぎで見落とされる — 次の担当者が気づかない可能性があります

出典: https://developer.wordpress.org/advanced-administration/plugins/mu-plugins/

functions.php の代償
  • テーマを変えると消える — リニューアル時に処理ごと失われます
  • 肥大化しやすい — 何でも書けてしまうため、数千行に膨らんでいる現場は珍しくありません
  • 子テーマとの関係が複雑 — 親と子の両方が読み込まれるため、どちらに書いたかで挙動が変わります
通常プラグインの代償
  • 止められる — 悪意がなくても、整理のつもりで停止されることがあります

まとめ

  • mu-plugins にしかできないのは「通常プラグインより先に動く」ことだけ。それ以外は他でも書ける
  • functions.php はテーマの一部。テーマを変えると消える。だから「テーマに紐づくもの」を書く
  • functions.php はいちばん最後に読まれる。プラグインの読み込みには割り込めないが、すべて揃った状態で動く
  • 使い分けは 「テーマを変えても残るべきか」→「止められると困るか」 の順で判断する
  • 通常プラグインで足りるならそれが基本。mu-plugins は明確な理由があるときだけ

「なんとなく functions.php」から抜け出すと、後任者にも説明できる構成になります。なぜそこに書いたかを説明できることが、いちばん実用的な基準かもしれません。

入門編で解説
mu-plugins を初めて聞いた方へ

管理画面に表示されない特殊なフォルダです。基本と確認方法は入門編で解説しています。

管理画面に出てこないプラグインがある?