Ir al contenido principal

Sección 06

06 · Metafields

Tipos, namespaces, y cómo los lees desde Liquid y GraphQL.

En una frase

Los metafields son campos clave-valor tipados que puedes adjuntar a entidades existentes de Shopify: productos, variantes, pedidos, clientes, colecciones, páginas, blogs, artículos, y la tienda misma. Son la forma oficial de extender el modelo de datos de Shopify sin montar tu propia base de datos.

Qué es y por qué existe

Shopify gestiona entidades de negocio — Product, Order, Customer, Collection — con un esquema fijo. Durante años, los desarrolladores que necesitaban agregar datos adicionales recurrían a workarounds: meter información en el campo de descripción, abusar de los tags, o mantener una base de datos externa sincronizada con la tienda. Los metafields existen para eliminar esos workarounds.

Un metafield es un campo adicional que se adjunta a una entidad Shopify existente. Tiene tres partes obligatorias: un namespace (espacio de nombres para evitar colisiones), un key (nombre del campo), y un value tipado. Esa tipificación es la diferencia entre un metafield de 2026 y el sistema ad-hoc de hace cinco años — Shopify valida el valor antes de guardarlo, lo que significa que no puedes guardar texto donde se espera un entero, ni un color inválido donde se espera un color.

El resultado práctico es que puedes modelar datos de dominio específico directamente en Shopify. Para Brew Atlas, el catálogo de cafés tiene atributos que no existen en el modelo estándar de Shopify: origen geográfico, nivel de tueste, notas de cata, altitud de la finca. Esos datos viven como metafields en el producto — y Shopify los almacena, los valida, y los expone vía Liquid, GraphQL, y la Admin UI.

Definitions vs metafields ad-hoc

Antes de crear un metafield, tienes que decidir si vas a usar una definición o dejarlo como ad-hoc.

Una definición de metafield (metafield definition) es un esquema que declaras una vez para un namespace/key determinado. Cuando la definición existe, Shopify sabe el tipo esperado del valor, puede validarlo, puede mostrarlo en la Admin UI como un campo editable por el comerciante, y lo hace elegible como dynamic source en el Theme Editor.

Un metafield ad-hoc — creado directamente vía API sin definición previa — funciona técnicamente, pero tiene restricciones importantes: no aparece en la Admin UI (el comerciante no puede editarlo desde el panel), no puede ser referenciado como dynamic source en un schema de section, y es más difícil de documentar porque no existe un registro central de qué campos ad-hoc tiene tu aplicación.

La regla práctica: si el metafield es para datos que el comerciante puede necesitar editar, siempre crea una definición. Si es para datos que solo tu aplicación escribe y lee (flags internos, datos de sincronización), un metafield ad-hoc puede ser aceptable — pero documéntalo.

El catálogo de tipos

El tipo de un metafield determina cómo Shopify valida y serializa el valor. La lista completa al 2026 incluye:

Texto:

  • single_line_text_field — texto de una línea, sin HTML
  • multi_line_text_field — texto de múltiples líneas
  • rich_text_field — texto enriquecido (HTML limitado, serializado como JSON)

Numéricos:

  • number_integer — entero con signo
  • number_decimal — número decimal

Booleano y fechas:

  • boolean — true o false
  • date — fecha ISO 8601 (YYYY-MM-DD)
  • date_time — fecha y hora ISO 8601

Referencias y media:

  • url — URL absoluta
  • color — color en formato hexadecimal (#RRGGBB)
  • file_reference — referencia a un archivo en la biblioteca de medios de Shopify
  • page_reference, product_reference, variant_reference, collection_reference — referencias a entidades de Shopify
  • metaobject_reference — referencia a una entrada de metaobjeto (verás esto en la sección 07)

Medidas:

  • weight, volume, dimension — con valor y unidad
  • rating — número decimal con escala mínima y máxima

Dinero:

  • money — monto en la moneda de la tienda

Listas:

  • list.single_line_text_field, list.number_integer, list.product_reference, etc. — la mayoría de los tipos tienen una variante list.* que almacena un array de valores del mismo tipo.

Namespaces

El namespace agrupa metafields y previene colisiones entre diferentes aplicaciones. Hay tres categorías:

custom — el namespace por defecto para datos que crea el comerciante directamente desde la Admin UI o con tu app. Los metafields en custom persisten cuando una app se desinstala.

$app: — prefijo especial para apps. El CLI de Shopify transforma $app:tasting_notes en un namespace interno que es privado a tu app. Cuando la app se desinstala, estos metafields se eliminan automáticamente. La declaración en shopify.app.toml te permite desplegar las definiciones con shopify app deploy.

shopify: — reservado para uso interno de Shopify. No puedes crear metafields en este namespace.

Para Brew Atlas, los datos del catálogo de café van en custom porque el comerciante tiene que poder editarlos desde la Admin UI. Si tu app maneja datos internos de sincronización o flags de estado, $app: es la elección correcta.

Acceso desde Liquid

Leer un metafield desde Liquid es directo:

{%- comment -%} ~/proyectos/shopify/brew-atlas/brew-atlas-theme/snippets/product-card.liquid {%- endcomment -%}
{%- liquid
  assign roast = product.metafields.custom.roast_level.value
  assign origin = product.metafields.custom.origin.value
-%}
 
<article class="card">
  {%- comment -%} ... resto del card ... {%- endcomment -%}
 
  {%- if roast != blank -%}
    <span class="badge badge--roast badge--roast-{{ roast }}">
      {{ roast | replace: '-', ' ' | capitalize }}
    </span>
  {%- endif -%}
 
  {%- if origin != blank -%}
    <p class="card__origin">{{ origin }}</p>
  {%- endif -%}
</article>

El path product.metafields.custom.roast_level devuelve el objeto metafield completo (con propiedades value, type, namespace, key). Para acceder solo al valor, agrega .value. El filtro | metafield_tag existe para renderizar ciertos tipos (como file_reference o rating) con el HTML apropiado automáticamente.

Para metafields de tipo list.*, el valor es un array que puedes iterar:

{%- comment -%} ~/proyectos/shopify/brew-atlas/brew-atlas-theme/snippets/tasting-notes.liquid {%- endcomment -%}
{%- assign notes = product.metafields.custom.tasting_notes.value -%}
{%- if notes != blank -%}
  <ul class="tasting-notes">
    {%- for note in notes -%}
      <li class="tasting-notes__item">{{ note }}</li>
    {%- endfor -%}
  </ul>
{%- endif -%}

Acceso desde GraphQL — Storefront API

El Storefront API acepta un array de identifiers para traer múltiples metafields en una sola query:

# ~/proyectos/shopify/brew-atlas/brew-atlas-app/src/queries/product-metafields.graphql
query ProductMetafields($handle: String!) {
  product(handle: $handle) {
    title
    metafields(identifiers: [
      { namespace: "custom", key: "tasting_notes" }
      { namespace: "custom", key: "roast_level" }
      { namespace: "custom", key: "origin" }
      { namespace: "custom", key: "altitude_masl" }
    ]) {
      key
      value
      type
    }
  }
}

El campo metafields en el Storefront API devuelve un array con un elemento por identifier solicitado. Si un metafield no existe para ese producto, la posición correspondiente en el array es null. Siempre verifica null antes de usar el valor.

Acceso desde GraphQL — Admin API

Desde el Admin API puedes leer metafields con la sintaxis de selector directo:

# ~/proyectos/shopify/brew-atlas/brew-atlas-app/src/queries/admin-products-origin.graphql
query {
  products(first: 10) {
    edges {
      node {
        id
        title
        metafield(namespace: "custom", key: "origin") {
          value
          type
        }
      }
    }
  }
}

El Admin API también expone metafields(first: N) como conexión paginada si necesitas leer todos los metafields de un recurso. El selector metafield(namespace, key) es más preciso cuando sabes exactamente qué campo necesitas.

Dynamic sources en el Theme Editor

Cuando tienes una definición de metafield creada, puedes conectar los settings de una section a ese metafield directamente desde el Theme Editor. En el schema de una section, un setting de tipo text puede recibir su valor desde product.metafields.custom.origin sin que el comerciante tenga que escribir el valor manualmente — lo toma del metafield del producto.

Esto es poderoso porque separa la configuración del tema de los datos del catálogo. El theme editor solo conecta la fuente; los datos viven en el producto y son editables desde el panel de Admin, no desde el Editor.

Puentes mentales

Desde Angular/React/Node

Los metafields son a Shopify lo que las columnas custom serían a una tabla de base de datos que no controlas. Piensa en Shopify como un SaaS: la tabla products es de ellos — tú no puedes hacer ALTER TABLE products ADD COLUMN roast_level VARCHAR(20). Lo que sí puedes hacer es agregar metafields, que son equivalentes a esas columnas extra, pero con validación de tipos integrada y sin migraciones.

La distinción entre definición y ad-hoc es análoga a tener un esquema declarado vs guardar datos sin esquema en un campo JSON genérico. El sistema funciona en ambos casos, pero el esquema declarado te da validación automática, visibilidad en la UI, y descubrimiento para otros desarrolladores del equipo.

El namespace $app: con cleanup automático en desinstalación es el equivalente de namespacing de módulos — tu app tiene un espacio de nombres aislado, y cuando el módulo se elimina, sus datos también desaparecen.

Estado 2026

Los metafields tienen API estable. Lo relevante al 2026:

  • El tipo rich_text_field serializa su contenido como JSON (no como HTML puro) usando el formato de Shopify. Cuando lo imprimes en Liquid, usa {{ metafield.value }} directamente — Shopify lo convierte a HTML automáticamente en ese contexto.
  • El tipo metaobject_reference permite referenciar una entrada de metaobjeto, creando relaciones entre entidades. Esto es lo que conecta un Product con una Finca en Brew Atlas — lo verás en detalle en la sección 07.
  • Las validaciones en definiciones permiten reglas como lista de valores permitidos, rangos numéricos, y patrones de regex para texto. Las validaciones se declaran en la definición y Shopify las aplica antes de guardar cualquier valor.

Sintaxis y anatomía

Ejemplo completo: definición de metafield creada vía Admin API y lectura completa desde Liquid y GraphQL.

Crear una definición (Admin API, mutation):

# Ejecutada una sola vez para configurar el namespace/key en la tienda
mutation CreateMetafieldDefinition($definition: MetafieldDefinitionInput!) {
  metafieldDefinitionCreate(definition: $definition) {
    createdDefinition {
      id
      name
      namespace
      key
      type { name }
    }
    userErrors { field message }
  }
}

Con variables:

{
  "definition": {
    "name": "Nivel de tueste",
    "namespace": "custom",
    "key": "roast_level",
    "type": "single_line_text_field",
    "ownerType": "PRODUCT",
    "validations": [
      {
        "name": "choices",
        "value": "[\"light\",\"medium-light\",\"medium\",\"medium-dark\",\"dark\"]"
      }
    ]
  }
}

Una vez que la definición existe, Shopify valida cualquier valor que intentes guardar en ese campo contra la lista de choices. Si envías "extra-dark", la mutación devuelve un userError — no un error HTTP.

Proyecto · Brew Atlas

Nota

Esta sección no requiere comandos de terminal. Vas a crear las definiciones de metafield desde el Admin de tu dev store (Settings → Custom data → Products) y editar un snippet en el scaffold local del theme.

Brew Atlas · Paso 6

Brew Atlas necesita cinco metafields en el recurso product para representar las características del café.

Qué vas a crear/tocar (archivos):

  • ~/proyectos/shopify/brew-atlas/brew-atlas-theme/snippets/product-card.liquid — agregar lectura de roast_level y origin como badge y texto de procedencia.

Dónde crear los metafields: Las definiciones de metafield se crean en el Admin de tu dev store, no en archivos del proyecto. Navega a: Settings → Custom data → Products → Add definition y crea los cinco campos:

  1. custom.originsingle_line_text_field — país y región de origen (ej: “Colombia, Huila”)
  2. custom.roast_levelsingle_line_text_field con validación de choices: light, medium-light, medium, medium-dark, dark
  3. custom.tasting_noteslist.single_line_text_field — notas de cata del café
  4. custom.altitude_maslnumber_integer — altitud de la finca en metros sobre el nivel del mar
  5. custom.farmmetaobject_reference — referencia a la Finca (metaobjeto que se define en la sección 07)

Comandos a ejecutar: ninguno. Todo el trabajo de esta sección es en el Admin UI y en el snippet del theme.

Edita ~/proyectos/shopify/brew-atlas/brew-atlas-theme/snippets/product-card.liquid con el siguiente contenido actualizado que incluye el badge de tueste:

{%- comment -%} ~/proyectos/shopify/brew-atlas/brew-atlas-theme/snippets/product-card.liquid {%- endcomment -%}
{%- liquid
  assign price = product.price | money
  assign sold_out = false
  unless product.available
    assign sold_out = true
  endunless
 
  if product.compare_at_price > product.price
    assign on_sale = true
    assign savings = product.compare_at_price | minus: product.price | money
  else
    assign on_sale = false
  endif
 
  assign roast = product.metafields.custom.roast_level.value
  assign origin = product.metafields.custom.origin.value
-%}
 
<article class="card {%- if sold_out %} card--sold-out{%- endif -%} {%- if on_sale %} card--on-sale{%- endif -%}">
  <a href="{{ product.url }}" class="card__link">
 
    {%- if product.featured_image -%}
      <div class="card__media">
        <img
          src="{{ product.featured_image | image_url: width: 600 }}"
          srcset="{{ product.featured_image | image_url: width: 300 }} 300w,
                  {{ product.featured_image | image_url: width: 600 }} 600w,
                  {{ product.featured_image | image_url: width: 900 }} 900w"
          sizes="(min-width: 768px) 300px, 100vw"
          width="600"
          height="600"
          alt="{{ product.featured_image.alt | escape }}"
          loading="lazy"
        />
      </div>
    {%- endif -%}
 
    <div class="card__info">
      {%- if show_vendor and product.vendor != blank -%}
        <p class="card__vendor">{{ product.vendor }}</p>
      {%- endif -%}
 
      <h3 class="card__title">{{ product.title }}</h3>
 
      {%- if origin != blank -%}
        <p class="card__origin">{{ origin }}</p>
      {%- endif -%}
 
      <div class="card__pricing">
        <span class="price {%- if on_sale %} price--sale{%- endif -%}">
          {{ price }}
        </span>
 
        {%- if on_sale -%}
          <span class="price price--compare">
            {{ product.compare_at_price | money }}
          </span>
          <span class="badge badge--sale">Ahorra {{ savings }}</span>
        {%- endif -%}
 
        {%- if sold_out -%}
          <span class="badge badge--sold-out">Agotado</span>
        {%- endif -%}
      </div>
 
      {%- if roast != blank -%}
        <span class="badge badge--roast badge--roast-{{ roast }}">
          {{ roast | replace: '-', ' ' | capitalize }}
        </span>
      {%- endif -%}
    </div>
 
  </a>
</article>

Si usas la Storefront API desde la app, guarda la query en ~/proyectos/shopify/brew-atlas/brew-atlas-app/src/queries/product-with-metafields.graphql:

# ~/proyectos/shopify/brew-atlas/brew-atlas-app/src/queries/product-with-metafields.graphql
query ProductWithMetafields($handle: String!) {
  product(handle: $handle) {
    id
    title
    handle
    metafields(identifiers: [
      { namespace: "custom", key: "origin" }
      { namespace: "custom", key: "roast_level" }
      { namespace: "custom", key: "tasting_notes" }
      { namespace: "custom", key: "altitude_masl" }
    ]) {
      key
      value
      type
    }
  }
}

Errores comunes

Cuidado

Los metafields con namespace $app: se eliminan automáticamente cuando la app se desinstala. Los metafields en custom persisten. Esto no es configurable — es por diseño. Si usas $app: para datos que el comerciante necesita conservar (por ejemplo, notas del editor sobre cada producto), esos datos desaparecen en la desinstalación. Para datos de larga vida que pertenecen al comerciante, usa custom.

Cuidado

No uses el namespace shopify:. Está reservado para uso interno de Shopify. Las mutaciones que intentan escribir en ese namespace fallan con un error de permisos. Si ves metafields en shopify: en la API, son datos de Shopify — solo lectura.

Nota

Los metafields ad-hoc (sin definición previa) no aparecen en la Admin UI. Si creas un metafield directamente vía API sin crear la definición correspondiente, el comerciante no va a poder verlo ni editarlo desde el panel. No es un bug — es que la Admin UI solo muestra los campos para los que existe una definición registrada. Para debug, puedes leerlos desde la API; para uso en producción, crea siempre la definición.

Tip

Para leer todos los metafields de un recurso y descubrir qué campos existen (útil al trabajar con una tienda que tiene metafields ad-hoc heredados), usa metafields(first: 20) sin filtrar por namespace/key. Desde Liquid, {{ product.metafields | json }} imprime el objeto completo de metafields del producto para depuración.

Checklist senior

Checklist senior

  • Puedes explicar la diferencia entre una definición de metafield y un metafield ad-hoc, y cuándo usar cada uno
  • Conoces el catálogo de tipos principales y puedes elegir el correcto para un caso de negocio dado
  • Entiendes la diferencia entre los namespaces custom, $app: y shopify: y el comportamiento de cleanup de cada uno
  • Puedes leer un metafield en Liquid con product.metafields.namespace.key.value y manejar el caso en que el valor es blank
  • Sabes la sintaxis del Storefront API para traer múltiples metafields con identifiers: [...]
  • Puedes escribir una mutation de Admin API para crear una definición de metafield con validaciones
  • Entiendes que userErrors en las mutations de Admin API son errores de validación, no errores HTTP
  • Sabes qué es un dynamic source y cómo los metafields con definición lo habilitan en el Theme Editor

Quiz

Quiz · ¿Lo tenés claro?

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

  1. 1. ¿Qué ventaja concreta tiene una definición de metafield sobre un metafield ad-hoc?

  2. 2. ¿Qué comportamiento tiene el namespace `$app:` respecto a cleanup?

  3. 3. En Liquid quieres leer un metafield y mostrar un fallback si está vacío. ¿Cómo?

  4. 4. Desde el Storefront API quieres traer 3 metafields específicos de un producto. ¿Cuál es el patrón?

  5. 5. Una mutation del Admin API para crear un metafield devolvió status 200 pero el metafield no aparece. ¿Primera cosa que revisas?

Siguiente

Con los metafields modelando los atributos del producto, el siguiente paso es modelar entidades completamente nuevas — aquellas que no son productos, variantes, ni ninguna de las entidades estándar de Shopify. Para Brew Atlas, la Finca (el lugar físico donde se produce el café) es una de esas entidades. Eso es el territorio de los metaobjetos.