Envie
Getting started

Hydrogen quickstart

Wishlists in a Hydrogen or any headless storefront — public key in the browser, private key in the loader, your own UI.

This page wires wishlist state into a Hydrogen storefront: reads and toggles from the browser with a public key, customer login handled server-side.

Envie is headless-native: the API returns identity (productId, variantId, handle), you render from your own product data. No iframe, no hosted page, no markup you didn't write.

1. Get a public key and declare your origin

In the Shopify admin, open Envie → Developers:

  • Copy the public key (env_pk_…). It is browser-safe by design — anonymous scope only.
  • Add your storefront's origin (for example https://shop.example.com) to Headless origins. CORS is enforced against this list; until the origin is declared, browser calls are blocked.

2. Install the SDK

npm i @getenvie/client

3. Create the client in the browser

// app/lib/envie.client.ts
import { createEnvie } from "@getenvie/client";

export const envie = createEnvie({
	mode: "public",
	shop: "your-store.myshopify.com",
	publicKey: "env_pk_your_store_xxxxxxxx",
});

The client manages the shopper's anonymous id for you (generated once, kept in localStorage), retries idempotent reads, and honors Retry-After on rate limits.

4. Read and toggle

// app/components/SaveButton.tsx
import { useEffect, useState } from "react";
import { envie } from "~/lib/envie.client";

export function SaveButton({ productId, handle }: { productId: string; handle: string }) {
	const [saved, setSaved] = useState(false);

	useEffect(() => {
		return envie.subscribe((state) => {
			setSaved(state.items.some((item) => item.productId === productId));
		});
	}, [productId]);

	return (
		<button
			type="button"
			aria-pressed={saved}
			onClick={() => envie.wishlist.toggle({ productId, handle })}
		>
			{saved ? "Saved" : "Save for later"}
		</button>
	);
}

subscribe gives you a reactive snapshot (count, items, config) shared across every component — one fetch, N buttons in sync.

Prefer ready-made elements?

@getenvie/components works headless too, and the wiring is automatic: import both packages and createEnvie connects itself to the components' shared store.

// Anywhere in your client bundle — order doesn't matter:
import "@getenvie/components"; // registers <envie-button>, <envie-badge>, <envie-list>, <envie-share>
import { envie } from "~/lib/envie.client"; // your createEnvie(...) instance
// Then use the elements exactly as a Liquid theme would:
<envie-button product-id={product.id} handle={product.handle} />

Bringing your own client instead of @getenvie/client? Hand it over explicitly: window.__envie.setClient(myClient) — anything implementing getConfig, getWishlist and toggle qualifies.

5. Attach the customer on login (server-side)

A public key cannot act as a customer — that's the point of it being safe to ship to browsers. When a shopper logs in, merge their anonymous list into their customer list from your server, with a private key:

// app/routes/account.login.tsx (in your action, after authentication succeeds)
import { createEnvie } from "@getenvie/client";

const envieServer = createEnvie({
	mode: "private",
	shop: "your-store.myshopify.com",
	secretKey: context.env.ENVIE_SECRET_KEY, // env_sk_… — server only, never in the bundle
	anonymousId, // sent up from the browser — the SDK keeps it in localStorage as `envie:aid`
});

await envieServer.identify({
	customerId, // the Shopify customer id you just authenticated
});

Items are merged (duplicates collapse), and the shopper's next read returns the combined list.

Common mistakes

  • Calling identify() from the browser. Rejected in public mode, always — a browser asserting "I am customer 42" is exactly what the mode exists to prevent. Identity comes from your server.
  • Forgetting to declare the origin. Everything works in server-side rendering, then browser calls fail CORS preflight. Add the origin in Developers first.
  • Shipping env_sk_… in client code. Private keys have full shop scope. If one ever reaches a browser bundle, rotate it in Developers immediately.
  • Rendering from titleSnapshot. It exists for exports, not UIs. Render from your own product data (Storefront API) keyed by productId/handle — prices and titles stay correct.

On this page