Give feedback as an early user and get the docs plus a full website free. Code FRIEND: valid on one-time purchases only.

E-commerce with Shopify

This starter is a fast, content-driven 11ty site, styled with LESS, edited through Decap CMS, and deployed on Netlify. It doesn't ship a store, but Shopify is happy to own the hard parts, cart, payments, and fulfilment, so adding commerce mostly comes down to pulling product data in and dropping a buy button on a page. There are two ways in: a Buy Button for the quickest path, or the Storefront API to fetch products at build time and render a fully custom storefront.

Before You Start

Make sure you've read the initial setup guide.

See Quick Setup

Option 1: Shopify Buy Button

The easiest way to sell, an embeddable button that works on any static page.

Shopify generates an embeddable product button you can paste into any page. Shopify hosts the cart and checkout, so there's no data fetching and nothing to configure in the build, it's pure client-side JavaScript that works as-is on a static 11ty site.

Steps

  1. 1In your Shopify admin, install the Buy Button sales channel.
  2. 2Generate a Buy Button for a product or collection.
  3. 3Copy the JavaScript snippet Shopify gives you.
  4. 4Create a page in src/content/pages/ that extends the base layout.
  5. 5Paste the snippet inside its body block.

Example shop page

Create a page in src/content/pages/ that extends the base layout, then paste Shopify's snippet inside {% block body %}. Swap in your store domain, Storefront access token, and product ID.

---
title: "Shop | Eleventy Starter Template"
permalink: "/shop/"
lang: en
---

{% extends "layouts/base.html" %}

{% block body %}
  <div id="product-component-1234567890"></div>

  <script type="text/javascript">
  /*<![CDATA[*/
  (function () {
    var scriptURL = 'https://sdks.shopifycdn.com/buy-button/latest/buy-button-storefront.min.js';
    if (window.ShopifyBuy && window.ShopifyBuy.UI) {
      ShopifyBuyInit();
    } else {
      var script = document.createElement('script');
      script.async = true;
      script.src = scriptURL;
      document.head.appendChild(script);
      script.onload = ShopifyBuyInit;
    }
    function ShopifyBuyInit() {
      var client = ShopifyBuy.buildClient({
        domain: 'your-store.myshopify.com',
        storefrontAccessToken: 'your-storefront-token',
      });
      ShopifyBuy.UI.onReady(client).then(function (ui) {
        ui.createComponent('product', {
          id: 'YOUR_PRODUCT_ID',
          node: document.getElementById('product-component-1234567890'),
        });
      });
    }
  })();
  /*]]>*/
  </script>
{% endblock %}

Pros

  • Very fast to set up
  • No data fetching or build step
  • Shopify handles cart and checkout

Cons

  • Limited customization
  • Renders client-side, not pre-rendered
Recommended

Option 2: Storefront API

The headless approach: 11ty fetches your catalog once at build time and renders it with its own Nunjucks templates, so product pages ship as static HTML. Shopify still owns the cart and checkout. Everything below covers this path.

How it works

  1. 1

    Connect

    Add your Storefront API token to .env

  2. 2

    Fetch

    A _data file pulls your catalog at build

  3. 3

    Render

    A shop page loops over the products

  4. 4

    Sell

    Cart API builds the cart, Shopify hosts checkout

Connecting your store

Generate a Storefront access token and keep it in your environment.

In your Shopify admin, go to Settings → Apps and sales channels → Develop apps → Create an app → Storefront API and grant read access to products. Copy the Storefront access token it generates.

Store it in a .env file, add .env to .gitignore (it isn't ignored by default), and load it with dotenv so the build can read it: npm install dotenv.

# .env ,  add ".env" to your .gitignore, never commit it
SHOPIFY_STORE_DOMAIN=your-store.myshopify.com
SHOPIFY_STOREFRONT_TOKEN=your_public_storefront_token
SHOPIFY_API_VERSION=2025-01

Deploying on Netlify? Add the same variables under Site settings → Environment variables so production builds can reach Shopify.

The Storefront token is safe in client code

The Storefront access token is publicly exposed in client-side code by design and is safe for that. Don't confuse it with the Admin API key, which can write to your store, that one must never ship to the browser.

Fetch products at build time

A global data file pulls your catalog once per build.

Add a global data file at src/_data/products.js, following the same pattern as the kit's existing client.js. Because it lives in _data, 11ty runs it once per build and exposes the return value to every template as a products array. Node 18+ (which Eleventy 3 requires) has a global fetch, so no extra HTTP library is needed.

// src/_data/products.js, runs once at build time
require("dotenv").config();

const endpoint = `https://${process.env.SHOPIFY_STORE_DOMAIN}/api/${process.env.SHOPIFY_API_VERSION}/graphql.json`;

const QUERY = `{
  products(first: 50) {
    edges {
      node {
        id
        title
        handle
        description
        featuredImage { url altText }
        priceRange { minVariantPrice { amount currencyCode } }
        variants(first: 1) { edges { node { id } } }
      }
    }
  }
}`;

module.exports = async function () {
  const res = await fetch(endpoint, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Shopify-Storefront-Access-Token": process.env.SHOPIFY_STOREFRONT_TOKEN,
    },
    body: JSON.stringify({ query: QUERY }),
  });

  const { data } = await res.json();

  // Flatten the GraphQL edges into a template-friendly shape. Every
  // template can now read the result as a global `products` array.
  return data.products.edges.map(({ node }) => ({
    title: node.title,
    handle: node.handle,
    description: node.description,
    image: node.featuredImage,
    price: node.priceRange.minVariantPrice,
    variantId: node.variants.edges[0].node.id,
  }));
};

Extend the query with any fields your store needs from the Storefront API reference.

Render the shop page

Loop over the products with the kit's own page convention.

Create src/content/pages/shop.html: the same convention the starter uses for About, Contact, and the rest. It extends the base layout and loops over the products array to render a card per item, loading a small cart script with <script defer> in its head block.

---
title: "Shop | Eleventy Starter Template"
description: "Browse our products"
permalink: "/shop/"
lang: en
---

{% extends "layouts/base.html" %}

{% block head %}
  <link rel="stylesheet" href="/assets/css/shop.css">
  <script defer src="/assets/js/shop.js"></script>
{% endblock %}

{% block body %}
  <section id="shop">
    <ul class="products">
      {% for product in products %}
      <li class="product">
        <img src="{{ product.image.url }}" alt="{{ product.image.altText }}" width="400" height="400">
        <h2>{{ product.title }}</h2>
        <p>{{ product.price.amount }} {{ product.price.currencyCode }}</p>
        <button class="buy" data-variant-id="{{ product.variantId }}">Add to cart</button>
      </li>
      {% endfor %}
    </ul>
  </section>
{% endblock %}

Use a plain <img> for product images

Product images are remote Shopify URLs, so a normal <img> is right here. The kit's {% image %} shortcode is for optimising local files in src/assets: it can't process a remote URL.

Cart & Checkout

Each Add-to-cart button creates a cart and redirects to Shopify.

The button on each product only has to do one thing: create a cart and send the shopper to Shopify's checkout. Add a script at src/assets/js/shop.js (everything under src/assets is passthrough-copied to /assets). It creates a cart with the Storefront Cart API and follows the checkoutUrl it returns.

// src/assets/js/shop.js, passthrough-copied to /assets/js/shop.js
const DOMAIN = "your-store.myshopify.com";
const TOKEN = "your_public_storefront_token"; // Storefront token, safe in client code
const API_VERSION = "2025-01";

async function checkout(variantId) {
  const res = await fetch(`https://${DOMAIN}/api/${API_VERSION}/graphql.json`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Shopify-Storefront-Access-Token": TOKEN,
    },
    body: JSON.stringify({
      query: `mutation Create($id: ID!) {
        cartCreate(input: { lines: [{ merchandiseId: $id, quantity: 1 }] }) {
          cart { checkoutUrl }
        }
      }`,
      variables: { id: variantId },
    }),
  });

  const { data } = await res.json();
  // Hand the buyer off to Shopify's hosted, PCI-compliant checkout.
  window.location.href = data.cartCreate.cart.checkoutUrl;
}

document.querySelectorAll(".buy").forEach((btn) =>
  btn.addEventListener("click", () => checkout(btn.dataset.variantId))
);

Pros

  • Full control over markup and styling
  • Products pre-rendered as static HTML
  • Fast and SEO-friendly

Cons

  • Requires API integration
  • Rebuild needed when products change

Your store is ready

Products fetched at build time, rendered with your own Nunjucks templates, and a Cart API button handing shoppers off to Shopify's hosted checkout, all on top of the static 11ty site you already have.