# `plandalf.present()`

Open an offer and get an awaitable flow handle.

Implemented by the [browser object](https://github.com/plandalf/numi/blob/fc6f42b5ade3d63b71623934838a6509f17203e2/packages/sdk/src/browser.ts#L434); parameter and return types follow the [delegated class declaration](https://github.com/plandalf/numi/blob/fc6f42b5ade3d63b71623934838a6509f17203e2/packages/sdk/src/core/sdk.ts#L450).

Call this after `plandalf.ready()`. The browser wrapper can return `undefined` before initialization.

## Signature

```typescript
plandalf.present(
  slug: string,
  opts?: Partial<FlowOpenOptions>,
): FlowHandle<FlowResult>
```

## Arguments

| Name | Type | Meaning |
| --- | --- | --- |
| `slug` | `string` | Published offer slug. |
| `opts?` | `Partial`\<[`FlowOpenOptions`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions)\> | Flow options passed through to the class. The browser wrapper declares this argument as `any` and passes it to the typed class method. |

## Example

<scalar-tabs default="JavaScript">
<scalar-tab title="JavaScript">

```javascript title="present.js" lineNumbers lineNumberStart=1
plandalf.ready(async () => {
  const handle = plandalf.present('upgrade', {
    frame: 'modal',
    size: 'standard',
  })
  const result = await handle
  if (result.status === 'complete') {
    // Confirm access on your server.
    console.log(result.session?.uuid)
  }
})
```

</scalar-tab>
<scalar-tab title="React">

```tsx title="present.tsx" lineNumbers lineNumberStart=1
import { PlandalfProvider, usePresent } from '@plandalf/react'

function UpgradeButton() {
  const { present, presenting } = usePresent()

  async function upgrade() {
    const result = await present('upgrade', { frame: 'modal' })
    if (result.status === 'complete') {
      // Refresh access from your server before granting it.
    }
  }

  return <button disabled={presenting} onClick={upgrade}>Upgrade</button>
}

export function Upgrade() {
  return (
    <PlandalfProvider apiBase="https://your-org.plandalf.dev">
      <UpgradeButton />
    </PlandalfProvider>
  )
}
```

</scalar-tab>
</scalar-tabs>

## Present options

The `opts` object accepts these [`FlowOpenOptions`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions) fields.

| Field | Type | Meaning |
| --- | --- | --- |
| [`confirmClose?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#confirmclose) | () => `boolean` \| `Promise`\<`boolean`\> | Merchant-supplied "are you sure?" gate. Resolved `true` allows the close to proceed, `false` keeps the frame open. Replaces the previous boolean \+ label/title strings combo. Off when omitted. |
| [`context?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#context) | `Record`\<`string`, `unknown`\> | Initial values for the flow context. |
| [`continuity?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#continuity) | [`ContinuityScope`](https://plandalf.com/docs/packages/sdk/referenced-types/ContinuityScope) | Per-call continuity override. |
| [`frame?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#frame) | [`FrameMode`](https://plandalf.com/docs/packages/sdk/type-aliases/FrameMode) | Frame modality. Was previously `mode` — renamed because `mode` now means the test/live/preview environment (see below). |
| [`metadata?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#metadata) | `Record`\<`string`, `string`\> | Per-call Stripe-customer metadata. Eager string values only — for window-global lookups use the page-global PlandalfConfig.metadata thunk form. Wins over PlandalfConfig.metadata on overlap. |
| [`mode?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#mode) | `"test"` \| `"live"` \| `"preview"` | Environment override: 'test' \| 'live' \| 'preview'. Was previously `environment`. The server uses this to route the session through the org's test or live integration; super-admin only. |
| [`promo?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#promo) | [`PromoContext`](https://plandalf.com/docs/packages/sdk/interfaces/PromoContext) | Resolved promotion context. The current `present()` implementation also resolves a promo slug string at runtime. |
| [`properties?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#properties) | `Record`\<`string`, `unknown`\> | Per-call form-field prefill defaults. Wins over PlandalfConfig.properties on overlap. See specs/sdk-checkout-config.md. |
| [`session?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#session) | `string` \| `null` | Session continuity token (`cks_…`). Pass explicitly to resume a specific session server-side — cross-device handoff, email "continue your checkout" links, anywhere the merchant has a token in hand. Pass `null` to force a fresh session and skip any stored token. Omit entirely to let the SDK auto-detect. |
| [`sessionId?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#sessionid) | `string` | Reuse an existing session UUID instead of creating a fresh one. Used by the auto-resume path to finalize against the ORIGINAL session after a redirect-based auth bounce — creating a new session would lose the draft upsell invoice that needs settling. |
| [`size?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#size) | [`FlowSize`](https://plandalf.com/docs/packages/sdk/referenced-types/FlowSize) \| \{ `h`: `string`; `w`: `string`; \} | Size of the modal panel. Either a named preset OR an explicit `{ w, h }` object for one-off cases. Defaults to 'standard'. |
| [`templateId?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#templateid) | `string` | Lower-level template selector. The method supplies `templateSlug` from its slug argument, so this does not select the offer here. |
| [`templateSlug?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#templateslug) | `string` | Can override the slug argument in the current implementation. Pass the desired slug as the method argument instead. |
| [`user?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#user) | `string` | Customer JWT for THIS call only — overrides the SDK-wide identity from `plandalf.setUser()` or the `user` init option. Useful for "buy as customer" admin tooling, impersonation flows, or offers that need a different identity than the global one. The value is the raw JWT string. |

### Supported compatibility options

The current runtime still reads these older fields.

| Field | Type | Meaning |
| --- | --- | --- |
| [`animation?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#animation) | `"slide"` \| `"fade"` \| `"none"` | Frame entrance animation. |
| [`backdrop?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#backdrop) | `boolean` | Whether to render a backdrop behind the frame. |
| [`closeButton?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#closebutton) | `boolean` | Whether to show the frame close button. |
| [`closeOnBackdrop?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#closeonbackdrop) | `boolean` | Whether a backdrop click closes the frame. |
| [`confirmLeaveLabel?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#confirmleavelabel) | `string` | Leave button text in the built-in close confirmation. |
| [`confirmMessage?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#confirmmessage) | `string` | Message in the built-in close confirmation. |
| [`confirmStayLabel?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#confirmstaylabel) | `string` | Stay button text in the built-in close confirmation. |
| [`confirmTitle?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#confirmtitle) | `string` | Title in the built-in close confirmation. |
| [`duration?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#duration) | `number` | Frame animation duration in milliseconds. |
| [`environment?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#environment) | `"test"` \| `"live"` | Test or live session routing. The newer per-call name is `mode`. |
| [`height?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#height) | `string` | Explicit frame height. |
| [`onClose?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#onclose) | (`reason`) => `void` | Callback when the presented flow closes. Await the handle for the final result. |
| [`onComplete?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#oncomplete) | (`result`) => `void` | Callback on flow completion. Await the handle for the final result. |
| [`onError?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#onerror) | (`error`) => `void` | Callback on flow failure. A rejected handle can also be caught. |
| [`position?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#position) | `"left"` \| `"right"` | Frame position, left or right. |
| [`width?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#width) | `string` | Explicit frame width. This takes precedence over a size preset. |

### Declared without current effect

| Field | Type | Meaning |
| --- | --- | --- |
| [`dragToClose?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#dragtoclose) | `boolean` | Collected into frame config, but not passed to the rendered frame. |
| [`headless?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowOpenOptions#headless) | `boolean` | Declared in the type, but the checkout implementation does not read it. |

## Awaited result

Await the handle for a [`FlowResult`](https://plandalf.com/docs/packages/sdk/interfaces/FlowResult). The result fields are:

| Field | Type | Meaning |
| --- | --- | --- |
| [`customer?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowResult#customer) | [`Customer`](https://plandalf.com/docs/packages/sdk/interfaces/Customer) | Convenience — same as `result.session.customer`. |
| [`reason?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowResult#reason) | `"complete"` \| `"cancel"` \| `"escape"` | When dismissed, why — backdrop / close button / escape. |
| [`redirectUrl?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowResult#redirecturl) | `string` | Where to redirect the customer after complete (if the offer configures one). |
| [`session?`](https://plandalf.com/docs/packages/sdk/interfaces/FlowResult#session) | [`CheckoutSessionPayload`](https://plandalf.com/docs/packages/sdk/interfaces/CheckoutSessionPayload) | The full CheckoutSession at the moment of completion (populated on status='complete' only). Invoice + everything else lives in here — inspect via `result.session.uuid`, `result.session.total`, `result.session.invoice`, etc. |
| [`status`](https://plandalf.com/docs/packages/sdk/interfaces/FlowResult#status) | [`FlowResultStatus`](https://plandalf.com/docs/packages/sdk/type-aliases/FlowResultStatus) | 'complete' = user finished the flow. 'dismissed' = closed early. |

## Returns

[`FlowHandle`](https://plandalf.com/docs/packages/sdk/interfaces/FlowHandle)\<[`FlowResult`](https://plandalf.com/docs/packages/sdk/interfaces/FlowResult)\>

## Behaviour

The returned handle is awaitable. It also has an `events` async iterable and `close()` method. A completed browser flow still needs server-side entitlement verification.

[All plandalf methods](https://plandalf.com/docs/sdk)

Source: https://plandalf.com/docs/sdk/present
