How to create a welcome panel in admin with PHP and JS in WordPress

Contents

Introduction

This tutorial explains, in every required detail, how to create a custom welcome panel in the WordPress admin dashboard using PHP and JavaScript. The example below creates a small plugin (or code you can paste into your themes functions.php) that:

  • Replaces the default welcome panel with a custom panel.
  • Provides a dismiss control that uses AJAX to save a dismissal to the current user so the panel remains hidden afterwards.
  • Uses best practices: nonces for security, capability checks, escaping and internationalization-ready strings.

High-level overview

  1. Remove the default WordPress welcome panel and add your own via the welcome_panel hook.
  2. Render the panel HTML from a PHP callback. Include a button or link that triggers JS to dismiss the panel.
  3. Enqueue a JS file and a CSS file only on the dashboard (index.php admin page).
  4. Use admin-ajax.php and a wp_ajax_ action to store a user meta flag that marks the panel dismissed.
  5. Secure the AJAX call using a nonce and validate the current user in the handler.

Why this approach

  • Separation of concerns: PHP outputs the initial markup and handles server-side storage JS handles UI events and AJAX.
  • User-specific persistence: Using user meta keeps dismissal per-user (recommended for welcome content).
  • Performance scope: Scripts and styles are enqueued only on the dashboard (admin index), not globally.
  • Security: Nonces and capability/user checks prevent unauthorized changes.

File structure (recommended plugin)

Create a folder such as admin-welcome-panel and inside it:

  • admin-welcome-panel.php (main plugin file)
  • assets/admin-welcome.js (JS behavior)
  • assets/admin-welcome.css (styling)

The complete PHP plugin code

Place the following code into a single file, for example admin-welcome-panel.php. You can also split logic into separate files as you prefer. The code below is ready to drop into the plugins directory and activate.


    

>

>

>

admin_url( admin-ajax.php ), nonce => wp_create_nonce( awp_welcome_nonce ), ) ) } / AJAX handler to dismiss the welcome panel for the current user. This updates user meta: awp_welcome_dismissed => 1 / add_action( wp_ajax_awp_dismiss_welcome, awp_ajax_dismiss_welcome ) function awp_ajax_dismiss_welcome() { // Check nonce if ( empty( _POST[nonce] ) ! wp_verify_nonce( wp_unslash( _POST[nonce] ), awp_welcome_nonce ) ) { wp_send_json_error( array( message => __( Invalid nonce, admin-welcome-panel ) ), 403 ) } // Ensure user is logged in user_id = get_current_user_id() if ( ! user_id ) { wp_send_json_error( array( message => __( Not logged in, admin-welcome-panel ) ), 403 ) } // Update user meta to mark as dismissed. updated = update_user_meta( user_id, awp_welcome_dismissed, 1 ) if ( updated ) { wp_send_json_success( array( message => __( Dismissed, admin-welcome-panel ) ) ) } else { // If update failed, still respond gracefully. Sometimes update_user_meta returns false if the value was already same. wp_send_json_success( array( message => __( Already dismissed or update failed but panel will hide., admin-welcome-panel ) ) ) } } ?>

Notes on the PHP code

  • Hooks: We hook into wp_dashboard_setup to remove the core welcome and register our own. The core welcome is attached to the welcome_panel action, so we remove it and re-add our callback.
  • Per-user storage: We use get_user_meta/update_user_meta keyed by awp_welcome_dismissed.
  • Security: The AJAX handler checks a nonce (awp_welcome_nonce) and ensures the user is logged in.
  • Localization: Strings are wrapped with translation functions so the plugin can be translated later.

JavaScript: handle the dismiss action and AJAX

Place this JS in assets/admin-welcome.js. It uses jQuery (available in admin) and the data localized by wp_localize_script.

jQuery( document ).ready( function(  ) {
    // Use delegated event in case DOM is modified.
    ( document ).on( click, #awp-welcome-dismiss, function( e ) {
        e.preventDefault()

        var btn = ( this )

        // Prefer data attribute nonce on the button if present, otherwise use localized value.
        var nonce = btn.data( nonce )  ( window.awpWelcomeData ? window.awpWelcomeData.nonce :  )
        var ajaxUrl = ( window.awpWelcomeData  window.awpWelcomeData.ajax_url ) ? window.awpWelcomeData.ajax_url : ajaxurl

        .post( ajaxUrl, {
            action: awp_dismiss_welcome,
            nonce: nonce
        } ).done( function( response ) {
            if ( response  response.success ) {
                // Hide the panel with a small animation
                ( #awp-welcome-panel ).slideUp( 250, function() {
                    ( this ).remove()
                } )
            } else {
                // Show a simple alert for failure. You can replace this with a nicer UI.
                var msg = Unable to dismiss the panel.
                if ( response  response.data  response.data.message ) {
                    msg = response.data.message
                }
                / global alert /
                alert( msg )
            }
        } ).fail( function( jqXHR ) {
            // Network or server error
            var msg = Server error:    ( jqXHR.status ? jqXHR.status : unknown )
            / global alert /
            alert( msg )
        } )
    } )
} )

Notes on the JS

  • The script uses the localized awpWelcomeData.ajax_url and awpWelcomeData.nonce values passed via wp_localize_script.
  • It sends a POST to admin-ajax.php with action awp_dismiss_welcome and the nonce.
  • On success it removes the panel from the DOM so the UI updates immediately.

Styling the panel (CSS)

The dashboard has a built-in welcome-panel class you can style to match or extend it. Put the CSS in assets/admin-welcome.css.

/ Basic layout matching WP welcome style /
#welcome-panel #awp-welcome-panel,
.awp-welcome-panel,
#welcome-panel .awp-welcome-grid {
    box-sizing: border-box
}

/ Make our grid of items responsive and compact /
.awp-welcome-grid {
    display: flex
    gap: 18px
    flex-wrap: wrap
    margin-top: 12px
}

.awp-welcome-item {
    flex: 1 1 220px
    min-width: 200px
    background: #fff
    border: 1px solid #e3e4e6
    border-radius: 4px
    padding: 12px
}

/ Dismiss button: align to the top-right visually /
#awp-welcome-dismiss {
    float: right
    margin-top: -6px
    margin-right: -6px
}

/ Small improvements for mobile /
@media (max-width: 640px) {
    .awp-welcome-grid {
        flex-direction: column
    }
}

Where to place the code

  1. As a plugin: Create the folder, create admin-welcome-panel.php in it with the PHP code, put JS and CSS under an assets/ folder, and activate the plugin.
  2. As a theme tweak: Paste the PHP into your themes functions.php and enqueue inline scripts/styles appropriately. Using a plugin is cleaner and portable.

Advanced options and variations

  • Dismiss permanently for all users: If you want an admin-level option that hides the panel for everyone, store a flag in the options table (update_option) and check current_user_can(manage_options) when toggling.
  • Temporary hiding / reopen: Instead of removing the panel completely when dismissed, you might display a small Show welcome link elsewhere that toggles it back on by removing the user meta.
  • Use the REST API: Instead of admin-ajax.php, you can create a small REST endpoint under wp-json and use fetch() in modern JS. If you use the REST API, ensure proper permission callbacks and nonce handling (or use Application Passwords/OAuth for external clients).
  • Per-role logic: Check current_user_can() to show different text/links to different roles.
  • Autoshow for new users: To show the panel only for users who havent visited the dashboard before, you can detect a user meta key like awp_first_seen and set it when they first load the dashboard, then conditionally show the panel.

Security considerations and best practices

  • Always verify nonces on AJAX endpoints (wp_verify_nonce).
  • Ensure you check the current user (get_current_user_id) before updating user meta.
  • Escape all output — use esc_html, esc_attr, esc_url as appropriate.
  • Use translation functions (__(), _e(), esc_html__()) for any user-visible string so your plugin can be localized.
  • Enqueue assets only when required — limiting to the dashboard reduces overhead and avoids potential conflicts.

Troubleshooting

  1. If the panel does not appear:
    • Confirm the plugin is active or the code is loaded by your theme.
    • Make sure you are on the Dashboard (index.php). The panel is shown only there.
    • Check for PHP errors in the server error log or WP_DEBUG output.
  2. If the dismiss button does nothing:
    • Open your browser console and check for JS errors.
    • Confirm the localized awpWelcomeData object exists (inspect window.awpWelcomeData).
    • Inspect the AJAX response in the Network tab to see status and body (maybe a nonce or permissions error).
  3. If the panel hides but reappears after refresh:
    • Verify that update_user_meta succeeded and check the users meta in the database or via get_user_meta in a temporary debug snippet.
    • Make sure the AJAX handler returned success. If update_user_meta returned false because the meta already existed and had same value, logic in the handler still sends success.

Alternative: Add content via admin_notices

If you prefer the welcome content to appear as an admin notice rather than a dashboard welcome panel, you can hook into admin_notices and print similar markup. For long-term content that should remain on top of every admin screen, admin_notices is appropriate for dashboard-specific onboarding, welcome_panel is the right place.

API references and useful docs

Final notes

This article provided a complete, secure, and practical method to add a custom welcome panel in the WordPress admin using PHP and JavaScript. The example is intentionally small and focused on clarity extend it to add analytics events, multiple languages, persistence options, or REST-based control as you need.



Acepto donaciones de BAT's mediante el navegador Brave 🙂



Leave a Reply

Your email address will not be published. Required fields are marked *