Como crear un panel de ajustes global con la Data API (JS) en WordPress

Contents

Introducción

Este artículo muestra paso a paso cómo crear un panel de ajustes global para WordPress usando la Data API de JavaScript (wp.data). El objetivo es ofrecer una solución moderna y reactiva que almacene la configuración en el servidor mediante REST y que use un store personalizado para integrarse con el ecosistema de Gutenberg y las librerías de WordPress (@wordpress/data, @wordpress/api-fetch, @wordpress/element, @wordpress/components).

Visión general y arquitectura

La arquitectura propuesta incluye:

  • Un plugin PHP que registra endpoints REST para obtener y actualizar ajustes (y que protege estos endpoints con capacidades).
  • Un script de administración en JavaScript que registra un store con registerStore de @wordpress/data. Ese store usa apiFetch como control para comunicarse con los endpoints REST.
  • Un componente React (usando @wordpress/element y hooks de @wordpress/data) que muestra el panel y permite editar y guardar ajustes globales.
  • Integración con el menú de administración para mostrar la pantalla de ajustes.

Paso 1 — Crear la estructura del plugin y endpoints REST

Comencemos por el archivo principal del plugin. Aquí registramos dos rutas REST: una para obtener los ajustes y otra para guardarlos. También protegemos las rutas con una comprobación de capacidad (por ejemplo, manage_options).

lt?php
/
  Plugin Name: Mi Panel Global con Data API
  Description: Ejemplo: panel de ajustes con store de wp.data y REST.
  Version: 1.0
  Author: Tu Nombre
 /

if ( ! defined( ABSPATH ) ) {
    exit
}

add_action( rest_api_init, function() {
    register_rest_route(
        mi-plugin/v1,
        /settings,
        array(
            methods             =gt GET,
            callback            =gt mi_plugin_get_settings,
            permission_callback =gt function() {
                return current_user_can( manage_options )
            },
        )
    )

    register_rest_route(
        mi-plugin/v1,
        /settings,
        array(
            methods             =gt POST,
            callback            =gt mi_plugin_update_settings,
            permission_callback =gt function() {
                return current_user_can( manage_options )
            },
            args                =gt array(
                settings =gt array(
                    required =gt true,
                    type     =gt array,
                ),
            ),
        )
    )
} )

function mi_plugin_get_settings( request ) {
    defaults = array(
        texto =gt ,
        activar_feature =gt false,
        color =gt #000000,
    )
    options = get_option( mi_plugin_settings, defaults )
    return rest_ensure_response( options )
}

function mi_plugin_update_settings( request ) {
    params = request->get_param( settings )
    // Sanitiza según el campo
    sanitized = array()
    sanitized[texto] = isset( params[texto] ) ? sanitize_text_field( params[texto] ) : 
    sanitized[activar_feature] = ! empty( params[activar_feature] ) ? true : false
    sanitized[color] = isset( params[color] ) ? sanitize_hex_color( params[color] ) : #000000

    update_option( mi_plugin_settings, sanitized )
    return rest_ensure_response( sanitized )
}
?gt

Notas sobre seguridad

  • Usamos current_user_can(manage_options) en permission_callback para evitar accesos no autorizados.
  • En la función de guardado sanitizamos los campos antes de actualizar la opción.

Paso 2 — Encolar scripts y pasar datos desde PHP

Encolaremos el script de administración y añadiremos dependencias de los paquetes de WordPress. Además pasaremos la URL base de la REST API y el nonce para llamadas seguras (apiFetch puede usarlo).

add_action( admin_enqueue_scripts, function( hook ) {
    // Solo mostrar en la página del plugin (ejemplo: toplevel_page_mi-plugin)
    if ( toplevel_page_mi-plugin !== hook ) {
        return
    }

    asset_file = plugin_dir_path( __FILE__ ) . build/index.asset.php
    ver = file_exists( asset_file ) ? require asset_file : array()

    wp_enqueue_script(
        mi-plugin-admin,
        plugin_dir_url( __FILE__ ) . build/index.js,
        isset( ver[dependencies] ) ? ver[dependencies] : array( wp-element, wp-data, wp-api-fetch, wp-components, wp-i18n ),
        isset( ver[version] ) ? ver[version] : filemtime( plugin_dir_path( __FILE__ ) . build/index.js ),
        true
    )

    wp_localize_script(
        mi-plugin-admin,
        miPluginSettings,
        array(
            root  =gt esc_url_raw( rest_url() ),
            nonce =gt wp_create_nonce( wp_rest ),
        )
    )
} )

Paso 3 — Registrar la página de administración

Necesitamos una página en el menú para alojar el panel React. El callback de la página imprime un contenedor donde React montará la aplicación.

add_action( admin_menu, function() {
    add_menu_page(
        Mi Panel Global,
        Mi Panel,
        manage_options,
        mi-plugin,
        mi_plugin_render_page,
        dashicons-admin-generic,
        80
    )
} )

function mi_plugin_render_page() {
    // Montaje de la app React
    echo ltdiv id=mi-plugin-rootgtlt/divgt
}

Paso 4 — Crear un store con wp.data (JavaScript)

Registraremos un store que ofrezca acciones para cargar y guardar ajustes. El store usará controles que llaman a apiFetch para interactuar con los endpoints REST definidos en PHP.

/
  src/store/index.js
 /
import { registerStore } from @wordpress/data
import apiFetch from @wordpress/api-fetch

const STORE_NAME = mi-plugin/settings

const DEFAULT_STATE = {
    loaded: false,
    isSaving: false,
    settings: {
        texto: ,
        activar_feature: false,
        color: #000000,
    },
}

const actions = {
    fetchSettings() {
        return {
            type: FETCH_SETTINGS,
        }
    },
    receiveSettings( settings ) {
        return {
            type: RECEIVE_SETTINGS,
            settings,
        }
    },
    saveSettings( settings ) {
        return {
            type: SAVE_SETTINGS,
            settings,
        }
    },
    savedSettings( settings ) {
        return {
            type: SAVED_SETTINGS,
            settings,
        }
    },
}

const controls = {
    FETCH_SETTINGS() {
        return apiFetch( { path: /mi-plugin/v1/settings } )
    },
    SAVE_SETTINGS( action ) {
        return apiFetch( {
            path: /mi-plugin/v1/settings,
            method: POST,
            data: { settings: action.settings },
        } )
    },
}

const reducer = ( state = DEFAULT_STATE, action ) =gt {
    switch ( action.type ) {
        case RECEIVE_SETTINGS:
            return {
                ...state,
                loaded: true,
                settings: action.settings,
            }
        case SAVE_SETTINGS:
            return {
                ...state,
                isSaving: true,
            }
        case SAVED_SETTINGS:
            return {
                ...state,
                isSaving: false,
                settings: action.settings,
            }
    }
    return state
}

const resolvers = {
    getSettings() {
        const settings = yield actions.fetchSettings()
        yield actions.receiveSettings( settings )
    },
}

const selectors = {
    getSettings( state ) {
        return state.settings
    },
    isLoaded( state ) {
        return state.loaded
    },
    isSaving( state ) {
        return state.isSaving
    },
}

registerStore( STORE_NAME, {
    reducer,
    actions,
    controls,
    resolvers,
    selectors,
} )

Puntos clave del store

  • controls hace la llamada con apiFetch a nuestras rutas REST. apiFetch automáticamente incluye el nonce si está configurado correctamente por WordPress.
  • resolvers permite que useSelect solicite la carga inicial (p. ej. useSelect( (select) =gt select(mi-plugin/settings).getSettings() ) ).

Paso 5 — Construir la interfaz con hooks de @wordpress/data

Un componente React que use useSelect y useDispatch para leer y actualizar los datos del store. Aquí un ejemplo simple con campos de texto, checkbox y color.

/
  src/components/SettingsPanel.js
 /
import { useEffect, useState } from @wordpress/element
import { useSelect, useDispatch } from @wordpress/data
import { TextControl, CheckboxControl, Button, __experimentalPanelColorGradientSettings as ColorPicker } from @wordpress/components

export default function SettingsPanel() {
    const { getSettings, isLoaded, isSaving } = useSelect( ( select ) => {
        const store = select( mi-plugin/settings )
        return {
            getSettings: store.getSettings,
            isLoaded: store.isLoaded(),
            isSaving: store.isSaving(),
        }
    }, [] )

    // Forzar la carga (resolver)
    useEffect( () =gt {
        getSettings()
    }, [ getSettings ] )

    const settings = useSelect( ( select ) =gt select( mi-plugin/settings ).getSettings(), [] )
    const { saveSettings } = useDispatch( mi-plugin/settings )  {}

    const [ local, setLocal ] = useState( settings )

    // Mantener local sincronizado cuando los settings se carguen
    useEffect( () =gt {
        if ( settings ) {
            setLocal( settings )
        }
    }, [ settings ] )

    if ( ! settings ) {
        return ltpgtCargando...lt/pgt
    }

    const onSave = () =gt {
        if ( saveSettings ) {
            saveSettings( local ).then( ( updated ) =gt {
                // El store ya manejará el estado aquí se puede mostrar notificación si se desea
            } )
        }
    }

    return (
        ltdivgt
            ltTextControl
                label=Texto global
                value={ local.texto }
                onChange={ ( value ) =gt setLocal( { ...local, texto: value } ) }
            /gt
            ltCheckboxControl
                label=Activar feature
                checked={ !! local.activar_feature }
                onChange={ ( checked ) =gt setLocal( { ...local, activar_feature: checked } ) }
            /gt
            ltlabelgtColor principallt/labelgt
            ltinput
                type=color
                value={ local.color }
                onChange={ ( e ) =gt setLocal( { ...local, color: e.target.value } ) }
            /gt
            ltbr /gt
            ltButton isPrimary onClick={ onSave } isBusy={ isSaving }gtGuardar ajusteslt/Buttongt
        lt/divgt
    )
}

Paso 6 — Registrar el store y montar la aplicación

En el archivo principal JS se importa y registra el store, y se monta el componente en el contenedor generado por PHP en la pantalla de administración.

/
  src/index.js
 /
import ./store
import SettingsPanel from ./components/SettingsPanel
import { render } from @wordpress/element
import apiFetch from @wordpress/api-fetch

// Configurar apiFetch con la URL raíz y nonce (inyectados desde PHP con wp_localize_script)
if ( typeof miPluginSettings !== undefined ) {
    apiFetch.use( ( options, next ) =gt {
        if ( ! options.headers ) {
            options.headers = {}
        }
        options.headers[ X-WP-Nonce ] = miPluginSettings.nonce
        return next( options )
    } )
}

// Montar la app si existe el contenedor
document.addEventListener( DOMContentLoaded, () =gt {
    const root = document.getElementById( mi-plugin-root )
    if ( root ) {
        render( SettingsPanel(), root )
    }
} )

Opcional: respuestas del store y manejo de promesas

En el ejemplo del store hemos definido controles que devuelven la promesa de apiFetch. Al llamar a las acciones que disparan esos controles, apiFetch resolverá con los datos. Puedes encadenar .then en el dispatch o confiar en que el store actualice su estado y los selectores reflejen el nuevo valor.

Cómo funcionan las piezas juntas

  1. El componente monta y llama al selector/resolver para cargar ajustes: el resolver ejecuta el control que hace apiFetch GET a /mi-plugin/v1/settings.
  2. Al editar y pulsar Guardar, el componente llama a la acción saveSettings que dispara al control SAVE_SETTINGS este hace apiFetch POST con los datos al endpoint REST.
  3. El endpoint PHP valida, sanitiza y actualiza la opción en la base de datos (update_option) y retorna la configuración actualizada.
  4. El control devuelve la respuesta, el store despacha SAVED_SETTINGS y el selector reflecta los nuevos valores la UI reacciona automáticamente.

Buenas prácticas y recomendaciones

  • Sanitiza y valida siempre los datos en el servidor. No confíes únicamente en validación cliente.
  • Protege los endpoints REST con permission_callback y capacidades adecuadas.
  • Usa wp_create_nonce(wp_rest) y pásalo al cliente apiFetch lo incluirá en la cabecera X-WP-Nonce.
  • Si tienes muchos ajustes o tipos complejos, considera usar register_setting y REST schema, o bien endpoints por secciones.
  • Evita bloquear la UI durante llamadas largas utiliza estados de carga y mensajes de error claros.
  • Si se necesita compatibilidad con multi-site, maneja la lectura/escritura con get_site_option/update_site_option según corresponda.

Ejemplo final de flujo de trabajo resumido

  1. Usuario abre la página de ajustes en admin.
  2. El JavaScript monta el componente y el store solicita los datos (GET).
  3. El servidor devuelve los ajustes guardados con get_option.
  4. El usuario edita y pulsa Guardar el componente llama a la acción save que ejecuta apiFetch POST.
  5. El servidor valida y guarda con update_option y devuelve el resultado.
  6. El store actualiza su estado y la UI muestra los cambios.

Conclusión

Crear un panel de ajustes global con la Data API de WordPress aporta una experiencia reactiva y coherente con el ecosistema de bloques y herramientas modernas. Separando responsabilidades (endpoints REST para persistencia, store de wp.data para gestión de estado y componentes React para UI) se consigue una solución escalable, testable y mantenible.

Recursos ú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 *