14 · Customer Account UI
Las nuevas customer accounts — legacy está muerta.
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.
-
shopify app generate extension --type customer_account_ui --name subscription-control— Scaffold de la extensión de cuenta (una sola vez) -
shopify app dev— Preview local con el editor de cuentas del dev store -
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ón | Checkout UI | Customer Account UI |
|---|---|---|
| Contexto | Durante el proceso de pago | En el portal de cuenta del cliente |
| Autenticación | Comprador anónimo o autenticado | Cliente siempre autenticado |
| Datos centrales | Carrito, líneas, entrega, pago | Pedidos, perfil del cliente, subscripciones |
| API GraphQL | Storefront / checkout context | Customer 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.jsonUna 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 servidorEste 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
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
passwordya no existe en el registro del cliente bajo las nuevas cuentas. Si tu app tenía lógica que dependía decustomer.password_seto 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 en2025-07.
Proyecto · Brew Atlas
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/:
shopify app dev
Desde ~/proyectos/shopify/brew-atlas/brew-atlas-app/:
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):
shopify app generate extension --type customer_account_ui --name subscription-controldesde~/proyectos/shopify/brew-atlas/brew-atlas-app/(scaffold, una sola vez).shopify app devdesde~/proyectos/shopify/brew-atlas/brew-atlas-app/(preview local).shopify app deploydesde~/proyectos/shopify/brew-atlas/brew-atlas-app/(publicar).
Errores comunes
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.
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.
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.
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.
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
- 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
passwordno existe en las nuevas cuentas y puedes identificar código legacy que asume su existencia - Entiendes cómo usar
Linkcon el proptopara 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 · ¿Lo tenés claro?
-
1. ¿Cuál es la diferencia fundamental entre las cuentas legacy y las nuevas customer accounts?
Cambios: (1) auth por OTP sin password, (2) nuevo Customer Account API distinto del Storefront/Admin, (3) nuevo sistema de extensiones incompatible con las legacy. Es reescritura, no migración progresiva.
-
2. ¿Qué diferencia al Customer Account API del Storefront API?
Customer Account API es el 'Storefront autenticado': siempre tienes customer en contexto. Puedes pedir orders, payment methods, subscriptions del cliente. El Storefront API es para catálogo y carrito, con o sin login.
-
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?
Admin token es secreto del servidor — nunca va al cliente. Patrón: extensión pasa session token al propio backend, el backend lo valida y usa su token offline con permisos del merchant para el Admin API.
-
4. Estás portando una extensión legacy que usa `customer.password_reset_url`. ¿Qué cambia en las nuevas cuentas?
Sin password → sin reset-password → los campos asociados no existen. Código legacy que asume el modelo de password falla en compilación o runtime. Es el indicador más claro de código que no fue portado.
-
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?
El router de customer accounts tiene su propio espacio de URLs. `Link to='extension:...'` es la forma soportada; `window.location` o `<a>` pierden el contexto de autenticación y recargan el iframe.
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.