Ir al contenido principal

Sección 10

10 · Theme App Extensions

Cómo tu app inyecta UI en cualquier theme.

En una frase

Las Theme App Extensions permiten que una app distribuya snippets de Liquid que los comerciantes colocan en su theme desde el Theme Editor — sin que el comerciante tenga que tocar el código del theme. El código vive en el repositorio de la app; Shopify lo registra como blocks disponibles. Tres tipos: app blocks (colocables en secciones), app embeds (inyección global), y app proxy (rutas del servidor bajo el dominio del comerciante).

Qué vas a ejecutar en esta sección

  1. shopify app generate extension --type theme — Scaffold de la extensión (una sola vez) — desde ~/proyectos/shopify/brew-atlas/brew-atlas-app/
  2. shopify app dev — Dev server para iterar el bloque en el Theme Editor del dev store — desde ~/proyectos/shopify/brew-atlas/brew-atlas-app/
  3. shopify app deploy — Publicar una versión inmutable al registro de Shopify — desde ~/proyectos/shopify/brew-atlas/brew-atlas-app/

Buscá los bloques con badge ✓ Ejecutar en el contenido. Los bloques con 📖 Referencia son ilustrativos — no los ejecutes.

Qué es y por qué existe

Antes de las Theme App Extensions, el flujo estándar de una app que necesitaba inyectar UI en el storefront era: “copiad y pegad este snippet en vuestra carpeta snippets/, luego añadid {% render 'mi-snippet' %} donde queráis”. Ese flujo era frágil, no escalable, y rompía con cualquier actualización del theme. Las Theme App Extensions existen para resolver exactamente ese problema.

Con una Theme App Extension, el código de tu snippet — Liquid, CSS, JavaScript — vive en el repositorio de tu app, versionado y desplegado por el CLI de Shopify. El comerciante no necesita editar ningún archivo; simplemente va al Theme Editor, elige su sección, hace clic en “Añadir bloque” y ve tu extensión listada bajo la categoría “Apps”. La coloca donde quiere y configura sus settings desde la interfaz visual. Si desinstala la app, el bloque desaparece automáticamente del theme.

Esto es fundamental para distribuir apps al App Store de Shopify: no puedes pedirle a cada comerciante que edite su theme manualmente. Tienes que distribuir la UI como una extensión gestionada por el sistema.

Los tres tipos

App block es el tipo más frecuente. Se puede colocar dentro de cualquier sección como un bloque adicional. El comerciante lo arrastra al lugar que prefiere — en la PDP, en la página de colección, en el home. Es el mecanismo para features como “barra de envío gratis”, “selector de café”, “timer de preparación”, “calculadora de suscripción”.

App embed es una extensión global: se activa o desactiva para el theme entero, sin colocarse en una sección específica. Shopify lo inyecta en el layout theme.liquid automáticamente. El caso de uso clásico es el tracking script de analytics, el banner de cookies, o el widget de chat de soporte. No le das al comerciante control de posición; o está activo en todo el theme, o no está.

App proxy no es UI — es una ruta. Cuando el comerciante activa el proxy, cualquier request a https://su-tienda.com/apps/tu-app/* se redirige al servidor de tu app. Shopify actúa como proxy: el visitante ve el dominio del comerciante, pero el contenido lo genera tu servidor. Útil para widgets renderizados en servidor (como un estado de pedido o un buscador avanzado) que necesitan datos frescos de tu backend sin comprometer el dominio.

Estructura del proyecto

El CLI de Shopify genera la estructura correcta cuando ejecutas shopify app generate extension --type theme desde ~/proyectos/shopify/brew-atlas/brew-atlas-app/. El resultado dentro del repositorio de la app:

// ~/proyectos/shopify/brew-atlas/brew-atlas-app/extensions/brewing-timer/
extensions/
└── brewing-timer/
    ├── blocks/
    │   └── brewing-timer.liquid
    ├── assets/
    │   └── brewing-timer.js
    ├── locales/
    │   └── en.default.json
    └── shopify.extension.toml

La carpeta blocks/ contiene los archivos Liquid de la extensión. La carpeta assets/ contiene JavaScript y CSS propios de la extensión — no los del theme. Los archivos en assets/ se sirven desde la CDN de Shopify; el filtro asset_url en Liquid los referencia correctamente.

La carpeta locales/ contiene las traducciones de los settings del bloque. El schema del bloque puede usar claves de traducción — si no hay traducciones, Shopify muestra el valor crudo del label.

El TOML de la extensión

El archivo shopify.extension.toml declara la extensión y sus metadatos:

# ~/proyectos/shopify/brew-atlas/brew-atlas-app/extensions/brewing-timer/shopify.extension.toml
api_version = "2026-04"
 
[[extensions]]
name = "Brewing Timer"
handle = "brewing-timer"
type = "theme"

El handle debe ser único en tu app. Una vez desplegada, no lo cambies — Shopify lo usa como identificador estable. El type = "theme" le indica al CLI que esto es una Theme App Extension. El campo api_version determina qué features de la plataforma tienes disponibles.

El bloque Liquid

El archivo principal de un app block combina el HTML/Liquid con el schema JSON que define sus settings configurables.

Edita ~/proyectos/shopify/brew-atlas/brew-atlas-app/extensions/brewing-timer/blocks/brewing-timer.liquid:

{%- comment -%} ~/proyectos/shopify/brew-atlas/brew-atlas-app/extensions/brewing-timer/blocks/brewing-timer.liquid {%- endcomment -%}
<brewing-timer
  data-minutes="{{ block.settings.minutes }}"
  data-method="{{ block.settings.method }}"
></brewing-timer>
 
<script src="{{ 'brewing-timer.js' | asset_url }}" defer></script>
 
{% schema %}
{
  "name": "Brewing Timer",
  "target": "section",
  "settings": [
    {
      "type": "number",
      "id": "minutes",
      "label": "Tiempo de preparación (minutos)",
      "default": 4
    },
    {
      "type": "select",
      "id": "method",
      "label": "Método de preparación",
      "options": [
        { "value": "v60",       "label": "V60" },
        { "value": "aeropress", "label": "Aeropress" },
        { "value": "french",    "label": "French Press" },
        { "value": "chemex",    "label": "Chemex" }
      ],
      "default": "v60"
    }
  ]
}
{% endschema %}

El target: "section" indica que este bloque se puede colocar dentro de cualquier sección que acepte bloques de tipo @app. Esto es lo que tienes que agregar al schema de tus propias secciones para hacerlas compatibles.

Edita ~/proyectos/shopify/brew-atlas/brew-atlas-theme/sections/product-template.liquid (fragmento del schema):

{%- comment -%} ~/proyectos/shopify/brew-atlas/brew-atlas-theme/sections/product-template.liquid {%- endcomment -%}
{% schema %}
{
  "name": "Producto",
  "blocks": [
    { "type": "@app" }
  ]
}
{% endschema %}

Sin { "type": "@app" } en el schema de la sección, los bloques de app no aparecen en el editor para esa sección. Los themes de Shopify modernos (como Dawn) ya lo incluyen en sus secciones principales — si estás construyendo un theme propio, tienes que agregarlo explícitamente.

El JavaScript del bloque

El JS vive en assets/ y Shopify lo sirve desde su CDN. Como el bloque se renderiza como un Web Component, el JS registra el custom element.

Edita ~/proyectos/shopify/brew-atlas/brew-atlas-app/extensions/brewing-timer/assets/brewing-timer.js:

// ~/proyectos/shopify/brew-atlas/brew-atlas-app/extensions/brewing-timer/assets/brewing-timer.js
class BrewingTimer extends HTMLElement {
  connectedCallback() {
    const minutes = parseInt(this.dataset.minutes ?? '4', 10);
    const method = this.dataset.method ?? 'v60';
    this.render(minutes, method);
  }
 
  render(minutes, method) {
    const totalSeconds = minutes * 60;
    this.innerHTML = `
      <div class="brewing-timer">
        <p class="brewing-timer__method">${method.replace(/-/g, ' ').toUpperCase()}</p>
        <p class="brewing-timer__time">${minutes}:00</p>
        <button class="brewing-timer__btn" data-total="${totalSeconds}">Iniciar</button>
      </div>
    `;
    // Lógica del timer omitida — conecta aquí tu countdown
  }
}
 
if (!customElements.get('brewing-timer')) {
  customElements.define('brewing-timer', BrewingTimer);
}

El guard !customElements.get('brewing-timer') es importante: si el comerciante coloca dos instancias del bloque en la misma página, el script se carga una sola vez (porque está en el mismo URL), pero customElements.define fallaría en la segunda definición sin el guard.

App proxy: el tercer tipo

Cuando tu extensión necesita datos frescos de tu servidor — por ejemplo, mostrar el estado de suscripción del cliente o los precios actuales de una tarificación dinámica — el app proxy es la respuesta.

En el shopify.app.toml de la app (no de la extensión) declaras el proxy:

# ~/proyectos/shopify/brew-atlas/brew-atlas-app/shopify.app.toml (fragmento)
[app_proxy]
url = "https://tu-servidor.com"
subpath = "brew-atlas"
prefix = "apps"

Con esa declaración, las requests a https://tienda.myshopify.com/apps/brew-atlas/* se redirigen a https://tu-servidor.com/*. Tu servidor recibe el request con un header X-Shopify-Shop-Domain que identifica la tienda, más un hash HMAC que debes validar para confirmar que el request viene de Shopify.

El contenido que devuelve tu servidor se inyecta en el tema del comerciante si usas la cabecera Content-Type: application/liquid — Shopify renderiza el Liquid que devuelvas en el contexto del tema. Para contenido JSON, devuelve Content-Type: application/json.

Flujo de despliegue

El ciclo de vida de una Theme App Extension tiene dos fases:

Desarrollo: shopify app dev levanta un servidor local con un tunnel de Cloudflare. Las extensiones en el dev store del comerciante apuntan a tu servidor local. Los cambios en el Liquid se reflejan casi en tiempo real.

Producción: shopify app deploy compila y sube una nueva versión inmutable de todas las extensiones al registro de Shopify. Las versiones son inmutables — no puedes modificar una versión publicada, solo publicar una nueva. Los comerciantes que ya tienen la extensión instalada reciben la actualización automáticamente si hay una sola versión publicada, o pueden elegir versión si hay múltiples activas simultáneamente.

Este modelo de versionado inmutable es deliberado: si una versión rompe algo, puedes publicar una corrección como nueva versión sin afectar retroactivamente a los comerciantes que estaban en la versión anterior.

Puentes mentales

Desde npm / Web Components

Una Theme App Extension es como publicar un Web Component en un registro privado — pero el registro es Shopify, no npm, y el consumidor es el Theme Editor del comerciante, no un import en tu código. El comerciante instala el componente arrastrando un bloque en el editor, no escribiendo npm install. La distribución es visual; la implementación es código tuyo que Shopify hospeda.

La distinción entre app block y app embed es la distinción entre un componente que el usuario coloca donde quiere (un <dialog> que abres bajo demanda) y un script global que se monta en el document.body al cargar la página (un analytics tracker). Misma tecnología, diferente modelo de integración.

Estado 2026

Lo relevante al 2026:

  • Theme blocks (la feature de Sections Anywhere de 2025) y app blocks coexisten. Tus bloques de app aparecen en el editor junto a los bloques nativos del theme sin colisión.
  • Built for Shopify: para la certificación, Shopify revisa que tus bloques no hagan llamadas a servidores externos desde Liquid (solo desde JS en el cliente, con network_access declarado en el TOML cuando aplique), que pasen la auditoría de accesibilidad, y que no degraden el Core Web Vitals del theme por encima de un umbral.
  • Versiones múltiples: desde 2024, puedes tener hasta tres versiones publicadas simultáneamente. Esto permite hacer rollout gradual o dar a comerciantes tiempo para migrar a la versión nueva.
  • La CLI 3.x gestiona el versionado automáticamente con shopify app deploy --force para forzar una nueva versión aunque no haya cambios detectados.

Anatomía: el flujo completo

Resumen del ciclo completo, desde el CLI hasta el Theme Editor del comerciante:

  1. shopify app generate extension --type theme — crea la carpeta con la estructura correcta
  2. Editas el Liquid, JS, y el TOML de la extensión
  3. shopify app dev — levanta dev server; el extension preview aparece en el theme editor del dev store
  4. El comerciante añade el bloque en el Theme Editor y configura sus settings
  5. shopify app deploy — sube una versión inmutable; todos los comerciantes que tienen la app instalada reciben la actualización

El comerciante nunca ve ningún código. Ve “Brewing Timer” como una opción en el picker de bloques, lo coloca donde quiere, y ajusta los minutos y el método desde el panel lateral del editor.

Proyecto · Brew Atlas

Brew Atlas · Paso 10

La extensión brewing-timer de Brew Atlas permite a los comerciantes que usan la app colocar un timer interactivo de preparación de café en cualquier sección del theme — en la PDP, en el home, o en una página de contenido.

La estructura vive en ~/proyectos/shopify/brew-atlas/brew-atlas-app/extensions/brewing-timer/. Para generarla por primera vez, desde ~/proyectos/shopify/brew-atlas/brew-atlas-app/:

Ejecutar Scaffold de la extension (una sola vez)

shopify app generate extension —type theme

Para probarla en un dev store, desde ~/proyectos/shopify/brew-atlas/brew-atlas-app/:

Ejecutar Dev server de la app

shopify app dev

El dev store del comerciante tiene que tener la app instalada. En el Theme Editor, abrir cualquier sección que tenga bloques habilitados → Añadir bloque → Apps → Brewing Timer.

Para desplegar a producción, desde ~/proyectos/shopify/brew-atlas/brew-atlas-app/:

Ejecutar Deploy inmutable al registro de Shopify

shopify app deploy

Shopify muestra la lista de extensiones detectadas, sus cambios respecto a la versión anterior, y pide confirmación antes de publicar.

Qué vas a crear/editar en esta sección (archivos):

  • ~/proyectos/shopify/brew-atlas/brew-atlas-app/extensions/brewing-timer/blocks/brewing-timer.liquid (ya existe en el repo del tutorial, referenciá y modificá).
  • ~/proyectos/shopify/brew-atlas/brew-atlas-app/extensions/brewing-timer/assets/brewing-timer.js.
  • ~/proyectos/shopify/brew-atlas/brew-atlas-app/extensions/brewing-timer/shopify.extension.toml.

Comandos a ejecutar (orden) — todos desde ~/proyectos/shopify/brew-atlas/brew-atlas-app/:

  1. shopify app generate extension --type theme (solo la primera vez — el scaffold).
  2. shopify app dev — cada vez que quieras iterar el bloque en el dev store.
  3. shopify app deploy — cuando estés listo/lista para publicar una versión.

Errores comunes

Cuidado

Los app blocks no pueden hacer requests HTTP al servidor de tu app desde Liquid en tiempo de render. El Liquid de un app block se renderiza en el contexto del theme, y el resultado es HTML estático — no puedes llamar a fetch('https://tu-servidor.com/datos') desde Liquid. Si necesitas datos frescos de tu servidor, tienes dos opciones: app proxy (el servidor genera el fragmento) o JavaScript en el cliente (el bloque carga y hace el fetch desde el browser después del render inicial).

Cuidado

El filtro asset_url en una extensión de theme apunta a la carpeta assets/ de la extensión, no a la carpeta assets/ del theme. Si el comerciante tiene un archivo con el mismo nombre en su theme, no hay colisión — son rutas distintas. Pero si intentas referenciar un asset del theme desde tu extensión con asset_url, el resultado es inesperado. Los assets de tu extensión y los del theme son espacios separados.

Nota

El handle de la extensión en el TOML es un identificador permanente. Shopify lo usa como clave para rastrear qué bloques están colocados en qué themes de qué comerciantes. Si cambias el handle después de haber desplegado, Shopify trata la extensión como una nueva; los comerciantes pierden la configuración de los bloques colocados con el handle anterior y ven el bloque como “no disponible”. Define el handle correcto antes del primer deploy.

Tip

Para probar el comportamiento del app embed sin publicar la extensión, puedes previsualizarlo directamente en el Theme Editor del dev store. El CLI en modo dev sincroniza los cambios en tiempo real — editas el Liquid local, recargas el Theme Editor, y ves el resultado. No necesitas hacer deploy para iterar durante el desarrollo.

Checklist senior

Checklist senior

  • Puedes explicar la diferencia entre app block, app embed, y app proxy: qué hace cada uno, cuándo usar cuál
  • Sabes que { "type": "@app" } en el schema de una sección es lo que habilita los bloques de app en esa sección
  • Entiendes que los assets de la extensión viven en la CDN de Shopify, no en el theme del comerciante, y que asset_url los referencia correctamente
  • Sabes que las versiones de Theme App Extensions son inmutables — un deploy siempre crea una versión nueva
  • Puedes articular por qué los app blocks no pueden hacer requests HTTP a servidores externos desde Liquid
  • Entiendes el flujo de despliegue: shopify app dev para iteración local, shopify app deploy para producción
  • Sabes que el handle de la extensión no debe cambiar después del primer deploy
  • Puedes describir cómo un app proxy funciona como intermediario entre el dominio del comerciante y tu servidor

Quiz

Quiz

Quiz · ¿Lo tenés claro?

5 preguntas · respondé para marcar la sección como completada.

  1. 1. ¿Cuál es la diferencia esencial entre un `app block` y un `app embed`?

  2. 2. ¿Qué permite colocar bloques de tipo 'Apps' en una sección de theme custom?

  3. 3. ¿Se pueden hacer requests HTTP a un servidor externo desde Liquid de un app block en tiempo de render?

  4. 4. ¿Qué pasa con una versión publicada de Theme App Extension después de `shopify app deploy`?

  5. 5. ¿Por qué no cambiar el `handle` de la extensión después del primer deploy?

Siguiente

Con las Theme App Extensions cubriendo la inyección de UI en el storefront, el siguiente bloque entra en el territorio del Admin: cómo construir una app embebida que vive dentro del panel de administración del comerciante, usando React Router v7, Polaris como sistema de diseño, y App Bridge para las interacciones nativas del Admin.