Webflow
May 6, 2026

Shopify Storefront API + Webflow: Custom UX Without Limitations

Author Image
Author
Parth Parmar
Blog Main Image

Table of content

Transform your website with expert Webflow development

Let’s discuss how our team can bring your digital vision to life.

Man in red hoodie working on a website design displayed on a large curved monitor at a wooden desk with plants and a coffee mug nearby.

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:

  1. Webflow hosts the front end: landing pages, product pages, collection pages, and the cart UI.
  2. Custom JavaScript embedded in Webflow via custom code blocks makes GraphQL requests to the Shopify Storefront API.
  3. Shopify handles cart creation, checkout redirect, payment, fulfillment, and order management.
  4. 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_listings
  • unauthenticated_read_product_inventory
  • unauthenticated_write_checkouts
  • unauthenticated_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=600 to any Shopify CDN image URL returns a resized version automatically.
  • Load your Storefront JavaScript asynchronously using the defer attribute 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.

Previous
Previous

More Blogs

Next
No next post

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