IQIT Elementor es uno de los constructores visuales más usados en tiendas PrestaShop montadas sobre temas como Warehouse. Trae decenas de widgets listos para usar, pero tarde o temprano llega el momento en que necesitas uno que no existe: una insignia de promoción, un bloque con datos del catálogo, un componente de marca propio. La buena noticia es que el módulo expone toda su arquitectura de widgets para añadir los tuyos sin tocar el core.
Dos caminos para extender el módulo
Dentro de modules/iqitelementor conviven dos sistemas de widgets y es importante no confundirlos:
- includes/widgets/*.php — widgets nativos al estilo Elementor (Widget_Button, Widget_Alert, Widget_IconBox...). Tienen controles de contenido y estilo completos, con vista previa en vivo en el editor.
- src/widgets/IqitElementorWidget_*.php — widgets más simples orientados a contenido propio de PrestaShop (Newsletter, ProductsList, ContactForm, Search, Menu, Brands...), registrados automáticamente a través de un wrapper llamado Widget_Prestashop.
Si necesitas controles de estilo ricos (colores, tipografías, márgenes responsive) y una vista previa fluida en el editor, el camino correcto es el primero. Es el que vamos a recorrer en esta guía.
Anatomía de un widget Elementor
Todo widget vive en su propio archivo dentro de includes/widgets/ y extiende la clase abstracta Widget_Base. El nombre del archivo determina automáticamente el nombre de clase que el módulo espera encontrar al registrarlo, así que la convención es estricta.
<?php
namespace Elementor;
if ( ! defined( 'ELEMENTOR_ABSPATH' ) ) exit;
class Widget_Promo_Badge extends Widget_Base {
public function get_id() {
return 'promo_badge';
}
public function get_title() {
return \IqitElementorWpHelper::__( 'Promo Badge', 'elementor' );
}
public function get_icon() {
return 'alert';
}
protected function _register_controls() {
// controles de contenido y estilo
}
protected function render( $instance = [] ) {
// HTML que se imprime en el front
}
protected function content_template() {
// plantilla JS para la vista previa del editor
}
}El chequeo ELEMENTOR_ABSPATH al inicio del archivo no es opcional: es lo único que impide que alguien acceda al PHP directamente por URL.
Registrar controles con _register_controls()
Los controles se agrupan en secciones (Controls_Manager::SECTION) y viven en dos pestañas: la de Contenido (por defecto) y la de Estilo, que se activa con 'tab' => self::TAB_STYLE. El módulo trae más de veinte tipos de control listos para usar; los que más vas a usar en la práctica son:
- TEXT, TEXTAREA y WYSIWYG — campos de texto simple, multilínea y con editor enriquecido
- SELECT y CHOOSE — listas desplegables y selectores tipo grupo de botones
- COLOR, MEDIA e ICON — selector de color, imagen y librería de iconos
- SLIDER y DIMENSIONS — valores numéricos con unidad (px, %, em) y márgenes/paddings de cuatro lados
- REPEATER — listas repetibles de sub-campos, útil para sliders o acordeones
- URL — campo de enlace con soporte para abrir en nueva pestaña
$this->add_control(
'section_content',
[
'label' => \IqitElementorWpHelper::__( 'Contenido', 'elementor' ),
'type' => Controls_Manager::SECTION,
]
);
$this->add_control(
'badge_text',
[
'label' => \IqitElementorWpHelper::__( 'Texto', 'elementor' ),
'type' => Controls_Manager::TEXT,
'default' => \IqitElementorWpHelper::__( 'Envío gratis hoy', 'elementor' ),
'section' => 'section_content',
]
);
$this->add_control(
'badge_link',
[
'label' => \IqitElementorWpHelper::__( 'Enlace', 'elementor' ),
'type' => Controls_Manager::URL,
'placeholder' => 'https://tutienda.com/ofertas',
'section' => 'section_content',
]
);
$this->add_control(
'section_style',
[
'label' => \IqitElementorWpHelper::__( 'Estilo', 'elementor' ),
'type' => Controls_Manager::SECTION,
'tab' => self::TAB_STYLE,
]
);
$this->add_control(
'badge_color',
[
'label' => \IqitElementorWpHelper::__( 'Color de fondo', 'elementor' ),
'type' => Controls_Manager::COLOR,
'default' => '#00c987',
'tab' => self::TAB_STYLE,
'section' => 'section_style',
'selectors' => [
'{{WRAPPER}} .promo-badge' => 'background-color: {{VALUE}};',
],
]
);Renderizar el widget en el front: render()
El método render() recibe los valores guardados en $instance y debe imprimir el HTML final. El patrón que usa el propio módulo es construir los atributos con add_render_attribute() en vez de concatenar strings: así otros controles (animaciones de hover, clases responsive) pueden añadirse después sin que tengas que tocar el render.
protected function render( $instance = [] ) {
if ( empty( $instance['badge_text'] ) ) {
return;
}
$tag = 'span';
$this->add_render_attribute( 'badge', 'class', 'promo-badge' );
if ( ! empty( $instance['badge_link']['url'] ) ) {
$tag = 'a';
$this->add_render_attribute( 'badge', 'href', $instance['badge_link']['url'] );
}
?>
<<?php echo $tag . ' ' . $this->get_render_attribute_string( 'badge' ); ?>>
<?php echo \IqitElementorWpHelper::esc_html( $instance['badge_text'] ); ?>
</<?php echo $tag; ?>>
<?php
}Vista previa en vivo: content_template()
Si solo implementas render(), el widget funciona pero cada cambio de un control obliga al editor a hacer una petición AJAX para repintar el bloque. content_template() resuelve esto: es una plantilla en sintaxis Underscore.js que Elementor ejecuta directamente en el navegador, así que la previsualización es instantánea.
La sintaxis tiene tres formas que se confunden fácil: {{ settings.x }} escapa el valor, {{{ settings.x }}} lo imprime sin escapar (para HTML), y <# código #> ejecuta JavaScript plano dentro de la plantilla.
protected function content_template() {
?>
<#
var tag = settings.badge_link.url ? 'a' : 'span';
#>
<{{{ tag }}} class="promo-badge" href="{{ settings.badge_link.url }}">
{{{ settings.badge_text }}}
</{{{ tag }}}>
<?php
}Registrar el widget en el módulo
Crear el archivo no basta: el widget debe aparecer en la lista que carga widgets-manager.php. Ahí se mapea el nombre de archivo al nombre de clase con una convención automática (guiones a guion bajo, primera letra de cada palabra en mayúscula, prefijo Widget_), así que solo hace falta añadir el nombre del archivo sin extensión al array:
$build_widgets_filename = [
'heading',
'image',
'button',
'icon-box',
// ...resto de widgets existentes
'promo-badge', // tu widget nuevo
];Si el nombre de archivo no sigue exactamente el patrón que espera ucwords() + str_replace(), el módulo no encuentra la clase y el widget simplemente no aparece en el editor — sin error visible. Es el bug más tonto y más común al añadir un widget nuevo.
Buenas prácticas
- Usa siempre IqitElementorWpHelper::esc_html() o esc_attr() al imprimir valores del usuario en render() — los controles de texto no sanitizan automáticamente.
- Mantén content_template() sincronizado con render(): si agregas un control nuevo, replícalo en ambos lados o la vista previa del editor mostrará algo distinto al resultado real.
- Reutiliza las secciones de estilo ya existentes (tipografía, borde, sombra) con add_group_control() en vez de reinventar controles — el módulo ya trae Group_Control_Typography y Group_Control_Border listos.
- Prueba el widget en el editor y en el front por separado: son dos motores de renderizado distintos (JS y PHP) y es fácil que uno funcione y el otro no.
Un widget bien hecho en IQIT Elementor no se nota — se siente exactamente igual que los que vienen de fábrica. Esa es la métrica de éxito real: que el cliente no pueda distinguir cuál construiste tú y cuál trae el módulo.