> ## Documentation Index
> Fetch the complete documentation index at: https://help.the-meridian.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# useMeridian

`useMeridian()` is the hook every component uses to read the shop's plan and act on it. It throws if there is no [`MeridianProvider`](/sdk-provider) above it.

```tsx theme={null}
import { useMeridian } from "@the-meridian/sdk"

const { isEnabled, getUsage, track } = useMeridian()
```

## State

| Member | Type | Notes |
| - | - | - |
| `customer` | `MeridianCustomer \| null` | The shop's CRM record, including its [custom field values](/custom-fields-api) |
| `entitlements` | `Entitlements \| null` | The resolved snapshot the gates read from |
| `plans` | `Plan[] \| null` | Your active plans, priced in the shop's currency |
| `loading` | `boolean` | `true` until the first load resolves |
| `error` | `string \| null` | The message from a failed load. Gates read as ungated while it is set |
| `shopToken` | `string` | The token currently in use, after any refresh |

Everything is `null` until `loading` turns `false`. Gate on `loading` before you gate on a feature.

## Gates

Four synchronous lookups, safe to call in render:

| Call | Returns | Answers |
| - | - | - |
| `isEnabled(featureKey)` | `boolean` | Does the shop's plan grant this feature? |
| `getLimit(featureKey)` | `number \| undefined` | What numeric limit does the plan set? |
| `getUsage(key)` | `EntitlementEvent \| undefined` | How much of a metered event (or a [view](/usage-views)) has the store consumed? |
| `getView(viewKey)` | `EntitlementView \| undefined` | The same for a view, plus the window it covers |

```tsx theme={null}
const { isEnabled, getLimit, getUsage } = useMeridian()

if (!isEnabled("advanced_reports")) return <UpgradePrompt />

const seats = getLimit("team_seats")                    // 5, or undefined
const used = getUsage("api_calls")?.usedThisPeriod ?? 0
```

Features and events are addressed by their **key**: the app-local identifier you set when you define the [feature](/introduction-13) or the [event](/introduction-6). A key that does not exist reads as `false` / `undefined`, so a typo gates nothing and fails silently. See [Feature gating](/features).

`getUsage()` also takes a **[view](/usage-views)** key: views and events share one key namespace per app, so you hold one string and don't have to know which kind it is. A view's value comes back as `usedThisPeriod` over that view's own window; `getView()` is the version that also tells you what that window is.

If the store's plan **prices** that view, `getUsage()` carries `includedQuantity` and `cappedAmount` too, exactly as it does for a metered event, so one usage meter in your UI renders either kind without branching on which it got. Their absence is the signal that nothing is billed on the metric.

## Actions

| Call | Returns |
| - | - |
| `subscribe(planId, returnUrl?, billingInterval?, code?)` | `SubscribeResult` |
| `cancelSubscription()` | `CancelResult` |
| `confirmSubscription(chargeId?)` | `SubscriptionConfirmResult`: `{ status, planId }` for the charge, after Meridian asked Shopify |
| `updateUsageCap({ eventKey, cappedAmount })` | `UpdateUsageCapResult`. `eventKey` takes an event key or a [view](/usage-views) key, since the two share one namespace |
| `track({ eventKey, quantity?, properties?, idempotencyKey? })` | `TrackEventResult`. Takes no `timestamp`: send [dated events](/events#dated-events) from your server |
| `validateDiscount(code, planId, billingInterval?)` | `DiscountPreview` |
| `refresh(options?)` | `Promise<void>`, re-reads `/me`. Pass `{ silent: true }` to keep the current snapshot on screen meanwhile |

`cancelSubscription` refreshes the snapshot for you. `subscribe` returns a `confirmationUrl` the merchant has to be sent to in order to approve the charge. A free plan has no approval step, so it activates immediately, returns no URL, and refreshes the snapshot instead. See [Pricing page](/plans).

`confirmSubscription` is for the merchant's return from that approval screen, if you build your own pricing page. Pass the `charge_id` Shopify appends to your `returnUrl`, or nothing to confirm the shop's newest pending charge. Meridian asks Shopify for the charge's status instead of waiting for Shopify's subscription webhook, records it (your [automations](/automations) fire exactly as they would for the webhook), and returns it with the plan's ID. `status` is `ACTIVE` once approved, `PENDING` while Shopify has not settled it, `DECLINED` or `EXPIRED` when it never became a subscription, and `null` when Meridian knows no such charge. It does not refresh the snapshot: call `refresh({ silent: true })` once the status is settled. Meridian asks Shopify at most once every 5 seconds per store and otherwise answers what it already recorded, so calling it every few seconds is fine. [`MeridianPricingPage`](/plans#coming-back-from-shopify) does all of this for you. A charge nobody confirms (the merchant never came back, and the webhook was lost) is not left pending: Meridian also asks Shopify about it on a schedule, a few minutes after it was created and again at growing intervals, and records whatever Shopify answers.

A silent `refresh` leaves `loading` alone, keeps the current snapshot when the request fails (no `error` either), and keeps the same objects when nothing changed, so effects that depend on `plans` or `entitlements` do not re-run on every poll. A plain `refresh()` sets `loading` and replaces everything, as before.

<Warning>
  `returnUrl` on `subscribe` is optional in the type only. Omitted, it falls back to `window.location.href`, which inside an embedded app is the iframe URL and is rejected. Always pass a shop-admin `https` URL built server-side.
</Warning>

`validateDiscount` previews a code, or the best automatic discount when you pass `undefined`. It never throws on a bad code. It resolves `{ valid: false }` with a generic message, so the existence of your codes is never leaked to a merchant guessing at them.

## Pure helpers

The same three gates exist as standalone functions over an `Entitlements` snapshot, exported from both entries. Use them when you already hold a snapshot and are not inside a component: a `useMemo`, a reducer, a server route:

```ts theme={null}
import { featureEnabled, featureLimit, eventUsage } from "@the-meridian/sdk"

featureEnabled(entitlements, "advanced_reports")   // boolean
featureLimit(entitlements, "team_seats")           // number | undefined
eventUsage(entitlements, "api_calls")              // EntitlementEvent | undefined
```

A `null` snapshot reads as ungated rather than throwing.

## useOptionalMeridian

`useOptionalMeridian()` returns the same context value, or `null` when there is no provider, for code that has to keep working in an app running without Meridian credentials. The SDK's own page components use it to degrade to an empty state instead of crashing.

Reach for it only in shared components that genuinely mount in both worlds. In an app that always mounts the provider, `useMeridian()`'s throw is the more useful behaviour.


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