---
title: Elements
description: Terudo Elements reference. The five drop-in elements (destination-picker, plans, trip-offer, booking-banner, esim-status), loading, options, callbacks, styling, sessions, checkout, test mode, CSP.
---

# Elements

Elements are pieces of Terudo's eSIM shop that render inside your own page: no iframe, your font, your colours. The traveller chooses a plan in your page and pays on Terudo's hosted checkout, where Terudo is the seller (commission model). One script, one publishable key.

## Load the script

```html
<script src="https://terudo.com/elements/v1.js" data-key="pk_live_…" data-locale="fr" defer></script>
```

Script attributes:

| Attribute | Meaning |
| --- | --- |
| `data-key` | Your publishable key (`pk_live_…`, `pk_test_…`, or the demo key `pk_test_terudo_demo_public`). With it, the script creates `window.terudo` and mounts every element marked `data-terudo` once the page is ready. |
| `data-locale` | `fr`, `en`, `de`, `es`, `it`, `pt`, `zh`, `ar` or `hi`. Default: the page's `<html lang>`, then `fr`. |
| `data-session` | A `client_token` from an element session (see Sessions). |

Never put a secret key (`sk_…`) in a page.

## Two ways to mount

**Declarative**: any element with `data-terudo="<name>"` and its options as `data-*` attributes (camelCase options become kebab-case: `returnUrl` → `data-return-url`, `esimId` → `data-esim-id`, `plansInto` → `data-plans-into`).

```html
<div data-terudo="trip-offer" data-destination="JP" data-start="2027-04-02" data-end="2027-04-16" data-profile="heavy"></div>
```

Elements added to the page later (single-page apps) are mounted by calling `terudo.autoMount()` (or `terudo.autoMount(containerElement)`). An element is mounted once; it carries `data-terudo-mounted` afterwards.

**JavaScript**: create an instance and mount where you want. Callbacks are only available this way.

```js
const terudo = Terudo({
  key: 'pk_live_…',
  locale: 'en',
  session: clientToken,                       // optional, from POST /element_sessions
  appearance: { color: '#0a5b8a', onColor: '#ffffff', radius: '10px', font: 'inherit' },
});

const offer = terudo.mount('trip-offer', '#esim-offer', {
  destination: 'MA', start: '2026-12-03', end: '2026-12-09', profile: 'normal',
  email: 'marie@example.com', returnUrl: 'https://www.your-agency.example/bookings/48213',
  onCheckout(order) { analytics.track('esim_checkout', { order: order.order_id }); },
  onError(error) { console.warn(error.code, error.message); },
});

offer.update({ start: '2026-12-04' });        // re-renders with new options
```

`Terudo(config)` returns `{ mount(name, target, options), autoMount(scope?), api(path, options), locale, key, origin, elements }`. `target` is a CSS selector or a DOM element; `mount` empties it first. `mount` returns `{ host, update(options) }`.

## The elements

### destination-picker

The destinations of the catalogue, each with its "from" price.

| Option | Type | |
| --- | --- | --- |
| `title` | string | Heading. Default "Choose your destination". |
| `onSelect(code, plan)` | function | Called when a destination is picked, with its code and cheapest plan. |
| `plansInto` | selector or element | Mounts `plans` for the picked destination there. |

### plans

Every plan of one destination, cheapest first, each with its price and a button that asks the traveller's email and opens the checkout.

| Option | Type | |
| --- | --- | --- |
| `destination` | string, required | ISO code (`MA`) or region (`EU-35`). |
| `days` | integer 1–30 | Adds daily plans of that length. |
| `email`, `returnUrl`, `onSelect(plan)`, `onCheckout(order)`, `onError(error)` | | See Checkout and Callbacks. |

### trip-offer

Destination and dates in, the recommended plan out (the cheapest that covers the stay plus one buffer day), the other plans a click away. The piece for a visa approval, an itinerary or a trip page.

| Option | Type | |
| --- | --- | --- |
| `destination` | string, required | |
| `start`, `end` | `YYYY-MM-DD`, required | Arrival and departure. 60 days at most. |
| `profile` | `light` \| `normal` \| `heavy` | Estimated use: 0.5, 1 or 2 GB a day. Default `normal`. |
| `title` | string | Replaces "Your trip: dates". |
| `email`, `returnUrl`, `onSelect`, `onCheckout`, `onError` | | |

### booking-banner

One line for a booking confirmation: the recommended plan and its price, with a button that opens into the full `trip-offer` in place.

| Option | Type | |
| --- | --- | --- |
| `destination`, `start`, `end`, `profile` | | As `trip-offer`. |
| `title`, `subtitle`, `cta` | string | Override the banner's texts and the button label. |
| `onOpen(quote)` | function | Called when the traveller opens the offer. |
| `email`, `returnUrl`, `onCheckout`, `onError` | | |

### esim-status

The traveller's eSIM inside your own customer area: state, data used, install buttons while not installed, validity, link to Terudo's full eSIM page.

| Option | Type | |
| --- | --- | --- |
| `esimId` | string, required | The `esim.id` of the order (API) or the token in the traveller's eSIM page link. |
| `pageUrl` | `false` | Hides the link to the full page. |
| `onLoad(esim)` | function | Called with the eSIM data (no activation code: that stays server-side). |

States shown: `preparing`, `not_installed`, `active`, `used_up`, `ended`, `suspended`, `deleted`, `refunded`.

## Checkout

The traveller never pays in your page. After choosing a plan they give their email (unless you passed `email`), the element creates a commission order (`POST /public/checkout`) and sends the browser to `checkout_url` on terudo.com. There the traveller accepts Terudo's terms and pays in their currency; the eSIM arrives by email right after payment. Then they are sent to `returnUrl`:

- kept when its origin is one of the sites listed on your publishable key, or when it comes from an element session;
- otherwise ignored, and the traveller stays on Terudo's confirmation page.

`onCheckout(order)` runs just before the redirect, with `{ object: "checkout", order_id, checkout_url, status }`. Return `false` to stop the redirect and handle `order.checkout_url` yourself (open it in a new tab, a webview, …).

Checkout from a page is limited to 30 orders an hour per IP address.

## Callbacks

| Callback | Elements | Called with |
| --- | --- | --- |
| `onSelect` | destination-picker: `(code, plan)`; plans, trip-offer: `(plan)` | The choice |
| `onCheckout` | plans, trip-offer, booking-banner | The order, before redirecting. `false` stops the redirect. |
| `onOpen` | booking-banner | The quote |
| `onLoad` | esim-status | The eSIM |
| `onError` | all | An `Error` with `message` (shown to the traveller too), `code` and `status` |

## Styling

Elements inherit the page's font and render in a `.tdo` container. Set the brand with `appearance` or CSS variables on the host element:

| Variable | `appearance` key | |
| --- | --- | --- |
| `--tdo-color` | `color` | Buttons, selected tiles, links |
| `--tdo-on` | `onColor` | Text on the colour |
| `--tdo-radius` | `radius` | Corners |
| `--tdo-font` | `font` | Font family (default: inherit) |

Elements are responsive and accessible (buttons, ARIA states, progress bar for usage). Test keys show a small "Test" badge.

## Sessions

Without a session, an element shows public prices to anyone and asks the traveller for their email. An **element session**, created by your server with your secret key, signs what the page may not decide on its own:

```bash
curl -X POST https://terudo.com/api/partner/v1/element_sessions \
  -H "Authorization: Bearer sk_live_…" -H "Content-Type: application/json" \
  -d '{
        "discount_percent": 10,
        "reference": "visa-48213",
        "client_email": "marie@example.com",
        "trip": { "destination": "MA", "start_date": "2026-12-03", "end_date": "2026-12-09", "profile": "normal" },
        "return_url": "https://www.your-agency.example/bookings/48213",
        "locale": "fr",
        "metadata": { "booking_id": "48213" }
      }'
# → { "object": "element_session", "client_token": "…", "expires_at": "…", "session": { … } }
```

- `discount_percent`: client discount 0–15 (default 5), taken from your commission.
- `reference`: your booking or file id; the order carries it, and webhooks with it.
- `client_email`, `return_url`, `locale`, `trip`, `metadata`: prefilled and trusted.
- Your member rate applies when your account has it.

Pass `client_token` as `data-session` on the script or `session` in `Terudo({...})`. It lasts two hours: create it when you render the page.

## Test mode

`pk_test_…` keys work exactly like live ones but create sandbox sales: the pay page simulates the payment, the eSIM is not a real one, nothing is billed. The demo key `pk_test_terudo_demo_public` shows real data but refuses checkout (`demo_key`). See [Testing](https://terudo.com/developers/docs/testing.md).

## Content Security Policy

Allow `script-src https://terudo.com`, `connect-src https://terudo.com` and `img-src https://terudo.com`.

## Errors

A failed element shows a short message in place (in the element's language) and calls `onError`. Common codes: `unauthorized` (key unknown or revoked), `forbidden` (page origin not listed on the key), `invalid_trip` (dates), `unknown_plan`, `demo_key` (checkout with the demo key), `rate_limited`.

## Raw calls

`terudo.api(path, { method, query, body })` calls the public routes with your key (and session): `destinations`, `plans`, `quotes`, `checkout`, `esims/<id>`. It returns a promise of the JSON. Useful to build your own interface on Terudo's data; the checkout still happens on terudo.com.
