May 5, 2026
Shopify Storefront API + Webflow: Custom UX Without Limitations

.png)
Table of content
Transform your website with expert Webflow development
Let’s discuss how our team can bring your digital vision to life.

Talk to Our Webflow Experts
Transform your website with expert Webflow development
From brand identity to Webflow development and marketing, we handle it all. Trusted by 50+ global startups and teams.
Most ecommerce projects reach a wall. Shopify gives you powerful commerce infrastructure, but its themes can feel like a cage. Webflow gives you pixel-perfect design control, but lacks native commerce depth. At Appsrow, we have spent years solving exactly this tension by combining Shopify's Storefront API with Webflow as the front end layer. The result is a fully custom shopping experience where neither design nor commerce capability is compromised.
This guide walks through the architecture, the real code, and the decisions that matter when you want commerce that is genuinely built around your brand, not templated around someone else's.
Why This Stack Exists
Shopify's native storefront is Liquid-based. Liquid is reliable, but it tightly couples your design to Shopify's rendering pipeline. You cannot easily build scroll-triggered product reveals, complex animation sequences, or layout logic that breaks from the standard grid without fighting the theme layer constantly.
Webflow is a visual development environment that compiles clean HTML, CSS, and JavaScript. Designers can build production-quality pages without touching code. But Webflow Commerce is limited in product variant logic, headless integrations, and checkout customization.
The Shopify Storefront API is the bridge. It is a public-facing GraphQL API that exposes products, collections, carts, and checkout to any frontend, regardless of where it is hosted. Webflow becomes the design layer. Shopify handles inventory, payments, and orders. Both tools do what they are genuinely best at.
Architecture Overview
Before writing a single line of code, understand the data flow:
- Webflow hosts the front end: landing pages, product pages, collection pages, and the cart UI.
- Custom JavaScript embedded in Webflow via custom code blocks makes GraphQL requests to the Shopify Storefront API.
- Shopify handles cart creation, checkout redirect, payment, fulfillment, and order management.
- No server is required for basic implementations. The Storefront API is publicly accessible with a storefront access token, not your Admin API key.
Security note: The Storefront API token is safe to expose in client-side JavaScript. It is read-only for products and write-only for cart operations. Never embed your Admin API credentials in Webflow custom code.
Step 1: Enable the Storefront API in Shopify
In your Shopify admin, navigate to Settings > Apps and sales channels > Develop apps. Create a new app, then under the Storefront API configuration, grant these scopes at minimum:
unauthenticated_read_product_listingsunauthenticated_read_product_inventoryunauthenticated_write_checkoutsunauthenticated_read_checkouts
Copy the Storefront access token that is generated. This is the token your Webflow JavaScript will use.
Step 2: Query Products with GraphQL
The Storefront API speaks GraphQL. The function below is your universal API wrapper. Every subsequent call routes through it. Place this in Webflow's custom code section before the closing body tag.
// shopify-storefront.js
// Paste in Webflow custom code (before </body>)
const SHOPIFY_DOMAIN = 'your-store.myshopify.com';
const STOREFRONT_TOKEN = 'your-storefront-access-token';
const API_VERSION = '2024-04';
async function storefrontQuery(query, variables = {}) {
const res = await fetch(
`https://${SHOPIFY_DOMAIN}/api/${API_VERSION}/graphql.json`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Shopify-Storefront-Access-Token': STOREFRONT_TOKEN
},
body: JSON.stringify({ query, variables })
}
);
const { data, errors } = await res.json();
if (errors) throw new Error(errors[0].message);
return data;
}
// Fetch a single product by URL handle
const PRODUCT_QUERY = `
query GetProduct($handle: String!) {
productByHandle(handle: $handle) {
id
title
description
priceRange {
minVariantPrice { amount currencyCode }
}
images(first: 6) {
edges { node { url altText } }
}
variants(first: 20) {
edges {
node {
id
title
availableForSale
priceV2 { amount currencyCode }
selectedOptions { name value }
}
}
}
}
}
`;
async function loadProduct() {
const handle = window.location.pathname.split('/').pop();
const data = await storefrontQuery(PRODUCT_QUERY, { handle });
const product = data.productByHandle;
renderProduct(product);
}
document.addEventListener('DOMContentLoaded', loadProduct);
Step 3: Render Product Data Into Webflow Elements
In Webflow, build your product page layout with custom attributes on elements to serve as render targets. Give your product title element a custom attribute of data-product="title", for example. This keeps your JavaScript decoupled from CSS class names, which Webflow generates and can change without warning.
function renderProduct(product) {
if (!product) return;
setText('[data-product="title"]', product.title);
setText('[data-product="description"]', product.description);
setText(
'[data-product="price"]',
formatPrice(
product.priceRange.minVariantPrice.amount,
product.priceRange.minVariantPrice.currencyCode
)
);
const mainImg = document.querySelector('[data-product="main-image"]');
if (mainImg && product.images.edges.length) {
const { url, altText } = product.images.edges[0].node;
mainImg.src = url;
mainImg.alt = altText || product.title;
}
buildVariantSelectors(product.variants.edges.map(e => e.node));
}
function setText(selector, value) {
const el = document.querySelector(selector);
if (el) el.textContent = value;
}
function formatPrice(amount, currency) {
return new Intl.NumberFormat('en-US', {
style: 'currency', currency
}).format(parseFloat(amount));
}
Step 4: Build a Headless Cart
The Storefront API uses a Cart object introduced in API version 2022-01, which is far cleaner than the older Checkout object. A cart is created once and persisted in localStorage so it survives page navigation inside Webflow.
const CREATE_CART = `
mutation CreateCart($lines: [CartLineInput!]) {
cartCreate(input: { lines: $lines }) {
cart {
id
checkoutUrl
lines(first: 50) {
edges {
node {
id
quantity
merchandise {
... on ProductVariant {
id title priceV2 { amount currencyCode }
}
}
}
}
}
cost { totalAmount { amount currencyCode } }
}
}
}
`;
const ADD_TO_CART = `
mutation AddLines($cartId: ID!, $lines: [CartLineInput!]!) {
cartLinesAdd(cartId: $cartId, lines: $lines) {
cart {
id
lines(first: 50) {
edges {
node {
id
quantity
merchandise {
... on ProductVariant {
id title priceV2 { amount currencyCode }
}
}
}
}
}
cost { totalAmount { amount currencyCode } }
}
}
}
`;
async function getOrCreateCart() {
const cartId = localStorage.getItem('shopify_cart_id');
if (cartId) return cartId;
const data = await storefrontQuery(CREATE_CART, { lines: [] });
const newCartId = data.cartCreate.cart.id;
localStorage.setItem('shopify_cart_id', newCartId);
return newCartId;
}
async function addToCart(variantId, quantity = 1) {
const cartId = await getOrCreateCart();
const data = await storefrontQuery(ADD_TO_CART, {
cartId,
lines: [{ merchandiseId: variantId, quantity }]
});
const cart = data.cartLinesAdd.cart;
updateCartUI(cart);
return cart;
}
document.querySelector('[data-product="add-to-cart"]')?.addEventListener('click', async () => {
const selectedVariantId = document.querySelector('[data-variant="selected"]')?.dataset.variantId;
if (!selectedVariantId) {
alert('Please select a variant.');
return;
}
await addToCart(selectedVariantId);
});
Pro tip: Store the checkoutUrl returned by the Cart API. When the user clicks your Checkout button, redirect them to that URL. Shopify handles payment, taxes, and order confirmation from there.
Step 5: Variant Selection Logic
Variants in Shopify are combinations of options like Size and Color. The Storefront API returns each variant with its selectedOptions array. At Appsrow, we build a matrix-based selector that maps active option choices to the correct variant ID without relying on Shopify's native dropdowns.
function buildVariantSelectors(variants) {
const variantMap = {};
variants.forEach(v => {
const key = v.selectedOptions
.map(o => `${o.name}:${o.value}`)
.sort()
.join('|');
variantMap[key] = {
id: v.id,
available: v.availableForSale
};
});
const activeOptions = {};
document.querySelectorAll('[data-option-name][data-option-value]').forEach(btn => {
btn.addEventListener('click', () => {
const { optionName, optionValue } = btn.dataset;
activeOptions[optionName] = optionValue;
document.querySelectorAll(`[data-option-name="${optionName}"]`)
.forEach(b => b.classList.toggle('is-active', b === btn));
const key = Object.entries(activeOptions)
.map(([n, v]) => `${n}:${v}`)
.sort()
.join('|');
const matched = variantMap[key];
if (matched) {
const atcBtn = document.querySelector('[data-product="add-to-cart"]');
atcBtn.dataset.variantId = matched.id;
atcBtn.disabled = !matched.available;
atcBtn.textContent = matched.available ? 'Add to Cart' : 'Sold Out';
}
});
});
}
In Webflow, each size or color button carries data-option-name="Size" and data-option-value="M" as custom attributes. No JavaScript framework is required. This runs as vanilla JS inside any Webflow page.
Step 6: Cart Drawer
Instead of redirecting to a cart page, a slide-in cart drawer keeps the user on the product page and improves conversion. Build the drawer panel in Webflow, then use JavaScript to toggle its visibility and populate it with live data from the Storefront API.
function updateCartUI(cart) {
const drawer = document.querySelector('[data-cart="drawer"]');
const lineList = document.querySelector('[data-cart="lines"]');
const total = document.querySelector('[data-cart="total"]');
const checkoutBtn = document.querySelector('[data-cart="checkout"]');
lineList.innerHTML = '';
cart.lines.edges.forEach(({ node }) => {
const li = document.createElement('div');
li.innerHTML = `
<span>${node.merchandise.title}</span>
<span>x${node.quantity}</span>
<span>${formatPrice(
node.merchandise.priceV2.amount,
node.merchandise.priceV2.currencyCode
)}</span>
`;
lineList.appendChild(li);
});
const { amount, currencyCode } = cart.cost.totalAmount;
total.textContent = formatPrice(amount, currencyCode);
checkoutBtn.href = cart.checkoutUrl;
drawer.style.display = 'block';
}
Step 7: Collection Pages and Pagination
Webflow collection pages can be fully powered by the Storefront API. Instead of using Webflow CMS collections for products, query directly from a Shopify collection and render into a Webflow-designed grid. Cursor-based pagination from the API maps cleanly to infinite scroll or a "Load more" button.
const COLLECTION_QUERY = `
query GetCollection($handle: String!, $first: Int!, $after: String) {
collection(handle: $handle) {
title
products(first: $first, after: $after) {
pageInfo { hasNextPage endCursor }
edges {
node {
id
handle
title
priceRange { minVariantPrice { amount currencyCode } }
featuredImage { url altText }
}
}
}
}
}
`;
async function loadCollection(handle, pageSize = 12, cursor = null) {
const data = await storefrontQuery(COLLECTION_QUERY, {
handle,
first: pageSize,
after: cursor
});
const { products } = data.collection;
products.edges.forEach(({ node }) => renderProductCard(node));
if (products.pageInfo.hasNextPage) {
const loadMoreBtn = document.querySelector('[data-collection="load-more"]');
if (loadMoreBtn) {
loadMoreBtn.style.display = 'block';
loadMoreBtn.onclick = () =>
loadCollection(handle, pageSize, products.pageInfo.endCursor);
}
}
}
Performance Considerations
A headless Webflow and Shopify build has excellent performance potential. A few patterns matter in production:
- Cache product data in sessionStorage so repeat visits to the same product page do not fire a new API call on every load.
- Debounce variant selection if you update pricing dynamically on every option click.
- Use Shopify's image URL parameters to serve correctly sized images. Appending
?width=600to any Shopify CDN image URL returns a resized version automatically. - Load your Storefront JavaScript asynchronously using the
deferattribute so it does not block Webflow's critical rendering path. - Preload fonts and above-the-fold images in Webflow's page settings so the visual shell loads instantly while JavaScript hydrates the commerce data.
How Appsrow Builds This in Production
At Appsrow, our Webflow and Shopify Migration projects go beyond a basic API integration. Here is what our production workflow looks like.
Modular JavaScript Architecture
We build the Storefront layer as a set of ES modules bundled with Vite and hosted on a CDN. Webflow pages import only the module they need, keeping page-level JavaScript payloads small and load times fast across the entire site.
Environment Management
Storefront tokens are stored as Webflow project variables and injected at build time via a lightweight CI pipeline. No hardcoded credentials exist in any page-level code.
Webflow CMS as the Content Layer
We use Webflow CMS for editorial content like brand storytelling and campaign imagery, while Shopify owns product and inventory data. The two are linked by product handle, creating a seamless content and commerce experience in one URL structure with no duplication of effort.
Custom Cart Attributes
Using cart attributes in the Storefront API, we pass custom metadata such as gift messages, personalization inputs, and referral codes directly through to the Shopify order without any third-party app.
const UPDATE_CART_ATTRIBUTES = `
mutation UpdateCartAttributes($cartId: ID!, $attributes: [AttributeInput!]!) {
cartAttributesUpdate(cartId: $cartId, attributes: $attributes) {
cart { id }
}
}
`;
await storefrontQuery(UPDATE_CART_ATTRIBUTES, {
cartId: await getOrCreateCart(),
attributes: [
{ key: 'gift_message', value: 'Happy Birthday!' },
{ key: 'referral_code', value: 'FRIEND20' }
]
});
Conclusion
Combining Shopify's Storefront API with Webflow gives you a commerce stack where design and functionality genuinely coexist without compromise. Webflow handles the visual layer with the precision that brand-driven projects demand. Shopify handles the commerce infrastructure that high-volume stores require. The Storefront API is the connective tissue that makes both work together without either side giving ground.
The patterns in this guide cover the foundation: product queries, cart management, variant logic, collection pagination, and cart attributes. From here, the same architecture extends to wishlists, multi-currency pricing, customer authentication, and subscription commerce using the same GraphQL layer.
At Appsrow, we have shipped this stack across fashion, lifestyle, and DTC brands where standard Shopify themes simply could not deliver the experience the brand demanded. If your project needs a frontend that reflects the quality of your product, this is how we build it.
Recent Insights
Appsrow transformed our website with a fresh layout that adheres to our new design guidelines while integrating CMS-driven updates. Their responsiveness and rapid implementation of changes ensured a visually appealing, fully responsive platform delivered right on schedule.
Carsten Schwant
Founder
Appsrow Solutions revolutionized our digital presence by designing and building our website from the ground up to perfectly capture our legal advisory expertise. Their agile approach, meticulous attention to detail, and on-time delivery resulted in a dynamic, user-friendly platform that exceeded our expectations.
Adam Leipzig
Owner
Appsrow team turned our agency homepage into a visually stunning and highly efficient platform. Their expert design, fast execution, and clear communication not only boosted user engagement and conversion rates but also elevated our brand’s online style to a level our team truly loves.
Josef Kujawski
Owner


.png)
.png)