WordPress Hosting

php only blocks wordpress

Ya puedes crear bloques de WordPress solo con PHP, sin JavaScript, así se hace con la nueva API de WordPress 7.x

Si llevas años desarrollando con WordPress y en su día dejaste de crear bloques porque lo de montar un entorno con Node.js, React y Webpack te parecía demasiado para lo que necesitabas, tengo una noticia que te va a gustar mucho: WordPress 7.0 permite crear bloques personalizados usando solo PHP. Ni una línea de JavaScript. Ni un npm install. Nada de compilar.

La funcionalidad se llama PHP-only block registration y llega gracias al flag autoRegister en la función register_block_type(). Registras tu bloque con PHP, defines los atributos, escribes el callback de renderizado y WordPress se encarga del resto: el bloque aparece en el insertador del editor y genera automáticamente los controles en el panel lateral para que el usuario pueda configurarlo.

¿Es un sustituto de los bloques JavaScript completos? Pues no, y tampoco pretende serlo.

Está pensado para bloques que solo necesitan renderizado del lado del servidor, los típicos que muestran datos dinámicos (últimas entradas, cajas de autor, alertas, widgets de datos) y que no requieren edición en tiempo real dentro del editor.

Para estos casos, que son la mayoría de los que necesita un desarrollador PHP en su día a día, es una solución limpia y sin complicaciones.

En esta guía tienes todo lo necesario para empezar a crear tus propios bloques PHP-only, desde la referencia técnica completa, explicada como yo la he entendido, hasta tres ejemplos prácticos que van de menos a más:

  1. Un bloque creado desde cero
  2. Migración de un shortcode clásico a bloque
  3. Integración real de un bloque en un plugin ya publicado en WordPress.org.

Requisitos para seguir este tutorial: Todos los códigos de esta guía necesitan que tengas instalado ya WordPress 7.0 o superior, disponible desde el 9 de abril de 2026. Si quieres probarlo antes de esa fecha puedes instalar la última beta de WordPress 7.0 con el plugin WordPress Beta Tester o el plugin Gutenberg en un sitio de pruebas. En versiones anteriores de WordPress el flag autoRegister (a continuación lo explico) se ignora y los bloques no aparecerán en el editor.

Cómo funciona autoRegister

Lo que tienes a continuación es la referencia técnica que necesitas como desarrollador PHP para trabajar con esta API. Lo he preparado para que no tengas que ir a buscar en la documentación en inglés cada vez que quieras consultar algo, y por si te sirve mi manera de explicar las cosas.

La estructura básica

Un bloque PHP-only se registra con register_block_type() en el hook init, como cualquier otro bloque, pero con dos requisitos obligatorios:

  • El flag autoRegister a true dentro de supports
  • Un render_callback que devuelva el HTML del bloque

Demostración, como diría el Malaguita…

add_action( 'init', 'ayudawp_register_my_block' );

function ayudawp_register_my_block() {
    register_block_type(
        'mi-plugin/mi-bloque',
        array(
            'title'           => 'Mi bloque',
            'icon'            => 'admin-generic',
            'category'        => 'widgets',
            'description'     => 'Descripción del bloque.',
            'keywords'        => array( 'ejemplo', 'php' ),
            'attributes'      => array(
                // Aquí van los atributos
            ),
            'render_callback' => 'ayudawp_render_my_block',
            'supports'        => array(
                'autoRegister' => true,
            ),
        )
    );
}

function ayudawp_render_my_block( $attributes ) {
    $wrapper = get_block_wrapper_attributes();
    return sprintf( '<div %s>Contenido del bloque</div>', $wrapper );
}

Cuando WordPress detecta el flag autoRegister registra automáticamente el bloque en el editor del lado del cliente a través de un global JavaScript (autoRegisterBlocks) y utiliza ServerSideRender para la previsualización.

O sea, cada vez que el usuario cambia un atributo en el panel lateral el editor hace una llamada a la API REST de WordPress, ejecuta tu render_callback en PHP y muestra el resultado. Sencillo, limpio, eficaz, como es PHP.

Tipos de atributos y controles automáticos

La gracia de autoRegister es que WordPress genera automáticamente los controles del Inspector (panel lateral del editor) a partir de los atributos que definas. Pero no todos los tipos generan controles, solo los que WordPress sabe representar como campo de formulario.

Estos son los tipos admitidos y el control que genera cada uno:

Tipo de atributo Control generado Ejemplo de uso
string TextControl (campo de texto) Títulos, URLs, textos libres
string con enum SelectControl (desplegable) Elegir entre opciones predefinidas
integer NumberControl (campo numérico) Cantidad de elementos, anchos
boolean ToggleControl (interruptor) Activar/desactivar funcionalidades

Los atributos que no encajen en estos tipos (arrays, objetos) o los que tengan el rol local no generan ningún control en el panel.

Un detalle importante es que definas siempre un valor default para cada atributo. Si no lo haces el bloque puede no renderizar nada la primera vez que lo insertes, porque los atributos llegarán vacíos al callback.

Así se definen los atributos en la práctica, cubriendo los cuatro tipos:

'attributes' => array(
    // String → TextControl
    'titulo' => array(
        'type'    => 'string',
        'default' => 'Título por defecto',
    ),
    // String con enum → SelectControl
    'tamano' => array(
        'type'    => 'string',
        'enum'    => array( 'pequeno', 'mediano', 'grande' ),
        'default' => 'mediano',
    ),
    // Integer → NumberControl
    'cantidad' => array(
        'type'    => 'integer',
        'default' => 5,
    ),
    // Boolean → ToggleControl
    'mostrar_fecha' => array(
        'type'    => 'boolean',
        'default' => true,
    ),
),

Los supports nativos funcionan igual

Además de autoRegister, puedes activar los mismos supports que en cualquier bloque (color, espaciado, tipografía, bordes). WordPress aplicará los estilos heredados del tema activo sin que tengas que escribir CSS propio.

'supports' => array(
    'autoRegister' => true,
    'color'        => array(
        'background' => true,
        'text'       => true,
    ),
    'spacing'      => array(
        'margin'  => true,
        'padding' => true,
    ),
    'typography'   => array(
        'fontSize' => true,
    ),
),

Cuando activas estos supports WordPress añade los controles correspondientes en el panel lateral (selector de color, controles de márgenes, etc.) y aplica las clases y estilos inline automáticamente. Para que funcionen correctamente en el HTML de salida, usa siempre get_block_wrapper_attributes() en tu callback de renderizado.

Cargar CSS y JavaScript

Si tu bloque necesita estilos propios o algún script, lo habitual es registrarlos en el hook init y con enqueue desde el callback. De esta forma solo se cargan en las páginas donde esté disponible el bloque.

add_action( 'init', 'ayudawp_register_block_assets' );

function ayudawp_register_block_assets() {
    wp_register_style(
        'ayudawp-mi-bloque-style',
        plugins_url( 'assets/css/mi-bloque.css', __FILE__ ),
        array(),
        '1.0.0'
    );
}

function ayudawp_render_my_block( $attributes ) {
    // Solo se encola cuando se renderiza el bloque
    wp_enqueue_style( 'ayudawp-mi-bloque-style' );

    $wrapper = get_block_wrapper_attributes();
    return sprintf( '<div %s>...</div>', $wrapper );
}

Ejemplo 1: Bloque de caja de información autor (desde cero)

Vamos con el primer ejemplo práctico, que es un bloque muy práctico que muestra la información de un autor de WordPress (avatar, nombre, biografía y enlace a su web). Lo vamos a crear como mu-plugin para que sea fácil de probar.

Este bloque tiene un detalle interesante, y es que el selector de autor se genera dinámicamente a partir de los usuarios de WordPress mediante un enum construido con get_users(). Es un buen ejemplo de cómo aprovechar toda la potencia de PHP dentro de la definición del bloque.

Crea el archivo dentro de /wp-content/mu-plugins/ayudawp-author-box-block.php con el siguiente contenido:

<?php
/**
* Plugin Name: Bloque de caja de autor (PHP)
* Plugin URI: https://servicios.ayudawp.com
* Description: Bloque de caja de autor registrado solo con PHP. Requiere WordPress 7.0 o superior o la última versión del plugin Gutenberg.
* Version: 1.0.0
* Author: Fernando Tellado
* Author URI: https://ayudawp.com
* License: GPL-2.0-or-later
* Requires PHP: 7.4
*/

// Prevent direct file access
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

add_action( 'init', 'ayudawp_register_author_box_block' );

/**
 * Register the author box PHP-only block.
 *
 * Generates the user list dynamically for the author selector.
 */
function ayudawp_register_author_box_block() {

    // Build the list of users for the selector
    $users    = get_users( array( 'fields' => array( 'ID', 'display_name' ) ) );
    $user_ids = array();

    foreach ( $users as $user ) {
        $user_ids[] = (string) $user->ID;
    }

    // If no users, fallback to avoid empty enum
    if ( empty( $user_ids ) ) {
        $user_ids = array( '1' );
    }

    register_block_type(
        'ayudawp/author-box',
        array(
            'title'           => __( 'Caja de autor', 'ayudawp' ),
            'icon'            => 'admin-users',
            'category'        => 'theme',
            'description'     => __( 'Muestra información del autor: avatar, nombre, biografía y sitio web.', 'ayudawp' ),
            'keywords'        => array( 'author', 'bio', 'avatar' ),
            'attributes'      => array(
                'user_id'      => array(
                    'type'    => 'string',
                    'enum'    => $user_ids,
                    'default' => $user_ids[0],
                ),
                'avatar_size'  => array(
                    'type'    => 'integer',
                    'default' => 96,
                ),
                'show_bio'     => array(
                    'type'    => 'boolean',
                    'default' => true,
                ),
                'show_website' => array(
                    'type'    => 'boolean',
                    'default' => true,
                ),
            ),
            'render_callback' => 'ayudawp_render_author_box_block',
            'supports'        => array(
                'autoRegister' => true,
                'color'        => array(
                    'background' => true,
                    'text'       => true,
                ),
                'spacing'      => array(
                    'padding' => true,
                    'margin'  => true,
                ),
            ),
        )
    );
}

/**
 * Render callback for the author box block.
 *
 * @param array $attributes Block attributes.
 * @return string Block HTML output.
 */
function ayudawp_render_author_box_block( $attributes ) {

    $user_id      = absint( $attributes['user_id'] ?? 1 );
    $avatar_size  = absint( $attributes['avatar_size'] ?? 96 );
    $show_bio     = (bool) ( $attributes['show_bio'] ?? true );
    $show_website = (bool) ( $attributes['show_website'] ?? true );

    $user = get_userdata( $user_id );

    if ( ! $user ) {
        return '';
    }

    $wrapper = get_block_wrapper_attributes(
        array(
            'style' => 'display:flex;gap:1.5em;align-items:flex-start;',
        )
    );

    $output  = sprintf( '<div %s>', $wrapper );
    $output .= get_avatar( $user_id, $avatar_size );
    $output .= '<div>';
    $output .= sprintf(
        '<strong style="font-size:1.2em;">%s</strong>',
        esc_html( $user->display_name )
    );

    if ( $show_bio && $user->description ) {
        $output .= sprintf(
            '<p style="margin-top:0.5em;">%s</p>',
            esc_html( $user->description )
        );
    }

    if ( $show_website && $user->user_url ) {
        $output .= sprintf(
            '<p><a href="%s" target="_blank" rel="noopener noreferrer">%s</a></p>',
            esc_url( $user->user_url ),
            esc_html( $user->user_url )
        );
    }

    $output .= '</div></div>';

    return $output;
}

Una vez guardado el archivo ve al editor de WordPress, busca «Caja de autor» en el insertador de bloques y deberías tenerlo disponible para insertarlo, hazlo.

insertar bloque solo php wordpress 7

Con el bloque ya insertado y seleccionado, en el panel lateral verás un desplegable con los IDs de los usuarios, un campo numérico para el tamaño del avatar y dos interruptores para mostrar u ocultar la biografía y el enlace web.

bloque solo php wordpress 7

Además, si activas los supports de color y espaciado, podrás personalizar los colores de fondo y texto, y los márgenes y relleno directamente desde el editor.

Un apunte importante sobre el selector de usuarios es que el control SelectControl que genera WordPress muestra los valores del enum tal cual, en este caso los IDs numéricos de los usuarios. No es lo más elegante visualmente (lo ideal sería mostrar el nombre), pero es la limitación actual de la generación automática de controles.

Para un bloque de producción con un selector más refinado ya necesitarías JavaScript, pero para un uso interno o un tema personalizado funciona de sobra, y se ve perfecto.

Si quieres profundizar en otras formas de mostrar una caja de información del autor en WordPress, por si no te termina de convencer el uso del bloque, y te ha entrado la curiosidad y ganas de usar algo así, echa un vistazo a esta guía sobre cómo mostrar la información del autor sin plugins.

Ejemplo 2: Migrar el shortcode de entradas recientes a bloque

Ahora empieza la parte más interesante del tutorial, pues para empezar a darle caña a esto vamos a tomar un shortcode que ya funciona y convertirlo en un bloque PHP-only.

Usaremos como base el shortcode de entradas recientes que publiqué hace un tiempo, y lo vamos a mejorar de paso con un par de atributos nuevos.

Pero la mejora real no son los atributos extra, sino dónde puedes usar este bloque.

Como shortcode estabas limitado a pegarlo dentro del contenido de una entrada o página, como bloque PHP-only puedes insertarlo en cualquier plantilla del editor de sitio (la página de inicio, el pie de página, la plantilla de entradas individuales, la 404, etc.) sin depender de plugins ni tocar los archivos del tema.

El bloque nativo de últimas entradas de WordPress hace algo parecido, pero no te deja ordenar por número de comentarios, ni mostrar entradas aleatorias, ni activar el extracto con un interruptor, este sí.

El shortcode original

El shortcode [entradas_recientes] usa get_posts() para mostrar una lista de entradas filtrable por categoría, con parámetros de orden y cantidad. Así era el código:

function ayudawp_recent_posts_shortcode( $atts, $content = null ) {
    global $post;
    extract( shortcode_atts( array(
        'cat'     => '',
        'num'     => '5',
        'order'   => 'DESC',
        'orderby' => 'post_date',
    ), $atts ) );
    $args = array(
        'cat'            => $cat,
        'posts_per_page' => $num,
        'order'          => $order,
        'orderby'        => $orderby,
    );
    $output = '';
    $posts  = get_posts( $args );
    foreach ( $posts as $post ) {
        setup_postdata( $post );
        $output .= '<li><a href="' . get_the_permalink() . '">' . get_the_title() . '</a></li>';
    }
    wp_reset_postdata();
    return '<ul>' . $output . '</ul>';
}
add_shortcode( 'entradas_recientes', 'ayudawp_recent_posts_shortcode' );

Qué cambia al convertirlo en bloque

Los parámetros del shortcode se convierten en atributos del bloque, y cada uno se mapea al tipo que mejor encaje para generar un control automático en el editor:

Parámetro del shortcode Atributo del bloque Tipo Control generado
num num integer Campo numérico
cat cat string Campo de texto (ID de categoría)
order order string + enum Desplegable (ASC/DESC)
orderby orderby string + enum Desplegable
(nuevo) show_date boolean Interruptor
(nuevo) show_excerpt boolean Interruptor

Los dos atributos nuevos (show_date y show_excerpt) son de tipo boolean, así que generarán un interruptor cada uno en el panel lateral. De esta forma, en un solo ejemplo ves los cuatro tipos de control que genera autoRegister.

El bloque completo

Crea el archivo wp-content/mu-plugins/ayudawp-recent-posts-block.php:

<?php
/**
* Plugin Name: Bloque de entradas recientes
* Description: Bloque PHP-only de entradas recientes. Migración del shortcode [entradas_recientes]. Requiere WordPress 7.0 o superior, o el plugin Gutenberg 14.0+ en versiones anteriores.
* Plugin URI: https://servicios.ayudawp.com
* Version: 1.0.0
* Author: Fernando Tellado
* Author URI: https://ayudawp.com
* License: GPL-2.0-or-later
* Requires PHP: 7.4
*/

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

add_action( 'init', 'ayudawp_register_recent_posts_block' );

/**
 * Register the recent posts PHP-only block.
 */
function ayudawp_register_recent_posts_block() {
    register_block_type(
        'ayudawp/recent-posts',
        array(
            'title'           => __( 'Entradas recientes (PHP)', 'ayudawp' ),
            'icon'            => 'list-view',
            'category'        => 'theme',
            'description'     => __( 'Muestra una lista personalizable de entradas recientes.', 'ayudawp' ),
            'keywords'        => array( 'posts', 'recent', 'latest', 'entries' ),
            'attributes'      => array(
                'num'          => array(
                    'type'    => 'integer',
                    'default' => 5,
                ),
                'cat'          => array(
                    'type'    => 'string',
                    'default' => '',
                ),
                'order'        => array(
                    'type'    => 'string',
                    'enum'    => array( 'DESC', 'ASC' ),
                    'default' => 'DESC',
                ),
                'orderby'      => array(
                    'type'    => 'string',
                    'enum'    => array( 'post_date', 'title', 'modified', 'rand', 'comment_count' ),
                    'default' => 'post_date',
                ),
                'show_date'    => array(
                    'type'    => 'boolean',
                    'default' => false,
                ),
                'show_excerpt' => array(
                    'type'    => 'boolean',
                    'default' => false,
                ),
            ),
            'render_callback' => 'ayudawp_render_recent_posts_block',
            'supports'        => array(
                'autoRegister' => true,
                'color'        => array(
                    'text' => true,
                    'link' => true,
                ),
                'spacing'      => array(
                    'margin'  => true,
                    'padding' => true,
                ),
                'typography'   => array(
                    'fontSize' => true,
                ),
            ),
        )
    );
}

/**
 * Render callback for the recent posts block.
 *
 * @param array $attributes Block attributes.
 * @return string Block HTML output.
 */
function ayudawp_render_recent_posts_block( $attributes ) {

    $num          = absint( $attributes['num'] ?? 5 );
    $cat          = sanitize_text_field( $attributes['cat'] ?? '' );
    $order        = in_array( $attributes['order'] ?? 'DESC', array( 'ASC', 'DESC' ), true )
                    ? $attributes['order']
                    : 'DESC';
    $orderby      = sanitize_key( $attributes['orderby'] ?? 'post_date' );
    $show_date    = (bool) ( $attributes['show_date'] ?? false );
    $show_excerpt = (bool) ( $attributes['show_excerpt'] ?? false );

    $args = array(
        'posts_per_page' => $num,
        'order'          => $order,
        'orderby'        => $orderby,
        'post_status'    => 'publish',
    );

    if ( ! empty( $cat ) ) {
        $args['cat'] = absint( $cat );
    }

    $posts = get_posts( $args );

    if ( empty( $posts ) ) {
        return '';
    }

    $wrapper = get_block_wrapper_attributes();
    $output  = sprintf( '<div %s><ul style="list-style:none;padding:0;">', $wrapper );

    foreach ( $posts as $post ) {
        $output .= '<li style="margin-bottom:0.75em;">';
        $output .= sprintf(
            '<a href="%s">%s</a>',
            esc_url( get_permalink( $post ) ),
            esc_html( get_the_title( $post ) )
        );

        if ( $show_date ) {
            $output .= sprintf(
                ' <span style="color:#666;font-size:0.85em;">— %s</span>',
                esc_html( get_the_date( '', $post ) )
            );
        }

        if ( $show_excerpt && $post->post_excerpt ) {
            $output .= sprintf(
                '<br><span style="font-size:0.9em;color:#555;">%s</span>',
                esc_html( wp_trim_words( $post->post_excerpt, 20 ) )
            );
        }

        $output .= '</li>';
    }

    $output .= '</ul></div>';

    return $output;
}

/*
 * Opcional: mantiene funcional el shortcode por retro-compatibilidad.
 * Los usuarios que ya tengan [entradas_recientes] en su contenido
 * pueden seguir usándolo mientras migran al bloque.
 */
add_shortcode( 'entradas_recientes', 'ayudawp_recent_posts_shortcode_compat' );

/**
 * Shortcode compatibility wrapper.
 *
 * @param array  $atts    Shortcode attributes.
 * @param string $content Shortcode content (unused).
 * @return string HTML output.
 */
function ayudawp_recent_posts_shortcode_compat( $atts, $content = null ) {
    $atts = shortcode_atts(
        array(
            'cat'     => '',
            'num'     => '5',
            'order'   => 'DESC',
            'orderby' => 'post_date',
        ),
        $atts,
        'entradas_recientes'
    );

    return ayudawp_render_recent_posts_block(
        array(
            'num'          => (int) $atts['num'],
            'cat'          => $atts['cat'],
            'order'        => $atts['order'],
            'orderby'      => $atts['orderby'],
            'show_date'    => false,
            'show_excerpt' => false,
        )
    );
}

Qué hemos ganado con la migración

Fíjate en lo que ha cambiado, que es mucho:

  • El usuario ya no necesita recordar la sintaxis [entradas_recientes num="5" cat="12"]. Inserta el bloque desde el insertador, configura todo desde el panel lateral con controles visuales y ve una previsualización en tiempo real de las entradas seleccionadas.
  • El callback de renderizado es prácticamente el mismo que el del shortcode, con las mejoras de seguridad (validación de los parámetros, esc_url(), esc_html()) y los dos atributos nuevos.

Al final del archivo hemos incluido además una función de compatibilidad que mantiene el shortcode [entradas_recientes] funcionando, reutilizando el mismo callback del bloque. Así, las entradas o páginas que ya tengan el shortcode insertado seguirán mostrando las entradas recientes sin tener que tocar nada. Puedes ir migrando a tu ritmo.

Este patrón de migración (bloque nuevo + shortcode de compatibilidad que llama al mismo renderizado) es exactamente lo que deberías aplicar siempre que conviertas un shortcode a bloque. Nunca elimines el shortcode de golpe, mantenlo como alternativa.

Para saber más sobre shortcodes y cómo crearlos, tienes la guía completa de shortcodes de WordPress. Y si te has encontrado con el problema de los shortcodes que dejan de funcionar al desactivar un plugin, echa un vistazo a la guía de shortcodes huérfanos.

Ejemplo 3: Migrar el shortcode de botones de AI Share & Summarize a bloque

Para cerrar los ejemplos prácticos vamos con un caso real sobre un plugin publicado en WordPress.org, y no es ni más ni menos que la migración del shortcode [ayudawp_share_buttons] del plugin AI Share & Summarize a un bloque PHP-only.

Este plugin combina botones de compartir en redes sociales con botones para generar resúmenes en IA (Claude, ChatGPT, Perplexity, DeepSeek y muchas más). Tiene inserción automática, pero también un shortcode muy completo con parámetros para el estilo, tamaño, iconos, alineación y selección de botones concretos.

El shortcode que vamos a migrar

El shortcode admite estos parámetros principales:

[ayudawp_share_buttons style="brand" size="normal" show_icons="true" alignment="left" icon_style="circular"]

Cada uno de estos parámetros se traduce directamente en un atributo del bloque:

Parámetro Tipo de atributo Valores del enum
style string + enum minimal, brand, outline, dark, custom, icons-only
size string + enum compact, normal, large, fluid
show_icons boolean
alignment string + enum left, center
icon_style string + enum circular, square
show_title boolean

Cómo se integra en el plugin

A diferencia de los ejemplos anteriores (que son mu-plugins independientes) aquí el bloque se registra dentro del propio plugin, en un archivo nuevo de la carpeta /includes.

La clave está en el render_callback: en vez de pasar por do_shortcode(), llama directamente al método estático AyudaWP_AISS_Buttons::ayudawp_generate_buttons_html(), que es la misma función que ya usa internamente el shortcode para generar el HTML de los botones. Es mucho más eficiente y sin duplicar código.

Si miras la clase AyudaWP_AISS_Shortcode del plugin, verás que el shortcode pilla las opciones guardadas con get_option( 'ayudawp_aiss_options' ), sobrescribe las que vengan como parámetro del shortcode y pasa todo a AyudaWP_AISS_Buttons::ayudawp_generate_buttons_html().

El render callback del bloque hace exactamente lo mismo, pero recibiendo los valores desde los atributos del bloque en vez de desde los parámetros del shortcode.

<?php
/**
 * PHP-only block registration for AI Share & Summarize
 *
 * @package AiShareSummarize
 * @since   2.0.0
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

add_action( 'init', 'ayudawp_aiss_register_share_block' );

/**
 * Register the share buttons as a PHP-only block.
 */
function ayudawp_aiss_register_share_block() {
    register_block_type(
        'ayudawp/share-buttons',
        array(
            'title'           => __( 'AI Share & Summarize', 'ai-share-summarize' ),
            'icon'            => 'share',
            'category'        => 'widgets',
            'description'     => __( 'Share buttons and AI summary buttons.', 'ai-share-summarize' ),
            'keywords'        => array( 'share', 'social', 'ai', 'summarize' ),
            'attributes'      => array(
                'style'      => array(
                    'type'    => 'string',
                    'enum'    => array( 'minimal', 'brand', 'outline', 'dark', 'custom', 'icons-only' ),
                    'default' => 'minimal',
                ),
                'size'       => array(
                    'type'    => 'string',
                    'enum'    => array( 'compact', 'normal', 'large', 'fluid' ),
                    'default' => 'normal',
                ),
                'show_icons' => array(
                    'type'    => 'boolean',
                    'default' => true,
                ),
                'alignment'  => array(
                    'type'    => 'string',
                    'enum'    => array( 'left', 'center' ),
                    'default' => 'left',
                ),
                'icon_style' => array(
                    'type'    => 'string',
                    'enum'    => array( 'circular', 'square' ),
                    'default' => 'circular',
                ),
                'show_title' => array(
                    'type'    => 'boolean',
                    'default' => true,
                ),
            ),
            'render_callback' => 'ayudawp_aiss_render_share_block',
            'supports'        => array(
                'autoRegister' => true,
                'spacing'      => array(
                    'margin'  => true,
                    'padding' => true,
                ),
            ),
        )
    );
}

/**
 * Render callback for the share buttons block.
 *
 * Replicates the shortcode logic: loads plugin options,
 * overrides with block attributes, and calls the existing
 * AyudaWP_AISS_Buttons renderer directly.
 *
 * @param array $attributes Block attributes.
 * @return string Block HTML output.
 */
function ayudawp_aiss_render_share_block( $attributes ) {

    // Check the rendering class exists (plugin active)
    if ( ! class_exists( 'AyudaWP_AISS_Buttons' ) ) {
        return '';
    }

    // Load the saved plugin options as base
    $options = get_option( 'ayudawp_aiss_options' );

    if ( ! is_array( $options ) ) {
        return '';
    }

    $block_options = $options;

    // Override options with block attributes (same as shortcode does)
    if ( ! empty( $attributes['style'] ) ) {
        $block_options['button_colors'] = sanitize_text_field( $attributes['style'] );
    }

    if ( ! empty( $attributes['size'] ) ) {
        $block_options['button_size'] = sanitize_text_field( $attributes['size'] );
    }

    if ( isset( $attributes['show_icons'] ) ) {
        $block_options['show_icons'] = (bool) $attributes['show_icons'];
    }

    if ( ! empty( $attributes['icon_style'] ) ) {
        $block_options['icon_style'] = sanitize_text_field( $attributes['icon_style'] );
    }

    if ( isset( $attributes['show_title'] ) && ! $attributes['show_title'] ) {
        $block_options['title_text'] = '';
    }

    // Get the enabled buttons from plugin settings
    $enabled_buttons = isset( $options['enabled_buttons'] ) ? $options['enabled_buttons'] : array();

    if ( empty( $enabled_buttons ) || ! is_array( $enabled_buttons ) ) {
        return '';
    }

    $current_url   = get_permalink();
    $current_title = get_the_title();

    // Call the same renderer the shortcode uses
    $html = AyudaWP_AISS_Buttons::ayudawp_generate_buttons_html(
        $enabled_buttons,
        $block_options,
        $current_url,
        $current_title
    );

    if ( empty( $html ) ) {
        return '';
    }

    $wrapper = get_block_wrapper_attributes();

    return sprintf( '<div %s>%s</div>', $wrapper, $html );
}

Fíjate en el render callback, hace exactamente lo mismo que el método ayudawp_shortcode_share_buttons() de la clase AyudaWP_AISS_Shortcode, carga las opciones del plugin, sobrescribe con los valores que vienen del bloque (en vez de los del shortcode), y pasa todo a AyudaWP_AISS_Buttons::ayudawp_generate_buttons_html() junto con la URL y título de la entrada actual.

El resultado es que bloque y shortcode comparten el mismo motor de renderizado, sin duplicar nada de código.

El usuario podrá insertar los botones de compartir y resumir como un bloque nativo del editor, configurando el estilo, tamaño, iconos y alineación desde el panel lateral, con una previsualización que se actualiza con cada cambio.

El shortcode [ayudawp_share_buttons] seguirá funcionando exactamente igual para todos los que ya lo tengan insertado en su contenido, que la compatibilidad no se toca.

Esta integración estará disponible en una próxima versión del plugin. Aún no está perfecta pero lo estará.

Cuándo usar bloques PHP-only y cuándo no

Después de ver los tres ejemplos, conviene tener claro para qué sirve esta herramienta y para qué no. No todo debería ser un bloque PHP-only, igual que no todo debería ser un shortcode.

Es buena elección cuando…

Los bloques PHP-only son la opción adecuada para contenido dinámico que se genera en el servidor, sin necesidad de interacción en el editor más allá de configurar unos parámetros.

Hay muchos ejemplos de esto, como listados de entradas, cajas de autor, alertas o avisos personalizables, CTAs, widgets de datos, embeds de herramientas propias, botones de compartir, bloques informativos que tiran de la base de datos o de APIs externas, suma y sigue.

Todo lo que antes resolvías con un shortcode o con una función en el functions.php pero con la ventaja de una interfaz visual en el editor.

También son ideales para temas de bloques y FSE, donde puedes crear componentes específicos para tu tema sin depender de plugins de terceros.

No es la mejor opción cuando…

Si necesitas edición en tiempo real dentro del editor, como por ejemplo, escribir texto directamente en el bloque, arrastrar elementos o manipular contenido inline. En estos casos necesitas un bloque JavaScript completo. Pasa lo mismo si tu bloque debe contener otros bloques dentro (InnerBlocks), que es algo que los bloques PHP-only no admiten de momento.

Ten en cuenta que la previsualización de un bloque PHP-only usa ServerSideRender, por lo que cada vez que cambias un atributo, el editor hace una petición REST al servidor para obtener el HTML actualizado. Para bloques sencillos esto es imperceptible pero si tu callback hace consultas pesadas o llamadas a APIs externas, cada cambio de atributo puede generar un retardo visible en el editor.

¿Y los shortcodes?

Los shortcodes siguen siendo perfectamente válidos para retro-compatibilidad y para situaciones o webs donde no puedes usar el editor de bloques (campos personalizados, widgets clásicos, plantillas PHP). Pero si estás creando algo nuevo los bloques PHP-only son su sucesor natural porque ofrecen la misma facilidad de desarrollo con una experiencia de usuario mucho mejor en el editor.

Consejos prácticos y errores comunes

Para terminar unas cuantas cosas que no están en la documentación oficial y que te pueden ahorrar algún dolor de cabeza:

  • Define siempre valores default en los atributos. Si un atributo llega vacío o sin definir al callback, tu bloque puede renderizar en blanco o dar un error. Un default te garantiza que siempre hay un valor válido con el que trabajar.
  • Usa siempre get_block_wrapper_attributes(). Esta función genera los atributos HTML del contenedor del bloque (clases, estilos inline de los supports como color o espaciado). Si no la usas, los supports que actives no tendrán efecto.
  • El namespace del bloque es obligatorio. El primer argumento de register_block_type() debe llevar el formato namespace/nombre-bloque (por ejemplo, ayudawp/author-box). Sin el namespace, el registro fallará.
  • Los enum solo muestran el valor, no una etiqueta legible. Si tu enum tiene valores como array( 'small', 'medium', 'large' ), el desplegable en el editor mostrará exactamente esos textos. No hay forma (de momento) de mostrar una etiqueta más descriptiva al lado de cada valor sin recurrir a JavaScript.
  • Las etiquetas de los controles son el nombre del atributo. Los controles que genera autoRegister en el panel lateral usan directamente el nombre del atributo como etiqueta, en mayúsculas y sin formato. No hay ninguna propiedad para personalizar esa etiqueta desde PHP. Por eso conviene nombrar los atributos de forma legible desde el principio (por ejemplo, autor en vez de user_id, o biografia en vez de show_bio). No es lo ideal pero es lo que hay de momento. Si necesitas etiquetas descriptivas y una interfaz más cuidada en el panel lateral, ya te toca escribir JavaScript con InspectorControls.
  • Cuidado con las plantillas en el editor de sitio. Se ha detectado un problema en las betas de WordPress 7.0 donde los bloques PHP-only se renderizan correctamente en las previsualizaciones de plantillas, pero el CSS aplicado globalmente o por bloque no siempre se muestra. Es un tema que el equipo de desarrollo está resolviendo.
  • InnerBlocks no están soportados. No puedes crear un bloque PHP-only que acepte otros bloques dentro. Si necesitas esta funcionalidad, toca usar bloques JavaScript.
  • El rendimiento del editor depende de tu callback. Como cada cambio de atributo genera una petición REST que ejecuta tu render_callback, intenta que sea lo más ligero posible. Si haces consultas a la base de datos, plantéate usar transients para cachear los resultados.
  • No necesitas block.json. Para bloques PHP-only, toda la definición del bloque (título, icono, categoría, atributos, supports) va directamente en el array que pasas a register_block_type(). No necesitas crear un archivo block.json separado.

Próximos pasos

WordPress 7.0 está disponible desde el 9 de abril de 2026. Si quieres probar todo esto antes puedes instalarte una versión beta o el plugin Gutenberg en un sitio de pruebas.

De cara a WordPress 7.1, el equipo de desarrollo ya está trabajando en Block Fields, una evolución de esta misma idea que promete ampliar los tipos de controles automáticos disponibles, y en mejorar la compatibilidad con Block Bindings, el sistema que conecta los atributos de los bloques con fuentes de datos externas.

Todo apunta a que la creación de bloques solo con PHP seguirá mejorando en las próximas versiones.

Si quieres aprender a crear estos bloques usando inteligencia artificial como copiloto, en la guía de vibe coding y agentic coding para WordPress tienes técnicas y prompts concretos para generar código de plugins y bloques con Claude o ChatGPT.

Y si necesitas repasar los fundamentos de PHP antes de meterte con los bloques, la guía de PHP básico para WordPress es un buen punto de partida, y cuando descubras que esta magia del código es lo tuyo, aquí tienes el único curso de programación para WordPress que existe, preparado por mi y muchos de los mejores profesionales que hay.

Para todo lo demás aquí me tienes.

Compartir en redes
Resumir con IA

¿De cuánta utilidad te ha parecido este contenido?

¡Haz clic en las estrellas para valorarlo!

Promedio de puntuación 5 / 5. Total de votos: 3

¡Todavía no hay votos! Sé el primero en valorar este contenido.

Ya que has encontrado útil este contenido...

¡Sígueme en las redes sociales!

¿Te gustó este artículo? ¡Ni te imaginas lo que te estás perdiendo en YouTube!



Sobre el autor

Deja un comentario

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

Scroll al inicio