La API v2 de Webflow archiva productos completos en lugar de SKU individuales, por lo que una ruta protegida en Webflow Cloud archiva el producto o establece el stock de una variante en cero.
La mayoría de los procesos de archivado de SKU comienzan como un script rápido en el portátil de alguien y, a veces, fallan de formas confusas. La solicitud se procesa, pero la variante sigue apareciendo en la tienda. La API de datos v2 de Webflow no tiene un indicador de archivo en los SKU. El punto de conexión Update SKU edita los campos de una variante, como el precio, mientras que el indicador de archivo reside en el producto dentro de Update Product.
En este desarrollo, trasladamos ambas soluciones a una aplicación Next.js en Webflow Cloud. Un controlador de ruta protegido archiva un producto completo o retira una variante de la venta estableciendo su stock en cero, y una página de administración permite que tu equipo de tienda ejecute cualquiera de las dos acciones sin ver nunca el token.
¿Qué necesitas para solucionar el archivado de SKU con la API de Webflow en Webflow?
Debes cubrir cinco requisitos previos:
- Sitio de comercio electrónico: Un sitio de Webflow con un plan de comercio electrónico y un producto de prueba que puedas archivar y restaurar sin riesgos.
- Token del sitio: Con los ámbitos
ecommerce:readyecommerce:write. En el sitio de un cliente, haz que el propietario lo genere, lo guarde y lo rote junto con el secreto compartido al realizar la entrega. - ID: El ID del sitio desde la configuración del sitio y el ID de la colección de SKU que requiere el punto de conexión de inventario.
- Next.js 15 y Node.js 22: Los requisitos mínimos que establecen los documentos de bring-your-own-app de Webflow Cloud para las implementaciones.
- Proyecto de Webflow Cloud: Conectado al sitio, con un entorno en el que puedas implementar para realizar pruebas antes de pasar a producción.
Con estos requisitos previos, puedes continuar con el desarrollo.
5 pasos para solucionar el archivado de SKU con la API de Webflow en Webflow Cloud
El desarrollo consiste en una aplicación Next.js con un controlador de ruta detrás de un middleware de seguridad, además de una página de administración que lo invoca. El controlador ofrece dos acciones porque la API proporciona dos herramientas diferentes.
1. Crea la aplicación Next.js con npm
Prepara la estructura del proyecto con npm, ya que los documentos de "bring-your-own-app" de Webflow Cloud indican: "Actualmente, Webflow Cloud solo admite el gestor de paquetes npm". Fijar @15 mantiene la estructura en Next.js 15, donde middleware.ts aún funciona en Webflow Cloud.
Si comienzas desde un repositorio existente que contiene un pnpm-lock.yaml o un yarn.lock, elimínalo y vuelve a generarlo con npm install antes de la primera implementación.
Ejecuta la creación de la estructura desde la carpeta donde guardas tus proyectos:
npx create-next-app@15 sku-archiver --ts --app --use-npm --yes
cd sku-archiver
npm run dev
Ahora tienes un proyecto de TypeScript App Router con un package-lock.json y un servidor de desarrollo en localhost. No toques la configuración generada; la página de configuración de Webflow Cloud dice: "No necesitas añadir un adaptador, una ruta base ni un modo de salida".
2. Almacena el token y los IDs como variables de entorno
Coloca todos los valores que necesite la ruta en variables de entorno, localmente en .env.local y en tu entorno de Webflow Cloud para los despliegues.
La página de variables de entorno de Webflow Cloud confirma que "tanto las variables de entorno secretas como las no secretas están disponibles para el proceso de compilación de tu aplicación y para la aplicación desplegada en tiempo de ejecución", y oculta los secretos de los registros de compilación.
Crea .env.local en la raíz del proyecto con estas claves:
# Site token with ecommerce:read and ecommerce:write (mark as secret in Webflow Cloud)
WEBFLOW_SITE_TOKEN=
# The site ID and the SKUs collection ID
WEBFLOW_SITE_ID=
WEBFLOW_SKU_COLLECTION_ID=
# Shared secret your store team types into the admin page (mark as secret)
ADMIN_SHARED_SECRET=
# Mount path of the app, for client-side fetch calls; leave empty locally
NEXT_PUBLIC_BASE_PATH=
Para los despliegues, añade las mismas cinco variables en el entorno de Webflow Cloud, con NEXT_PUBLIC_BASE_PATH configurado en la ruta de montaje del entorno y el token y el secreto compartido marcados como secretos. Webflow Cloud no crea NEXT_PUBLIC_BASE_PATH por ti. Inicia un nuevo despliegue para que la compilación tome los valores.
Busca el ID de la colección de SKUs en la lista de colecciones del sitio; los sitios de comercio electrónico tienen colecciones separadas para Productos y SKUs, y la llamada de inventario devolverá un 404 si utilizas el ID de la colección de Productos en su lugar.
3. Escribe el gestor de rutas de archivo
Crea un gestor POST con dos acciones. archive-product envía Actualizar Producto con isArchived: true y publishStatus: "live", para que el producto salga de la tienda publicada sin esperar a que se publique el sitio.
retire-skuenvía Actualizar Inventario de Artículo con una cantidad finita de cero, lo que muestra esa variante individual como agotada mientras el resto del producto sigue a la venta. Deja export const runtime = 'edge' fuera de este archivo; el adaptador OpenNext Cloudflare detrás de Webflow Cloud no lo admite y la compilación fallará.
Guarda lo siguiente como app/api/skus/archive/route.ts:
// app/api/skus/archive/route.ts
import { NextResponse } from 'next/server';
type ArchiveRequest = { action?: string; productId?: string; skuId?: string };
const ID_PATTERN = /^[a-f0-9]{24}$/;
function safeEqual(a: string, b: string): boolean {
if (a.length !== b.length) return false;
let diff = 0;
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
return diff === 0;
}
async function webflow(path: string, method: string, body: unknown) {
return fetch(`https://api.webflow.com/v2${path}`, {
method,
headers: {
Authorization: `Bearer ${process.env.WEBFLOW_SITE_TOKEN}`,
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify(body),
});
}
export async function POST(request: Request) {
// Checked here as well as in middleware, so the route fails closed on its own.
const expected = process.env.ADMIN_SHARED_SECRET ?? '';
if (!expected || !safeEqual(request.headers.get('x-admin-secret') ?? '', expected)) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const siteId = process.env.WEBFLOW_SITE_ID;
const skuCollectionId = process.env.WEBFLOW_SKU_COLLECTION_ID;
if (!process.env.WEBFLOW_SITE_TOKEN || !siteId || !skuCollectionId) {
return NextResponse.json({ error: 'Route is not configured' }, { status: 500 });
}
let body: ArchiveRequest;
try {
body = (await request.json()) as ArchiveRequest;
} catch {
return NextResponse.json({ error: 'Request body must be JSON' }, { status: 400 });
}
let upstream: Response;
if (body.action === 'archive-product' && ID_PATTERN.test(body.productId ?? '')) {
upstream = await webflow(`/sites/${siteId}/products/${body.productId}`, 'PATCH', {
publishStatus: 'live',
product: { isArchived: true },
});
} else if (body.action === 'retire-sku' && ID_PATTERN.test(body.skuId ?? '')) {
upstream = await webflow(
`/collections/${skuCollectionId}/items/${body.skuId}/inventory`,
'PATCH',
{ inventoryType: 'finite', quantity: 0 },
);
} else {
return NextResponse.json(
{ error: 'Send action archive-product with productId, or retire-sku with skuId' },
{ status: 400 },
);
}
const detail = await upstream.text();
if (!upstream.ok) {
console.error('Webflow API rejected the request', upstream.status, detail);
return NextResponse.json(
{ error: 'Webflow API rejected the request', status: upstream.status, detail },
{ status: 502 },
);
}
return NextResponse.json({ done: true, action: body.action });
}
Una entrada incorrecta devuelve un 400, y una configuración faltante devuelve un 500. Un rechazo de la API de Webflow devuelve un 502 con el estado y el cuerpo del servidor ascendente, para que la persona en la página de administración pueda distinguir entre "mi entrada estaba mal" y "la API dijo que no" sin abrir los registros. Validar ambos IDs como cadenas hexadecimales de 24 caracteres significa que nada de lo que se escriba en el formulario puede cambiar la ruta ascendente.
4. Protege la ruta con middleware.ts
Añade un archivo middleware.ts para que las solicitudes sin el secreto compartido nunca lleguen al gestor. La página de personalización del framework de Webflow Cloud dice: "El middleware del entorno de ejecución de Node.js no es compatible".
Solo el middleware del entorno de ejecución Edge funciona en el entorno de ejecución de Workers. Next.js 16 cambia el nombre del middleware a proxy, que se ejecuta en el entorno de ejecución de Node, así que mantén este archivo llamado middleware.ts.
Añade este archivo en la raíz del proyecto, junto a package.json:
// middleware.ts
import { NextResponse, type NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const expected = process.env.ADMIN_SHARED_SECRET;
if (!expected || request.headers.get('x-admin-secret') !== expected) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
return NextResponse.next();
}
export const config = {
matcher: '/api/skus/:path*',
};
El middleware es la primera barrera, y el gestor repite la comprobación con una comparación que no se detiene en la primera discrepancia.
El análisis post-mortem de Vercel sobre una omisión de middleware en 2025 desaconseja hacer del middleware la única protección, y la propia comprobación del gestor significa que un archivo de middleware renombrado u omitido sigue dejando la ruta bloqueada. Una solicitud sin la cabecera devuelve un 401 desde cualquiera de las capas.
5. Llama a la ruta desde una página de administración con la ruta base
Crea una pequeña página de cliente donde tu equipo de tienda elija una acción e introduzca el ID junto con el secreto compartido.
Antepone NEXT_PUBLIC_BASE_PATH a cada llamada fetch del lado del cliente, porque la documentación de "trae tu propia aplicación" es explícita al decir que "las llamadas fetch del lado del cliente deben incluir manualmente la ruta base para llegar correctamente a tus endpoints". Un fetch a /api/skus/archive sin el prefijo irá a la raíz de tu dominio en lugar de a tu aplicación.
Reemplaza el contenido de app/page.tsx con este componente:
// app/page.tsx
'use client';
import { useState, type FormEvent } from 'react';
const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? '';
export default function ArchivePage() {
const [action, setAction] = useState('archive-product');
const [id, setId] = useState('');
const [secret, setSecret] = useState('');
const [result, setResult] = useState('');
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
setResult('Working...');
const payload = action === 'archive-product' ? { action, productId: id } : { action, skuId: id };
try {
const response = await fetch(`${basePath}/api/skus/archive`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'x-admin-secret': secret },
body: JSON.stringify(payload),
});
const data = await response.json();
setResult(`${response.status}: ${JSON.stringify(data, null, 2)}`);
} catch {
setResult('The response was not JSON. Check NEXT_PUBLIC_BASE_PATH.');
}
}
return (
<main style={{ maxWidth: 480, margin: '40px auto', fontFamily: 'sans-serif' }}>
<h1>Archive products and SKUs</h1>
<form onSubmit={handleSubmit} style={{ display: 'grid', gap: 12 }}>
<select value={action} onChange={(e) => setAction(e.target.value)}>
<option value="archive-product">Archive a whole product</option>
<option value="retire-sku">Take one SKU off sale</option>
</select>
<input
placeholder={action === 'archive-product' ? 'Product ID' : 'SKU ID'}
value={id}
onChange={(e) => setId(e.target.value)}
required
/>
<input
type="password"
placeholder="Admin secret"
value={secret}
onChange={(e) => setSecret(e.target.value)}
required
/>
<button type="submit">Run</button>
</form>
<pre style={{ whiteSpace: 'pre-wrap' }}>{result}</pre>
</main>
);
}
El usuario escribe el secreto y lo envía como encabezado, por lo que nunca se incluye en el paquete de JavaScript. Vuelve a desplegar el entorno, abre la aplicación en su ruta de montaje y archiva tu producto de prueba.
Un 200 con done: true significa que Webflow aceptó el cambio y, como la solicitud establece publishStatus en live, el producto desaparece de la tienda publicada sin necesidad de publicar el sitio.
¿Qué causa que el archivado de SKU de la API de Webflow falle?
La mayoría de los fallos provienen del endpoint elegido para la tarea; el resto se debe a la configuración o a las reglas de compilación y enrutamiento de Webflow Cloud.
La actualización del SKU se realiza correctamente, pero la variante permanece en la tienda
Causa: La solicitud se envió a Actualizar SKU o al endpoint de elementos de CMS con un campo de archivo, esperando que archivara la variante. En la API v2, el archivado solo existe a nivel de producto, por lo que una actualización de SKU cambia los campos que documenta y nada más. Actualizar un SKU también cambia el tipo de producto a Avanzado, lo que toma por sorpresa a los equipos que solo querían archivarlo.
Solución: Usa retire-sku para mostrar la variante individual como sin existencias, o archive-product cuando todo el producto deba salir de la tienda. "Sin existencias" es lo más cerca que llega la API a retirar una variante mientras sus hermanas siguen a la venta; para eliminar la variante en sí, edita las opciones de variante del producto en Webflow.
La página de administración devuelve 500 o 502 en lugar de done: true
Causa: Un 500 significa que falta una variable del paso 2 en el entorno donde se ejecuta la aplicación. Un 502 significa que Webflow rechazó la solicitud y el controlador copia el estado ascendente en la respuesta. Un 401 o 403 ascendente apunta a los alcances del token, un 404 apunta a un ID incorrecto y un 409 significa que el sitio no tiene habilitado el comercio electrónico.
Solución: Para un 500, añade la variable que falta al entorno de Webflow Cloud en el que realizas el despliegue, o a .env.local localmente, y vuelve a desplegar. Para un 502, lee primero status y details. Regenera el token con ambos alcances de comercio electrónico para un 401 o 403, y vuelve a copiar los ID desde Webflow para un 404.
La compilación de Webflow Cloud falla en un controlador de ruta que funcionaba localmente
Causa: La palabra "edge" significa dos cosas diferentes en la misma página de documentación. Webflow Cloud se ejecuta en Cloudflare Workers, una plataforma edge, y los documentos lo describen como un despliegue "usando el runtime Edge". Luego, la página te indica que añadas export const runtime = 'edge', un destino de runtime de Next.js independiente que el adaptador de OpenNext Cloudflare no admite.
Solución: Elimina la directiva de cada controlador de ruta y página; tu código ya se ejecuta en Workers sin ella. Busca runtime = en el proyecto antes de cada despliegue, ya que los fragmentos copiados de tutoriales antiguos suelen arrastrar esa línea.
La página de administración muestra un 404 o un error HTML solo después del despliegue
Causa: La solicitud fetch del cliente se dirige a /api/skus/archive en la raíz de tu dominio en lugar de bajo la ruta de montaje de la aplicación. Localmente, la aplicación se encuentra en la raíz, por lo que el error permanece oculto hasta que la aplicación desplegada reside en algo como /sku-admin. La respuesta es la página 404 de tu sitio de Webflow, por lo que response.json() arroja un error en el HTML.
Solución: Establece NEXT_PUBLIC_BASE_PATH en la ruta de montaje exacta en tu entorno de Webflow Cloud y anteponla a cada solicitud fetch del cliente. Vuelve a desplegar después de cambiar la variable, ya que Next.js integra los valores públicos durante la compilación.
Qué puedes construir a continuación con la API de Webflow y Webflow Cloud
Para el stock de variantes, la guía de la API de inventario muestra cómo leer y actualizar los recuentos por SKU.
Para cada campo de producto y SKU que puedes cambiar, los documentos para desarrolladores de Webflow detallan el objeto de producto completo.
Preguntas frecuentes
¿Puede la misma ruta desarchivar un producto?
Sí, con una acción más. Envía la misma solicitud de Actualizar Producto con isArchived: false y publishStatus: "live". Mantenla como una acción independiente tras el mismo middleware de coincidencia, para que el botón de archivar nunca pueda restaurar un producto por accidente.
¿Archivar un producto requiere publicar el sitio?
No con esta ruta. Actualizar Producto solo modifica el producto en fase de pruebas a menos que publishStatus sea live, que es lo que establece esta ruta. Si eliminas ese campo, el producto permanecerá en la tienda activa hasta la próxima publicación del sitio.
¿Puede la ruta leer un CSV de IDs de producto desde el sistema de archivos?
No. El entorno de ejecución de Webflow Cloud Workers no tiene acceso al sistema de archivos, como indica su página de compatibilidad con Node.js, por lo que el código que intenta leer un CSV de la carpeta del proyecto no funcionará. Acepta la lista en el cuerpo de la solicitud y procésala en memoria.
¿Quién debe custodiar el token de API y el secreto compartido en un proyecto de cliente?
Haz que el propietario del sitio genere el token para que la tienda mantenga el control. Tu agencia solo debe guardarlo como un secreto en el entorno de Webflow Cloud, nunca en el código ni en chats. En la entrega, el propietario renueva el token y el secreto compartido actualizando ambos valores en el entorno y volviendo a desplegar.





