> For the complete documentation index, see [llms.txt](https://the-wishlist-documentation.gitbook.io/the-wishlist-documentation-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://the-wishlist-documentation.gitbook.io/the-wishlist-documentation-docs/developer-reference/shopify-guest-wishlists.md).

# Shopify Guest Wishlists

TWC strongly recommends against using guest wishlists as you will have materially reduced marketing opportunities.    Offering wishlists for signed-in customers only will increase wishlist sales

*For the merchant's front-end developer.*&#x20;

Guest wishlists let a shopper add items to a wishlist **before they log in or create an account**, and keep that list when they later sign in. TWC delivers this as a backend change and an SDK upgrade. Your integration is **configuration and two function calls** — there is nothing to implement.

If you follow this guide exactly, every store behaves identically and we can support you. If you build your own identifier handling instead, each site diverges and we cannot.

***

### Who does what

| Who                         | Does                                                                                                                                    |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| **TWC**                     | Anonymous support in the SDK; the backend; the tenant feature flag; this guide.                                                         |
| **You (merchant / agency)** | Install the TWC App Proxy app; upgrade the SDK; add the two calls below; confirm consent classification with your own privacy advisers. |
| **Shopify app developers**  | Correct the published proxy docs. Nothing else.                                                                                         |

***

### 1. Prerequisites

Before the calls will do anything, the following must be true:

* **TWC App Proxy app installed on the store.** Follow the existing **TWC App Proxy install guide** first.

> ⚠️ **The App Proxy app is custom-distributed, which binds it to ONE store permanently.** When installing, **copy the store domain from Settings → Domains — do not type it.** A typo cannot be corrected later; it means a fresh app.

* **Tenant feature flag enabled by TWC.** Guest wishlists are off until we enable the flag for your tenant. With the flag off, the guest calls are safe no-ops (they call `onSuccess(null)` — see §2.4). Ask your TWC contact to confirm it is on before you test.

The SDK is initialised once per page load, as it already is for logged-in wishlists:

```js
import { initTWC } from "@thewishlistco/twc";

initTWC({ shopDomain: "mystore.myshopify.com" });
```

> `initTWC` accepts an optional `anonymousWishlistEnabled` flag. It **defaults to on** — leave it unset. Set `anonymousWishlistEnabled: false` only if you need to hard-disable guest wishlists in the theme regardless of the tenant flag.

***

### 2. The two calls

Both functions are **module-level imports** that take a single options object with `onSuccess` / `onError` callbacks — the same idiom as the rest of the SDK. Neither returns a promise you need to await, and neither takes an identifier.

#### 2.1 `getOrCreateAnonymous()` — on logged-out pages only

Call this at wishlist widget initialisation **when no customer is logged in.**

```js
import { getOrCreateAnonymous } from "@thewishlistco/twc";

getOrCreateAnonymous({
  tenant: "<tenant id>",
  onSuccess: (wishlist) => {
    // `null` means the SDK deliberately did nothing (see §2.4).
    if (wishlist) render(wishlist);
  },
  onError: (error) => console.error(error),
});
```

* **Create-or-return.** Safe on every logged-out page load — if an anonymous list already exists for this browser, it returns that same list rather than creating a second one.
* **⚠️ Do not call it on authenticated pages.** It creates an anonymous customer and wishlist as a side effect. On logged-in pages, use `mergeOnLogin()` instead.

#### 2.2 `mergeOnLogin()` — on authenticated pages

Call this on **any page load where a customer is logged in.**

```js
import { mergeOnLogin } from "@thewishlistco/twc";

mergeOnLogin({
  customerEmail: "<the logged-in customer's real email>",
  tenant: "<tenant id>",
  onSuccess: () => refreshWishlist(),
  onError: (error) => console.error(error),
});
```

* **Picks up the stored anonymous wishlist id automatically** — you do not pass it.
* **Idempotent, once per session.** Once a merge has run this session it will not re-fire; merging twice changes nothing.
* **Does not block render, and stays quiet.** If the shopper is not yet a TWC customer (or there is nothing to merge), it is a silent no-op, not an error.
* **Optional fields:** `anonymousWishlistId` (overrides the stored one), `customerRef`, `wishlistRef` (target list; otherwise the customer's default), `wishlistName` (used only if a new default list is created).

#### 2.3 `resetAnonymousWishlistState()` — on logout / account switch

```js
import { resetAnonymousWishlistState } from "@thewishlistco/twc";

resetAnonymousWishlistState(); // on logout, or when a different customer signs in this session
```

Call it **on logout, or when a different customer signs in during the same session.** It clears the stored anonymous wishlist id and the "merged this session" marker so a fresh guest list can be created and merged again. Skipping it can attach one shopper's guest list to the next person on a shared device.

#### 2.4 The `onSuccess(null)` contract

Both functions call **`onSuccess(null)` — never `onError`** — when they deliberately do nothing:

* no durable identifier is available (e.g. cookies blocked in private browsing);
* guest wishlists are disabled (tenant flag off, or `anonymousWishlistEnabled: false`);
* (`mergeOnLogin` only) a merge already ran this session, `customerEmail` is blank, no anonymous wishlist id is known, or the backend returns 404 (customer or anonymous list not found).

**Always null-check the `onSuccess` argument.** Nothing is ever surfaced to the shopper in these cases.

***

### 3. The identifier is the SDK's job — never yours

The SDK resolves and stores the anonymous identifier itself and sends it as the existing `onlineSessionID` parameter. Resolution order:

1. Shopify's **`_shopify_y`** cookie (≈1 year, set by the Shopify edge, no ITP cap);
2. else the SDK's own **`twc_awid`** cookie;
3. else it **mints a UUID v4** and stores it in `twc_awid`.

**You never construct, read, cache, or pass an identifier.** There is no identifier parameter on either call, by design. Handling it yourself is exactly how list-ownership bugs return.

***

### 4. Consent classification — your decision to make

Two cookies are involved. How your consent banner treats each is **your merchant decision**, to confirm with your own privacy advisers — TWC does not set your banner policy.

| Cookie       | What it is                          | TWC's proposed classification                                                          |
| ------------ | ----------------------------------- | -------------------------------------------------------------------------------------- |
| `twc_awid`   | TWC's own guest-wishlist identifier | **Functional / necessary** — it exists only to hold the guest's own wishlist for them. |
| `_shopify_y` | Shopify's analytics cookie          | Shopify's to classify; commonly **analytics**.                                         |

**What degrades under a decline:**

* If **analytics cookies are declined**, `_shopify_y` is unavailable and the SDK falls back to (or mints) `twc_awid`. Guest wishlists still work.
* If **cookies cannot be written at all** (functional cookies declined, or private browsing blocks the write), **no anonymous list can be held.** The guest calls return `onSuccess(null)` and the shopper must log in to keep a wishlist. This is the expected, correct behaviour — nothing is broken.

State your chosen classification for each cookie in your own cookie policy.

***

### 5. Documented limits

Set these expectations with your stakeholders up front:

* **Same browser only.** A guest list lives in one browser. A different browser or device starts fresh.
* **No cross-device before login.** Guest lists reconcile across devices only *after* the shopper logs in (that is what `mergeOnLogin()` does).
* **Anonymous lists expire.** A guest list is retained for a configured inactivity window (currently **90 days** of no activity) and is then removed.
* **Merge is once per session.** After a successful merge, `mergeOnLogin()` will not re-fire until the session ends or `resetAnonymousWishlistState()` is called.
* **Declined analytics still works; blocked cookies do not.** See §4.

***

### 6. Troubleshooting

| Symptom                                                              | Most likely cause                                                                | What to do                                                                                                                                                                                                                    |
| -------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`onSuccess` fires with `null`; no list created**                   | Tenant flag **off**, `anonymousWishlistEnabled: false`, or cookies blocked.      | Confirm the tenant flag with TWC; check you did not set `anonymousWishlistEnabled: false`; check for a `twc_awid`/`_shopify_y` cookie.                                                                                        |
| **`401`/`403` on the wishlist token or endpoint**                    | App Proxy app not installed, or installed against the wrong domain.              | Re-check the install guide; confirm the proxy app is present and bound to this exact store domain. (A 401 on a proxy-issued token is auto-retried once by the SDK; a persistent one means the proxy/secret is misconfigured.) |
| **A second anonymous customer/list appears**                         | `getOrCreateAnonymous()` called on an authenticated page.                        | Only call it when logged out; use `mergeOnLogin()` on authenticated pages.                                                                                                                                                    |
| **Guest list not appearing after login**                             | `mergeOnLogin()` not called on the authenticated page, or `customerEmail` blank. | Confirm §2.2 fires on authenticated loads with the customer's real email.                                                                                                                                                     |
| **One shopper's guest list attaches to the next on a shared device** | `resetAnonymousWishlistState()` not called on logout/switch.                     | Call it on logout and on account switch (§2.3).                                                                                                                                                                               |

**To confirm an anonymous list exists for a given browser:** check the browser for a `twc_awid` (or `_shopify_y`) cookie. If one is present and the tenant flag is on, the guest list is keyed to it.

***

### 7. What NOT to do

* ❌ **Don't call `getOrCreateAnonymous()` on authenticated pages** — it creates an anonymous customer and wishlist as a side effect.
* ❌ **Don't skip `resetAnonymousWishlistState()` on logout / account switch** — it prevents one shopper's guest list attaching to the next.
* ❌ **Don't construct, cache, or pass an identifier.** Neither call takes one; the SDK owns it.
* ❌ **Don't cache the identifier server-side in the theme**, and **don't put it in a URL.** It is a per-browser client value, not a shareable token.
* ❌ **Don't call the merge endpoint directly with a hand-built payload.** Use `mergeOnLogin()`. The endpoint contract is not part of this integration and may change.

***
