Plugin dependencies with Requires Plugins

Declare which plugins yours needs, check for them at runtime, and fail politely when one is missing.

  • Intermediate
  • 8 min read
  • Updated 17 Oct 2026

What you will learn

  • Declare plugin dependencies
  • Handle a missing dependency
  • Check a dependency's version and features before using them

Before you start

Plugins often build on other plugins: an add-on for a shop, a connector for a forms plugin, or a premium extension for a free core plugin. If the dependency is missing and your code calls its functions, the site fails with a fatal error. This lesson shows how to declare dependencies so WordPress enforces them, and how to check for them in code so your plugin degrades gracefully instead.

The running example: an add-on

To keep the dependency inside the course project, imagine a second plugin, myplugin-reminders, that emails attendees a day before each event. It reads bookings from the events plugin, so without myplugin it has nothing to do.

Plain text
wp-content/plugins/
├── myplugin/
│   └── myplugin.php
└── myplugin-reminders/
    └── myplugin-reminders.php

Declaring the dependency

Add a Requires Plugins line to the add-on's header. Its value is a comma-separated list of plugin slugs: the folder name the plugin uses on WordPress.org.

PHP
<?php
/**
 * Plugin Name:      My Plugin Reminders
 * Description:      Emails attendees before their events.
 * Version:          1.0.0
 * Requires at least: 6.5
 * Requires PHP:     8.1
 * Requires Plugins: myplugin
 * Text Domain:      myplugin-reminders
 */

With this header, current versions of WordPress:

  • refuse to activate the add-on until every listed plugin is installed and active, and link to install them;
  • refuse to deactivate myplugin while an active plugin depends on it;
  • show the relationship on the Plugins screen, under both plugins.

The header has limits worth knowing:

It doesIt does not
Check that the dependency is installed and activeCheck the dependency's version
Use slugs, so woocommerce, not "WooCommerce"Accept file paths or display names
Offer to install dependencies hosted on WordPress.orgInstall plugins from anywhere else
Apply in the admin Plugins screenStop a dependency disappearing another way, such as its folder being deleted over FTP or a failed update

The last row matters most. A deployment script, WP-CLI or a host can remove or deactivate a plugin without passing through the checks. The header prevents common mistakes; it does not make the dependency guaranteed. Your code must still check.

Plugins hosted on WordPress.org may only declare dependencies that are also hosted there. A premium add-on distributed privately can list a free WordPress.org plugin, and the user gets an install link for it.

Checking at runtime

Plugins load in an order you do not control. The add-on may load before myplugin, so checking defined( 'MYPLUGIN_VERSION' ) at the top of the file can give the wrong answer. Wait until every plugin has loaded, then check:

PHP
<?php
// myplugin-reminders/myplugin-reminders.php (after the header)

defined( 'ABSPATH' ) || exit;

define( 'MYPLUGIN_REMINDERS_PATH', plugin_dir_path( __FILE__ ) );
const MYPLUGIN_REMINDERS_MIN_CORE = '1.4.0';

add_action( 'plugins_loaded', 'myplugin_reminders_boot', 20 );

function myplugin_reminders_boot(): void {
    $problem = myplugin_reminders_dependency_problem();

    if ( null !== $problem ) {
        add_action(
            'admin_notices',
            static function () use ( $problem ) {
                if ( ! current_user_can( 'activate_plugins' ) ) {
                    return;
                }

                printf(
                    '<div class="notice notice-error"><p>%s</p></div>',
                    esc_html( $problem )
                );
            }
        );

        return;
    }

    require_once MYPLUGIN_REMINDERS_PATH . 'includes/reminders.php';
    myplugin_reminders_init();
}

/**
 * Returns a message describing what is missing, or null if all is well.
 */
function myplugin_reminders_dependency_problem(): ?string {
    if ( ! defined( 'MYPLUGIN_VERSION' ) ) {
        return __( 'My Plugin Reminders needs My Plugin to be installed and active. Reminders are paused.', 'myplugin-reminders' );
    }

    if ( version_compare( MYPLUGIN_VERSION, MYPLUGIN_REMINDERS_MIN_CORE, '<' ) ) {
        return sprintf(
            /* translators: 1: required version, 2: installed version. */
            __( 'My Plugin Reminders needs My Plugin %1$s or newer. You have %2$s. Reminders are paused.', 'myplugin-reminders' ),
            MYPLUGIN_REMINDERS_MIN_CORE,
            MYPLUGIN_VERSION
        );
    }

    return null;
}

The pattern has three parts:

  1. Boot late. Hooking plugins_loaded at priority 20 runs after every active plugin's main file has been included and after most plugins' own plugins_loaded callbacks at the default priority 10.
  2. Load nothing that depends on the other plugin until the check passes. Code that calls myplugin_ functions lives in includes/reminders.php, which is only included once the dependency is confirmed. A file that is never included cannot cause a fatal error.
  3. Tell the right person. The notice appears only to users who can fix it, says what is missing and says what the plugin is doing about it.

Checking features, not just presence

A version number is a proxy. When you need one function or class, check for it directly as well:

PHP
<?php
// myplugin-reminders/includes/reminders.php

defined( 'ABSPATH' ) || exit;

function myplugin_reminders_init(): void {
    add_action( 'myplugin_reminders_send', 'myplugin_reminders_send_for_tomorrow' );
}

function myplugin_reminders_send_for_tomorrow(): void {
    if ( ! class_exists( '\MyPlugin\Bookings\Repository' ) ) {
        return;
    }

    // Safe to use the repository here.
}

Prefer public, documented extension points of the other plugin: its hooks, functions it documents as public and its REST routes. Calling its internal classes ties your release cycle to every refactor it makes.

Use the same approach for optional integrations. If the events plugin can show events in a calendar plugin's widget when that plugin happens to be active, that is not a dependency. Do not list it in Requires Plugins; detect it at runtime and add the integration only when it is present:

PHP
<?php
// myplugin/includes/integrations.php

defined( 'ABSPATH' ) || exit;

add_action( 'plugins_loaded', 'myplugin_load_integrations', 20 );

function myplugin_load_integrations(): void {
    // Hypothetical calendar plugin; hook into its documented filter if it exists.
    if ( has_filter( 'example_calendar_sources' ) || function_exists( 'example_calendar_register_source' ) ) {
        require_once MYPLUGIN_PATH . 'includes/integrations/calendar.php';
    }
}

What graceful degradation looks like

When a dependency is missing, decide for each feature what happens:

  • Pause and explain. Background jobs, such as reminders, stop running and a notice says why. They resume when the dependency returns.
  • Hide the feature. A settings tab or block that only works with the dependency is not registered.
  • Never lose data. Do not delete options or tables because a dependency is missing. The user may be halfway through an update.
  • Never block the whole site. Do not call wp_die() or deactivate yourself on the front end. One plugin's missing dependency should not take down unrelated pages.

Some plugins deactivate themselves when a dependency is missing. With Requires Plugins in place there is little reason to: WordPress already prevents most invalid states, and self-deactivation surprises users after an update.

Activation-time checks

Checking on activation gives the clearest feedback, because the user is looking at the Plugins screen. The header already handles presence, but it cannot check versions, so add a check for those:

PHP
<?php
// myplugin-reminders/myplugin-reminders.php

register_activation_hook( __FILE__, 'myplugin_reminders_activate' );

function myplugin_reminders_activate(): void {
    if ( defined( 'MYPLUGIN_VERSION' ) && version_compare( MYPLUGIN_VERSION, MYPLUGIN_REMINDERS_MIN_CORE, '<' ) ) {
        wp_die(
            esc_html(
                sprintf(
                    /* translators: %s: required version. */
                    __( 'Update My Plugin to version %s or newer before activating My Plugin Reminders.', 'myplugin-reminders' ),
                    MYPLUGIN_REMINDERS_MIN_CORE
                )
            ),
            esc_html__( 'Plugin dependency check', 'myplugin-reminders' ),
            array( 'back_link' => true )
        );
    }
}

During activation, other active plugins have already been loaded, so MYPLUGIN_VERSION is reliable here. wp_die() inside an activation hook stops activation and shows the message. Keep the runtime check as well: the core plugin can still be downgraded later.

The other header requirements

Two related headers protect you from environments your code cannot run in:

  • Requires at least is the minimum WordPress version.
  • Requires PHP is the minimum PHP version.

WordPress will not activate a plugin whose requirements are not met, and it warns before installing an update the site cannot run. Set them honestly and test against the minimums you declare.

Summary

  • Requires Plugins takes a comma-separated list of slugs and stops activation, and deactivation of dependencies, from the Plugins screen.
  • It does not check versions or guarantee the dependency stays present, so check at runtime on plugins_loaded at a later priority.
  • Include code that calls the dependency only after the check passes, and check for specific functions or classes you rely on.
  • Optional integrations are detected at runtime and are not listed as dependencies.
  • Degrade gracefully: pause and explain, hide dependent features, never delete data and never take down the site.

Further reading