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

> ## Agent Instructions
> For new direct REST integrations, use the v2026-04 surfaces. The @fluid-app FairShare SDK continues to use its own published public-v2025-06 contract.
> Authenticate with the header Authorization: Bearer <token>; public storefront read endpoints require no auth.
> Lists use cursor pagination via the page[cursor] and page[limit] query params; follow meta.pagination.next_cursor until it is null.
> When the same operation exists on more than one surface, use the newest: dated API versions are newer than unversioned ones, and later dates win (v2026-04 > v2025-06 > unversioned v0/v1.1). Fall back to a legacy or unversioned operation only when no newer versioned equivalent exists — the company-v0 notes below list the known superseded operations. /api/company/v1 and /api/v1/... paths are documented in no spec here and must never be used (/api/v1.1/... is distinct and documented in company-v0). Use page/per_page offset pagination only where a spec documents it — in practice the unversioned company-v0 admin surface; every versioned surface uses cursor pagination.
> Navigation menu management is documented in themes/navigation-menus. These unversioned admin endpoints (/api/menus and nested menu_items) are verified against the implementation but are not yet in the synced OpenAPI specs. Use that reference for menu payloads and its flat page/per_page pagination; missing spec coverage does not make these endpoints unavailable.
> The OpenAPI specs under api-reference/ are the authoritative contracts; prefer them over prose when in doubt. api-reference/storefront-v2026-04.yaml covers the v2026-04 storefront surface (/api/v202604/... paths); api-reference/auth-v0.yaml covers the unversioned auth surface (/api/... paths — authentication, MFA, social auth, and token exchange); api-reference/checkout-v2026-04.yaml covers the v2026-04 checkout surface (/api/checkout/v2026-04/... paths — carts, cart auth, discounts, items, subscriptions, orders, enrollments, and store config); api-reference/public-v2025-06.yaml covers the Public SDK surface used by the @fluid-app FairShare SDK, including its parallel cart lifecycle, browser integrations, versioned payment callbacks, unversioned public utilities, and the cart price-override operation; api-reference/payment-v2026-04.yaml covers the v2026-04 payment gateway admin surface (/api/payment/v2026-04/... paths, bearer-authenticated — gateway CRUD, gateway purchase/authorize/$0-verify, transaction list/show and capture/void/credit, and merchant payment configuration); api-reference/payments-v2026-04.yaml covers the v2026-04 cart payment surface (/api/payments/v2026-04/carts/{cart_token}/... paths, authenticated by the cart token in the path with no bearer — payment-method selection, VGS card tokenization, 3D Secure verification, and PayPal/Braintree/Klarna/Apple Pay flows); api-reference/commerce-v2026-04.yaml covers the v2026-04 commerce order-editing surface (/api/v202604/orders/{order_id}/edits paths, bearer-authenticated — post-checkout order edits that atomically insert items and add adjustments/discounts, with an optional dry-run preview); api-reference/webhooks-v0.yaml covers the unversioned webhooks surface (/api/... paths — webhook registration, delivery payloads, callback registrations, company events, and webhook/callback schemas); api-reference/company-v0.yaml covers the legacy unversioned company admin surface (/api/... paths, bearer-authenticated — company settings and management, customers, users, roles, subscription plans, subscription bundles, subscriptions, media, pages, catch-ups, inventory levels, domains, agreements, and admin order actions). company-v0 caveats: it is the legacy v0 admin contract and its lists use flat page/per_page offset pagination, which is expected there despite the general cursor-pagination rule; where an operation exists in both company-v0 and a versioned spec, prefer the versioned spec — the subscriptions lifecycle (list/create/show/update, cancel, pause, reactivate, resume, retry, skip, failed-cycle-waiver, discounts) and subscription bundles are superseded by checkout-v2026-04, and company pages/media CRUD plus the public pages, categories, products, and media list endpoints are superseded by storefront-v2026-04. Subscription plan management (/api/subscription_plans, resource-wrapped {"subscription_plan": {...}} bodies) exists only in company-v0. api-reference/members-v2025-06.yaml covers the v2025-06 unified Member identity surface (/api/v2025-06/members/... paths, bearer-authenticated — member list/create/show/update, lookup by email/username/external_id/legacy_customer_id, member-type assignment, and the sponsor genealogy read). Prefer it over the customers and reps surfaces when the member type matters: /customers does not serialize member_type. api-reference/analytics-v2026-04.yaml covers the v2026-04 Home dashboard analytics surface (/api/v202604/analytics/dashboard/... paths, bearer-authenticated — read-only endpoints for the Home > Overview, Home > Live, and Home > Field tabs, each accepting an optional country ISO alpha-2 query param that scopes aggregations to a single country).
> api-reference/analytics-v0.yaml covers the unversioned analytics surface that backs the fluid-admin Traffic tab (/api/analytics/... and /api/analytics/traffic/... paths, bearer-authenticated — the legacy shares/views/visitors summary plus traffic overview, ranked campaigns, sources, geographies, flows, and per-rep breakdown, all sharing one reporting-period contract).
> Successful responses wrap the resource payload alongside a top-level integer status and a meta object.
> Portal Definition authoring edits and synchronizes the portal JSON resource graph. Widget Package authoring builds either a company-owned or Droplet-owned Remote DOM package. These are separate contracts; do not imply that one defines the other.
> For Widget Package worker code, use only @fluid-app/portal-sdk/widgets/worker. Use only the Portal Definition and Widget Package workflows and public entry points documented here; do not infer support for undocumented surfaces.
> Every portal function and declarative capability used by a widget must appear in that widget's uses array. Use the same typed function value in uses; do not invent capability-name strings.
> Widget styling must use the portal's semantic theme variables for colors, typography, spacing, radii, borders, focus, and charts whenever a token represents the visual decision. Do not create a separate light or dark palette or duplicate theme controls as widget properties.
> Prefer worker-safe Fluid UI components exported by @fluid-app/portal-sdk/widgets/worker when they fit the interaction. When no exported component fits, use semantic HTML, accessible behavior, and the portal theme variables.
> A Portal Definition push updates the remote working definition. A portal version is an immutable snapshot, and activation is a separate live release operation.

# Member pricing

> Every price your theme prints is member-aware by default. No filter, no tag, no theme change. Cached HTML keeps the public price and the browser swaps in the signed-in member's.

<Warning>
  Member storefront is in a limited pilot. Confirm that member storefront is enabled for your company before relying on this behavior.
</Warning>

**Every product and variant price your theme prints is already member-aware.** There is no filter to add, no tag to wrap, and no theme change to make. If your template prints `{{ product.price }}`, a signed-in member sees their own price there.

A storefront page is cached once and served to everyone, so it can never contain one member's price. Member pricing works around that: Fluid renders the public price into the cached HTML inside a small placeholder called a **shell**, and the SDK replaces the text once it has confirmed who is viewing.

## What becomes member-aware

Any visible product or variant price you print:

```liquid theme={null}
<p class="product-card__price">{{ product.price }}</p>
```

With the feature enabled, that renders a shell wrapping the public price:

```html theme={null}
<fluid-member-price
  data-product-id="1842"
  data-variant-id="5127"
  data-country-iso="US"
>$48.00</fluid-member-price>
```

The shell sits in the light DOM, so your existing price styles apply unchanged. With the feature disabled, the same template prints `$48.00` and nothing else.

`price`, `price_in_currency`, and `formatted_price` are recognised on products and variants. A price needs a country to be resolvable, so a value with no country context prints plain.

## Prices you print are hydrated. Prices you calculate with are not

This is the rule that keeps the rest of your theme working.

A price becomes a shell only where it is printed as **visible text**. Everywhere else it stays the ordinary number or string it has always been:

```liquid theme={null}
{% if product.price > 50 %}Free shipping{% endif %}   {% comment %} a number {% endcomment %}
<div data-price="{{ product.price }}">                {% comment %} plain text {% endcomment %}
<script>const p = {{ product.price }};</script>       {% comment %} plain text {% endcomment %}
{{ product.price | json }}                            {% comment %} plain text {% endcomment %}
{% capture p %}{{ product.price }}{% endcapture %}    {% comment %} plain text {% endcomment %}
```

Comparisons, arithmetic, HTML attributes, `<script>` and `<style>` bodies, JSON, and captured strings all keep scalar values. Only visible page text gets a shell, so your conditionals and your data attributes behave exactly as before.

## Filters still apply

Filters on a printed price are carried with the shell and re-applied to the member's price on Fluid's side:

```liquid theme={null}
{{ product.price | times: 0.9 | round: 2 | prepend: "$" }}
```

The guest sees the filtered public price. A member sees the same chain applied to their own price. Filters applied through an `assign` compose with filters on the final output:

```liquid theme={null}
{% assign product_price = product.price | replace: '$', 'USD ' %}
{{ product_price | append: ' each' }}
```

Arithmetic, rounding, `money`, `default`, escaping, and text operations such as `append`, `replace`, and `truncate` are supported. Filters that load resources or build HTML are not, and an expression using one simply prints its public value with no shell. The browser never calculates money; it only writes the text Fluid returns.

## You no longer need the member\_price filter

Earlier releases required `{{ product | member_price }}` on every price you wanted hydrated. That is no longer necessary. Printing the price is enough.

The filter still works, so themes that already use it keep working and need no edit:

```liquid theme={null}
{{ product | member_price }}
{{ product | member_price: formatted_product_price }}
```

Reach for it only when you are printing something the automatic rule does not recognise, such as a value that is not a product or variant. Passing your own formatted string as the argument makes that string the guest fallback.

Do not apply both to one price. And never hand-write a `<fluid-member-price>` element: a hand-written shell has no guaranteed fallback and no country, so the SDK cannot resolve it safely.

## What the browser does

The SDK is one script that Fluid adds for companies with member storefront enabled. You do not add it to your layout.

A guest costs nothing. The SDK looks for a small non-secret cookie that marks a signed-in session, and when it is absent it makes no requests at all — no session check, no price requests. Nothing about the page changes.

For a signed-in member it confirms the session against Fluid, then asks for the prices the page needs and writes each one into its shell. Fluid resolves the price from the member's own pricing tier using the signed-in cookie. The tier is never chosen in the browser and never appears in the page.

Two details worth knowing:

* **The public price stays visible while requests run.** There is no blanked-out or masked state. A member sees the public price briefly and then their own.
* **Identical prices share one request.** The same product printed twice on a page is resolved once. Different variants and different countries are always resolved separately.

When the page regains focus, or comes back from the browser's back/forward cache, the SDK re-checks the session and resolves again, so a price never outlives the session that produced it.

### When nothing changes

The public price staying on screen is the designed outcome of every failure, not an error:

| Situation                                  | What the member sees                          |
| ------------------------------------------ | --------------------------------------------- |
| Signed out, or session expired             | The public price                              |
| Member's type buys at retail               | The public price, because that is their price |
| Network error or slow request              | The public price                              |
| Product has no member price configured     | The public price                              |
| Bundle product                             | Its existing range, unchanged                 |
| SDK not yet configured for the environment | The public price                              |

Nothing writes an error into the page, and nothing is ever left blank.

## What to do, and what not to

Member pricing works by replacing the **text inside** the element your theme prints the price into. Everything on this list follows from that one fact: if something else owns that text, it wins.

### Do

* **Print the price and leave the element alone.** `<p class="price">{{ product.price }}</p>` is the whole requirement.
* **Let the price be the element's own content.** Hydration replaces text in place, so the price needs to be text on the page, not a value you stash and render later.
* **Reserve space** for the price so a longer or shorter member price does not shift the layout.
* **Style on the resolution attributes** rather than on the price text.
* **Test with JavaScript disabled** when a price does not personalise. That shows what the server actually sent, which separates a rendering problem from a theme-script problem.

### Do not

* **Do not render the price with your own script.** Writing `{{ product.price }}` into a `data-` attribute and then setting `textContent` from it replaces the hydrated value with the public one. This is the single most common cause of a price that never personalises.
* **Do not set `textContent` or `innerHTML` on an element that holds a price.** Your write lands after hydration and silently undoes it.
* **Do not re-render the price container from client state** — an AJAX section replace, or a component that redraws on variant change — unless you re-render from the server's HTML.
* **Do not do arithmetic on a price in the browser.** Read the price, and let Fluid apply discounts, currency and rounding. A member price is not the public price times a factor.
* **Do not cache a rendered price** in a JavaScript variable, `localStorage`, or a template you re-apply later. It belongs to one viewer and one session.
* **Do not hand-write a shell element.** It has no guaranteed fallback and no country, so it cannot be resolved safely.
* **Do not strip unknown elements or attributes.** A sanitizer or optimiser that removes custom elements removes the price shell with them.
* **Do not assume the price text at page load is the member's.** It is the public price until hydration completes, which is the point of the fallback.

## Style around resolution

Each shell gains `data-fluid-member-settled` once it has either written a value or decided to keep the fallback. Section wrappers become `resolved` or `guest`.

Reserve space so a card does not shift when a longer or shorter price arrives:

```css theme={null}
fluid-member-price {
  display: inline-block;
  min-width: 5ch;
}

[data-fluid-member-state="resolved"] fluid-member-price {
  color: var(--color-accent, #2464ec);
  font-weight: 600;
}
```

## Test member pricing

1. **Feature disabled.** Render the page and confirm the output matches the version before any member work. No shells, no markers.
2. **Guest, feature enabled.** Confirm the page looks identical. In the HTML, confirm shells carry the public price and the right `data-country-iso`, and that your data attributes and JSON still hold plain numbers.
3. **Signed-in member.** Confirm prices swap to the member's own. Sign out and confirm they revert.

Also check a member whose type buys at retail. They should see the public price with no error. On a multi-country storefront, switch countries and confirm each shell carries that country's code.

## Troubleshoot

| Symptom                                            | What to check                                                                                                                                                 |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Prices print plain, with no shell                  | Confirm the feature is enabled for the company and that you are viewing the published theme. Confirm the value has country context.                           |
| A member still sees the public price               | Confirm they are signed in on this storefront, and that their member type receives member pricing. Customers buy at retail.                                   |
| Nothing resolves and no requests are sent          | The session marker cookie is set at sign-in. A session that predates the cookie rollout needs one fresh sign-in.                                              |
| A filtered price never becomes a shell             | The chain probably contains an unsupported filter. Arithmetic, rounding, `money`, and text filters are supported; resource and HTML filters are not.          |
| A number changed somewhere it should not have      | Prices are only replaced in visible text. If a calculation changed, check whether you are printing into an attribute or script rather than reading the value. |
| A bundle shows a range instead of a member price   | Expected. Bundles keep their existing range.                                                                                                                  |
| A price drawn by your own JavaScript never changes | The automatic rule works on Liquid output. A value your script writes has no shell.                                                                           |
| `{% if member %}` renders nothing                  | Expected. There is no `member` variable on cached storefront pages.                                                                                           |

## Related

* [Member greetings](/themes/member-storefront/member-aware-sections) for greetings and member-only content with `{% member_name %}` and `{% member %}`.
* [Sign in and sign out](/themes/member-storefront/authentication) for the session these prices depend on.
* [Account menu](/themes/member-storefront/account-navigation) for a cached header that knows the member.
