---
url: 'https://dxpr.com/es/hc/documentacion/aprender-dxpr-builder/desarrollo/elementos-personalizados'
title: 'Creación de elementos personalizados | DXPR'
author:
  name: Jurriaan
  url: 'https://dxpr.com/users/jurriaan'
date: '2026-10-24T11:18:21+00:00'
updated: '2026-10-24T17:19:22+00:00'
copyright: 'Copyright DXPR. All rights reserved.'
type: article
summary: |-
  Cada elemento de DXPR Builder es un objeto JavaScript puesto en
  window.dxprBuilder.dxpr_elements. El editor convierte cada objeto
  en una clase de elemento con las pestañas Style y Animation ya
  adjuntas. Esta página muestra cómo se definen los elementos incluidos en
  dxpr_builder/dxpr_elements.js, cómo un módulo añade su propia
  definición al editor y cómo es el HTML resultante.
  No hay plantillas Twig de servidor por elemento; los elementos
  se renderizan en el navegador.
tags:
  - 'Build on it'
image: 'https://dxpr.com/themes/custom/dxpr_com/images/dxpr-share-card.jpg'
published: true
og:
  site_name: DXPR
  updated_time: '2026-10-24T17:19:22+0000'
  'image:alt': 'DXPR, powered by Drupal: AI-powered Drupal for marketers.'
schema:
  '@context': 'https://schema.org'
  '@graph':
    -
      mainEntity: {  }
    -
      '@type': TechArticle
      headline: 'Creación de elementos personalizados'
      description: |-
        Cada elemento de DXPR Builder es un objeto JavaScript puesto en
        window.dxprBuilder.dxpr_elements. El editor convierte cada objeto
        en una clase de elemento con las pestañas Style y Animation ya
        adjuntas. Esta página muestra cómo se definen los elementos incluidos en
        dxpr_builder/dxpr_elements.js, cómo un módulo añade su propia
        definición al editor y cómo es el HTML resultante.
        No hay plantillas Twig de servidor por elemento; los elementos
        se renderizan en el navegador.
      image:
        '@type': ImageObject
        url: 'https://dxpr.com/themes/custom/dxpr_com/images/dxpr-share-card.jpg'
        width: '1200'
        height: '630'
      datePublished: '2026-10-24T11:18:21+0000'
      dateModified: '2026-10-24T17:19:22+0000'
      author:
        '@type': Organization
        '@id': 'https://dxpr.com/#organization'
        name: DXPR
        url: 'https://dxpr.com/'
        logo:
          '@type': ImageObject
          url: 'https://dxpr.com/themes/custom/dxpr_com/dxpr-logo-dark.svg'
          width: '1348'
          height: '501'
      publisher:
        '@type': Organization
        '@id': 'https://dxpr.com/#organization'
        name: DXPR
        url: 'https://dxpr.com/'
        logo:
          '@type': ImageObject
          url: 'https://dxpr.com/themes/custom/dxpr_com/dxpr-logo-dark.svg'
          width: '1348'
          height: '501'
      mainEntityOfPage: 'https://dxpr.com/es/hc/documentacion/aprender-dxpr-builder/desarrollo/elementos-personalizados'
      articleSection: 'Desarrollar encima'
    -
      '@type': WebPage
      '@id': 'https://dxpr.com/es/hc/documentacion/aprender-dxpr-builder/desarrollo/elementos-personalizados'
      description: |-
        Cada elemento de DXPR Builder es un objeto JavaScript puesto en
        window.dxprBuilder.dxpr_elements. El editor convierte cada objeto
        en una clase de elemento con las pestañas Style y Animation ya
        adjuntas. Esta página muestra cómo se definen los elementos incluidos en
        dxpr_builder/dxpr_elements.js, cómo un módulo añade su propia
        definición al editor y cómo es el HTML resultante.
        No hay plantillas Twig de servidor por elemento; los elementos
        se renderizan en el navegador.
      breadcrumb:
        '@type': BreadcrumbList
        itemListElement:
          -
            '@type': ListItem
            position: 1
            name: Inicio
            item: 'https://dxpr.com/es'
      publisher:
        '@type': Organization
        '@id': 'https://dxpr.com/#organization'
        name: DXPR
      inLanguage: es
---
[Centro de ayuda](/es/hc)/[Documentación](/es/hc/documentacion) /[Aprende DXPR Builder](/es/hc/documentation/learn-dxpr-builder) /[Extender](/es/hc/documentation/learn-dxpr-builder/extend) /[Desarrollar encima](/es/hc/documentation/learn-dxpr-builder/extend/build-on-it) # Creación de elementos personalizados.

 Aprende DXPR BuilderExtenderDesarrollar encima

   ## En esta guía

 Guías relacionadas

- [Referencia de la API de hooks](/es/hc/documentacion/aprender-dxpr-builder/desarrollo/api-hooks)
- [Arquitectura](/es/hc/documentacion/aprender-dxpr-builder/desarrollo/arquitectura)

Cada elemento de DXPR Builder es un objeto JavaScript puesto en `window.dxprBuilder.dxpr_elements`. El editor convierte cada objeto en una clase de elemento con las pestañas Style y Animation ya adjuntas. Esta página muestra cómo se definen los elementos incluidos en `dxpr_builder/dxpr_elements.js`, cómo un módulo añade su propia definición al editor y cómo es el HTML resultante. No hay plantillas Twig de servidor por elemento; los elementos se renderizan en el navegador.

## Cómo el editor registra elementos

El editor carga seis scripts en este orden, definidos en `dxpr_builder_library_info_build()` en `dxpr_builder.module` como la biblioteca `dxpr_builder/editor.builder`:

1. `dxpr_templates.js`: plantillas twig.js de cliente compiladas desde `dxpr_builder/templates/`.
2. `dxpr_global.js`: ayudas compartidas y la integración CKEditor 5.
3. `dxpr_events.js`
4. `dxpr_param_types.js`: pone un objeto por tipo de entrada en `window.dxprBuilder.dxpr_param_types`.
5. `dxpr_elements.js`: crea el array `window.dxprBuilder.dxpr_elements`y pone una definición por elemento.
6. `dxpr_builder.js`: el editor. Cuando este archivo se ejecuta llama a `create_dxpr_elements()`, que recorre `window.dxprBuilder.dxpr_elements` y para cada definición:
    - llama a `register_animated_element(base, is_container, Class)`, así que la clase extiende `AnimatedElement` (pestaña Style, pestaña Animation, HTML ID, HTML classes y parámetros de colocación);
    - añade los parámetros heredados a los `params` de la definición;
    - convierte cada parámetro con `make_param_type()`, que cruza `param.type` con los tipos de parámetro registrados;
    - mezcla las propiedades de la definición en el prototipo de la clase.

Una definición tiene que estar en el array después de que `dxpr_elements.js` se ejecutó y antes de que `dxpr_builder.js` se ejecute. Los seis archivos se cargan con el atributo `defer`, así que el orden de documento de las etiquetas de script es el orden de ejecución.

## La definición de elemento

Este es el elemento Alert de `dxpr_builder/dxpr_elements.js`(fuente: `dxpr_builder/build/dxpr-elements/elements/az_alert.js`), recortado a las propiedades que cada elemento necesita. El original también fija `el_class`, un manejador `onCancel` y un listener `focusout`.

```javascript
window.dxprBuilder.dxpr_elements.push({
  base: "az_alert",
  name: Drupal.t("Alert"),
  nameEn: "Alert",
  icon: "az-custom-icon az-alert-icon",
  largeSettingsWindow: true,
  hidden: false,
  params: [
    {
      type: "textarea",
      heading: Drupal.t("Message"),
      param_name: "content",
      value: Drupal.t(
        "This is a placeholder text. Your actual content will go here. Edit this to include your own information.",
      ),
    },
    {
      type: "dropdown",
      heading: Drupal.t("Type"),
      param_name: "type",
      description: Drupal.t("Select message type."),
      getText: (opt) => opt.text,
      getValue: (opt) => opt.value,
      getDefault: (param) => param.value["alert-success"].value,
      value: {
        "alert-success": { text: Drupal.t("Success"), value: "alert-success" },
        "alert-info": { text: Drupal.t("Info"), value: "alert-info" },
        "alert-warning": { text: Drupal.t("Warning"), value: "alert-warning" },
        "alert-danger": { text: Drupal.t("Danger"), value: "alert-danger" },
      },
      passthrough: true,
    },
  ],
  show_settings_on_create: true,
  has_content: true,
  render(...args) {
    const state = {
      class: `az-element az-alert alert ${this.attrs.type} ${this.get_el_classes()} ${this.get_content_classes()}`,
      style: this.attrs.style,
      message: this.attrs.content,
    };
    this.dom_element = this.baseclass.prototype.render_template(
      "elements/alert/alert",
      state,
    );
    this.baseclass.prototype.render.apply(this, args);
  },
  save_history() {
    const container = this.get_my_container();
    container.save_container_history(Drupal.t("Edit alert"));
  },
});

```

| Propiedad | Propósito |
|---|---|
| `base` | Nombre de máquina. Se escribe en el HTML guardado como `data-azb="az_alert"` y se usa para encontrar la clase cuando la página se analiza de nuevo. Conserva el prefijo `az_`. |
| `name`, `nameEn` | Etiqueta en el selector de elementos y su forma sin traducir. |
| `icon` | Clases CSS para el icono del selector. Los iconos incluidos se definen en `dxpr_builder/sass/dxpr-builder-backend/components/_custom-icons.scss`; un elemento personalizado necesita su propio CSS para su clase de icono. |
| `category` | Pestaña del selector. Los elementos de diseño usan `Drupal.t("Layout")`; bloques y vistas usan `"CMS"`. Omítelo para la pestaña de contenido predeterminada. |
| `is_container` | `true` cuando el elemento contiene otros elementos. |
| `largeSettingsWindow` | Abre el diálogo de ajustes en el tamaño grande. |
| `hidden` | Oculta el elemento del selector manteniéndolo renderizable. |
| `params` | Ajustes mostrados en el diálogo. Ver abajo. |
| `show_settings_on_create` | Abre el diálogo de ajustes en cuanto el elemento se suelta. |
| `has_content` | El elemento tiene un área de contenido editable (`dom_content_element`). |
| `render(...args)` | Construye `this.dom_element` desde `this.attrs`, y luego llama al `render` base. |
| `save_history()` | Pone una entrada de deshacer con etiqueta. |
| `parse_attrs_from_dom(element)` | Opcional. Lee valores de parámetros de vuelta desde el marcado guardado, usado junto con `skip_data_azb` y `skip_params` por elementos como Image que guardan valores en los atributos `<img>` en lugar de `data-azat-*`. |

### Parámetros

Cada entrada en `params` tiene:

| Clave | Significado |
|---|---|
| `type` | Uno de los tipos de parámetro registrados (siguiente sección). |
| `heading` | Etiqueta del campo. |
| `param_name` | Clave en `this.attrs` y, en el HTML guardado, el atributo `data-azat-<param_name>`. `content` es especial: es el cuerpo del elemento, no un atributo. |
| `value` | Valor predeterminado. Para `dropdown` es un objeto de opciones con callbacks `getText`, `getValue` y `getDefault`. |
| `description` | Texto de ayuda bajo el campo. |
| `tab` | Agrupa el campo en un acordeón con nombre, por ejemplo `Drupal.t("SEO")`. |
| `can_be_empty` | Guarda una cadena vacía en lugar de quitar el atributo. |
| `safe` | Omite la codificación URL del valor guardado. |

Los tipos de parámetro son los valores `type` puestos en `window.dxprBuilder.dxpr_param_types` en `dxpr_builder/dxpr_param_types.js`: `boxmodel`, `checkbox`, `checkboxes`, `colorpicker`, `datetime`, `dropdown`, `dxb_slider`, `html`, `icon`, `image`, `images`, `link`, `links`, `numericfield`, `row_layout`, `saved_datetime`, `social_links`, `textarea`, `textfield` y `video`. Un tipo que no registras vuelve a una entrada de texto simple (`BaseParamType`).

No reutilices un `param_name` que cada elemento hereda, como `color`, `background`, `el_class`, `hash` o los nombres de animación `an_*`. La pestaña Style ya posee `color`, así que un segundo parámetro con ese nombre nunca se escribe en `data-azat-color`.

### Qué se guarda

`update_data()` en `dxpr_builder.js` escribe el elemento a HTML:

- `data-azb="<base>"` en el elemento raíz;
- `data-azat-<param_name>="<value>"` para cada parámetro cuyo valor difiere de su predeterminado, codificado en URL salvo que el parámetro fija `safe: true`;
- los parámetros de la pestaña Style como `style` en línea y clases, nunca como atributos `data-azat-*`;
- `data-azcnt="true"` en el elemento de contenido cuando `has_content`está fijado.

Quienes no reciben el editor reciben este HTML sin sus atributos `data-az*`: `DxprBuilderService::parseDocumentForCleanup()`los quita, salvo una lista fija que conserva en elementos animados y en elementos dinámicos integrados como Carousel. Si el elemento necesita JavaScript o CSS para visitas, pon un atributo `data-dxpr-builder-libraries="<key>"` en su raíz: `parseDocumentForTemplateLibrary()` adjunta `dxpr_builder/elements.<key>` por cada clave sin barra. Las claves son las bibliotecas `elements.*` en `dxpr_builder.libraries.yml`; una clave con barra solo se respeta cuando es el `library` de un conjunto de iconos registrado.

## Ejemplo mínimo funcional

El ejemplo añade un elemento "Notice" que renderiza un párrafo con un color elegido. Crea su DOM directamente porque las plantillas twig.js compiladas en `dxpr_templates.js` son parte del paquete del editor y otro módulo no puede extenderlas.

1. Crea `mymodule/js/notice-element.js`:

    ```javascript
    window.dxprBuilder.dxpr_elements.push({
      base: "az_notice",
      name: Drupal.t("Notice"),
      nameEn: "Notice",
      icon: "az-custom-icon az-alert-icon",
      params: [
        {
          type: "textfield",
          heading: Drupal.t("Text"),
          param_name: "text",
          value: Drupal.t("Notice text"),
        },
        {
          type: "colorpicker",
          heading: Drupal.t("Colour"),
          param_name: "notice_color",
          value: "#0d6efd",
        },
      ],
      show_settings_on_create: true,
      render(...args) {
        const el = document.createElement("div");
        el.className = `az-element az-notice ${this.get_el_classes()}`;
        if (this.attrs.style) {
          el.setAttribute("style", this.attrs.style);
        }
        const p = document.createElement("p");
        p.style.color = this.attrs.notice_color;
        p.textContent = this.attrs.text;
        el.appendChild(p);
        this.dom_element = el;
        this.baseclass.prototype.render.apply(this, args);
      },
      save_history() {
        this.get_my_container().save_container_history(Drupal.t("Edit notice"));
      },
    });

    ```
2. Inserta el archivo en la biblioteca del editor entre `dxpr_elements.js` (peso `-4`) y `dxpr_builder.js` (peso `-3`) en `mymodule.module`:

    ```php
    /**
     * Implements hook_library_info_alter().
     */
    function mymodule_library_info_alter(array &$libraries, string $extension): void {
      if ($extension === 'dxpr_builder' && isset($libraries['editor.builder'])) {
        $path = '/' . \Drupal::service('extension.list.module')->getPath('mymodule');
        $libraries['editor.builder']['js'][$path . '/js/notice-element.js'] = [
          'weight' => -3.5,
          'preprocess' => FALSE,
          'attributes' => ['defer' => TRUE],
        ];
      }
    }

    ```

    `preprocess: FALSE` y `defer` coinciden con las entradas que el módulo define para sus propios archivos, así que el archivo conserva su lugar en la secuencia vengan los recursos del editor de la CDN o de una compilación local.
3. Limpia la caché y abre una página del constructor:

    ```bash
    drush cr

    ```
4. Añade el elemento desde el selector, fija su texto y color, y guarda. El campo guarda:

    ```html
    <div class="az-element az-notice" id="..." data-azb="az_notice"
         data-azat-text="Hello" data-azat-notice_color="%23198754">
      <p style="color: #198754;">Hello</p>
    </div>

    ```

    Una visita recibe el mismo marcado sin los atributos `data-az*`. El párrafo conserva su color en línea, así que este elemento no necesita biblioteca de visitas.

## Límites que debes conocer

- **Los perfiles no enumeran elementos personalizados.** La lista de elementos en un perfil del constructor viene de una lista fija en `src/Form/DxprBuilderProfileForm.php` y `src/Service/Handler/ProfileHandler.php`. Los elementos fuera de esa lista nunca los oculta un perfil.
- **Icons for the picker**: los iconos del selector necesitan CSS de tu módulo. Las clases `az-custom-icon` incluidas solo cubren los elementos incluidos.
- **`DxprBuilderService::getDxprElementsFolders()`** y su `hook_dxpr_builder_elements_folders_alter()` devuelven una lista de carpetas `elements` en temas, pero nada en el editor lee esa lista. No es una ruta de registro.
- **Style and Animation tabs** las añade por ti `register_animated_element()`; no redefinas esos parámetros.

## ¿Qué sigue?

- [API de hooks](/es/hc/documentacion/aprender-dxpr-builder/desarrollo/api-hooks) para los hooks PHP que añaden clases de utilidades y conjuntos de iconos.
- [Arquitectura](/es/hc/documentacion/aprender-dxpr-builder/desarrollo/arquitectura) para ver cómo encajan el editor y el formato de campo.