How to add a custom admin notice that is dismissible and persists per-user?

So, you want to add a custom admin notice in WordPress that not only pops up but also lets users get rid of it and remembers their choice? That’s a smart move. It helps keep your WordPress dashboard clean and focuses on what users actually need to see. Think of it as adding a helpful sticky note that only the person who needs it sees, and they can just flick it away when they’re done.

Adding a dismissible, user-specific admin notice in WordPress involves a bit of code, but it’s totally doable. We’ll be diving into how to store the dismissal status (so it remembers who dismissed what), how to display the notice, and critically, how to make that ‘dismiss’ button actually work, keeping it gone for that specific user. We’ll break it down step-by-step, explaining what’s happening along the way.

First off, why would you even bother with this? Custom admin notices are fantastic for communicating important information without cluttering up the main content area.

Common Use Cases for Admin Notices

  • Plugin Updates or Alerts: Letting users know about recommended updates or crucial security patches.
  • Onboarding Tips: Guiding new users through key features or settings after a fresh install.
  • Configuration Reminders: Prompting users to complete a specific setup step.
  • System Status Messages: Informing admins about potential issues or maintenance periods.
  • Feature Introductions: Announcing new functionalities and how to access them.

The “Dismissible” and “Per-User” Magic

The “dismissible” part is crucial for user experience. Nobody likes persistent pop-ups. Allowing users to dismiss a notice means they can clear their dashboard when they’re done with the information.

The “per-user” aspect takes it a step further. Imagine you have multiple administrators on a site. If one admin dismisses a notice, it shouldn’t disappear for everyone else. Each user should have their own control over what they see. This is where we’ll be focusing our technical efforts.

If you’re looking to enhance your WordPress admin experience by adding custom admin notices that are both dismissible and persistent for each user, you might find it helpful to check out a related article that dives deeper into this topic. This resource provides step-by-step instructions and code snippets to help you implement this feature effectively. For more information, visit this article.

The Code Foundation: Hooks and Actions

WordPress is built on action and filter hooks. These are the essential tools we’ll use to inject our custom code into the WordPress environment.

Understanding WordPress Hooks

Essentially, hooks are points in the WordPress core, themes, or plugins where developers can “hook into” to add or modify functionality without altering the core files. This is super important for maintainability and preventing your changes from being lost during updates.

  • Action Hooks: These hooks allow you to execute a function at a specific point in the WordPress execution flow. For example, admin_notices is an action hook that fires in the backend, making it the perfect place to display our custom notices.
  • Filter Hooks: While not our primary focus here, filters allow you to modify data before it’s used or displayed.

The admin_notices Hook

This is our main stage for displaying our notice. Whenever WordPress renders notices in the admin area, this hook is triggered. We’ll attach a function to this hook that checks if our notice should be displayed for the current user.

Where to Put Your Code

  • Your Theme’s functions.php File: This is the simplest place for small, site-specific customizations. However, if you switch themes, your notices will disappear.
  • A Custom Plugin: This is the recommended approach for anything beyond very minor tweaks. It keeps your functionality separate from your theme, ensuring it persists even if you change your theme. For this guide, we’ll assume you’re comfortable creating a simple plugin or adding to your functions.php if you’re just experimenting.

Displaying Your Custom Notice

Now for the fun part: actually making something appear!

The Core Function for Display

We’ll create a PHP function that will be hooked into admin_notices. This function will contain the HTML for our notice and the logic for checking if it should be shown.

“`php

// In your plugin file or theme’s functions.php

function my_custom_admin_notice() {

// Check if the current user has dismissed this notice

if ( ! current_user_can( ‘manage_options’ ) ) {

// Optional: Only show to specific roles, e.g., administrators

return;

}

// Get the current user’s ID

$user_id = get_current_user_id();

$notice_id = ‘my_important_notice’; // A unique identifier for this notice

// Check if the user has dismissed this notice

$dismissed = get_user_meta( $user_id, ‘dismissed_admin_notices’, true );

if ( ! empty( $dismissed ) && is_array( $dismissed ) && in_array( $notice_id, $dismissed ) ) {

// User has dismissed this notice, so do nothing

return;

}

// If not dismissed, display the notice

?>

This is my important custom admin notice!

It contains crucial information you need to know about your site.

):

  • notice: This is a core WordPress CSS class that styles our message as a standard admin notice.
  • notice-info: This class gives the notice a specific color and icon (usually blue for informational messages). Other options include notice-success, notice-warning, and notice-error.
  • is-dismissible: This is a critical class. WordPress’s core JavaScript automatically adds a close button (an ‘x’) to any div with the is-dismissible class. We’ll leverage this.
  • The Dismiss Button: Notice there’s no explicit button HTML here for dismissing. The is-dismissible class is what tells WordPress to add it.
  • Making it Dismissible: WordPress’s Built-in Handler

    WordPress is actually pretty helpful here. By adding the is-dismissible class to your notice’s div, you’re telling WordPress’s JavaScript to automatically include a dismiss button.

    The Role of is-dismissible

    When WordPress loads the admin area, it enqueues JavaScript that looks for elements with the is-dismissible class. When it finds one, it typically adds a small ‘x’ button to the right of the notice.

    When this ‘x’ button is clicked, the core JavaScript handles the visual dismissal of the notice by hiding the div. However, this is only visual. The notice is still there in your HTML, just hidden. For true persistence, we need to tell WordPress to remember that the user dismissed it.

    The Problem with Visual Dismissal Alone

    Without additional code, clicking the ‘x’ only hides the notice for the current page load. If you refresh the page, the notice will reappear because the server-side code (my_custom_admin_notice function) doesn’t know it was dismissed.

    This is where we need to capture the click event and tell WordPress to store this dismissal status.

    If you’re looking to enhance your WordPress admin experience, you might find it useful to explore a related article on creating custom admin notices that are not only dismissible but also persist for individual users. This can greatly improve user interaction and streamline your admin dashboard. For more insights on this topic, check out this informative piece on admin notices that can help you implement these features effectively.

    Implementing Per-User Dismissal: The JavaScript Solution

    To make the dismissal permanent for a specific user, we need to intercept the click on the dismiss button, record that the user has dismissed this notice, and then then visually hide it. This requires a bit of JavaScript.

    Enqueuing Your Custom JavaScript

    You need to load your JavaScript file only in the WordPress admin area. You’ll do this using the admin_enqueue_scripts action hook.

    Add this to your functions.php file or your plugin file:

    “`php

    // In your plugin file or theme’s functions.php

    function my_custom_admin_notice_script() {

    // Only load this script on admin pages and for users who can manage options

    if ( ! is_admin() || ! current_user_can( ‘manage_options’ ) ) {

    return;

    }

    // Register and enqueue your JavaScript file

    wp_enqueue_script(

    ‘my-custom-admin-notice-script’, // Unique handle for your script

    get_template_directory_uri() . ‘/js/admin-notices.js’, // Path to your JS file (adjust if in plugin)

    array( ‘jquery’ ), // Dependencies (jQuery is common)

    ‘1.0’, // Version number

    true // Load in footer

    );

    // Pass data to your JavaScript if needed (optional but good practice)

    // For example, you might want to pass the nonce for security if doing AJAX

    // wp_localize_script( ‘my-custom-admin-notice-script’, ‘myAdminNoticeParams’, array(

    // ‘ajax_url’ => admin_url( ‘admin-ajax.php’ ),

    // ‘nonce’ => wp_create_nonce( ‘dismiss_notice_nonce’ ),

    // ) );

    }

    add_action( ‘admin_enqueue_scripts’, ‘my_custom_admin_notice_script’ );

    ?>

    “`

    Explanation for the JavaScript Enqueue:

    • is_admin() and current_user_can( 'manage_options' ): Ensures the script only loads in the admin and for relevant users.
    • wp_enqueue_script(): This WordPress function is used to properly load JavaScript files.
    • 'my-custom-admin-notice-script': A unique name (handle) for your script.
    • get_template_directory_uri() . '/js/admin-notices.js': This is the path to your JavaScript file. If you’re using a theme, it assumes a js folder in your theme’s root directory with a file named admin-notices.js. If you are creating a plugin, you’ll need to adjust this path. For a plugin, it would typically look something like:

    “`php

    plugin_dir_url( __FILE__ ) . ‘js/admin-notices.js’,

    “`

    (assuming your JS is in a js subfolder of your plugin).

    • array( 'jquery' ): This specifies that your script depends on jQuery.
    • '1.0': A version number. Useful for cache busting.
    • true: Tells WordPress to load the script in the footer of the page, which is generally good for performance.

    The JavaScript File (admin-notices.js)

    Now, create the admin-notices.js file in the location you specified above and add the following code:

    “`javascript

    jQuery(document).ready(function($) {

    // Target our specific notice by its ID

    // Make sure this ID matches the ID in your PHP notice HTML

    var noticeId = ‘#my-custom-notice’;

    // Use WordPress’s built-in dismissible logic, but hijack the click

    $(document).on(‘click’, noticeId + ‘ .notice-dismiss’, function(e) {

    e.preventDefault(); // Prevent the default dismissal action

    // Get the current user ID (if available or passed from PHP)

    // For simplicity here, we’ll rely on WordPress’s user settings to save

    // the dismissal, but in more complex scenarios, you might use AJAX.

    // Get the unique notice identifier from the notice div’s data attribute

    // Let’s assume we’ve added a data-notice-id attribute to our notice HTML like:

    //

    // This is crucial for identifying which notice was dismissed.

    var $notice = $(this).closest(noticeId);

    var noticeIdentifier = $notice.data(‘notice-id’);

    if (!noticeIdentifier) {

    console.error(‘Admin notice identifier not found. Add data-notice-id attribute to the notice.’);

    return;

    }

    // Now, we need to save this dismissal status for the current user.

    // The simplest way is to trigger a WordPress AJAX action.

    // We’ll need a PHP AJAX handler for this.

    // For this simplified example, we’ll just hide it visually.

    // For persistent dismissal, we’ll explain the AJAX approach in the next section.

    $notice.hide();

    // This is where the real persistence logic goes

    // For now, we’ve just hidden it. The next section will add AJAX.

    // This part is just to see the JS running and visually hiding.

    // To make it truly dismissible per user, we need to save this.

    // Let’s look at the AJAX approach.

    });

    // Important Note: The above simply hides the notice visually.

    // For true persistence, we need to save this state.

    // The next section will detail how to do this using AJAX.

    });

    “`

    Crucial Update to the PHP my_custom_admin_notice function:

    We need to add a data- attribute to our notice HTML so our JavaScript can easily identify which* notice is being dismissed.

    “`php

    // In your plugin file or theme’s functions.php

    function my_custom_admin_notice() {

    if ( ! current_user_can( ‘manage_options’ ) ) {

    return;

    }

    $user_id = get_current_user_id();

    $notice_id = ‘my_important_notice’; // Unique identifier for this notice

    $saved_dismissed_notices = get_user_meta( $user_id, ‘dismissed_admin_notices’, true );

    if ( ! empty( $saved_dismissed_notices ) && is_array( $saved_dismissed_notices ) && in_array( $notice_id, $saved_dismissed_notices ) ) {

    return;

    }

    // Add the data-notice-id attribute

    ?>

    “>Learn More

    }

    add_action( ‘admin_notices’, ‘my_custom_admin_notice’ );

    // Also adjust the JS enqueue path if you’re in a plugin

    function my_custom_admin_notice_script() {

    if ( ! is_admin() || ! current_user_can( ‘manage_options’ ) ) {

    return;

    }

    // Adjust path for plugin vs theme

    $script_url = ”;

    if ( defined(‘PLUGIN_DIR_URL’) ) { // Assuming a constant or plugin context

    $script_url = PLUGIN_DIR_URL . ‘js/admin-notices.js’; // Example for a plugin

    } else {

    $script_url = get_template_directory_uri() . ‘/js/admin-notices.js’; // Example for theme

    }

    wp_enqueue_script(

    ‘my-custom-admin-notice-script’,

    $script_url, // Use the determined path

    array( ‘jquery’ ),

    ‘1.0’,

    true

    );

    }

    add_action( ‘admin_enqueue_scripts’, ‘my_custom_admin_notice_script’ );

    ?>

    “`

    If you’re looking to enhance your WordPress admin experience, you might find it useful to explore a related article on creating custom admin notices. This resource provides step-by-step guidance on how to implement dismissible notices that can be tailored for individual users. You can read more about it in this insightful piece on custom admin notices, which complements the topic of adding persistent notifications effectively.

    Saving the Dismissal State: The AJAX Approach

    The JavaScript above only hides the notice visually. To make it persist across page loads and sessions, we need server-side action. This is where WordPress AJAX comes in.

    Understanding WordPress AJAX

    WordPress has a built-in AJAX handler (admin-ajax.php) that allows your JavaScript to communicate with your PHP code without a full page reload. You can trigger actions and receive responses.

    Setting up the PHP AJAX Handler

    We need a function hooked into WordPress AJAX actions to receive the dismissal request and save the user meta.

    Add this to your functions.php file or plugin file:

    “`php

    // In your plugin file or theme’s functions.php

    // Hook for logged-in users to perform AJAX actions

    add_action( ‘wp_ajax_dismiss_admin_notice’, ‘handle_dismiss_admin_notice’ );

    function handle_dismiss_admin_notice() {

    // Verify the nonce for security

    if ( ! isset( $_POST[‘nonce’] ) || !wp_verify_nonce( $_POST[‘nonce’], ‘dismiss_notice_nonce’ ) ) {

    wp_send_json_error( ‘Security check failed!’ );

    wp_die();

    }

    // Get the notice ID from the POST data

    if ( ! isset( $_POST[‘notice_id’] ) || empty( $_POST[‘notice_id’] ) ) {

    wp_send_json_error( ‘Notice ID not provided!’ );

    wp_die();

    }

    $notice_id = sanitize_text_field( $_POST[‘notice_id’] ); // Sanitize notice ID

    $user_id = get_current_user_id();

    // Make sure we have a valid user ID

    if ( ! $user_id ) {

    wp_send_json_error( ‘User not logged in or anonymous!’ );

    wp_die();

    }

    // Get current list of dismissed notices

    $dismissed_notices = get_user_meta( $user_id, ‘dismissed_admin_notices’, true );

    // Ensure it’s an array

    if ( ! is_array( $dismissed_notices ) ) {

    $dismissed_notices = array();

    }

    // Add the new notice ID if it’s not already there

    if ( ! in_array( $notice_id, $dismissed_notices ) ) {

    $dismissed_notices[] = $notice_id;

    }

    // Save the updated array back to user meta

    update_user_meta( $user_id, ‘dismissed_admin_notices’, $dismissed_notices );

    // Send a success response back to JavaScript

    wp_send_json_success( ‘Notice dismissed successfully!’ );

    wp_die(); // Always die after an AJAX handler

    }

    ?>

    “`

    Explanation of the AJAX Handler:

    • add_action( 'wp_ajax_dismiss_admin_notice', 'handle_dismiss_admin_notice' );: This hooks our handle_dismiss_admin_notice function to a specific AJAX action named dismiss_admin_notice. The wp_ajax_ prefix is for logged-in users.
    • wp_verify_nonce( $_POST['nonce'], 'dismiss_notice_nonce' ): Crucial for security. Nonces are one-time tokens used to verify that the request came from your WordPress site and not from a malicious source.
    • $_POST['notice_id']: We expect the JavaScript to send the notice’s unique identifier via POST data.
    • get_current_user_id(): Retrieves the current user’s ID.
    • get_user_meta() and update_user_meta(): These are used to retrieve and save the dismissed_admin_notices array for the current user.
    • wp_send_json_error() / wp_send_json_success(): Functions to send JSON responses back to the JavaScript.
    • wp_die();: WordPress AJAX handlers must end with wp_die() to prevent unintended output.

    Modifying the JavaScript to Trigger AJAX

    Now, let’s update our admin-notices.js file to actually send the AJAX request.

    “`javascript

    jQuery(document).ready(function($) {

    // Target our specific notice by its ID

    var noticeSelector = ‘#my-custom-notice’;

    // Use WordPress’s built-in dismissible logic, but hijack the click

    $(document).on(‘click’, noticeSelector + ‘ .notice-dismiss’, function(e) {

    e.preventDefault(); // Prevent the default dismissal action

    var $notice = $(this).closest(noticeSelector);

    var noticeIdentifier = $notice.data(‘notice-id’);

    if (!noticeIdentifier) {

    console.error(‘Admin notice identifier not found. Add data-notice-id attribute to the notice.’);

    return;

    }

    // Hide the notice visually now

    $notice.hide();

    // Trigger the AJAX call to save dismissal

    // We need to pass the notice ID and a nonce for security.

    // The nonce needs to be passed from PHP using wp_localize_script if used.

    // For simplicity here, let’s assume we passed it.

    // If you haven’t used wp_localize_script, you’ll need it.

    // Let’s add it to the enqueue function first.

    // Assuming you’ve added wp_localize_script in functions.php like:

    // wp_localize_script( ‘my-custom-admin-notice-script’, ‘myAdminNoticeParams’, array(

    // ‘ajax_url’ => admin_url( ‘admin-ajax.php’ ),

    // ‘nonce’ => wp_create_nonce( ‘dismiss_notice_nonce’ ),

    // ) );

    // Check if myAdminNoticeParams is available (it should be if localized)

    if (typeof myAdminNoticeParams === ‘undefined’) {

    console.error(‘myAdminNoticeParams not defined. Did you localize the script?’);

    return;

    }

    $.ajax({

    url: myAdminNoticeParams.ajax_url, // Use the localized AJAX URL

    type: ‘POST’,

    data: {

    action: ‘dismiss_admin_notice’, // This matches the wp_ajax_ hook prefix

    notice_id: noticeIdentifier,

    nonce: myAdminNoticeParams.nonce // Use the localized nonce

    },

    success: function(response) {

    if (response.success) {

    console.log(‘Dismissal saved: ‘ + response.data);

    } else {

    console.error(‘Error saving dismissal: ‘, response.data);

    // Optionally, you might want to show the notice again if saving failed?

    // Or at least log a more detailed error.

    }

    },

    error: function(jqXHR, textStatus, errorThrown) {

    console.error(‘AJAX request failed: ‘, textStatus, errorThrown);

    // Handle network errors or server issues

    }

    });

    });

    });

    “`

    And crucially, update your admin_enqueue_scripts function in PHP to include wp_localize_script so your JavaScript can access the AJAX URL and nonce:

    “`php

    // In your plugin file or theme’s functions.php

    function my_custom_admin_notice_script() {

    if ( ! is_admin() || ! current_user_can( ‘manage_options’ ) ) {

    return;

    }

    $script_url = ”;

    if ( defined(‘PLUGIN_DIR_URL’) ) { // Example for a plugin

    $script_url = PLUGIN_DIR_URL . ‘js/admin-notices.js’;

    } else { // Example for a theme

    $script_url = get_template_directory_uri() . ‘/js/admin-notices.js’;

    }

    wp_enqueue_script(

    ‘my-custom-admin-notice-script’,

    $script_url,

    array( ‘jquery’ ),

    ‘1.0’,

    true

    );

    // Localize the script to pass data to JavaScript

    wp_localize_script( ‘my-custom-admin-notice-script’, ‘myAdminNoticeParams’, array(

    ‘ajax_url’ => admin_url( ‘admin-ajax.php’ ),

    ‘nonce’ => wp_create_nonce( ‘dismiss_notice_nonce’ ), // Make sure this matches JS

    ) );

    }

    add_action( ‘admin_enqueue_scripts’, ‘my_custom_admin_notice_script’ );

    ?>

    “`

    With these changes, when a user clicks the ‘x’ button on your notice:

    1. The JavaScript intercepts the click.
    2. It hides the notice visually.
    3. It sends an AJAX request to your WordPress site.
    4. Your PHP AJAX handler receives the request, verifies it, and saves the $notice_id to the current user’s meta.
    5. The next time the user visits an admin page, the my_custom_admin_notice function will check their meta, find the $notice_id, and not display the notice.

    Best Practices and Considerations

    You’ve got the core functionality down. Now, let’s talk about making it robust and user-friendly.

    Multiple Notices and User Meta

    If you plan to have more than one dismissible notice, your dismissed_admin_notices user meta should store an array of all dismissed notice IDs. Your PHP code already handles this correctly by checking in_array() and adding to the array.

    Storing Notice IDs

    Always use unique, descriptive IDs for your notices. Avoid spaces or special characters. For example:

    • welcome_message_v1
    • critical_update_reminder_2023
    • new_feature_intro_plugin_x

    Advanced Targeting

    You might want to show notices only to specific user roles or users who haven’t completed a certain task.

    • User Roles: Modify the current_user_can() check in your my_custom_admin_notice function. For example, to show only to administrators and editors:

    “`php

    if ( ! current_user_can( ‘manage_options’ ) && ! current_user_can( ‘edit_pages’ ) ) {

    return;

    }

    “`

    • User Capabilities: You can check for specific user capabilities if needed.
    • Custom User Meta: If a notice depends on a user having performed a specific action (e.g., configuring a setting), you can store a custom user meta flag and check for that.

    Error Handling and User Feedback

    • JavaScript Error Logging: The console.error messages are helpful for developers. For production, you might want more sophisticated error logging.
    • AJAX Failures: If the AJAX call fails, the notice will reappear on the next refresh. You could add a message to the user indicating that the dismissal couldn’t be saved and they might need to try again or contact support.
    • User Experience: Ensure your notices are concise and actions are clear. Providing a direct link to the relevant page for more information is a good practice.

    Updating/Removing Notices

    If you need to re-enable a notice that was previously dismissed, you would need to create another mechanism (e.g., a settings page where an admin can “reset” dismissals for themselves or all users). For example, you could add a button on a settings page that clears the dismissed_admin_notices user meta for the current user.

    Security Considerations

    • Nonces: Always use nonces for AJAX requests to prevent cross-site request forgery (CSRF) attacks.
    • Sanitization: Sanitize all data received from the client side (like $_POST['notice_id']) before using or saving it. sanitize_text_field() is a good start.
    • Capabilities: Restrict who can see and dismiss notices based on user roles and capabilities.

    Conclusion

    You’ve now got a solid blueprint for creating custom admin notices in WordPress that are both dismissible and persist per user. By combining PHP hooks, intelligent user meta storage, and a touch of JavaScript for the user interaction, you can significantly improve the usability and communication within your WordPress admin area. This approach keeps your dashboard clutter-free and ensures that important messages reach the right people without being annoying. Remember to always prioritize security and good coding practices, especially when dealing with user data and AJAX requests.