Add custom admin notices in WordPress with PHP

WordPress admin notices are the coloured banners that appear at the top of the dashboard pages — you see them after activating a plugin, when a required setting is missing, or when an update is available. Adding your own admin notices is a simple and highly effective way to guide administrators through setup steps, warn about incompatible configurations, or confirm that a bulk action completed successfully. WordPress provides four CSS classes for notices: notice-success (green), notice-warning (yellow), notice-error (red), and notice-info (blue). Adding the is-dismissible class makes the notice closeable with an X button. The notice HTML is output inside the admin_notices action hook. For notices that should disappear once the user acknowledges them, you store a user meta flag and check it before displaying the notice — this requires a small AJAX handler to save the dismissal state when the user clicks the X, which is slightly more complex but produces a much better user experience. You can also add notices that appear only on specific admin pages by checking get_current_screen()->id. Notice content must always be escaped before output — use esc_html() for text and wp_kses_post() for HTML content. This is a useful technique to combine with the Theme Customizer to prompt users to complete required settings after plugin activation.

Problem: You want to display a custom admin notice after plugin activation, on specific admin pages, or as a persistent reminder until the user completes a required action.

Solution: Add the following code to your functions.php or plugin file:

/**
 * Simple admin notice — shown to admins on all dashboard pages.
 */
add_action( 'admin_notices', 'helloadmin_simple_notice' );
function helloadmin_simple_notice() {
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }
    echo '<div class="notice notice-info is-dismissible">
        <p>' . esc_html__( 'Reminder: back up your database before running bulk updates.' ) . '</p>
    </div>';
}

/**
 * Persistent dismissible notice — stored in user meta so it disappears once dismissed.
 */
add_action( 'admin_notices', 'helloadmin_persistent_notice' );
function helloadmin_persistent_notice() {
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }
    if ( get_user_meta( get_current_user_id(), 'helloadmin_notice_dismissed', true ) ) {
        return;
    }
    echo '<div class="notice notice-warning helloadmin-dismissible-notice" data-nonce="' . esc_attr( wp_create_nonce( 'helloadmin_dismiss_notice' ) ) . '">
        <p><strong>Action required:</strong> Please <a href="' . esc_url( admin_url( 'options-general.php' ) ) . '">configure your API key</a> to enable all features.</p>
        <button type="button" class="notice-dismiss"></button>
    </div>';
}

// Handle AJAX dismissal
add_action( 'wp_ajax_helloadmin_dismiss_notice', 'helloadmin_handle_dismiss_notice' );
function helloadmin_handle_dismiss_notice() {
    check_ajax_referer( 'helloadmin_dismiss_notice', 'nonce' );
    update_user_meta( get_current_user_id(), 'helloadmin_notice_dismissed', true );
    wp_die();
}

// Enqueue the dismiss handler JavaScript
add_action( 'admin_footer', 'helloadmin_notice_dismiss_js' );
function helloadmin_notice_dismiss_js() {
    echo "<script>
    jQuery(document).on('click', '.helloadmin-dismissible-notice .notice-dismiss', function() {
        var nonce = jQuery(this).closest('.helloadmin-dismissible-notice').data('nonce');
        jQuery.post(ajaxurl, { action: 'helloadmin_dismiss_notice', nonce: nonce });
    });
    </script>";
}

NOTE: Do not display admin notices to roles that cannot act on them — an editor who sees a notice about API keys they cannot configure will find it annoying and confusing. Always gate notices with current_user_can(). Also, the default WordPress dismissal (is-dismissible class) only hides the notice for the current page load — it reappears on the next page refresh. For truly persistent dismissal you need the AJAX approach shown above.