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

# Advanced Installation

> Configure the Restoo.js API: initial view, language, triggers, and how the widget behaves in your integration.

This page assumes you've already completed the [Installation Quickstart](/widget/quickstart). It covers everything beyond that basic setup: the [Restoo.js API](#restoo-js-api), [deep linking](#deep-linking), [forced attribution](#forced-attribution), the [technical notes](#technical-notes), and the options of [hosted on Restoo](#hosted-on-restoo). To customize colors and fonts, see [Appearance](/widget/appearance).

<Note>
  Everything that follows is about having it **embedded in your site**, with the
  booking form inside your own website. If your guests book on the page Restoo
  hosts for you there is no code to write: what you configure and where is in [Hosted
  Restoo](#hosted-on-restoo), at the end of the page.
</Note>

## Restoo.js API

The widget shows up in three ways, and each one has its own value in code. Here and in [the events](/widget/events#fields-common-to-every-event) you will see them like this:

| How it shows up      | API value    | How it is mounted                            |
| -------------------- | ------------ | -------------------------------------------- |
| Modal over your page | `OVERLAY`    | [`.bindTriggers()`](#bindtriggers-settings)  |
| Inline in your page  | `INLINE`     | [`.mount()`](#mount-containerid)             |
| Hosted on Restoo     | `STANDALONE` | Nothing: it is the page Restoo hosts for you |

Every method returns the instance, so calls can be chained. And where a value only
accepts letters, digits, hyphens, and underscores (`[a-zA-Z0-9-_]`), it is noted.

### Full example

Everything the API exposes, together in one installation: a named widget instance, all five
`.create()` parameters, and a custom trigger attribute. The rest of this section covers each piece
on its own, and you don't need all of it — only what your integration needs.

```js theme={null}
const widget = window.Restoo("best-burger", { widgetId: "w-reservations" });

widget
  .create({
    options: {
      language: "es",
      view: "experiences",
      layout: { hasBorder: true },
    },
    // Replaces the account's appearance entirely, so it is declared in full.
    appearance: {
      fonts: [
        {
          cssSrc:
            "https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;600&display=swap",
        },
      ],
      variables: {
        colorAccent: "#c0392b",
        fontFamilyHeading: '"Playfair Display", serif',
      },
    },
    // This page has no GTM container; the other destinations come from the account.
    connect: { gtm: false },
    cmp: "GOOGLE_CONSENT_MODE",
    utmTags: { utmSource: "partner_portal", utmMedium: "referral" },
  })
  .bindTriggers({ attribute: "data-open-booking" });
```

### `window.Restoo(account, settings?)`

Creates a widget instance. Available as soon as the `restoo:loaded` event fires on `window`.

**Parameters**

<ParamField body="account" type="string" required>
  Your Restoo Account ID — e.g. `"best-burger"`. Outside the accepted character
  set it throws.
</ParamField>

<ParamField body="settings.widgetId" type="string">
  Unique ID for this instance. It defaults to the `account` value. Restoo adds
  the `restoo_` prefix, so `widgetId: "w-reservations"` appears as
  `"restoo_w-reservations"` in `source.widget_id` on [Restoo Widget
  Events](/widget/events#fields-common-to-every-event).
</ParamField>

Returns a widget instance with `create()`, `mount()`, `bindTriggers()`, and `setConsent()`.

<Tip>
  Set `widgetId` explicitly when you mount **more than one widget instance on
  the same page** for the same `account` — for example, two separate booking
  forms with different initial views. Each `widgetId` gets its own isolated
  state.
</Tip>

### `.create(settings?)`

**Optional.** Configures the instance before it mounts — if you don't need to customize anything, call `.mount()` or `.bindTriggers()` directly and the widget will use its defaults.

```js theme={null}
widget.create({ options, appearance, connect, cmp, utmTags });
```

**Parameters**

<ParamField body="options" type="object">
  How the widget behaves: its language, the view it opens on, and how it sits
  inside your page.

  <Expandable title="properties">
    <ParamField body="language" type="string">
      Widget language. Use one of the supported
      [CustomerLanguage](/getting-started/enums#customerlanguage) values. If
      omitted, the widget auto-detects the visitor's browser language. Example:
      `language: "es"`.
    </ParamField>

    <ParamField body="view" type="string" default="reservation">
      The initial view used by `.mount()`. For an overlay, set the desired view as
      the value of its `data-restoo-open` trigger. One of:

      | Value         | Description                                             |
      | ------------- | ------------------------------------------------------- |
      | `reservation` | The booking/reservation flow.                           |
      | `experiences` | Experiences and tasting menus.                          |
      | `store`       | Gift card / merchandise store.                          |
      | `announces`   | Announcements page.                                     |
      | `locations`   | Location picker, for accounts linked to several venues. |
      | `redeem`      | Redeem a gift card or voucher.                          |

      Example: `view: "experiences"`.
    </ParamField>

    <ParamField body="layout" type="object">
      Fine-tunes how the widget sits inside your page.

      <Expandable title="properties">
        <ParamField body="hasPadding" type="boolean">
          Inner spacing around the widget's content. Defaults to `false` for an
          embedded widget and `true` for a modal, which already floats over your page.
        </ParamField>

        <ParamField body="hasBorder" type="boolean">
          Border around the widget container. Defaults to `false` — turn it on if the
          widget needs to stand out against a background of the same color.
        </ParamField>
      </Expandable>
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="appearance" type="object">
  Appearance for this instance only: it replaces your account's entirely, it does
  not tweak it. See [Appearance](/widget/appearance) for more information.
</ParamField>

<ParamField body="connect" type="object">
  Destinations to switch off for this installation: `ga4`, `googleAds`,
  `metaPixel`, `tiktokPixel`, or `gtm`, each with the value `false`, the only one
  accepted. Everything else about a destination comes from your account. See
  [Restoo Connect](/widget/connect#turn-a-destination-off-for-one-installation)
  for more information.
</ParamField>

<ParamField body="cmp" type="&#x22;COOKIEBOT&#x22; | &#x22;GOOGLE_CONSENT_MODE&#x22;">
  [Consent management platform](/widget/consent#what-a-consent-management-platform-is)
  installed on this page, which Restoo reads the visitor's decision from. Omitting it means consent arrives only through
  [`setConsent()`](#setconsent-signals). See
  [Consent](/widget/consent#declare-your-consent-platform) for more information.
</ParamField>

<ParamField body="utmTags" type="object">
  Campaign attribution fixed for this installation: what you declare takes
  precedence over the UTM parameters arriving in the visitor's URL. Omitting it
  keeps the normal behavior, where the attribution comes from that URL. See [Forced
  attribution](#forced-attribution) for more information.

  <Expandable title="properties">
    <ParamField body="utmSource" type="string">
      Where the booking comes from — the `utm_source` parameter. Example:
      `"partner_portal"`.
    </ParamField>

    <ParamField body="utmMedium" type="string">
      The channel that brings it — the `utm_medium` parameter. Example:
      `"referral"`.
    </ParamField>

    <ParamField body="utmCampaign" type="string">
      The campaign it belongs to — the `utm_campaign` parameter. Example:
      `"summer_2026"`.
    </ParamField>

    <ParamField body="utmTerm" type="string">
      The paid keyword behind it — the `utm_term` parameter. Example:
      `"book_a_table"`.
    </ParamField>

    <ParamField body="utmContent" type="string">
      Which creative or placement it came from — the `utm_content` parameter.
      Example: `"sidebar_widget"`.
    </ParamField>
  </Expandable>
</ParamField>

### `.mount(containerId)`

Mounts the widget inside the given HTML element. Used for `INLINE` widgets.

```js theme={null}
widget.mount("restoo-widget");
```

**Parameters**

<ParamField body="containerId" type="string" required>
  The `id` of your container element — e.g. `"restoo-widget"`. Beyond the
  accepted character set, it can't start with a digit, start with a hyphen followed by a digit, or be
  a single hyphen. Accented or non-Latin `id`s such as `"menú"` or `"café-2"` are
  rejected — rename the element or give it a plainer `id` for the widget.
</ParamField>

### `.bindTriggers(settings?)`

Switches the instance to `OVERLAY` mode and registers elements carrying `data-restoo-open` (or a custom attribute) as click triggers. The widget **mounts lazily the first time a user clicks**; subsequent clicks navigate directly without remounting.

```js theme={null}
widget.bindTriggers();

// With a custom attribute
widget.bindTriggers({ attribute: "data-open-booking" });
```

**Parameters**

<ParamField body="settings.attribute" type="string" default="data-restoo-open">
  HTML attribute used as the trigger — e.g. `"data-open-booking"`.

  It only accepts letters, digits, hyphens, underscores, and colons, and must
  start with a letter; throws otherwise.
</ParamField>

**Usage in HTML:**

The attribute's value is the view the overlay opens on. Leave it empty to open the default view, or
set any of the values accepted by the [`view` option](#param-view) to send visitors straight to that
part of the widget.

```html theme={null}
<!-- No value: opens the default view (reservation) -->
<button data-restoo-open>Book a table</button>

<!-- With a value: opens directly on that view -->
<button data-restoo-open="experiences">See our experiences</button>
<button data-restoo-open="store">Gift cards</button>
```

### `.setConsent(signals)`

**Optional if you declared your [consent platform](/widget/consent#what-a-consent-management-platform-is) with [`cmp`](#param-cmp)**, since Restoo already reads that platform. Informs Restoo of the visitor's consent, using [Google Consent Mode v2](https://support.google.com/tagmanager/answer/10718549) signal names and values.

Call it from your cookie notice, whenever the visitor accepts, rejects, or changes their decision.

```js theme={null}
widget.setConsent({
  ad_storage: "granted",
  ad_user_data: "granted",
  ad_personalization: "denied",
  analytics_storage: "granted",
});
```

**Parameters**

<ParamField body="signals" type="object" required>
  One or more of the seven Google Consent Mode v2 signals. Each value must be
  exactly `"granted"` or `"denied"`. See
  [Consent](/widget/consent#param-signals) for what each signal means, how it
  accumulates across calls, and what it unlocks.
</ParamField>

***

## Deep linking

The widget mirrors the view it is on into your page's URL, inside a `?restoo_widgets=` parameter. That turns the address into a link you can share or bookmark: opening it starts the widget on the saved view instead of its default.

It works in both modes, with one difference:

| Mode      | On loading the page with that parameter                                           |
| --------- | --------------------------------------------------------------------------------- |
| `INLINE`  | The widget mounts straight onto the saved view.                                   |
| `OVERLAY` | The modal **opens by itself**, and its state is removed from the URL once closed. |

You don't have to build those URLs by hand: the widget writes the parameter on its own as the visitor navigates, and your page only has to keep it if you link to it or share it.

<Note>
  Screens that identify a booking or its guest are **never written to your
  page's URL**, so they are not saved and cannot be linked to. A page URL
  travels — into your analytics, into a shared link, into a browser history —
  and the guest's data has no business travelling with it.
</Note>

***

## Forced attribution

Some installations do not want the visitor's URL to decide the attribution, because the page itself **is** the channel. A booking portal that partners with the business is the usual case: the business wants to measure how many reservations that portal sends, and every booking made there belongs to it whatever brought the visitor to the portal in the first place.

Declare those values as `utmTags` and every booking and every event from that installation carries them. It is a **forced attribution**: what you declare wins over anything the visitor's URL brings.

```js theme={null}
window
  .Restoo("best-burger")
  .create({ utmTags: { utmSource: "partner_portal", utmMedium: "referral" } })
  .mount("restoo-widget");
```

What you declare is **immutable** and **complete**:

* **Immutable** — UTM parameters arriving in the URL cannot overwrite it, and neither can values remembered from an earlier visit through [Restoo Attribution Transfer](/widget/attribution-transfer).
* **Complete** — the declared set replaces the attribution as a whole: a field you leave out is never taken from the URL, so two different touches don't end up mixed into one record. Declaring only `utmSource` gives every booking that source and no campaign, with the medium falling back to [the default](/widget/attribution#default-attribution).

Anything that is not one of the five fields above, or that carries an empty value, is ignored — with a warning in the browser console naming what was dropped.

<Note>
  A forced attribution needs **no consent**. These values are part of your
  installation rather than information read from the visitor's device, so nothing
  is stored on it and nothing has to be asked. They are never written to
  `localStorage` either: a later visit through a different entry point is
  attributed on its own.
</Note>

***

## Technical notes

<AccordionGroup>
  <Accordion title="What Restoo.js does on your page">
    The snippet from the Quickstart loads **Restoo.js** (`restoo-widget`) from your
    account's Restoo subdomain. Restoo.js:

    1. Adds the `window.Restoo()` function to your page, then dispatches a `restoo:loaded` event on `window` to signal that it is ready to use.
    2. Creates an `<iframe>` pointing to your Restoo subdomain and renders the booking app inside it — on `.mount()`, or on the visitor's first click when you use `.bindTriggers()`.
    3. Keeps the parent page and the widget in sync automatically — resizing the `INLINE` embed to fit its content, forwarding [Restoo Widget Events](/widget/events) to `window`, and syncing browser back/forward navigation.
  </Accordion>

  <Accordion title="How Restoo.js loads">
    Restoo.js must be loaded with `type="module"`. Because module scripts are deferred,
    always initialize your widget from inside a `restoo:loaded` listener rather than immediately
    after the `<script>` tag — otherwise `window.Restoo` may not exist yet.
  </Accordion>

  <Accordion title="Auto-scroll on navigation (inline widget)">
    When a visitor navigates between internal widget pages (e.g. calendar → form → confirmation),
    an `INLINE` widget asks the parent page to scroll its container into view — but only if the top
    of the container isn't currently visible, so it never scrolls unnecessarily. The initial page
    load never triggers this.

    If your site has a sticky/fixed header that would cover the top of the widget, set
    `scroll-margin-top` on your container to match its height:

    ```css theme={null}
    #restoo-widget {
      scroll-margin-top: 80px; /* height of your fixed header */
    }
    ```
  </Accordion>

  <Accordion title="Modal presentation (mobile vs. desktop)">
    On mobile, the modal — the `OVERLAY` value — takes the full screen. On desktop, it's a
    centered floating panel over a dark backdrop. This is automatic; the only part
    you can customize is the panel's own colors, via `colorWidgetBackground` /
    `colorWidgetForeground` — see [Appearance](/widget/appearance).
  </Accordion>

  <Accordion title="Multiple widgets on one page">
    You can mount as many `OVERLAY`/`INLINE` widgets as you need on the same page
    — give each one a distinct `widgetId` if they share the same `account`.
    Deep-linking state (`?restoo_widgets=`) and browser back/forward navigation
    are tracked independently per widget.
  </Accordion>

  <Accordion title="Content Security Policy (CSP)">
    A Content Security Policy is a rule your server sends telling the browser which
    outside domains a page is allowed to use. **Most sites don't have one** — if
    you're not sure, ask whoever maintains your site. If yours does, it needs to
    allow three things for the widget, added to the directives you already have:

    | Directive     | Add                                               | What it's for                                                        |
    | ------------- | ------------------------------------------------- | -------------------------------------------------------------------- |
    | `frame-src`   | `https://*.myrestoo.net`                          | The widget itself, which loads from your Restoo subdomain.           |
    | `script-src`  | `https://*.myrestoo.net https://cdn.jsdelivr.net` | Restoo.js, and the library that resizes an `INLINE` widget.          |
    | `connect-src` | `https://*.myrestoo.net`                          | Lets Restoo.js read the settings configured for your Restoo account. |

    <Warning>
      **`connect-src` fails quietly, so it's worth double-checking.** Without it the
      widget still loads and still takes bookings, but it can't read your account
      settings: your brand colors and fonts fall back to the defaults, and the
      analytics destinations configured for your account never activate. No error
      appears anywhere — the widget simply doesn't look or report like it should.
    </Warning>

    <Note>
      An `INLINE` widget also needs **inline scripts** allowed — a nonce, a hash, or
      `'unsafe-inline'` — because the resizer is loaded through a small inline
      `<script>` tag. Allowing the `cdn.jsdelivr.net` domain on its own is not
      enough. Without it the widget works, but stops adjusting its height to fit its
      content. `OVERLAY` widgets don't resize, so they're unaffected.
    </Note>
  </Accordion>
</AccordionGroup>

***

## Hosted on Restoo

The widget hosted on Restoo has these configuration options.

### In your Restoo account

Saved once, and they apply to every one of your links:

* **Appearance** — your colors, your fonts, your style.
* **[The platforms you measure on](/widget/integrations)** — GA4, Google Ads, Meta Pixel, TikTok Pixel, and OpenAI Pixel.

Any change applies from the next page load, with nothing to deploy.

Here Restoo handles measurement end to end: **it installs those platforms' tags and asks for consent with its own cookie notice.** There is nothing for you to add. [Restoo Connect](/widget/connect#before-you-start) explains how it works.

### In the link

The language, the view it opens on, and the campaign the booking is credited to:

| Parameter                         | What it does                                                                                                                   |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `?lang=`                          | Sets the language. Without it, the visitor's browser language is detected. Example: `?lang=en`.                                |
| `?view=`                          | Opens the widget on a specific view, taking [the same values as the `view` option](#param-view). Example: `?view=experiences`. |
| `?utm_source=`, `?utm_medium=`, … | The five usual UTM parameters, as they are: see [Campaign attribution](/widget/attribution#hosted-on-restoo).                  |

```
https://best-burger.myrestoo.net/?lang=en&view=store
```

That way you can keep several links at once: one in English for your international guests, another that opens the gift card store directly.

<Note>
  **What doesn't exist with the widget hosted by Restoo.** There is no installation declaring
  anything, so there is no per-installation `appearance` or `connect`, no
  [`cmp`](#param-cmp) and no [`setConsent()`](#setconsent-signals) — the cookie
  notice is ours — no [`utmTags`](#forced-attribution), and no `bindTriggers()`. And since the widget
  doesn't live inside your site, there is nothing to adjust about CSP, autoscroll,
  or height.
</Note>

***

## Next steps

<CardGroup cols={2}>
  <Card title="Appearance" icon="palette" href="/widget/appearance">
    Match the widget to your brand: colors, fonts, and corner radii.
  </Card>

  <Card title="Restoo Connect" icon="chart-line" href="/widget/connect">
    Send booking events to GA4, Google Ads, Meta, TikTok, ChatGPT Ads, and GTM automatically.
  </Card>
</CardGroup>
