# Add checkout to your site

You can add checkout to an existing page as a button that opens the offer or as an inline checkout. First [publish an offer](https://plandalf.com/docs/product-guides/checkouts/create) and copy its slug. The examples use `upgrade` and the placeholder host `https://your-org.plandalf.dev`.

## 1. Choose how the page opens checkout

| Your site | Use | What your page gets |
| --- | --- | --- |
| Editable HTML or a site builder | Organization browser script and `data-plandalf-*` attributes | Automatic button or inline binding |
| JavaScript app | `@plandalf/sdk` | An awaitable `present()` result or a `mount()` handle |
| React app | `@plandalf/react` | Provider and hooks for a button, or an inline embed component |

For a first integration, use a button. It lets the existing page keep its layout and works with any of the three modes.

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

Add the organization script once per page. It scans for the button attribute.

```html title="page.html"
<script defer src="https://your-org.plandalf.dev/js/plandalf-sdk.js"></script>
<button data-plandalf-present="upgrade">
  Upgrade
</button>
```

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

Install the JavaScript package with `npm install @plandalf/sdk`. Use `status` for an on-page message, then read verified access from your server if this is a paid upgrade.

```javascript title="page.js"
import { Plandalf } from '@plandalf/sdk'

const sdk = new Plandalf({ apiBase: 'https://your-org.plandalf.dev' })
const button = document.querySelector('#upgrade')

button.addEventListener('click', async () => {
  button.disabled = true
  try {
    const result = await sdk.present('upgrade')
    if (result.status === 'complete') {
      // Refresh verified access from your own server.
    }
  } finally {
    button.disabled = false
  }
})
```

```html title="page.html"
<button id="upgrade">Upgrade</button>
```

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

Install the React package with `npm install @plandalf/react`. Hooks run inside `PlandalfProvider`.

```tsx title="UpgradePage.tsx"
import { PlandalfProvider, usePresent } from '@plandalf/react'

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

  async function upgrade() {
    const result = await present('upgrade')
    if (result.status === 'complete') {
      // Refresh verified access from your own server.
    }
  }

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

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

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

## 2. Choose inline checkout if the page needs it

An inline checkout lives in a container on the page. Use **one** mounting method for a given container. Do not put `data-plandalf-mount` on a container that your JavaScript also passes to `mount()`.

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

With the browser script already loaded, this container mounts the offer automatically:

```html title="page.html"
<script defer src="https://your-org.plandalf.dev/js/plandalf-sdk.js"></script>
<div data-plandalf-mount="upgrade"></div>
```

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

`mount()` resolves when the inline flow is ready. Its handle can emit later checkout events; the resolved value is **not** a completed purchase.

```javascript title="page.js"
import { Plandalf } from '@plandalf/sdk'

const sdk = new Plandalf({ apiBase: 'https://your-org.plandalf.dev' })
const handle = sdk.mount('#checkout', 'upgrade')
const flow = await handle
// The inline checkout is now mounted; your server still verifies any purchase.
```

```html title="page.html"
<div id="checkout"></div>
```

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

The React package source exports an inline offer embed. Pass the offer and organization domain; its completion callback is a page signal, not payment proof.

```tsx title="InlineUpgrade.tsx"
import { OfferStandardEmbed } from '@plandalf/react'

export function InlineUpgrade() {
  return <OfferStandardEmbed
    offer="upgrade"
    domain="https://your-org.plandalf.dev"
    onComplete={() => { /* Refresh verified access from your server. */ }}
  />
}
```

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

## 3. Check it on your real page

Confirm the correct offer and price open, the button can be used again after a dismissal, and the inline checkout fits your page at narrow widths. For any test payment, first verify that the offer and integration are explicitly configured for sandbox use. A `mode: 'test'` override can fall back to live for public visitors, and `mount()` in the checked-out SDK does not forward a `mode` option to the mounted flow. If the offer does not appear, check the organization host, published slug, script request, and any content security policy blocking the script or frame. Complete a provider-supported sandbox payment and verify it server-side before delivering access.

For a button with a fuller result example, see [Add checkout to a button](https://plandalf.com/docs/guides/react-checkout). [`present()`](https://plandalf.com/docs/sdk/present) returns a checkout result; [`mount()`](https://plandalf.com/docs/sdk/mount) returns an inline flow. Their [option types](https://plandalf.com/docs/packages/sdk/interfaces/MountOptions) show the exact configuration accepted by each method.

Source: https://plandalf.com/docs/start/add-to-site
