Ir al contenido principal

Sección 14

14 · Customer Account UI

Las nuevas customer accounts — legacy está muerta.

En una frase

Las Customer Account UI Extensions extienden las nuevas cuentas de cliente de Shopify — el reemplazo de las cuentas legacy con login de contraseña. La misma tecnología que las Checkout UI Extensions (preact / API de React, targets, hooks), pero en un contexto distinto: el portal de cuenta del cliente en tienda.com/account. Targets disponibles: página completa, bloque en el perfil, bloque en el estado del pedido, ítem en el menú de acciones del pedido.

Qué vas a ejecutar en esta sección

  1. shopify app generate extension --type customer_account_ui --name subscription-control — Scaffold de la extensión de cuenta (una sola vez)
  2. shopify app dev — Preview local con el editor de cuentas del dev store
  3. shopify app deploy — Publicar la extensión al registro de Shopify

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 lanzó las nuevas cuentas de cliente (new customer accounts) en 2023, primero como developer preview y después con disponibilidad general en 2024. El cambio central: las cuentas legacy usaban email + contraseña para el login. Las nuevas cuentas usan login sin contraseña — el cliente recibe un OTP de seis dígitos por email para autenticarse. Esto elimina el problema de “olvidé mi contraseña” y mejora la tasa de retorno de clientes.

El otro cambio es técnico: las nuevas cuentas tienen su propio motor de renderizado, su propio API de GraphQL para datos del cliente, y su propio sistema de extensiones. Las extensiones para cuentas legacy no migran — son mecanismos incompatibles.

Las Customer Account UI Extensions son la API de extensibilidad de las nuevas cuentas. Con ellas, tu app puede añadir páginas completas, bloques en el perfil, y acciones en el historial de pedidos del cliente. Para Brew Atlas, el caso de uso central es permitir al cliente gestionar su suscripción de café directamente desde su área de cuenta: ver qué plan tiene, pausarlo, modificar la frecuencia, o cancelarlo.

Diferencias respecto a Checkout UI Extensions

La tecnología de base es la misma — preact con API de React, targets, hooks — pero el contexto y los datos disponibles son distintos:

DimensiónCheckout UICustomer Account UI
ContextoDurante el proceso de pagoEn el portal de cuenta del cliente
AutenticaciónComprador anónimo o autenticadoCliente siempre autenticado
Datos centralesCarrito, líneas, entrega, pagoPedidos, perfil del cliente, subscripciones
API GraphQLStorefront / checkout contextCustomer Account GraphQL API
URL del host/checkouts/.../account y subrutas

El punto más importante: en el checkout, el comprador puede ser anónimo. En las customer accounts, el cliente está siempre autenticado — sus datos son del cliente logueado, no de una sesión de carrito.

Targets disponibles

Los targets de Customer Account UI Extensions cubren las superficies del portal de cuenta:

customer-account.page.render — una página completa, accesible en una ruta bajo /account/extensions/{handle}. El cliente llega ahí desde un link en el perfil o desde el menú de la cuenta. Es el target para features completas: gestión de suscripciones, historial avanzado de pedidos, portal de fidelización.

customer-account.profile.block.render — un bloque dentro de la página de perfil del cliente. Aparece junto a los datos del perfil. Útil para mostrar el resumen de la suscripción activa, los puntos de fidelización, o una acción rápida.

customer-account.order-status.block.render — un bloque en la vista de detalle de un pedido específico. El cliente ve el pedido y tu extensión añade información adicional — estado de la suscripción asociada, link para gestionar la siguiente entrega.

customer-account.order-action-menu-item.render — un ítem en el menú de acciones de un pedido (los tres puntos o el dropdown de opciones del pedido). Permite añadir acciones como “Volver a pedir” o “Pausar suscripción” directamente en el listado de pedidos.

Estructura del proyecto

// brew-atlas-app/extensions/subscription-control/ (directorio de la extensión)
extensions/
└── subscription-control/
    ├── src/
    │   ├── FullPage.tsx
    │   └── ProfileBlock.tsx
    ├── shopify.extension.toml
    └── package.json

Una misma extensión puede declarar múltiples targets en el TOML, cada uno con su propio módulo o compartiendo uno. Para Brew Atlas, la extensión tiene una página completa de gestión y un bloque de resumen en el perfil.

El TOML de la extensión

# brew-atlas-app/extensions/subscription-control/shopify.extension.toml
api_version = "2026-04"
 
[[extensions]]
name = "Subscription control"
handle = "subscription-control"
type = "ui_extension"
 
[[extensions.targeting]]
module = "./src/FullPage.tsx"
target = "customer-account.page.render"
 
[[extensions.targeting]]
module = "./src/ProfileBlock.tsx"
target = "customer-account.profile.block.render"

La página completa: gestión de suscripción

// brew-atlas-app/extensions/subscription-control/src/FullPage.tsx
import {
  reactExtension,
  Page,
  Card,
  BlockStack,
  InlineStack,
  Text,
  Button,
  Badge,
  Divider,
  useApi,
} from '@shopify/ui-extensions-react/customer-account';
 
export default reactExtension(
  'customer-account.page.render',
  () => <SubscriptionControl />,
);
 
function SubscriptionControl() {
  // useApi expone los métodos del contexto de la extensión.
  // Para hacer queries al Customer Account GraphQL API, usa query().
  // El token de sesión del cliente está disponible en api.sessionToken para
  // intercambiarlo en tu servidor por un access token si necesitas llamar
  // al Admin API desde el servidor.
  const api = useApi<'customer-account.page.render'>();
 
  // En una implementación real, harías una query al Customer Account API
  // para obtener las suscripciones activas del cliente. La forma exacta
  // de las queries de subscripciones depende del proveedor de suscripciones
  // que uses (Shopify Subscriptions API, ReCharge, etc.).
  // Consulta la doc oficial para el schema de customer.subscriptions.
 
  return (
    <Page title="Mis suscripciones">
      <BlockStack spacing="loose">
        {/* Suscripción activa */}
        <Card>
          <BlockStack spacing="base">
            <InlineStack alignment="center" blockAlignment="center">
              <Text size="large" emphasis="bold">Plan mensual — 1 bolsa</Text>
              <Badge tone="success">Activo</Badge>
            </InlineStack>
            <Text size="small" appearance="subdued">
              Próxima entrega: 15 de mayo · 250g de Geisha Washed
            </Text>
            <Divider />
            <InlineStack spacing="base">
              <Button
                kind="secondary"
                onPress={() => {
                  // Llamar a tu servidor para pausar la suscripción.
                  // Tu servidor usa el access token del Admin API para
                  // actualizar el contrato de suscripción.
                }}
              >
                Pausar
              </Button>
              <Button
                kind="secondary"
                onPress={() => {
                  // Saltar la próxima entrega
                }}
              >
                Saltar entrega
              </Button>
              <Button
                kind="plain"
                tone="critical"
                onPress={() => {
                  // Cancelar la suscripción
                }}
              >
                Cancelar
              </Button>
            </InlineStack>
          </BlockStack>
        </Card>
 
        {/* Historial de entregas */}
        <Card>
          <BlockStack spacing="base">
            <Text size="medium" emphasis="bold">Historial de entregas</Text>
            <Text size="small">15 abr · 250g Geisha Washed — Entregado</Text>
            <Text size="small">15 mar · 250g Ethiopia Yirgacheffe — Entregado</Text>
            <Text size="small">15 feb · 250g Colombia Huila — Entregado</Text>
          </BlockStack>
        </Card>
      </BlockStack>
    </Page>
  );
}

El bloque de perfil

// brew-atlas-app/extensions/subscription-control/src/ProfileBlock.tsx
import {
  reactExtension,
  Card,
  BlockStack,
  InlineStack,
  Text,
  Button,
  Badge,
  Link,
} from '@shopify/ui-extensions-react/customer-account';
 
export default reactExtension(
  'customer-account.profile.block.render',
  () => <SubscriptionProfileBlock />,
);
 
function SubscriptionProfileBlock() {
  return (
    <Card>
      <BlockStack spacing="base">
        <InlineStack alignment="center" blockAlignment="center">
          <Text size="medium" emphasis="bold">Mi suscripción</Text>
          <Badge tone="success">Activo</Badge>
        </InlineStack>
        <Text size="small" appearance="subdued">
          Plan mensual · 1 bolsa de 250g
        </Text>
        <Link to="/account/extensions/subscription-control">
          Gestionar suscripción
        </Link>
      </BlockStack>
    </Card>
  );
}

El componente Link con el prop to navega a la página completa de la extensión. La ruta es /account/extensions/{handle} donde handle es el valor declarado en el TOML. Shopify resuelve ese path al target customer-account.page.render de la extensión con ese handle.

El Customer Account GraphQL API

El Customer Account API es distinto del Storefront API y del Admin API. Es un API GraphQL autenticado como el cliente logueado, con acceso a los datos propios de ese cliente: sus pedidos, su perfil, sus subscripciones (si hay un proveedor de suscripciones activo).

Para hacer queries desde la extensión, usas el método query del objeto devuelto por useApi():

// Fragmento — patrón de query en Customer Account UI Extension
const api = useApi<'customer-account.page.render'>();
 
// Nota: el schema del Customer Account API para subscripciones
// depende del proveedor. Si usas Shopify Subscriptions (nativo),
// el campo es customer.subscriptionContracts.
// Si usas ReCharge u otro proveedor, el schema es diferente.
// Consulta la doc oficial antes de escribir esta query:
// https://shopify.dev/docs/api/customer-account-ui-extensions
const result = await api.query(/* GraphQL */`
  query CustomerOrders {
    customer {
      orders(first: 5) {
        edges {
          node {
            id
            name
            processedAt
            financialStatus
            totalPrice {
              amount
              currencyCode
            }
          }
        }
      }
    }
  }
`);

Para operaciones que requieren el Admin API (modificar un contrato de suscripción, que el cliente no puede hacer directamente), el flujo correcto es: la extensión llama a tu servidor con el session token del cliente (api.sessionToken), tu servidor verifica el token, y usa el access token del Admin API para hacer la mutación. La extensión no llama al Admin API directamente — eso es una operación de servidor.

Session token y autenticación

En el checkout, el comprador puede ser anónimo. En las customer accounts, el cliente está siempre autenticado. La extensión tiene acceso al session token del cliente a través del objeto de API:

const api = useApi<'customer-account.page.render'>();
// api.sessionToken es una Promise que resuelve al JWT del cliente
// Úsalo para autenticar llamadas a tu servidor

Este JWT identifica al cliente autenticado en la sesión actual. Tu servidor lo verifica usando las claves públicas de Shopify y puede usarlo para obtener el ID del cliente y después llamar al Admin API en nombre de ese cliente.

Analytics API (desde 2025-07)

Desde la versión de API 2025-07, las Customer Account UI Extensions pueden emitir eventos de analytics que fluyen al sistema de analytics de Shopify. Esto permite rastrear interacciones en el área de cuenta — clics en botones, páginas visitadas — en el mismo pipeline que los eventos del storefront.

// Fragmento — emitir un evento de analytics
const api = useApi<'customer-account.page.render'>();
 
function handlePauseClick() {
  // Emitir evento antes de la acción
  api.analytics.publish('subscription_paused', {
    subscription_id: 'gid://shopify/SubscriptionContract/123',
    reason: 'user_requested',
  });
  // Continuar con la pausa
}

Los eventos personalizados aparecen en el panel de analytics de la tienda junto a los eventos estándar del storefront.

Puentes mentales

Desde Checkout UI Extensions

Si las Checkout UI Extensions extienden la experiencia del comprador durante el pago, las Customer Account UI Extensions hacen lo mismo para el cliente después de la compra. Misma tecnología, distinto host, distinto alcance de datos. La diferencia más relevante es el estado de autenticación: en el checkout, el usuario puede ser anónimo; en las customer accounts, siempre está autenticado. Eso habilita queries personalizadas al Customer Account API — datos reales del cliente real, no un carrito temporal.

Piénsalo como el panel de cuenta de un usuario en cualquier SaaS: es la superficie donde el usuario gestiona su relación a largo plazo con el servicio, no donde hace una compra puntual. Shopify separó estos dos contextos porque tienen modelos de datos, flujos de autenticación, y objetivos de producto completamente distintos.

Estado 2026

Lo relevante al 2026:

  • Las nuevas customer accounts son el estándar: en 2026, la gran mayoría de tiendas Shopify usa las nuevas cuentas. Las cuentas legacy (con contraseña) están en modo mantenimiento. Para código nuevo, siempre usa el modelo de nuevas cuentas — no construyas extensiones para el sistema legacy.
  • Login sin contraseña (OTP): el campo password ya no existe en el registro del cliente bajo las nuevas cuentas. Si tu app tenía lógica que dependía de customer.password_set o flujos de reset de contraseña, revisa y actualiza.
  • Storefront API ≠ Customer Account API: son dos APIs distintas con autenticación distinta. El Storefront API expone datos públicos del catálogo y puede leer pedidos solo con el Customer Access Token (obtenido via Storefront API con email + contraseña, modelo legacy). El Customer Account API es el API moderno para datos del cliente autenticado con OTP.
  • api_version = "2026-04" es la versión más reciente del Customer Account UI Extension API. Incluye los nuevos targets de perfil y las mejoras al Analytics API introducidas en 2025-07.

Proyecto · Brew Atlas

Brew Atlas · Paso 14

La extensión subscription-control de Brew Atlas da al cliente una página completa dentro de su área de cuenta para gestionar su suscripción de café — ver el plan activo, la próxima entrega, el historial, y pausar o cancelar desde ahí sin contactar al soporte.

La extensión depende de que haya un proveedor de suscripciones activo en la tienda que exponga los contratos de suscripción a través del Customer Account API o del Admin API. En un contexto de desarrollo sin ese proveedor, las acciones pueden enlazarse a un endpoint de tu servidor que use metafields o metaobjetos como store de estado (un toy implementation para el tutorial).

Los archivos están en ~/proyectos/shopify/brew-atlas/brew-atlas-app/extensions/subscription-control/. Para desplegar y previsualizar:

Desde ~/proyectos/shopify/brew-atlas/brew-atlas-app/:

Ejecutar Preview local

shopify app dev

Desde ~/proyectos/shopify/brew-atlas/brew-atlas-app/:

Ejecutar Deploy

shopify app deploy

Después del deploy, el comerciante activa la extensión desde Admin → Configuración → Cuentas de cliente → Personalizar. El cliente ve “Mis suscripciones” en su portal de cuenta y un bloque de resumen en su página de perfil.

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

  • ~/proyectos/shopify/brew-atlas/brew-atlas-app/extensions/subscription-control/src/FullPage.tsx — página completa de gestión de suscripción.
  • ~/proyectos/shopify/brew-atlas/brew-atlas-app/extensions/subscription-control/src/ProfileBlock.tsx — bloque de resumen en el perfil del cliente.
  • ~/proyectos/shopify/brew-atlas/brew-atlas-app/extensions/subscription-control/shopify.extension.toml — targets y metadatos.
  • ~/proyectos/shopify/brew-atlas/brew-atlas-app/extensions/subscription-control/package.json — dependencias del paquete.

Comandos a ejecutar (orden):

  1. shopify app generate extension --type customer_account_ui --name subscription-control desde ~/proyectos/shopify/brew-atlas/brew-atlas-app/ (scaffold, una sola vez).
  2. shopify app dev desde ~/proyectos/shopify/brew-atlas/brew-atlas-app/ (preview local).
  3. shopify app deploy desde ~/proyectos/shopify/brew-atlas/brew-atlas-app/ (publicar).

Errores comunes

Cuidado

Las extensiones para las cuentas legacy de Shopify (el sistema con email + contraseña) NO son lo mismo que las Customer Account UI Extensions. Si encuentras documentación sobre “customer account extensions” en recursos anteriores a 2023, verifica que se refieren al sistema nuevo, no al legacy. Los dos sistemas son incompatibles — una extensión del sistema nuevo no funciona en una tienda que todavía usa cuentas legacy, y viceversa.

Cuidado

El Storefront API y el Customer Account API no son intercambiables. El Storefront API es público, sin autenticación de cliente, y expone datos del catálogo y del carrito. El Customer Account API requiere que el cliente esté autenticado y expone sus datos personales: pedidos, perfil, subscripciones. Usar el Storefront API donde necesitas datos autenticados del cliente produce resultados vacíos o errores de permisos — son endpoints distintos con tokens distintos.

Cuidado

Login sin contraseña significa que el campo password no existe en los registros de los nuevos clientes. Si tu app, webhook handler, o script de sincronización asume que los clientes tienen contraseña (por ejemplo, para forzar un reset de contraseña programático), esa lógica está rota en las nuevas cuentas. Los clientes de las nuevas cuentas solo pueden autenticarse por OTP de email. Revisa cualquier lógica de autenticación legacy antes de migrar a una tienda que use el sistema nuevo.

Nota

Las Customer Account UI Extensions requieren que la tienda del comerciante tenga activadas las nuevas cuentas de cliente. Si el comerciante todavía usa las cuentas legacy (cada vez más raro en 2026, pero posible en tiendas antiguas que no migraron), tus extensiones no aparecen. Incluye este requisito en la documentación de tu app y en el proceso de onboarding.

Tip

Para acciones que requieren llamar al Admin API (modificar un contrato de suscripción, crear un pedido, etc.), el patrón correcto es: la extensión client-side llama a tu servidor con el session token del cliente, tu servidor verifica el token con las claves públicas de Shopify, y usa el access token del Admin API que tienes almacenado para hacer la mutación. Nunca incluyas tokens de Admin API en el código de la extensión — eso es client-side y sería visible para cualquier persona que inspeccione el bundle.

Checklist senior

Checklist senior

  • Puedes explicar la diferencia entre las cuentas de cliente legacy (contraseña) y las nuevas cuentas (OTP sin contraseña), y por qué las extensiones no migran entre ambos sistemas
  • Conoces los cuatro targets principales de Customer Account UI Extensions y sabes cuándo usar cada uno
  • Entiendes la diferencia entre el Storefront API y el Customer Account API: autenticación, datos disponibles, y cuándo usar cada uno
  • Sabes que el cliente en las customer accounts siempre está autenticado, y cómo eso cambia qué datos puedes mostrar respecto al checkout
  • Puedes articular el flujo para operaciones que requieren el Admin API: extensión → tu servidor (con session token) → Admin API
  • Sabes que el campo password no existe en las nuevas cuentas y puedes identificar código legacy que asume su existencia
  • Entiendes cómo usar Link con el prop to para navegar entre la página completa y el bloque de perfil de la extensión
  • Conoces el Analytics API (2025-07+) y puedes usarlo para emitir eventos de customer account al pipeline de analytics de Shopify

Quiz

Quiz · ¿Lo tenés claro?

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

  1. 1. ¿Cuál es la diferencia fundamental entre las cuentas legacy y las nuevas customer accounts?

  2. 2. ¿Qué diferencia al Customer Account API del Storefront API?

  3. 3. Desde una Customer Account UI Extension necesitas hacer una operación que requiere Admin API (ej. aplicar un crédito al pedido). ¿Flujo correcto?

  4. 4. Estás portando una extensión legacy que usa `customer.password_reset_url`. ¿Qué cambia en las nuevas cuentas?

  5. 5. Tu extensión tiene página completa (`/account/brew-atlas`) y un bloque en el perfil. ¿Cómo navegas del bloque a la página completa?

Siguiente

El bloque de Apps y extensiones está completo. Cubriste el ciclo completo: Theme App Extensions para inyectar UI en el storefront, Admin Apps para construir herramientas embebidas en el panel del comerciante, Shopify Functions para lógica de backend sin servidor, Checkout UI Extensions para personalizar el pago, y Customer Account UI Extensions para el portal post-compra. El siguiente bloque entra en el territorio headless: Hydrogen + React Router v7, el stack oficial de Shopify para storefronts completamente personalizados.