Construye, valida y publica temas Liquid para tiendas con la CLI de VELSEFY. Todo lo que necesitas para llevar tu tema del editor local a la tienda del cliente.

Esta es la guía de TEMAS.
Si lo que desarrollas son APPS (integraciones de API, OAuth y webhooks), esa es una documentación distinta. Los flujos de apps no comparten estructura, tokens ni carpetas con los temas — no mezcles ambos contenidos. A continuación solo hablamos de temas Liquid.
Un tema en VELSEFY define la apariencia y la estructura de una tienda. Está hecho de archivos Liquid + JSON, organizado como una estructura de tienda estándar. Con la CLI puedes descargar un tema, editarlo localmente y publicarlo — sin tocar el panel web.
Lo que puedes construir y modificar:
Bloques arrastrables con su propio {% schema %}. Cada una expone settings y blocks editables.
Fragmentos reutilizables que incluís con {% render 'x' %}.
CSS, JS, imágenes y fuentes servidos desde assets/ con asset_url.
Páginas templates/*.json que componen la tienda con secciones.
El flujo típico es descargar → editar → validar → publicar. Empezá por el Quickstart para tener un tema corriendo en local en menos de dos minutos.
La CLI es el único requisito. Se instala global, se autentica con tu token de desarrollador y listo.
npm install -g @velsefy/cliGenera el token desde el panel de tu tienda → Aplicaciones Privadas. Este token identifica tu cuenta y define qué puedes hacer (ver Tokens y scopes).
Nunca compartas tu token
# Guarda el token (se almacena localmente en tu máquina)
velsefy login --token <TU_TOKEN>
# Lista los temas instalados en tu tienda
velsefy theme listvelsefy theme pull-catalog --sku THEME-DEFAULT -o ./mi-tema
# → "Descargados N asset(s) del catálogo en ./mi-tema."# Validación local (sin token, puro offline)
velsefy theme validate --dir ./mi-tema
# Sube los cambios a la instalación de tu tienda
velsefy theme push --install <INSTALL_ID> --dir ./mi-tematheme push, corre theme validate. Un tema inválido nunca se sube.VELSEFY_TOKEN) y evita escribirlo a mano en cada comando.Un tema es una carpeta con subcarpetas fijas. El servidor solo acepta estos directorios; cualquier ruta fuera de ellos se rechaza al publicar.
mi-tema/
├── layout/
│ └── theme.liquid # Plantilla base (HTML + {{ content_for_layout }})
├── sections/ # Secciones arrastrables (cada una con {% schema %})
│ ├── hero.liquid
│ └── product_main.liquid
├── snippets/ # Fragmentos reutilizables ({% render 'x' %})
│ └── price.liquid
├── assets/ # CSS / JS / imágenes / fuentes
│ ├── theme.css
│ └── theme.js
├── config/
│ ├── settings_schema.json # Settings globales (array de grupos)
│ └── settings_data.json # Valores actuales del tema
├── templates/ # Plantillas de página (*.json)
│ ├── index.json
│ ├── product.json
│ └── page.json
└── theme.json # (solo al exportar; wrapper con name/version)| Carpeta | Propósito |
|---|---|
layout/ | La plantilla base. Un solo theme.liquid con el shell HTML y el output dinámico con {{ content_for_layout }}. |
sections/ | Secciones editables desde el editor. Cada archivo Liquid termina en {% schema %} para declarar sus settings. |
snippets/ | Fragmentos que se reutilizan en varias vistas vía {% render 'x' %}. No se pueden arrastrar. |
assets/ | CSS, JS, SVGs, imágenes y fuentes. Se referencian con {{ 'x.css' | asset_url }}. |
config/ | settings_schema.json (declaración de settings) y settings_data.json (valores). |
templates/ | Páginas en JSON que componen el layout con secciones. |
Carpetas y extensiones permitidas
Carpetas permitidas: assets · sections · layout · snippets · config · templates.
Extensiones permitidas: css · js · mjs · json · svg · png · jpg · jpeg · webp · gif · ico · woff · woff2 · ttf · txt · xml · liquid.
Los path traversal (.., \\, %) están bloqueados.
assets, sections, layout, snippets, config o templates.css, js, json, svg o liquid..., con backslash o escapando barras. Estos valores se bloquean por seguridad.Cada sección es un archivo sections/nombre.liquid y DEBE terminar con un bloque {% schema %}…{% endschema %} que declara su configuración. Ahí se definen settings (configuración de la sección), blocks (piezas internas repetibles) y presets (cómo aparece al agregarla).
{% style %}
#velsefy-section-{{ section.id }} .mi-hero {
color: {{ section.settings.text_color }};
background: {{ section.settings.bg_color }};
}
{% endstyle %}Los tres pilares del schema:
| Clave | Qué define | Cómo se lee |
|---|---|---|
| settings | Inputs editables de la sección. | {{ section.settings.x }} |
| blocks | Sub-bloques repetibles y arrastrables. | {% for block in section.blocks %} → {{ block.settings.x }} |
| presets | Apariencia en el selector al crear la sección. | — |
VELSEFY soporta este subconjunto. Si usas un tipo que no está en la lista, el renderer no lo dibuja y velsefy theme validate lo reporta como error.
{% schema %}
{
"name": "Hero",
"tag": "section",
"max_blocks": 4,
"settings": [
{ "type": "text", "id": "title", "label": "Título", "default": "Bienvenido" },
{ "type": "color", "id": "text_color", "label": "Color de texto", "default": "#111827" },
{ "type": "image_picker", "id": "logo", "label": "Imagen" },
{ "type": "url", "id": "cta_link", "label": "Enlace del botón" },
{ "type": "range", "id": "font_scale", "label": "Escala", "min": 100, "max": 130, "step": 5, "unit": "%", "default": 100 }
],
"blocks": [
{
"type": "text",
"name": "Texto",
"settings": [
{ "type": "textarea", "id": "text", "label": "Texto" }
]
}
],
"presets": [
{ "name": "Hero", "blocks": [ { "type": "text" } ] }
]
}
{% endschema %}Los settings globales se declaran en config/settings_schema.json como un array de grupos. Cada grupo tiene un name y un array de settings con los mismos tipos permitidos que en las secciones.
[
{
"name": "theme_info",
"theme_name": "Mi Tema",
"theme_version": "1.0.0",
"theme_author": "Mi Agencia",
"theme_documentation_url": "https://docs.velsefy.com",
"theme_support_url": "https://support.velsefy.com"
},
{
"name": "Colores",
"settings": [
{ "type": "color", "id": "primaryColor", "label": "Color Principal", "default": "#000000" },
{ "type": "range", "id": "fontScale", "label": "Escala", "min": 100, "max": 130, "step": 5, "unit": "%", "default": 100 },
{ "type": "select", "id": "fontFamily", "label": "Fuente", "options": [{ "value": "Inter", "label": "Inter" }], "default": "Inter" },
{ "type": "checkbox","id": "enableX", "label": "Habilitar X", "default": false }
]
}
]Usa {{ settings.primaryColor }} o {{ settings.fontScale }} en cualquier Liquid del tema.
La versión vive solo en theme_info.theme_version (MAJOR.MINOR.PATCH). Es la única fuente de verdad del tema. El grupo theme_info se descarta al renderizar: es solo metadata.
Versiona SIEMPRE con semver
No pongas la versión en otro archivo. Al publicar con velsefy theme release, la plataforma maneja la versión del catálogo y hace bump (patch por defecto; puedes pedir major/minor en el changelog).
default para cada setting. Un tema sin defaults renderiza vacío y confunde al editor del cliente.id estables (en minúsculas, camelCase o snake_case) y no los cambies entre releases: romperías el settings_data.json existente.settings_schema.json plano y sin grupos es difícil de mantener.Fragmentos reutilizables vía {% render 'nombre', parametros: valor %}. Son la forma de no repetir markup.
{%- if price != blank -%}
<span class="vs-price">{{ price | money }}</span>
{%- endif -%}{% render 'price', price: product.price, compare_at: product.compare_price %}Los archivos de assets/ se referencian con el filtro asset_url.
{{ 'theme.css' | asset_url | stylesheet_tag }}
{{ 'theme.js' | asset_url | script_tag }}Las plantillas templates/*.json componen la página declarando qué secciones van y en qué orden.
{
"sections": {
"main": { "type": "hero", "settings": { "title": "Hola" } }
},
"order": ["main"]
}Problema común: la sección no aparece
Si en el editor no se ve tu sección, la causa casi siempre es un presets ausente o mal escrito en el {% schema %}, o que el type de la plantilla no coincide con el nombre del archivo de sections/. Verifica que la sección tenga al menos un preset y que el archivo exista en la carpeta sections/.
Valida siempre antes de publicar. Hay dos capas: la local con la CLI y la del servidor al publicar. El servidor aplica las mismas reglas, así que un validate limpio evita errores al subir.
# Local, sin necesidad de token (puede correr en CI)
velsefy theme validate --dir ./mi-tema
# → "Versión del tema (theme_info): 1.0.0"
# → "Tema válido." si todo está OK
# → "Tema inválido (N problema(s))." + detalle por archivo si hay errores{% schema %}/{% endschema %} balanceados (y presentes en secciones) y {% if %}/{% endif %} balanceados.
Valida settings[].type contra la whitelist y exige el campo id.
Todo .json debe parsear, y las rutas deben ser permitidas (sin ../\\/%).
Extrae y valida theme_info.theme_version.
Conecta el JSON Schema del paquete para que VS Code valide config/settings_schema.json en vivo (tipos autocompletados + errores). Agrega esto a tu .vscode/settings.json:
{
"json.schemas": [
{
"fileMatch": ["config/settings_schema.json"],
"url": "https://raw.githubusercontent.com/fivepulsespa-spec/velsefy-cli/main/packages/theme-validate/velsefy.settings_schema.json"
}
]
}El schema vive en packages/theme-validate/velsefy.settings_schema.json del repositorio de la CLI.
{% schema %} sin {% endschema %} o con las llaves desbalanceadas. Asegúrate de cerrar el bloque y validar el JSON dentro.settings[].type fuera de la whitelist o sin id. Usa exactamente uno de los tipos soportados.theme_info.theme_version ausente o no semver. El validador exige un MAJOR.MINOR.PATCH válido.Todos los comandos van bajo velsefy theme …, salvo login y logout que son globales.
| Comando | Qué hace | ¿Toca el tema? |
|---|---|---|
| login --token <t> | Guarda localmente tu token de acceso. | No |
| logout | Elimina el token guardado del equipo. | No |
| theme list | Lista los temas instalados en tu tienda (id, nombre, estado). | No |
| theme pull --install <id> | Descarga a local los assets de un tema instalado y guarda el base_updated_at para el push. | Sí (lectura) |
| theme pull-catalog --sku <sku> | Descarga los assets actuales de un tema del catálogo global (el base). | Sí (lectura) |
| theme push --install <id> --dir | Sube los assets locales al tema instalado. Devuelve 409 si hay conflicto. | Sí (escritura) |
| theme validate --dir | Valida el tema local (Liquid, JSON, paths, semver). No pide token. | No |
| theme release --sku --changelog --dir | Publica una release del tema al catálogo global. | Sí (catálogo) |
theme pull descarga TU instalación (la copia privada que editas). theme pull-catalog baja el base oficial del catálogo para empezar de cero.
theme validate es 100% local: útil para pre-push y CI sin credenciales. El resto de comandos de theme sí requieren sesión.
¿Cómo subo cambios sin pisar los del equipo?
El theme push usa el base_updated_at que guarda el theme pull para detectar conflictos. Si alguien más modificó el tema entre que descargaste y subiste, la CLI responde con 409 en lugar de sobrescribir ciegamente. La solución es hacer un theme pull de nuevo, resolver los cambios y reintentar el theme push.
Tu token define qué capacidades tienes, no cómo está construido por dentro. Es un contrato público: los scopes describen solo lo que puedes hacer con temas. La CLI los traduce en acciones concretas.
theme list).theme pull / theme pull-catalog).theme push).theme release).Permite listar y descargar temas: operaciones de lectura sobre tu instalación y el catálogo.
Permite subir cambios y publicar: operaciones de escritura sobre tus temas instalados y releases.
Token de la cuenta vs. credenciales de plataforma
Con el token de tu cuenta editas la copia privada de un tema (pull/push). Para publicar al catálogo global se usan credenciales de plataforma con permiso de publicación. Son dos contratos distintos; no los confundas.
¿Qué hago si mi token queda en el repositorio?
Si un token subió a git o a un historial público, considéralo comprometido y regenera uno nuevo de inmediato. El token viejo, aunque lo borres del archivo, ya no es seguro. Trabaja siempre con velsefy login --token desde una variable de entorno y no lo dejes en el código del tema.
Vamos a crear una sección hero desde cero, validarla y publicarla en tu tienda. Todo en un solo tema local.
velsefy theme pull-catalog --sku THEME-DEFAULT -o ./mi-temaCrea sections/hero.liquid con el render y su schema. Nota que el {% schema %} siempre cierra.
{% comment %} Sección hero construida para el tema de ejemplo {% endcomment %}
<section class="mi-hero" style="background: {{ section.settings.bg_color }}">
<h1 class="mi-hero__title">{{ section.settings.title }}</h1>
{% for block in section.blocks %}
<p class="mi-hero__text">{{ block.settings.text }}</p>
{% endfor %}
{% if section.settings.cta_label != blank %}
<a href="{{ section.settings.cta_link }}" class="mi-hero__cta">
{{ section.settings.cta_label }}
</a>
{% endif %}
</section>
{% schema %}
{
"name": "Hero",
"tag": "section",
"settings": [
{ "type": "text", "id": "title", "label": "Título", "default": "Bienvenido a VELSEFY" },
{ "type": "color", "id": "bg_color", "label": "Fondo", "default": "#f7c05a" },
{ "type": "text", "id": "cta_label", "label": "Texto del CTA", "default": "Comprar" },
{ "type": "url", "id": "cta_link", "label": "Enlace del CTA" }
],
"blocks": [
{
"type": "text",
"name": "Texto",
"settings": [ { "type": "textarea", "id": "text", "label": "Texto" } ]
}
],
"presets": [ { "name": "Hero", "blocks": [ { "type": "text" } ] } ]
}
{% endschema %}{
"sections": {
"main": { "type": "hero", "settings": { "title": "Hola" } }
},
"order": ["main"]
}velsefy theme validate --dir ./mi-tema
# → "Versión del tema (theme_info): 1.0.0"
# → "Tema válido."# Sube los assets a TU instalación
velsefy theme push --install <INSTALL_ID> --dir ./mi-tema
# (Opcional) Publica al catálogo con credenciales de plataforma
velsefy theme release --sku THEME-DEFAULT --changelog "v2: nuevo hero" --dir ./mi-temaSi algo falla al publicar
Que la theme release devuelva 400 validation_failedindica un problema con el archivo señalado (balance Liquid, tipo de setting no soportado o path fuera de la whitelist). Corrígelo en local, pasa theme validate y vuelve a intentar.
Materiales de apoyo para profundizar: la referencia de Liquid, el repositorio de la CLI y el esquema JSON que da autocompletado en VS Code.
Consulta la referencia de etiquetas, filtros y operadores para escribir un markup líquido correcto:{% if %}, {% for %}, {% render %}, asset_url y el resto de la sintaxis.
El código fuente de velsefy-cli. Útil para leer cómo se implementan validate, pull, push y release, y seguir los cambios de la herramienta.
velsefy.settings_schema.json dentro de packages/theme-validate. Permite validar config/settings_schema.json en vivo y con autocompletado.
Si dudas de un parámetro o de la salida de un comando, esta misma guía es el punto de partida: la sección de Comandos de la CLI describe cada subcomando y su efecto.
¿Por qué dice “tema no encontrado”?
Suele significar que el install_id no existe o que el token no tiene acceso a esa tienda. Verifica con velsefy theme list que el tema siga instalado, o que hayas descendido con theme pull desde esa misma instalación. Si el id es correcto pero el tema ya no está, vuelve a crear la instalación y descarga de nuevo.