Embedding the widget
IT / TechnicalMarketingA single script tag turns any element on your site into a Preb booking widget.
You can embed it inline (the calendar renders right in the page) or as a
popup (a button opens it in a modal). There is no config object and no build
step — everything is controlled with data-* attributes.
Generate it for you
You don't have to hand-write any of this. Open an event type, go to the Embed tab, pick a mode, and copy the ready-made snippet. This page explains what that snippet does so you can customise it.
Inline embed
Add the loader script once, then give any element a data-preb-link attribute.
Preb finds it on page load and mounts the widget inside it.
<div
data-preb-link="acme/intro-call"
style="min-height:600px;border-radius:8px;overflow:hidden"
></div>
<script src="https://app.preb.co/embed.js" async></script>
The value of data-preb-link is your booking link. It has two valid shapes:
- Personal:
username/event-slug(e.g.acme/intro-call) - Team:
team/team-slug/event-slug
Only lowercase letters, numbers, hyphens, and underscores are allowed in a link.
An invalid link is ignored (with a [Preb] warning in the browser console)
rather than rendered, so a typo can never inject anything into your page.
If the container has no height (or is shorter than 100px), Preb gives it a 600px starting height and then resizes it to fit — see Auto-resize.
Pointing at a different host
By default the widget loads from https://app.preb.co. If you run Preb on a
custom domain, add data-preb-host="https://your-domain.com" to the same
element. Only http/https origins are accepted; anything else falls back to
the default.
Popup embed
For a "Book a meeting" button that opens the widget in a centered modal, use
data-preb-popup instead. It works on any element — usually a button.
<button data-preb-popup="acme/intro-call">Book a meeting</button>
<script src="https://app.preb.co/embed.js" async></script>
Use popup mode on Wix
Wix renders embedded HTML in a foreign-origin frame, which breaks the inline widget's automatic resizing. On Wix, use popup mode — it renders in its own modal and isn't affected.
Floating button
For a button that stays in the bottom corner of every page, use
data-preb-floating. It opens the same modal as the popup embed.
<div
data-preb-floating="acme/intro-call"
data-preb-text="Book a call"
data-preb-position="bottom-right"
></div>
<script src="https://app.preb.co/embed.js" async></script>
data-preb-text— the caption (default "Termin buchen").data-preb-position—bottom-right(default) orbottom-left.data-preb-color/data-preb-text-color— button background and text colour; any CSS colour value. Defaults are a near-black pill with white text.data-preb-prefillanddata-preb-consentwork exactly as on the other embeds.
One floating button per page: further elements are ignored (marked
data-preb-mounted="duplicate"). Add the snippet to your site's footer to show
it everywhere.
Loading state
The script paints a skeleton of the widget the instant it runs — rounded card,
header, summary and form panels — and keeps it until the real widget reports
its first height, then crossfades. Visitors never see an empty box, and the
container never snaps to size. The skeleton's card colour comes from
data-preb-shell (a CSS colour; default #111111), which the Embed tab
adds automatically when your widget uses a light or custom shell. Since it is
baked into the snippet, copy the code again after changing the widget colour.
Site builders that wrap embeds in a frame
Some builders — Framer's Embed element in particular — run custom HTML inside
an iframe of their own. Preb detects that automatically: as long as the wrapper
is same-origin with your page (Framer's is), the widget still reads campaign
tags and click IDs from your real page URL, opens its popup over the whole page,
exposes window.Preb to your page's scripts, and grows the wrapper frame so
nothing is clipped. Just paste the complete snippet into the embed element — no
head code needed. Wix's wrapper is cross-origin, which nothing can reach through;
that's why Wix gets the popup recommendation above.
The embed snippet
The script can be included once per page; a guard prevents it from initialising twice if it appears more than once. Three snippet styles are available from the Embed tab:
| Style | What you get | Auto-resize | Conversion event |
|---|---|---|---|
| Inline | Widget rendered in a data-preb-link element | ✅ | ✅ |
| Popup | Button that opens the widget in a modal | ✅ | ✅ |
| Floating button | Fixed corner button that opens the modal | ✅ | ✅ |
| Plain iframe | A raw <iframe> you paste yourself | ❌ | ❌ |
The plain-iframe option is for environments where you can't load the script at all:
<iframe
src="https://app.preb.co/acme/intro-call?embed=true"
width="100%"
height="700"
frameborder="0"
style="border-radius:8px"
></iframe>
Plain iframe has trade-offs
A raw iframe does not auto-resize, does not fire the
bookingSuccessful event, and does not forward campaign tags or ad click
IDs (gclid, fbclid, utm_*) from your page — Google and Meta can't credit
a booking to an ad. You must set a fixed height (the snippet uses 700).
Prefer the inline or popup script unless your platform blocks third-party
scripts.
Auto-resize
With the inline or popup script, the widget measures its own content and tells the parent page how tall it should be, so there are never inner scrollbars or clipped content. It does this by posting a message to the parent window:
{ preb: true, action: "resize", height: 812 }
embed.js listens for this message only from iframes it created and on
allowed origins, then sets the container's height. You don't need to do anything
— it's automatic. This is also why the plain-iframe option can't resize: there's
no script on the page to receive the message.
Prefilling
You can pre-populate the first-step fields (name, email, phone) so a known
visitor doesn't have to retype them. Add a data-preb-prefill attribute with
semicolon-separated key=value pairs:
<div
data-preb-link="acme/intro-call"
data-preb-prefill="name=Jane Doe;email=jane@acme.com;phone=+49123456789"
></div>
Only name, email, and phone are honoured (other keys are dropped), and
values are length-capped (name 120, email 254, phone 40 characters). When you
toggle Pre-fill from the page URL in the Embed tab, the snippet uses
{{name}} / {{email}} / {{phone}} placeholders for you to replace with your
site's template variables.
The hosted booking page accepts the same values as repeatable query parameters, which is what the widget passes through to the iframe:
https://app.preb.co/acme/intro-call?prefill=name=Jane&prefill=email=jane@acme.com
Verify the install
Once the snippet is live, load the page once. Within seconds the event type's
Embed tab shows "Seen on your website" with the site and the embed mode —
no developer tools needed. The script sends one small beacon per page load
(/api/public/embed/ping) carrying the page origin, the booking link, the
embed mode and the script build. It contains no visitor data.
Which version is my page running?
The script is served from one unversioned URL so every page keeps receiving fixes, and browsers re-check it every five minutes. To see the build a page is running, open the browser console on that page and type:
Preb.version // e.g. "0.1.0+816e278"
Consent
The embedded widget never shows Preb's cookie banner — your page owns consent.
Hand the visitor's decision to the widget with Preb.consent('granted') from
your own banner, or declare it on the element with
data-preb-consent="granted" when your site needs no banner. Without either,
the widget runs with consent denied (no pixels, no funnel events). Details and
the exact behaviour: Tracking inside embeds.
Styling notes
- Theme. The widget renders its own card on a transparent background, so it blends into your page. There are no colour or theme attributes — sizing is automatic and styling is handled inside the widget.
- Attribution is forwarded automatically. When the inline or popup script
mounts, it copies known ad/marketing parameters from the parent page's URL
onto the booking iframe:
utm_source,utm_medium,utm_campaign,utm_content,utm_term,fbclid,gclid,ttclid, andli_fat_id. All other parameters are stripped. (If you instead link to the hosted page from a plain button, copy the Embed tab's link-forwarding snippet.) - React to a successful booking. The script exposes a tiny API so you can fire your own analytics or thank-you logic when a booking completes:
<script>
window.Preb = window.Preb || {
_q: [],
on: function (a, b) { this._q.push([a, b]); },
};
window.Preb.on("bookingSuccessful", function (data) {
console.log("Booked!", data.uid, data.startTime);
});
</script>
Define this before embed.js loads — the stub queue above captures early
calls and replays them once the script is ready. Besides bookingSuccessful
you can subscribe to the four preb_* funnel events — see
Tracking inside embeds. The
bookingSuccessful payload is:
{
uid: string; // the booking's unique id
title: string;
startTime: string; // UTC ISO-8601
endTime: string; // UTC ISO-8601
location: { kind: string; value?: string; conferencingLink?: string };
eventTypeId: string;
eventTypeSlug: string;
eventName: string;
}
This event only fires with the inline or popup script (it rides the same parent/iframe message channel as auto-resize).
See also: Public API reference for the server-side endpoints, and Data & privacy for how booker data is handled.