Ir al contenido principal

Sección 08

08 · Storefront GraphQL API

El API que alimenta themes y storefronts headless.

En una frase

El Storefront API es el gateway GraphQL orientado al comprador. Token público, seguro para enviarlo en bundles del cliente, diseñado para alimentar storefronts headless, apps mobile y cualquier superficie que necesite datos de catálogo o gestionar un carrito. Esta sección lo cubre en detalle — el Admin API tiene su propia sección.

Qué vas a ejecutar en esta sección

  1. npx tsx ~/proyectos/shopify/brew-atlas/scripts/storefront-query.ts — Consultar productos con metafields desde el Storefront API

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

Qué es y por qué existe

Shopify tiene dos APIs GraphQL y es fundamental no confundirlas. El Admin GraphQL API es la API de back-office — gestiona productos, pedidos, clientes, y tiene acceso privilegiado. El Storefront GraphQL API es la API del storefront — lee el catálogo, construye carritos, y opera en nombre del comprador. Esta sección trata exclusivamente el Storefront API.

La separación existe por razones de seguridad y de modelo de negocio. El token del Storefront API es un Storefront Access Token público — puede estar en un bundle de JavaScript que el cliente descarga, en una app mobile, o en un script de Node que corre en el borde. Comprometerlo expone solo los datos que el comprador ya puede ver en la tienda. El token del Admin API es un secreto de servidor — comprometerlo da acceso completo al back-office del comerciante.

El Storefront API existe desde 2017 y es la base técnica de toda la iniciativa headless de Shopify: Hydrogen, los storefronts custom, los apps de compra en redes sociales, y cualquier superficie que no sea el theme estándar de Online Store.

El endpoint del Storefront API tiene la forma:

https://{store}.myshopify.com/api/2026-04/graphql.json

El segmento 2026-04 es la versión del API. Shopify usa versiones calendáricas en formato YYYY-MM. La versión estable actual es 2026-04; la RC es 2026-07. Cada versión estable tiene soporte garantizado por un mínimo de 12 meses desde su publicación.

Autenticación

El Storefront API requiere un Storefront Access Token en el header X-Shopify-Storefront-Access-Token. Ese token se crea de dos formas:

  1. Desde la Admin UI: Admin → Apps → Develop apps → (tu app) → Storefront API access. También disponible en Admin → Storefront API access tokens para canales de venta headless.
  2. Desde el Admin API: con la mutation storefrontAccessTokenCreate, útil para apps que necesitan crear tokens programáticamente por canal.

El token es público por diseño. No lo trates como un secreto — está bien que aparezca en el bundle del cliente. Lo que no puedes hacer con un Storefront token es leer datos del back-office (pedidos de otros clientes, datos de admin, configuración de la tienda). Shopify lo restringe a nivel de schema.

El Storefront Access Token lo obtienes en Admin → Apps → Develop apps → (tu app) → Storefront API access. Guárdalo en ~/proyectos/shopify/brew-atlas/brew-atlas-app/.env bajo la clave SHOPIFY_STOREFRONT_TOKEN. El script de referencia abajo también lee SHOPIFY_STORE de esa misma ubicación.

Crea ~/proyectos/shopify/brew-atlas/scripts/storefront-query.ts:

// brew-atlas/scripts/storefront-query.ts
const SHOP = 'brew-atlas-dev.myshopify.com';
const TOKEN = process.env.SHOPIFY_STOREFRONT_TOKEN!;
 
const res = await fetch(`https://${SHOP}/api/2026-04/graphql.json`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Shopify-Storefront-Access-Token': TOKEN,
  },
  body: JSON.stringify({ query: `{ shop { name primaryDomain { url } } }` }),
});
const { data, errors } = await res.json();
if (errors?.length) throw new Error(JSON.stringify(errors));
console.log(data.shop);

El schema del Storefront API

El schema del Storefront API expone las entidades orientadas al comprador. Los recursos principales:

  • Product / ProductVariant — catálogo, variantes, precios, disponibilidad, imágenes, metafields
  • Collection — colecciones de productos con filtros
  • Cart — el carrito moderno (reemplaza Checkout en el Storefront API)
  • Customer — el cliente autenticado (requiere customerAccessToken)
  • Shop — configuración pública de la tienda: nombre, moneda, idiomas, políticas
  • Menu — menús de navegación definidos en el admin
  • Article / Blog / Page — contenido editorial
  • Localization — países, idiomas disponibles, monedas
  • Metafield / Metaobject — datos extendidos del catálogo (vistos en secciones 06 y 07)
  • Predictive Search — autocompletado de búsqueda

Lo que no está en el Storefront API: órdenes completas (solo el historial del cliente autenticado), datos de app, configuración de admin, analytics. Eso es territorio del Admin API.

Buyer Context con @inContext

La directiva @inContext es una de las features más importantes del Storefront API para negocios internacionales. Permite pasar el contexto del comprador — país, idioma, identidad — y recibir respuestas adaptadas.

Crea ~/proyectos/shopify/brew-atlas/scripts/queries/product-localized.graphql (puedes usar esta carpeta para guardar tus queries separadas del script):

# brew-atlas/scripts/queries/product-localized.graphql
query ProductByHandle(
  $handle: String!
  $country: CountryCode!
  $language: LanguageCode!
) @inContext(country: $country, language: $language) {
  product(handle: $handle) {
    id
    title
    descriptionHtml
    featuredImage {
      url
      altText
      width
      height
    }
    variants(first: 10) {
      edges {
        node {
          id
          title
          availableForSale
          price {
            amount
            currencyCode
          }
          compareAtPrice {
            amount
            currencyCode
          }
        }
      }
    }
    metafields(identifiers: [
      { namespace: "custom", key: "tasting_notes" }
      { namespace: "custom", key: "roast_level" }
      { namespace: "custom", key: "origin" }
      { namespace: "custom", key: "altitude_masl" }
    ]) {
      key
      value
      type
    }
  }
}

Con @inContext(country: "CO", language: "ES"), los precios retornan en COP (si la tienda tiene Markets configurado para Colombia), el título puede retornar en español si hay traducción, y el inventario refleja la disponibilidad para ese mercado. Es la diferencia entre una tienda que funciona para un solo país y una tienda global.

Pasa siempre @inContext, incluso si hoy solo operas en un país. Esta query es de referencia — no se ejecuta directamente, sino que se embebe en el script TypeScript de la sección “Sintaxis y anatomía”. Shopify optimiza internamente las queries con buyer context, y estás a una configuración de Mercados de expandirte sin cambiar el código de las queries.

El Cart API

El carrito es el modelo transaccional del Storefront API. Desde 2021, el carrito reemplazó al Checkout como objeto de trabajo en el Storefront API — el Checkout en sí se completa en las páginas de Shopify o en Checkout UI Extensions, pero el carrito es tuyo.

Operaciones del cart:

# Crear un carrito vacío
mutation CartCreate($input: CartInput!) {
  cartCreate(input: $input) {
    cart {
      id
      checkoutUrl
      lines(first: 10) {
        edges {
          node {
            id
            quantity
            merchandise {
              ... on ProductVariant {
                id
                title
                price { amount currencyCode }
              }
            }
          }
        }
      }
    }
    userErrors { field message }
  }
}
# Agregar líneas al carrito
mutation CartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
  cartLinesAdd(cartId: $cartId, lines: $lines) {
    cart {
      id
      totalQuantity
      cost {
        totalAmount { amount currencyCode }
        subtotalAmount { amount currencyCode }
      }
    }
    userErrors { field message }
  }
}

El flujo completo es: cartCreatecartLinesAdd → redirect a cart.checkoutUrl. El comprador completa el pago en las páginas de Shopify (o en un checkout UI customizado). Tu código no maneja el pago — Shopify lo hace.

Los carritos modernos soportan códigos de descuento (cartDiscountCodesUpdate), gift cards, múltiples monedas (con @inContext), y la identidad del comprador (cartBuyerIdentityUpdate para carritos autenticados). El carrito persiste en Shopify, no en tu front-end — guarda el cartId en localStorage o en una cookie y reutilízalo.

Caching

El Storefront API soporta queries vía GET o POST. Las queries GET pueden ser cacheadas por CDN; las POST no. Shopify recomienda usar Hydrogen para storefronts headless porque Hydrogen implementa una capa de caché configurada por query.

Para storefronts custom sin Hydrogen, el patrón es:

  1. Convertir las queries a GET requests con la query como parámetro (?query=...&variables=...).
  2. Agregar el header X-Shopify-Storefront-Access-Token en la cache key del CDN.
  3. Definir TTLs apropiados por tipo de dato: productos (TTL corto, el stock cambia), colecciones (TTL medio), contenido editorial como blogs (TTL largo).

Si usas Hydrogen, esto lo maneja createStorefrontClient con las directivas CacheShort(), CacheLong(), y CacheNone() por query.

Rate limits

El Storefront API usa rate limiting por IP del cliente. No es cost-based como el Admin API. El límite por defecto es generoso para uso normal de storefront — del orden de miles de requests por minuto por IP. Los límites exactos no son publicados por Shopify y pueden ajustarse por plan.

Si estás haciendo scraping, crawling, o cargas masivas de datos desde un solo servidor, puedes golpear el rate limit. En esos casos usa el Admin API con bulk operations, no el Storefront API.

Versionado

Shopify lanza versiones trimestralmente: YYYY-01, YYYY-04, YYYY-07, YYYY-10. La versión estable actual es 2026-04; la próxima RC es 2026-07. Cada versión estable tiene soporte garantizado por un mínimo de 12 meses.

Cuando Shopify depreca un campo, el campo aparece con deprecationReason en el schema durante al menos una versión antes de ser eliminado. El GraphiQL explorer de Shopify muestra warnings de deprecación cuando ejecutas queries con campos deprecados.

El versionado está en el endpoint — cambiar de 2026-04 a 2026-07 en la URL es todo lo que necesitas para cambiar de versión. No hay headers especiales ni flags de feature.

Puentes mentales

Desde Angular/React/Node

El Storefront API es a Shopify lo que un gateway GraphQL público es a un backend de microservicios. Token público, safe para el cliente, schema acotado al dominio del comprador. El Buyer Context con @inContext es análogo a un locale context en Angular i18n, pero más poderoso porque no solo afecta strings de traducción — afecta precios, inventario, impuestos, y disponibilidad de variantes según el país del comprador.

El patrón cartCreatecartLinesAddcart.checkoutUrl es el equivalente a un form de pago que termina en un redirect externo. Tu app controla la experiencia de selección de producto y carrito; Shopify controla el pago. La separación es intencional y robusta — Shopify tiene toda la certificación PCI, tú no tienes que manejarla.

Estado 2026

Lo relevante al 2026:

  • Versión estable: 2026-04. RC: 2026-07. El changelog entre versiones está en la documentación de cada versión con los campos añadidos, modificados, y deprecados.
  • Cart API: estable y es el modelo preferido. El objeto Checkout del Storefront API sigue existiendo para compatibilidad pero el desarrollo activo está en Cart.
  • Metafields en productos: desde 2023-01, los metafields de productos son accesibles en el Storefront API con el campo metafields(identifiers: [...]). Requiere que las definiciones de metafield tengan storefront access habilitado (lo tienen por defecto para metafields en custom).
  • Metaobjects: accesibles con metaobjects(type: "...", first: N) en el Storefront API, siempre que el tipo tenga storefront: PUBLIC_READ.

Sintaxis y anatomía

Consulta completa de productos con metafields, listo para ejecutar desde Node. Si todavía no creaste el script en el paso de Autenticación, crea ahora ~/proyectos/shopify/brew-atlas/scripts/storefront-query.ts:

// brew-atlas/scripts/storefront-query.ts
const SHOP = process.env.SHOPIFY_STORE ?? 'brew-atlas-dev.myshopify.com';
const TOKEN = process.env.SHOPIFY_STOREFRONT_TOKEN!;
const API_VERSION = '2026-04';
 
const PRODUCTS_QUERY = /* GraphQL */ `
  query ProductsWithMetafields(
    $first: Int!
    $country: CountryCode!
    $language: LanguageCode!
  ) @inContext(country: $country, language: $language) {
    products(first: $first) {
      edges {
        node {
          id
          title
          handle
          priceRange {
            minVariantPrice { amount currencyCode }
          }
          metafields(identifiers: [
            { namespace: "custom", key: "origin" }
            { namespace: "custom", key: "roast_level" }
            { namespace: "custom", key: "tasting_notes" }
            { namespace: "custom", key: "altitude_masl" }
          ]) {
            key
            value
            type
          }
        }
      }
    }
  }
`;
 
async function fetchProducts() {
  const res = await fetch(
    `https://${SHOP}/api/${API_VERSION}/graphql.json`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Shopify-Storefront-Access-Token': TOKEN,
      },
      body: JSON.stringify({
        query: PRODUCTS_QUERY,
        variables: { first: 20, country: 'CO', language: 'ES' },
      }),
    }
  );
 
  if (!res.ok) {
    throw new Error(`HTTP ${res.status}: ${await res.text()}`);
  }
 
  const { data, errors } = await res.json();
 
  if (errors?.length) {
    throw new Error(`GraphQL errors: ${JSON.stringify(errors, null, 2)}`);
  }
 
  for (const { node } of data.products.edges) {
    const metaMap = Object.fromEntries(
      (node.metafields ?? [])
        .filter(Boolean)
        .map((m: { key: string; value: string }) => [m.key, m.value])
    );
    console.log({
      title: node.title,
      handle: node.handle,
      origin: metaMap.origin ?? '—',
      roast: metaMap.roast_level ?? '—',
      notes: metaMap.tasting_notes ? JSON.parse(metaMap.tasting_notes) : [],
      altitude: metaMap.altitude_masl ?? '—',
    });
  }
}
 
fetchProducts().catch(console.error);

Proyecto · Brew Atlas

Brew Atlas · Paso 8

El archivo ~/proyectos/shopify/brew-atlas/scripts/storefront-query.ts es el primer script de Node del proyecto. Consulta los productos de la tienda con sus metafields de café y los imprime en consola de forma legible.

Las variables de entorno necesarias van en ~/proyectos/shopify/brew-atlas/brew-atlas-app/.env:

  • SHOPIFY_STORE — el dominio myshopify.com de tu dev store (ej. brew-atlas-dev.myshopify.com)
  • SHOPIFY_STOREFRONT_TOKEN — el Storefront Access Token (Admin → Apps → tu app → Storefront API access)

Creá el directorio si no existe:

Ejecutar Crear carpeta de scripts

mkdir -p ~/proyectos/shopify/brew-atlas/scripts

Desde ~/proyectos/shopify/brew-atlas/scripts/, ejecuta el script con tsx (recomendado) o ts-node:

Ejecutar Ejecutar storefront-query.ts

npx tsx ~/proyectos/shopify/brew-atlas/scripts/storefront-query.ts

Referencia Alternativa con ts-node

npx ts-node —esm ~/proyectos/shopify/brew-atlas/scripts/storefront-query.ts

Errores comunes

Cuidado

Nunca incluyas un token del Admin API en código del lado del cliente, en un bundle de JavaScript, ni en una app mobile. Solo los tokens del Storefront API son seguros para ser públicos. Si comprometes un Admin token, el atacante tiene acceso completo al back-office del comerciante — puede leer pedidos, modificar productos, y acceder a datos de clientes. Si esto ocurre, revoca el token inmediatamente desde Admin → Apps → tu app → API credentials.

Cuidado

El Checkout es un objeto diferente al Cart en el Storefront API. En versiones anteriores a 2021, el flujo era checkoutCreatecheckoutLineItemsAdd. Ese flujo sigue existiendo por compatibilidad, pero está prácticamente deprecado en el desarrollo activo. Si ves código que usa checkoutCreate, está desactualizado — migra a cartCreate. El objeto Cart tiene el campo checkoutUrl para redirigir al comprador cuando esté listo para pagar.

Nota

Los metafields de productos en el Storefront API devuelven null para las posiciones del array identifiers donde el metafield no existe para ese producto específico. El array siempre tiene el mismo largo que el número de identifiers que pediste, pero algunas posiciones pueden ser null. Filtra antes de mapear: metafields.filter(Boolean).

Tip

Usa siempre @inContext, incluso en una tienda de un solo país. El costo de incluirlo es cero; el beneficio es que tus queries están listas para Markets sin refactoring. Si más adelante habilitas múltiples países en Shopify Markets, los precios, inventario, y disponibilidad ya se van a comportar correctamente porque el contexto del comprador ya está en tus queries.

Checklist senior

Checklist senior

  • Puedes explicar la diferencia entre el Storefront API y el Admin API — cuándo usar cada uno y por qué
  • Sabes que el Storefront Access Token es público y puedes articular qué expone y qué no si alguien lo obtiene
  • Puedes escribir una query con @inContext para obtener precios y disponibilidad localizados
  • Entiendes el flujo completo: cartCreatecartLinesAddcart.checkoutUrl → pago en Shopify
  • Sabes que los metafields en el Storefront API devuelven null por posición y filtrás antes de mapear
  • Puedes construir un fetch al Storefront API en TypeScript con headers correctos y manejo de errores
  • Entiendes la diferencia de caching entre GET y POST queries en el Storefront API
  • Conocés el modelo de versionado calendárico y sabes cómo cambiar de versión en el endpoint

Quiz

Quiz · ¿Lo tenés claro?

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

  1. 1. El Storefront Access Token es 'público'. ¿Qué significa exactamente?

  2. 2. Tu headless storefront en Colombia muestra precios en USD en lugar de COP. ¿Causa típica?

  3. 3. ¿Cuál es el flujo correcto para llevar a un comprador al checkout?

  4. 4. Pediste `product { metafields(identifiers: [...]) { value } }` y el array viene con algún `null`. ¿Por qué?

  5. 5. ¿Cómo es el versionado del Storefront API?

Siguiente

El Storefront API es para el comprador. El Admin GraphQL API es para el comerciante — y para las apps que operan en nombre del comerciante. En la siguiente sección cubrimos mutations, bulk operations, webhooks, rate limiting cost-based, y el patrón userErrors que aparece en todas las mutations del Admin.