Como crear un dashboard widget personalizado en admin con PHP en WordPress

Este tutorial explica con todo lujo de detalles cómo crear un dashboard widget personalizado en el área de administración de WordPress usando PHP. Encontrarás un plugin de ejemplo completo, explicación línea a línea, consideraciones de seguridad, opciones de almacenamiento, cómo añadir estilos y scripts, y variantes (AJAX, guardado por usuario o global). El código de ejemplo viene listo para pegar en archivos .php/.js/.css según corresponda.

Contents

Requisitos y conceptos clave

  • Hook principal: wp_dashboard_setup — se utiliza para registrar widgets en el dashboard.
  • Función para crear widget: wp_add_dashboard_widget()
  • Almacenamiento: get_user_meta/update_user_meta para datos por usuario get_option/update_option para datos globales.
  • Seguridad: capacidades (current_user_can), nonces (wp_create_nonce / check_ajax_referer / wp_verify_nonce), y saneamiento (sanitize_text_field, esc_html, wp_kses_post según el caso).
  • Carga de scripts/estilos: admin_enqueue_scripts, y condicional para cargar solo en el dashboard (hook index.php).

Código completo: plugin de ejemplo

A continuación tienes un plugin funcional que añade un widget al dashboard que permite guardar un texto por usuario usando AJAX. Copia todo en un archivo, por ejemplo: dashboard-widget-personalizado.php.


    

style=width:100% /> />

admin_url( admin-ajax.php ), nonce => wp_create_nonce( dwp_save_widget_nonce ), // mismo action usado en PHP ) ) // CSS opcional wp_enqueue_style( dwp-admin-css, plugin_url . dwp-admin.css, array(), 1.0 ) } / Handler AJAX para guardar el valor (acción: dwp_save_widget) / add_action( wp_ajax_dwp_save_widget, dwp_ajax_save_widget ) function dwp_ajax_save_widget() { // Verificar nonce if ( empty( _POST[nonce] ) ! wp_verify_nonce( sanitize_text_field( wp_unslash( _POST[nonce] ) ), dwp_save_widget_nonce ) ) { wp_send_json_error( array( message => __( Nonce inválido, dwp ) ), 400 ) } // Verificar permisos: por ejemplo, permitir a cualquier usuario autenticado if ( ! is_user_logged_in() ) { wp_send_json_error( array( message => __( No autorizado, dwp ) ), 403 ) } user_id = get_current_user_id() // Recuperar y sanitizar value = isset( _POST[value] ) ? sanitize_text_field( wp_unslash( _POST[value] ) ) : // Guardar en user meta update_user_meta( user_id, dwp_widget_value, value ) wp_send_json_success( array( message => __( Guardado correctamente, dwp ), value => value ) ) } / Ejemplo: eliminar el widget para usuarios sin cierta capacidad (se ejecuta en la misma etapa wp_dashboard_setup) / add_action( wp_dashboard_setup, dwp_conditional_remove, 20 ) function dwp_conditional_remove() { // Por ejemplo, eliminar el widget para usuarios que no puedan edit_posts if ( ! current_user_can( edit_posts ) ) { remove_meta_box( dwp_widget_id, dashboard, normal ) } }

Archivo JavaScript (dwp-admin.js)

Guarda esto en el mismo directorio del plugin como dwp-admin.js. El código hace la petición AJAX y muestra mensajes.

jQuery(document).ready(function(){
    (#dwp-widget-form).on(submit, function(e){
        e.preventDefault()
        var value = (#dwp-value).val()
        var nonce = (input[name=nonce], this).val()

        (#dwp-response).empty()
        (#dwp-spinner).show()

        .post(dwp_ajax.ajax_url, {
            action: dwp_save_widget,
            value: value,
            nonce: nonce
        }, function(response){
            (#dwp-spinner).hide()
            if ( response.success ) {
                (#dwp-response).html(   response.data.message   )
            } else {
                var msg = response.data  response.data.message ? response.data.message : Error
                (#dwp-response).html(   msg   )
            }
        }).fail(function(xhr){
            (#dwp-spinner).hide()
            (#dwp-response).html(   (xhr.responseJSON  xhr.responseJSON.data  xhr.responseJSON.data.message ? xhr.responseJSON.data.message : Error en la petición)   )
        })
    })
})

Archivo CSS opcional (dwp-admin.css)

/ Estilos mínimos para el widget /
#dwp-widget input[type=text]{
    box-sizing: border-box
}
#dwp-response { font-size: 13px }

Explicación detallada del código

  • Plugin header: las líneas iniciales permiten que WordPress detecte el plugin.
  • dwp_register_dashboard_widget: engancha a wp_dashboard_setup y usa wp_add_dashboard_widget para registrar un widget con ID y callback.
  • dwp_render_dashboard_widget: obtiene el valor guardado con get_user_meta, crea un nonce y muestra un formulario simple. Escapa valores con esc_attr/esc_html.
  • dwp_admin_enqueue_assets: encola los recursos únicamente cuando hook === index.php (la pantalla del Escritorio). Se pasa información al JS con wp_localize_script para facilitar el uso de admin-ajax.php y el nonce.
  • dwp_ajax_save_widget: función que atiende la petición AJAX. Verifica nonce y permisos, sanea la entrada y guarda con update_user_meta. Responde con wp_send_json_success / wp_send_json_error.
  • remove_meta_box: ejemplo que elimina el widget para usuarios sin la capacidad requerida. El primer parámetro debe coincidir con el ID usado en wp_add_dashboard_widget (dwp_widget_id en el ejemplo).

Seguridad y buenas prácticas

  • Nonces: siempre verifica nonces en formularios y peticiones AJAX. Usa check_ajax_referer o wp_verify_nonce según el caso.
  • Capacidades: valora qué capability necesita un usuario para ver/interactuar con el widget (edit_posts, manage_options, etc.).
  • Saneamiento y escape: sana con sanitize_text_field, sanitize_email, etc., y escapa al imprimir con esc_html/esc_attr/wp_kses_post según admitas HTML.
  • Carga condicional de assets: evita encolar scripts en todas las pantallas del admin limita al Dashboard para mejorar rendimiento.
  • Privacidad: si almacenas datos de usuario, documenta el tratamiento y respeta la normativa aplicable.

Opciones de almacenamiento

  • User meta: ideal cuando cada usuario debe tener su propio contenido (ejemplo del plugin).
  • Option: usar update_option/get_option cuando el dato es global para todos los usuarios.
  • Custom post type o CPT: para contenido más estructurado o histórico (registro de cambios, etc.).

Variantes y ampliaciones

  • Guardar sin AJAX: podrías enviar el formulario a admin-post.php y procesarlo con add_action(admin_post_nopriv_…).
  • Múltiples campos y validación: añade campos adicionales, valida y sanear según tipo (emails, URLs, HTML permitidos).
  • Uso del REST API: en lugar de admin-ajax, puedes exponer un endpoint REST y usar fetch() desde el admin con nonce de REST (wp_rest). Ideal para integraciones modernas.
  • Permitir HTML seguro: si el widget admite HTML por parte del usuario, usa wp_kses() o wp_kses_post() con una lista blanca de etiquetas.
  • Internacionalización: envuelve cadenas con __() o _e() y añade Text Domain en el plugin header para permitir traducciones.

Cómo eliminar o desactivar el widget

Si necesitas remover temporal o permanentemente el widget (por ejemplo para ciertos roles), usa remove_meta_box con el mismo ID que registraste. Ejemplo ya incluido en el plugin con dwp_conditional_remove.

Consejos finales

  • Ten cuidado con permisos: decidir quién puede ver/editar el widget es clave para evitar filtraciones de datos.
  • Documenta si el widget persiste datos y dónde se almacenan (usermeta/option), para facilitar mantenimiento.
  • Prueba con distintos roles y en sitios multisite si tu plugin va a usarse en entornos más complejos.
  • Si el widget va a inyectar HTML avanzado, valida y escapa en función del contexto para evitar XSS.

El ejemplo que incluí es una base sólida: plug and play para aprender, y fácilmente ampliable (más campos, integración con API, guardado en opciones globales, etc.). Puedes usar y adaptar el código directamente en un plugin básico moviendo el JS y CSS a archivos separados tal como se indica.

Enlaces útiles



Acepto donaciones de BAT's mediante el navegador Brave 🙂



Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *