WireGum integration for Astro product pages
Astro can own routing, content, layout and product storytelling while WireGum owns sellable variants, stock, cart state, checkout creation, shipping country selection and customer email.
1. Add environment variables
Keep the WireGum app URL and workspace slug in public Astro variables. The storefront domain must also be authorized in WireGum Storefront before browser requests work.
PUBLIC_WIREGUM_APP_URL=https://app.example.com
PUBLIC_WIREGUM_ORGANIZATION=workspace-slugIf the project uses TypeScript env typing, add the variables to src/env.d.ts.
interface ImportMetaEnv {
readonly PUBLIC_WIREGUM_APP_URL: string;
readonly PUBLIC_WIREGUM_ORGANIZATION: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}2. Store the WireGum product reference in Astro content
Keep editorial content in Astro and store the WireGum public product ID as a field. Slugs are still supported, but the public ID survives slug changes.
import { defineCollection, z } from "astro:content";
const products = defineCollection({
type: "content",
schema: z.object({
title: z.string(),
description: z.string().optional(),
wiregumProduct: z.string(),
image: z.string().optional()
})
});
export const collections = { products };---
title: Linen tote
description: A lightweight tote for daily use.
wiregumProduct: wg_12ab34cd56
image: /products/linen-tote.jpg
---3. Load the WireGum widget once per page
Put the script in a small component so layouts can pass the current language. If the Astro site does not use i18n routing yet, pass a fixed locale such as en or it.
---
type Props = {
locale?: string;
};
const { locale = "en" } = Astro.props;
const appUrl = import.meta.env.PUBLIC_WIREGUM_APP_URL;
const organization = import.meta.env.PUBLIC_WIREGUM_ORGANIZATION;
const widgetSrc = appUrl + "/api/storefront/widget";
---
<script
async
src={widgetSrc}
data-wiregum-organization={organization}
data-wiregum-locale={locale}
></script>---
import WireGumScript from "../components/WireGumScript.astro";
type Props = {
title: string;
locale?: string;
};
const { title, locale = "en" } = Astro.props;
---
<!doctype html>
<html lang={locale}>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>{title}</title>
<WireGumScript locale={locale} />
</head>
<body>
<slot />
</body>
</html>4. Create a reusable buy box component
WireGum reads data attributes and renders the live price, variant selector, cart controls and checkout action. Astro keeps the surrounding HTML and CSS.
---
type Props = {
productRef: string;
locale?: string;
showCart?: boolean;
};
const { productRef, locale = "en", showCart = true } = Astro.props;
---
<section data-wiregum-product={productRef} lang={locale} class="buy-box">
<div data-wiregum-price></div>
<div data-wiregum-variant-selector></div>
<label class="quantity">
<span>Quantity</span>
<input data-wiregum-quantity type="number" min="1" value="1" />
</label>
<button data-wiregum-add-to-cart type="button">Add to cart</button>
</section>
{showCart && (
<aside class="cart">
<div data-wiregum-cart-count></div>
<div data-wiregum-country-selector></div>
<div data-wiregum-cart></div>
</aside>
)}5. Render add-to-cart buttons in a product grid
A listing page can show multiple WireGum buttons before the site has dedicated product detail pages. Each card carries its own product reference and all buttons write to the same cart.
---
import { getCollection } from "astro:content";
const locale = Astro.currentLocale ?? "en";
const products = await getCollection("products");
---
<section class="product-grid" lang={locale}>
{products.map((product) => (
<article data-wiregum-product={product.data.wiregumProduct}>
{product.data.image && (
<img src={product.data.image} alt={product.data.title} loading="lazy" />
)}
<h2>{product.data.title}</h2>
<div data-wiregum-price></div>
<button data-wiregum-add-to-cart type="button">Add to cart</button>
</article>
))}
</section>
<div data-wiregum-cart-count></div>
<div data-wiregum-country-selector></div>
<div data-wiregum-cart></div>6. Render a product page
The page loads product copy from Astro content and gives WireGum only the product reference and locale. Checkout uses the same locale when it creates the Stripe Checkout Session.
---
import { getEntry } from "astro:content";
import BaseLayout from "../../layouts/BaseLayout.astro";
import ProductBuyBox from "../../components/ProductBuyBox.astro";
const locale = Astro.currentLocale ?? "en";
const { slug } = Astro.params;
const product = slug ? await getEntry("products", slug) : null;
if (!product) {
return Astro.redirect("/404");
}
---
<BaseLayout title={product.data.title} locale={locale}>
<main class="product-page">
{product.data.image && (
<img src={product.data.image} alt={product.data.title} loading="eager" />
)}
<article>
<h1>{product.data.title}</h1>
{product.data.description && <p>{product.data.description}</p>}
<ProductBuyBox productRef={product.data.wiregumProduct} locale={locale} />
</article>
</main>
</BaseLayout>7. Use the API directly only for custom UI
The widget is the default path. If the site needs a fully custom island, fetch the public product endpoint from an authorized origin and create checkout through the same API contract used by the widget.
const response = await fetch(
"https://app.example.com/api/storefront/products?organization=workspace-slug&products=wg_12ab34cd56&locale=it",
{
headers: {
Accept: "application/json"
}
}
);
const payload = await response.json();