> ## Documentation Index
> Fetch the complete documentation index at: https://docs.abconvert.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Targeting on a custom storefront

> How ABConvert targeting works with the headless SDK, and which visitor details your storefront supplies.

Targeting decides which visitors enter a test. The headless SDK follows the same rules as a theme. Only where some visitor details come from changes.

## How it works

You set targeting in the ABConvert admin, as for any test. The SDK applies it when it assigns the visitor, in the [same order as on a theme](/targeting/overview#how-it-works): [mutually exclusive group](/targeting/overview#mutually-exclusive-group), [traffic allocation](/targeting/overview#traffic-allocation), [audience targeting](/targeting/audience-targeting), [targeting rules](/targeting/overview#targeting-rules), then the [random split](/targeting/overview#random-split).

The SDK decides on the visitor's first page, for every running test at once, and remembers each decision. A visitor who arrived from a paid ad stays in a test for paid traffic when they come back directly. A visitor the test left out stays out, even if a later visit matches.

A [preview link](/headless-sdk/preview) that forces a test group skips targeting. Without a forced test group, the link follows your targeting.

## Two countries

Two different countries matter, and they can differ for the same visitor:

| Country | What it is | Where it comes from | Condition it decides |
| - | - | - | - |
| Location | Where the visitor is | A request header your host adds. See [Location](#location). | **Countries** |
| `country` | The country your storefront shows prices for. See [Prices by country](/headless-sdk/prices#prices-by-country). | You pass it | **Markets** (coming soon) |

A visitor in France who shops your Canada storefront has a location of `FR` and a `country` of `CA`. They match a Countries condition for France, and will match a Markets condition for Canada once Markets are supported.

### Location

The SDK reads the visitor's location from the first of these request headers it finds, as a two-letter country code: `oxygen-buyer-country`, `cf-ipcountry`, `x-vercel-ip-country`, `x-country`. [Oxygen](https://shopify.dev/docs/storefronts/headless/hydrogen/deployments/oxygen-runtime), Cloudflare, and Vercel set theirs. On another host, have your load balancer or proxy add `x-country`. On Google Cloud, add it as a custom request header on your load balancer's backend service:

```bash theme={null}
gcloud compute backend-services update YOUR_BACKEND_SERVICE --global \
  --custom-request-header='x-country:{client_region}'
```

The flag replaces the backend service's custom request headers, so list any you already set alongside it.

If no header is set, a test with a Countries condition does not run for that request. See the next section.

## What your storefront supplies

[Audience targeting](/targeting/audience-targeting) needs to know about the visitor. The SDK reads most conditions from the request. Your storefront supplies the rest:

| Condition | Where the SDK gets it |
| - | - |
| Countries | The visitor's location, from the request |
| Devices, URL parameter, cookie, visitor type | The request |
| Traffic sources, referral domain, landing page | The first page of the visit, kept for the whole visit |
| Customer conditions (Beta) | The `customer` you pass |
| Markets (Beta) | Coming soon. See [Coming soon](#coming-soon). |
| Custom JavaScript (Beta) | Not supported. It needs a page. |
| Klaviyo segments | Not supported. It needs a page. |

A test with a condition the SDK cannot decide does not run on your custom storefront. The SDK decides nothing for it: the visitor sees your original store, nothing counts toward the test, and the SDK checks again on the next request.

## Customer

Pass what you know about the signed-in customer. Every field is optional.

| Field | For the condition | Where you get it |
| - | - | - |
| `loggedIn` | Logged in | Your session |
| `tags` | Tags | `tags` on the customer, in the [Customer Account API](https://shopify.dev/docs/api/customer/latest/objects/Customer) or the [Storefront API](https://shopify.dev/docs/api/storefront/latest/objects/Customer) |
| `ordersCount` | Orders count | `numberOfOrders` on the customer, in the [Storefront API](https://shopify.dev/docs/api/storefront/latest/objects/Customer). The Customer Account API does not return it. |
| `totalSpent` | Total spent | Your own backend. Shopify's storefront APIs do not return it. |

```ts Server theme={null}
const abconvert = await createABConvert({
  shop: 'your-store.myshopify.com',
  storefrontKey: 'abc_pk_4f9c2e7a1b3d5f6e8a0c2b4d6f8e0a1c',
  request,
  customer: {
    loggedIn: true,
    tags: customer.tags,
    ordersCount: Number(customer.numberOfOrders),
  },
});
```

These rules apply:

* If you pass no `customer`, the SDK treats the visitor as signed out.
* If you pass a `customer` without `loggedIn`, the SDK treats them as signed in.
* If you leave out `tags`, the SDK treats the customer as having no tags.
* If you leave out `ordersCount` or `totalSpent`, a condition on it does not match.
* Pass `totalSpent` in your store currency.

A customer condition is the one condition the SDK decides again, because a visitor can sign in or out after the decision. When the `customer` you pass changes, a visitor who stops matching leaves the test until they match again, and keeps their test group for when they do. See [Customer conditions can change](/targeting/audience-targeting#customer-conditions-can-change).

If you target by **Total spent**, read `amountSpent` for the customer from the Admin API on your own backend, or from a customer metafield where you already store it, and pass it as `totalSpent`. The same applies to `ordersCount` when your storefront uses the Customer Account API.

## Coming soon

* **Markets.** A visitor will be in a market when the `country` you pass is one of the market's countries.
* **Custom attributes.** You will pass your own data about the visitor, such as a loyalty tier, and target a test by it. On a custom storefront this replaces the [Custom JavaScript](/targeting/custom-javascript) condition.

## Common mistakes

* **Passing the visitor's location as `country`.** The visitor then sees the test price for the wrong country. The fix: pass the country your storefront shows prices for. The Countries condition reads the location on its own.
* **Passing a customer for a signed-out visitor.** The SDK treats them as signed in, so they match **Logged in** conditions. The fix: pass `customer` only when the visitor is signed in, or set `loggedIn: false`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.