Headless Commerce September 17, 2025 12 min read

The Embeddable Store: One iframe Tag, Infinite Distribution

One line of HTML. Any website. Full product catalog. Checkout included. Here is how to build a store widget that turns every partner site into a sales channel.

Tyler Colby · Founder, Colby's Data Movers

The Distribution Problem

Imagine you run a national organization with 100+ regional chapters. Each chapter has its own website. You sell branded merchandise, training materials, and equipment through a central Shopify store. Your members visit their chapter website daily. They visit your central store maybe once a year.

The products are in the wrong place. They live on your store. Your audience lives on chapter websites. Every click between those two locations loses buyers.

Or imagine you are a manufacturer with 50 authorized dealers. Each dealer has a website. You want them to sell your products, but you want to control pricing, imagery, and the checkout experience. You do not want 50 different product pages with 50 different descriptions and 50 different pricing mistakes.

Or imagine you are a brand with an affiliate program. You want affiliates to embed your products in their blog posts, reviews, and comparison pages. Not a link to your store. The actual product. Right there in their content.

The solution to all three scenarios is the same: an embeddable store widget.

Why iframe

There are three ways to embed content on a third-party site:

  1. JavaScript widget. Inject a script tag that renders content into a div. Used by Disqus, Intercom, and most chat widgets.
  2. Web Component. A custom HTML element that encapsulates its own styles and behavior. Used by some modern widget libraries.
  3. iframe. A separate HTML document loaded in a frame. Used by YouTube, Stripe, and every payment form on the internet.

For a store embed, iframe wins. Here is why:

  • Complete CSS isolation. The host site's styles cannot affect your store. Your store's styles cannot break the host site. This matters more than anything else when you are embedding on 100+ sites with 100 different WordPress themes and CSS frameworks.
  • Security boundary. The iframe runs in its own origin. The host page cannot read form inputs, intercept checkout data, or tamper with prices. For e-commerce, this is not optional.
  • No dependency conflicts. If the host site uses React 17 and your store uses React 19, there is no conflict. Different browsing contexts.
  • Simplicity for embedders. One HTML tag. No build step. No npm install. No JavaScript knowledge required. A chapter volunteer who can edit a WordPress page can embed the store.

The trade-off: iframes cannot easily communicate with the host page. Sizing is awkward. They feel "embedded" rather than native. These are real costs. I will show you how to mitigate each one.

The Embed Tag

Here is what the chapter webmaster copies and pastes:

<iframe
  src="https://store.example.com/embed?region=northeast&theme=light&ref=chapter-42"
  style="width:100%;border:none;min-height:600px;"
  title="Example Store"
  loading="lazy"
  allow="payment"
></iframe>

<script>
// Auto-resize iframe to fit content
window.addEventListener('message', function(e) {
  if (e.origin !== 'https://store.example.com') return;
  if (e.data.type === 'resize') {
    document.querySelector(
      'iframe[src*="store.example.com"]'
    ).style.height = e.data.height + 'px';
  }
});
</script>

Three URL parameters control the embed:

  • region: Filters products to show only items relevant to this chapter's region. A northeast chapter sees cold-weather gear. A southwest chapter sees desert equipment.
  • theme: Light or dark mode, matching the host site's design.
  • ref: Attribution parameter. Every order placed through this embed is attributed to chapter-42. This is how you track which chapters drive sales.

Building the Embed Page

The iframe loads a standalone HTML page from your domain. This is a complete mini-application, not a full storefront. It shows products, handles variant selection, and links to Shopify checkout.

// app/embed/page.jsx
import { getAllProducts } from '@/lib/shopify';

export default async function EmbedPage({ searchParams }) {
  const region = searchParams.region || 'all';
  const theme = searchParams.theme || 'light';
  const ref = searchParams.ref || 'direct';

  let products = await getAllProducts();

  // Filter by region tag
  if (region !== 'all') {
    products = products.filter(p =>
      p.tags.includes(`region:${region}`)
    );
  }

  return (
    <html data-theme={theme}>
      <head>
        <style>{embedStyles}</style>
        <script
          dangerouslySetInnerHTML={{
            __html: resizeScript
          }}
        />
      </head>
      <body>
        <div className="embed-store">
          <ProductGrid
            products={products}
            ref={ref}
          />
        </div>
      </body>
    </html>
  );
}

Key architectural decisions:

  • Inline styles. No external CSS files. Everything is in a style tag or inline. This eliminates a network request and ensures the embed renders in one round trip.
  • Minimal JavaScript. The embed page should be under 50KB total. No heavy framework bundle. Server-rendered HTML with a small client-side script for cart management and resize communication.
  • No navigation. The embed has no header, no footer, no breadcrumbs. It is a product grid with product detail modals. The buyer never leaves the host page until checkout.

The Resize Script

iframes do not automatically resize to fit their content. You need to measure the content height and post it to the parent window:

// Runs inside the iframe
const resizeScript = `
  function notifyResize() {
    const height = document.body.scrollHeight;
    window.parent.postMessage(
      { type: 'resize', height: height },
      '*'
    );
  }

  // Resize on load
  window.addEventListener('load', notifyResize);

  // Resize on content change (modal open, etc.)
  const observer = new ResizeObserver(notifyResize);
  observer.observe(document.body);

  // Resize on image load (images affect height)
  document.querySelectorAll('img').forEach(img => {
    img.addEventListener('load', notifyResize);
  });
`;

The ResizeObserver catches dynamic content changes. When a buyer opens a product detail modal, the iframe height adjusts. When they close it, it shrinks back. The host page never scrolls inside the iframe.

Theme Parameters

The embed needs to visually match the host site. Full theming is impractical (you cannot replicate every WordPress theme), but light/dark mode with configurable accent colors covers 90% of cases:

/* Embed CSS with theme support */
:root {
  --bg: #ffffff;
  --text: #1a1a1a;
  --text-secondary: #666666;
  --border: #e5e5e5;
  --accent: #06b6d4;
  --card-bg: #f9fafb;
}

[data-theme="dark"] {
  --bg: #1a1a1a;
  --text: #f0f0f0;
  --text-secondary: #999999;
  --border: #333333;
  --accent: #22d3ee;
  --card-bg: #242424;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont,
    'Segoe UI', sans-serif;
  margin: 0;
  padding: 16px;
}

.product-card {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
}

.product-price {
  color: var(--text);
  font-weight: 600;
}

.buy-button {
  background: var(--accent);
  color: white;
  border: none;
  padding: 10px 20px;
  border-radius: 6px;
  cursor: pointer;
  font-weight: 500;
}

For more granular control, accept an accent color as a URL parameter:

<iframe
  src="https://store.example.com/embed?theme=dark&accent=ff6b35"
></iframe>

Parse the accent color on the server and inject it as a CSS custom property. Now the embed's buttons and links match the host site's brand color.

Product Detail Without Navigation

In a normal storefront, clicking a product takes you to a product page. In an embed, you cannot navigate. The buyer should never leave the host page.

The solution: modal product detail. Click a product card and a modal slides up within the iframe, showing full product information.

// components/ProductModal.jsx
function ProductModal({ product, ref, onClose }) {
  const [selectedVariant, setSelectedVariant] = useState(
    product.variants[0]
  );

  // Checkout URL with attribution
  const checkoutUrl =
    `https://store.myshopify.com/cart/` +
    `${selectedVariant.id}:1` +
    `?ref=${ref}` +
    `&utm_source=embed` +
    `&utm_medium=iframe` +
    `&utm_campaign=${ref}`;

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div
        className="modal-content"
        onClick={e => e.stopPropagation()}
      >
        <button className="modal-close" onClick={onClose}>
          Close
        </button>

        <div className="modal-images">
          {product.images.map(img => (
            <img
              key={img.id}
              src={img.src}
              alt={img.alt || product.title}
              loading="lazy"
            />
          ))}
        </div>

        <div className="modal-info">
          <h2>{product.title}</h2>
          <p className="price">
            ${selectedVariant.price}
          </p>

          {product.variants.length > 1 && (
            <VariantSelector
              variants={product.variants}
              selected={selectedVariant}
              onChange={setSelectedVariant}
            />
          )}

          <div
            className="description"
            dangerouslySetInnerHTML={{
              __html: product.body_html
            }}
          />

          <a
            href={checkoutUrl}
            target="_blank"
            rel="noopener"
            className="buy-button"
          >
            Buy Now
          </a>
        </div>
      </div>
    </div>
  );
}

The "Buy Now" link opens Shopify checkout in a new tab (target="_blank"). The buyer completes checkout on Shopify's domain. The UTM parameters and ref attribute on the checkout URL ensure the order is attributed to the correct chapter or affiliate.

Attribution and Tracking

Every embed instance carries a ref parameter. This ref flows through to Shopify checkout as a URL parameter. On the backend, a Shopify webhook captures the order and its referring source:

// app/api/webhooks/order-attribution/route.js
export async function POST(request) {
  const order = await verifyAndParse(request);

  // Extract ref from the order's landing_site URL
  const landingSite = order.landing_site || '';
  const url = new URL(landingSite, 'https://placeholder.com');
  const ref = url.searchParams.get('ref');

  if (ref) {
    // Log attribution
    await db.insert('order_attributions', {
      orderId: order.id,
      orderTotal: order.total_price,
      ref: ref,
      createdAt: order.created_at
    });

    // Update affiliate/chapter dashboard
    await updateDashboard(ref, {
      orders: 1,
      revenue: parseFloat(order.total_price)
    });
  }

  return new Response('OK', { status: 200 });
}

This gives you a dashboard showing which embed locations drive the most orders. Chapter 42 in Boston generated $14,200 in sales this month. The Phoenix affiliate generated $8,900. You know exactly where your revenue comes from.

The Real-World Use Case

Let me describe the scenario that inspired this architecture.

A national motorsport organization. 70,000+ members across 100+ regional clubs. Each club has its own website, ranging from professional WordPress sites to GoDaddy page builders to hand-coded HTML from 2008. The national organization sells safety equipment, branded merchandise, and event registration through a Shopify store.

The problem: members spend their time on the club website. They check event schedules, read local news, and interact with their regional community. They rarely visit the national store. When they need safety gear, they Google it and end up on Amazon or a competitor.

The solution: one iframe tag. Every club website embeds the national store, filtered to show products relevant to their region and discipline. Desert clubs see cooling vests and sun protection. Northern clubs see cold-weather gear. Road racing clubs see HANS devices and fire suits.

<!-- Example embed for Northeast Road Racing Chapter -->
<iframe
  src="https://store.org/embed?region=northeast&discipline=road&ref=chapter-ne-road"
  style="width:100%;border:none;min-height:600px;"
  title="Official Gear Store"
  loading="lazy"
  allow="payment"
></iframe>

The results after 6 months:

Embed deployments:       67 club websites
Monthly embed pageviews: 42,000
Monthly orders via embed: 380
Revenue via embed:       $127,000/month
Average order via embed: $334

Previous monthly revenue
  from club referrals:   $18,000

Increase:                $109,000/month (6x)

The 6x increase came from one thing: putting the products where the audience already was. No new traffic acquisition. No ad spend. Just distribution.

Security Considerations

Embedding commerce in an iframe raises legitimate security questions. Here is how we handle them:

  • No payment processing in the iframe. The embed never handles credit card data. The "Buy Now" button links to Shopify checkout on Shopify's domain. PCI compliance is Shopify's responsibility.
  • Content Security Policy. The embed page sets strict CSP headers. No inline script execution except the resize handler. No external script loading.
  • X-Frame-Options. Set to ALLOW-FROM for approved domains, or use Content-Security-Policy: frame-ancestors to control which sites can embed your store.
  • HTTPS only. The embed URL is HTTPS. If the host page is HTTP, the browser will block it. This is fine. If a chapter website is still on HTTP in 2025, they need to fix that first.
// Middleware to set security headers on embed pages
export function middleware(request) {
  const response = NextResponse.next();

  if (request.nextUrl.pathname.startsWith('/embed')) {
    // Allow embedding from approved domains
    response.headers.set(
      'Content-Security-Policy',
      "frame-ancestors 'self' *.example.org *.example.com"
    );

    // Strict CSP for the embed content itself
    response.headers.set(
      'X-Content-Type-Options',
      'nosniff'
    );
  }

  return response;
}

The Limitations

Honest trade-offs:

  • SEO is zero. Content inside an iframe is invisible to search engines. The embed is a sales tool, not a discovery tool. You still need your main store for organic search.
  • Mobile is tricky. iframes on mobile require careful height management. Fixed-height iframes cause double-scrolling (the page scrolls and the iframe scrolls). The resize script mitigates this, but it is not perfect on all devices.
  • Third-party cookie restrictions. Some browsers block third-party cookies in iframes. This can affect cart persistence if the buyer navigates away and comes back. Using checkout permalinks (which carry the cart in the URL) avoids this.
  • Maintenance burden. You now have two frontends to maintain: your main store and the embed. Keep the embed simple to minimize this cost.

Despite these limitations, the embed pattern works. It works because it solves a distribution problem that no amount of SEO or advertising can solve: getting your products in front of people who will never visit your store.

If you have a network of partners, chapters, affiliates, or dealers and you want them selling your products from their own sites, this is the architecture. One tag. Infinite distribution.