Guide · Integrations
Build a custom booking page on Microsoft Bookings
Last updated 12 min read15 sources
You can put your own booking interface in front of Microsoft Bookings in three ways: embed the page Microsoft hosts in an iframe, call Microsoft Graph with application permissions and admin consent, or call the undocumented anonymous API that the public booking page itself uses. Only the last two give you a real custom front end, and both need a server, your own slot logic and your own checks before a booking is created. This guide is for developers who have seen the Stack Overflow questions about embedding Bookings in React and getting available slots, and want to know what a custom front end involves before they start. A working build takes days, most of it spent on time slots.
Microsoft offers no JavaScript SDK or event API for the booking page. A 2023 Q&A question asking how a React app could tell that a booking was made inside the iframe got only a redirect to another forum.
Before you start#
- A published shared booking page with at least one service. Use a test page with one staff member whose Outlook calendar you can edit. "Bookings with me" personal pages aren't covered by Graph and behave differently on the public page (see Shared Bookings vs Bookings with me).
- A server or edge function you control. Both API routes need one.
- For the Graph route: an app registration in Microsoft Entra ID and an admin who can grant tenant-wide admin consent.
- A way to record fixtures (captured responses and the slots the real page shows) so you can test against them.
Pick a route#
| Iframe | Microsoft Graph (application permissions) | Public page API (undocumented) | |
|---|---|---|---|
| Your own UI and styling | No | Yes | Yes |
| Your own events and analytics | No, the iframe exposes nothing | Yes | Yes |
| Approval needed | None | Admin consent, tenant-wide | None |
| Server needed | No | Yes (token must stay secret) | Yes (no CORS) |
| Documented and supported | Yes | Yes | No |
| "Bookings with me" pages | Link only, no embed code | Not supported | Works, with a different slot algorithm |
| Who enforces hours and lead time | Microsoft page | You (Microsoft says apps must follow the business rules) | You |
| Campaign tracking (RefID) | Works in the page URL | No documented field | Stored on the appointment |
| Risk of breaking | Low | Low | High: can change with any Microsoft deploy |
Choose the iframe if you only need the page on your site. The React, Next.js and Astro guide and the embed answer cover sizing and why the parent page can't see bookings.
Choose Graph if you own the tenant (or the customer will grant consent) and need a supported contract. Facts to plan around, as of September 2026:
getStaffAvailabilitysupports application permissions only. Delegated sign-in isn't supported. The least-privileged permission isBookings.Read.All. Creating appointments needsBookingsAppointment.ReadWrite.All.- Permissions are tenant-wide. A Q&A answer confirms there's no way to scope them to one booking page, so your app can read every booking page in the tenant. More in Graph permissions for Bookings.
- The Graph article on business rules says apps that create or update appointments with application permissions "must adhere to business rules": business hours, lead time, maximum advance, service duration and staff selection. In other words, you enforce them.
- Graph allows four concurrent requests per app and booking mailbox.
- Two developer questions are still unanswered on Microsoft Q&A: one reports that
getStaffAvailabilityignored a staff member's other meetings (2022), the other that a service's custom availability can't be read back (2024). We haven't tested Graph against a real tenant, so check both on yours. - The Graph overview still says the business needs a Microsoft 365 Business Premium subscription. The Bookings FAQ lists many more plans. The FAQ is the current list.
Choose the public page API if you can't get admin consent, need personal pages, or want exactly what the public page shows. It's the set of anonymous calls the booking page Microsoft hosts makes to load services, staff and availability and to create the booking. Microsoft doesn't document or support it. We describe its behaviour below, not its requests, because anything you copy today can change without notice.
What we saw when testing (September 2026)#
We captured the public booking page and the calls it makes in 13 live captures across several time zones (913 days, 7,924 slots). We rebuilt its slot logic until it matched every slot, and made, rescheduled and cancelled a handful of test bookings. Every test booking was cancelled.
- No CORS. The public API returns no CORS headers, so a script on your site can't call it, or read the embedded page, from the browser. We found nothing the iframe sends to the parent page.
- Availability arrives as blocks, not slots. The Microsoft server folds in working hours, the staff member's Outlook busy time, existing bookings and service windows. The page then turns free blocks into start times itself.
- Starts aren't aligned to the clock on shared pages. A block that frees up at 6:40 yields 6:40, 7:10 and so on. The page walks each block in steps of the largest of 60, 30, 15, 10, 5, 2 or 1 minutes that divides the time increment (45 gives 15, 20 gives 10). The pre-buffer shifts the first start. It then keeps a slot only if it's at least one time increment after the last kept slot, which is why lists like 5:00, 5:30, 6:30 appear when 6:00 to 6:30 was busy.
- Lead time. On shared pages, the earliest slot is now plus the minimum lead time plus about 3 minutes (the page adds 1 minute, then drops anything within 2 more). When a policy value is missing, the page code falls back to a 1-day lead time, 365 days in advance and a 30-minute increment.
- Personal pages use a different algorithm. Starts sit on a grid of the increment from local midnight, shifted by the pre-buffer, and blocks aren't merged. A slot must start more than lead time plus 15 minutes from now. The fallbacks are a 1-hour lead, 90 days and 30 minutes.
- The answer depends on the range you ask for. The same hour came back as out of office in one request and available in another, so a day near a month boundary can differ depending on which month the visitor navigated from.
- "Anyone" is picked in the browser. The page picks one of the free staff at random and sends the others as candidates. We don't know whether the server reassigns afterwards.
- Server-side checks are thin. Called directly, the service accepted a booking inside the lead-time window (even one in the past) and one over a busy Outlook block. It rejected a clash with another Bookings appointment. The booking page enforces the rest in the browser.
- Latency. Reads took roughly 0.3 to 1.3 seconds from our server. Creating a booking took about 5.5 seconds and a reschedule about 3 seconds.
- Email code. On pages that require it, the first submit fails and Microsoft emails a 6-digit code to the customer at that moment. The booking is created only when the form is resubmitted with the code. "Resend" is just another submit, and a wrong code is rejected. Our shared test page with default settings never asked for a code. The personal page did.
- Manage links. The manage page linked from the confirmation offered Reschedule and Cancel without sign-in, even for a service set so customers can't manage their booking. It also showed the Teams link.
- Form fields. The page has only two question types, free text and single choice. Notes are capped at 3,000 characters. A tenant admin setting can hide phone, address, notes and custom questions on every page. Personal-page bookings carry only name, email, notes and time zone.
Build it step by step#
These steps apply to both API routes. Where they differ, it says so.
Step 1: Put a server in the middle#
Your browser code calls your endpoint, and your endpoint calls Microsoft. Keep Graph tokens on the server. Cache slow-changing reads (business, services, staff, questions) for a few minutes, but not availability. Rate-limit the booking endpoint per IP, because it creates real appointments.
Step 2: Load the configuration and build the form#
- Skip services hidden from customers.
- Render only the two question types, text and single choice, and mark required ones per service.
- Respect each field's requirement level, and expect phone, address, notes or questions to be missing entirely when the tenant restricts them.
- Cap notes at 3,000 characters and validate email format before submitting.
Step 3: Turn availability into slots#
This is where most DIY front ends go wrong. Reproduce the page's rules from the testing section: block walking with the step rule, thinning to the increment, lead time plus the extra minutes, the fallback policy values, and the separate personal-page algorithm. Request the same date ranges the page requests (the visible month plus a day either side) and keep results per day, or month boundaries will disagree with the Microsoft page. Graph returns availability items too, so the same work applies. For the short version, see getting available time slots from the API. If you think a 6:40 start is a bug, read why Bookings shows odd start times first.
Step 4: Re-check the slot, then create#
Immediately before creating, fetch availability for that one slot again and confirm it's still free and outside the lead-time window. Check hours, lead time and required fields yourself. The public service won't reliably stop a past or double booking, and Microsoft says Graph apps must follow the business rules themselves.
Never retry a create automatically. A timeout doesn't mean it failed, and a retry can book the customer twice. Show "checking your booking" and look the appointment up instead.
Step 5: Design the UI for a 5-second wait#
With creation taking about 5.5 seconds, disable the button on first click, show progress text, and don't navigate away until you have a result. Put the thank-you redirect and your conversion event after the confirmed response, not on click.
Step 6: Handle the email-code step#
If the page requires verification, expect the first submit to fail with a verification-required error. Show a 6-digit code box, tell the customer to check their inbox, and resubmit the same booking with the code. Offer "Send a new code" as another plain submit. The page setting is visible in Graph beta as enforceOneTimePassword. You can't switch it off from your front end, and on personal pages nobody can. See email verification on Bookings with me.
Step 7: Treat manage links as bearer secrets#
Anyone holding a manage link can reschedule or cancel the appointment and see its Teams link. Never log it, put it in a URL you send to analytics or ad tags, or include it in webhook payloads to third parties. Show it only to the customer.
Step 8: Keep attribution on the booking#
On the public page, a RefID value is stored on the appointment as tracking data. In our tests it survived a reschedule and was missing from the calendar event the owner receives, and Microsoft documents it in the export's Tracking data column. Graph has no documented field for it (a 2023 Q&A question asking for tracking data through Graph is unanswered), so store the source yourself, keyed by appointment ID. See RefID tracking.
Step 9: Pin the behaviour#
The undocumented API can change with any deploy.
- Record real responses and the slots the real page showed, and replay them in CI.
- Run a daily read-only live check that alerts when a response shape or a slot list changes.
- Re-capture fixtures whenever the Microsoft page looks different.
- Keep a Graph implementation behind the same interface for tenants that will grant consent.
Test before launch#
- For three services and two time zones, your slot list matches the Microsoft page exactly, including the first day of next month.
- A busy event added to the staff member's Outlook calendar removes the slot after a refresh.
- A slot inside the lead time can't be booked, even by calling your own endpoint directly.
- Double-clicking Book creates one appointment.
- A page with the email code on completes a booking, and a wrong code shows a clear error.
- The appointment appears in the Bookings calendar and Outlook, and Microsoft sends its usual confirmation.
- No manage link appears in your logs, analytics hits or error reports.
Common problems#
- CORS error in the console. You're calling Microsoft from the browser. Route it through your server.
- Slots differ from the Microsoft page near a month boundary. You're requesting different date ranges than the page does.
- A booking landed in the past or on top of a meeting. Your re-check in step 4 is missing. The public service accepted both in our tests.
- Graph returns 403. Check you granted the least-privileged permission for each call. One Q&A report says
Bookings.Manage.Allalone returned 403 when updating a business. - Everything fails for one page. It may be restricted to your organization, so anonymous calls can't reach it. See why a page asks people to sign in.
- You need a "booking created" event. Neither route offers webhooks. See Bookings webhooks.
Doing this with BookingsXP#
The BookingsXP widget is this front end, already built: a server layer that reproduces the page's slot rules and is tested against the captures above, a re-check of the slot before every booking, and a code-entry step for pages that require it. It needs no admin consent or Graph permissions, because it works from your public booking page link, and appointments still land in Bookings and Outlook. The script embed works in any framework today:
<script src="https://bookingsxp.com/embed/v1.js" async></script>
<bookingsxp-widget booking-url="https://outlook.office.com/book/YourBusiness@contoso.com/"></bookingsxp-widget>The @bookingsxp/react, /vue, /svelte and /astro packages are written but their npm release is pending, so use the script tag for now. See embed features, the developer docs, or try it with your own page. Support for "Bookings with me" pages is best-effort. BookingsXP is independent and not affiliated with or endorsed by Microsoft.
Questions people also ask
Sources
- stackoverflow.com (opens in a new tab) · stackoverflow.com
- stackoverflow.com (opens in a new tab) · stackoverflow.com
- Microsoft Learn: Bookings how to check in the frontend if an appoin (opens in a new tab) · learn.microsoft.com
- Microsoft Learn: Microsoft bookings api bookingsbusiness getstaffav (opens in a new tab) · learn.microsoft.com
- Microsoft Learn: Booking api get service availability time and date (opens in a new tab) · learn.microsoft.com
- Microsoft Learn: Granular control for microsoft bookings graph api (opens in a new tab) · learn.microsoft.com
- Microsoft Learn: Export tracking data with graph api (opens in a new tab) · learn.microsoft.com
- Microsoft Learn: Booking api overview (opens in a new tab) · learn.microsoft.com
- Microsoft Learn: Bookingbusiness getstaffavailability (opens in a new tab) · learn.microsoft.com
- Microsoft Learn: Bookingbusiness post appointments (opens in a new tab) · learn.microsoft.com
- Microsoft Learn: Bookingsbusiness business rules (opens in a new tab) · learn.microsoft.com
- Microsoft Learn: Throttling limits (opens in a new tab) · learn.microsoft.com
- Microsoft Learn (opens in a new tab) · learn.microsoft.com
- Microsoft Learn: Bookings faq (opens in a new tab) · learn.microsoft.com
- Microsoft Learn: Customize booking page (opens in a new tab) · learn.microsoft.com