How to embed the scheduler with the Forms SDK redirect flow
Last updated: May 18, 2026
This guide walks you through embedding the Default scheduler directly on your own thank-you page after a form submission, instead of letting the SDK overlay it as a modal. You'll end up with the scheduler inline on your page — calendar, confirmation, reschedule, and cancel views all handled — using only a workflow setting and a single script on your thank-you page.
Use this pattern when you want the scheduler to feel like a native part of your thank-you page (e.g. alongside next-step copy, value props, or other content) rather than as a full-screen modal overlay.
How it works
A lead submits your form on the landing page. The Default Forms SDK posts the submission to Default and gets back a scheduler URL.
Because you've configured a thank-you page redirect on the workflow's scheduler node, the SDK redirects the lead's browser to that page with the scheduler URL attached as a query parameter named
default_post_redirect_scheduler_url.A small script on your thank-you page reads that query parameter and mounts the scheduler in an iframe inside a slot you place anywhere on the page.
The script listens for events from the scheduler (loaded, displayed, meeting booked, closed) and adjusts the iframe so the post-booking, reschedule, and cancel views all fit cleanly.
Step 1: Configure the workflow scheduler node
Open your workflow and click on the scheduler node.
Find the field labeled Display scheduler at this URL upon redirect.
Set it to the full URL of the thank-you page where you want the scheduler embedded (e.g.
https://www.example.com/thank-you).Save and publish the workflow.
Once this is set, Default will redirect every successful submission to that URL and append the scheduler URL as a default_post_redirect_scheduler_url query parameter — no changes needed to your landing page script.
Tip: You don't need to change anything in your landing page's SDK code. Keep autoSchedulerDisplay and autoRedirect on their defaults (or simply omit them). The redirect is driven entirely by the workflow setting.
Step 2: Add the embed script to your thank-you page
Paste the following snippet into your thank-you page. It includes the slot, the scrollbar-hiding CSS, and the script that handles every state of the scheduler.
<!-- 1. The slot — place this anywhere on your thank-you page where
you want the scheduler to appear -->
<div id="default-scheduler-slot"></div>
<!-- 2. Visually hide the iframe's scrollbar but keep scrolling functional.
This is the universal fallback for any view we can't size precisely. -->
<style>
#default-scheduler-slot iframe { scrollbar-width: none; }
#default-scheduler-slot iframe::-webkit-scrollbar { display: none; }
</style>
<!-- 3. The mount + state-machine script -->
<script>
(function () {
// Heights tuned per known state. Adjust these here if your
// scheduler has unusually long event titles, descriptions, or
// post-booking fields.
var HEIGHTS = {
BOOKING: 780, // calendar + timeslots view
CONFIRMED: 1100, // confirmation pane (and downstream reschedule/cancel)
FALLBACK: 1100 // used for any unrecognized state
};
function mountScheduler() {
var params = new URLSearchParams(window.location.search);
var rawUrl = params.get('default_post_redirect_scheduler_url');
if (!rawUrl) {
// Direct visit (no submission) — nothing to mount.
return;
}
var url;
try { url = new URL(rawUrl); }
catch (e) {
console.error('[Default] Invalid scheduler URL', rawUrl, e);
return;
}
// Inline layout (no dark backdrop), and suppress the in-iframe
// close button so the lead can't accidentally trigger the
// "Meeting not booked" workflow branch from the page.
url.searchParams.set('page_embed', 'true');
url.searchParams.set('hide_close', 'true');
var iframe = document.createElement('iframe');
iframe.src = url.toString();
iframe.style.width = '100%';
iframe.style.minHeight = HEIGHTS.BOOKING + 'px';
iframe.style.border = 'none';
iframe.allow = 'clipboard-write';
// Intentionally no scrolling="no" — the iframe must be able to
// scroll internally so reschedule/cancel/post-cancel views never
// get clipped. The CSS above hides the scrollbar chrome.
var slot = document.getElementById('default-scheduler-slot') || document.body;
slot.appendChild(iframe);
// Track the current state so listeners are idempotent and you
// can hook analytics/UI changes off it.
var state = 'BOOKING';
function setState(next) {
if (state === next) return;
state = next;
iframe.style.minHeight = (HEIGHTS[state] || HEIGHTS.FALLBACK) + 'px';
}
window.addEventListener('message', function (event) {
var data = event.data;
// Meeting was booked — grow to fit the confirmation pane.
// This also covers the reschedule and cancel views since they
// are only reachable from this state.
if (data && data.event === 'default.meeting_booked') {
setState('CONFIRMED');
// Hook for analytics, content swaps, etc.
// window.dataLayer && window.dataLayer.push({
// event: 'meeting_booked',
// payload: data.payload
// });
return;
}
// Close event fired from the iframe (timer expiry, etc.).
// The string format is "__default_scheduler_close__:re:<url-or-null>".
if (typeof data === 'string' && data.indexOf('__default_scheduler_close__') === 0) {
// Optional: hide the iframe, swap in success copy, redirect, etc.
// iframe.style.display = 'none';
return;
}
// Other events (default.scheduler_loaded, default.scheduler_displayed)
// are informational only — useful for analytics.
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', mountScheduler);
} else {
mountScheduler();
}
})();
</script>Save and publish your thank-you page. Submit a test form on your landing page and you should land on the thank-you page with the scheduler rendered inline.
Critical gotchas
Do not load the Default Forms SDK script on your thank-you page. The SDK has an auto-handler that detects the default_post_redirect_scheduler_url query parameter and immediately creates a full-screen overlay iframe — it will clobber your inline embed. The thank-you page only needs the snippet above. If you must load the SDK for unrelated reasons, rename the query parameter to something the SDK doesn't recognize.
The script and the slot must be in scope when the script runs. If you place the script in your CMS's "Additional <head> markup" field and the slot in a separate body module, the script may run before the slot exists. The snippet above uses DOMContentLoaded to handle that case safely. The simplest setup is to paste both the slot and the script into the same custom HTML module in your page body.
The scheduler URL in the query parameter does not include page_embed=true. The script above appends it explicitly. Without that flag, the scheduler renders as a modal-inside-an-iframe (dark backdrop, centered card) instead of an inline layout.
Tip — use hide_close=true to prevent accidental "Meeting not booked" triggers. The in-iframe close button and timer expiry both notify Default that the meeting was not booked, which routes the lead through your workflow's "Meeting not booked" branch. If you want a way for the lead to dismiss the scheduler from your thank-you page, build your own button on the page that simply hides the iframe — don't rely on the in-iframe close.
Note — the workflow's "Meeting not booked" timer runs server-side. Even if the lead never opens the thank-you page, or closes the tab, your workflow's "Meeting not booked" branch will still fire when the configured timeout elapses. To fully disable that branch, remove it from your workflow.
How the state machine works
The scheduler has several internal views — a booking calendar, a confirmation pane after the meeting is booked, a reschedule form, and a cancel form. Each can be a different height. Because the iframe is cross-origin, your page can't measure the scheduler's content directly. Instead, the script listens for the events the scheduler emits and presets the iframe height for each state. For any transition the scheduler doesn't broadcast (e.g. clicking Reschedule or Cancel from the confirmation pane), the iframe falls back to internal scrolling so the content is never clipped.
In practice, every post-booking flow (reschedule, cancel, post-cancel confirmation) is initiated from the confirmation pane — and the confirmation pane is already at the taller CONFIRMED height. So most leads will never see a scrollbar, and the few that do (small screens, unusually long content) will be able to scroll smoothly without any visible chrome.
postMessage event reference
The scheduler iframe emits the following events to the parent window. Listen for them with window.addEventListener('message', ...).
Event | Data shape | Fires when |
|---|---|---|
| String literal | Scheduler styles have finished loading inside the iframe. |
| Object with | Scheduler is visible to the lead with their info preloaded. |
| Object with | Lead successfully booked a meeting. |
| String, colon-delimited. Use | In-iframe close button clicked, timer expired, or booking finalized (when applicable). |
Tip — use these events for analytics. Listen for default.meeting_booked to push a conversion event into Google Tag Manager, Segment, or another analytics platform. The payload includes the attendee's email and the booked time, which you can pass through as event properties.
Useful iframe URL parameters
The script appends two parameters to the scheduler URL. You can add others to further customize the embed.
Parameter | Effect |
|---|---|
| Renders the scheduler full-width inside the iframe (no modal backdrop). Always include this for inline embeds. |
| Hides the in-iframe close button so the lead can't trigger a "not booked" event from inside the iframe. |
| Hides the countdown timer. The backend "Meeting not booked" timeout still runs. |
| Hides the left-side event details pane (title, description, duration, timezone). |
Troubleshooting
"Cannot read properties of null (reading 'appendChild')"
The script ran before the slot existed in the DOM. The snippet above is wrapped in DOMContentLoaded to handle this, but if you pasted the script in your CMS's <head> markup field and the slot in a body module, double-check the wrapper is in place. The simplest fix is to keep the slot and the script together in the same body-level HTML module.
Scheduler shows as a small modal inside the iframe with a dark backdrop
You're missing the page_embed=true URL parameter. The script above appends it automatically — verify the snippet wasn't modified.
Scheduler renders as a full-screen overlay instead of inline
The Default Forms SDK script is loaded on the thank-you page. Remove it — the thank-you page only needs the embed snippet, not the SDK. The SDK's auto-handler will hijack the query parameter and create its own full-screen iframe.
Confirmation view is cut off / I can't scroll down
Either your iframe has scrolling="no" set (remove it) or the CONFIRMED height in the script is too small for your setup. Increase HEIGHTS.CONFIRMED in the snippet above to 1200 or 1400. The hidden scrollbar will still let leads scroll if anything overflows.
Scheduler shows mobile layout even on desktop screens
The scheduler uses the iframe's own width to decide between desktop and mobile layouts, not the browser viewport. The iframe needs to be rendered at roughly 700px wide or more to show the side-by-side calendar / timeslots layout. Make sure the slot's container element gives the iframe enough width.
Related articles
Forms SDK — full SDK reference, including callbacks and manual scheduler control.