Skip to content
GWP Book a demo

Install the GWP tag

For developers integrating a bespoke storefront, a headless front end (Next.js, Nuxt, Remix, Astro, SvelteKit…), or any platform we do not ship a plugin for. This is the primary integration path, not a fallback. Most of our engineering effort has gone into making the tag platform-agnostic.

  • Basket pages and placement: which URLs count as a basket or confirmation page, where the gift block renders, and slide-out cart drawers.
  • Cart adapters sets out the three ways the widget can read your cart total and currency, and how the order id is read on the confirmation page.
  • The claim moment: your order-confirmation page, or your own order-confirmation email via the platform-neutral gwp:selection hook.
  • Verify and troubleshoot lists the browser checks to run before you call it done, and every [GWP debug] line with its cause and fix.
  • Local and staging development explains what an unregistered dev hostname actually does, and the two ways round it.
  • Config, CSP and device storage: a real config response, the consent modes, the CSP entries to add, and the complete list of what we write to the shopper's device.
  • Platform guides cover Shopify end to end, custom & headless specifics, and what we would confirm on the platforms we have not shipped yet.
What you addOne <script> tag. Optionally one <div> mount point per template.
What we renderA gift-choice block on your basket page, inside a closed Shadow DOM root. Your CSS cannot leak in, ours cannot leak out.
What we readYour cart total + currency, via one of three adapters you choose.
What we write to your pageOnly our own container, inside the slot you nominate (or one we inject and remove again). Never your items, prices, cart or markup.
What we storeFirst-party localStorage keys and first-party cookies on your own domain (gwp_ses, gwp_sel, gwp_shows, plus gwp_attrs on the Shopify order-email path, and a 5-second __gwp_probe cookie). Never a third-party cookie. Exhaustive list in device storage.
Failure modeFail-silent. Every entry point is wrapped; a failure renders nothing and never throws onto your page.
Blocking?No. async, and the widget boots after DOMContentLoaded.

The widget never changes items or prices. On a custom platform it makes no writes to your cart at all — the only cart-writing code path in the product is a Shopify-specific one, gated behind a per-store setting that does not apply to you.

<script async src="https://cdn.gwpingenuity.com/w.js" data-site="YOUR_SITE_KEY"></script>

Your site key is on your GWP dashboard at dashboard.gwpingenuity.com → Install, which also shows the snippet above with the key already filled in. It is a public identifier, not a secret: it is designed to sit in your page source. It is scoped to your registered domains.

No login yet? Accounts are created by us. There is no self-serve signup, and the key does not exist until the account does. If your company is already on GWP, an owner can invite you from Settings → Users; otherwise email us and allow for that round trip in your plan. The rest of this guide is writable against a placeholder key, but nothing will boot until you have the real one.

AttributeRequiredNotes
srcYesMust be https://cdn.gwpingenuity.com/w.js. The widget derives its API origin from this URL, so script, API calls and images all come from one hostname.
data-siteYesYour site key. Without it the widget does not boot at all.
asyncRecommendedNon-blocking load.
data-apiNo, don'tOverrides the API origin. Only used for GWP-supervised testing. Omit it.

Put it before </body> on your basket template and your order-confirmation template. Those are the two pages the widget can render on.

Two reasons to go site-wide (every page, e.g. in your root layout) instead:

  • Your basket is a slide-out mini-cart that opens without a URL change. A tag scoped to a basket route can never be present when the drawer opens.
  • Your storefront is an SPA whose route transitions never re-execute per-template scripts. A tag in the root layout is loaded once and follows navigation on its own (see below).

Site-wide is safe: on any page that is not a registered basket or confirmation page, the widget resolves its config, renders nothing, and disarms its document-wide DOM observer. (With a cart drawer enabled it keeps one deliberately cheap watcher, because a drawer can open anywhere: a MutationObserver scoped to your drawer element, plus a few passive cart-event listeners.) The cost is that config requests rise roughly 20–50×, absorbed by our edge cache. That cache is not a site-wide trade-off. It applies to every install, including a basket-template-only one (the config response).

Create a Custom HTML tag containing the snippet. Fire it on basket and confirmation page views, or on all page views if you need the site-wide behaviour above.

You do not need to re-inject the tag on route changes, and you should not: the loader boots once per document (a duplicate tag is a harmless no-op). It follows client-side navigation on its own by wrapping history.pushState / history.replaceState and listening for popstate, hashchange and load. It re-fetches config when the path changes; a query-string- or hash-only change is deliberately ignored (it cannot change the answer), so variant pickers and facet filters that rewrite the URL do not cause churn.

If your basket hydrates late, or you mutate the cart without a DOM change the widget can see, tell it explicitly:

window.dispatchEvent(new Event("gwp:cartchange"));

A dataLayer.push does this implicitly too: the loader wraps dataLayer.push transparently (your own pushes and any GTM consumers are unaffected), provided window.dataLayer already exists when the tag boots. The usual window.dataLayer = window.dataLayer || [] in <head> is enough. If your app creates or replaces dataLayer later, as commonly happens in a hydrated SPA, the wrap is never installed, and you must dispatch gwp:cartchange after each push.

To get from "tag installed" to "live", we need the list below. Send it to support@gwpingenuity.com with [INTEGRATION] in the subject line. The same mailbox answers shoppers asking about a gift they claimed, and the prefix tells us to put yours in front of an engineer instead (more on getting help).

  • Every domain you serve the storefront from (subdomains are covered automatically, so example.com also admits www. and checkout.), plus any staging or local dev hostname you need to work on, unless you are using a subdomain of one you have already registered (local development).
  • Your basket and confirmation URL patterns, as paths, including localised variants.
  • Your cart adapter choice, and its config if dom (the total selector) or if you want an orderIdSelector.
  • Your mount points, if they are not [data-gwp-slot].
  • Your drawer selectors (root, openWhen), if your basket is a slide-out.
  • Anything unusual about your CSP, especially a restrictive style-src.