Quickstart

By the end of this page a guest can open /book on your Next.js site, pick a slot, and book a meeting that shows up under Meetings in your clndr.pro dashboard. You need a clndr.pro account and a Next.js 14+ app using the App Router.

  1. Set your hours

    In the dashboard, open Availability and set the hours you take meetings. Slots only exist inside these hours, so a page with no availability shows no times at all.

    Connect Google Calendar when the dashboard asks. clndr.pro then keeps your existing events off the slot list and can add Meet links to bookings. It works without Google too: bookings still land in Meetings.

  2. Create a booking page

    Open Booking pages, choose New booking page, and note its Link, the slug at the end of its URL. A page at www.clndr.pro/ada/intro-call has the slug intro-call.

    Leave Confirmation on Instant for now, so test bookings confirm immediately. Needs approval is covered in How booking works.

  3. Create a publishable key

    Open API keys, choose Create API Key, pick Publishable key and give it a name like "Marketing site". Copy the key: it starts with clndr_pk_ and is shown once.

    A publishable key is meant to sit in browser code. It can read your booking pages and slots and create bookings, nothing else. API keys explains the two key types.

  4. Install the React package

    npm install @clndr-pro/react

    Then put the key in .env.local:

    .env.local
    NEXT_PUBLIC_CLNDR_PUBLISHABLE_KEY=clndr_pk_...

    Use @clndr-pro/react 0.2.1 or later. Earlier versions call https://clndr.pro, which redirects to www and loses the key on the way, so every request fails with 401 Missing API key. If you're stuck on an older version, pass baseUrl="https://www.clndr.pro" to ClndrProvider.

  5. Add the provider

    ClndrProvider holds the API client every component and hook uses. It's a client component, so give it its own file instead of turning your root layout into one:

    app/providers.tsx
    'use client';
     
    import { ClndrProvider } from '@clndr-pro/react';
     
    export function Providers({ children }: { children: React.ReactNode }) {
      return (
        <ClndrProvider publishableKey={process.env.NEXT_PUBLIC_CLNDR_PUBLISHABLE_KEY!}>
          {children}
        </ClndrProvider>
      );
    }
    app/layout.tsx
    import { Providers } from './providers';
     
    export default function RootLayout({ children }: { children: React.ReactNode }) {
      return (
        <html lang="en">
          <body>
            <Providers>{children}</Providers>
          </body>
        </html>
      );
    }
  6. Render the booking form

    app/book/page.tsx
    import { BookingWidget } from './booking-widget';
     
    export const metadata = { title: 'Book a call' };
     
    export default function BookPage() {
      return (
        <main className="mx-auto max-w-xl px-4 py-12">
          <BookingWidget />
        </main>
      );
    }
    app/book/booking-widget.tsx
    'use client';
     
    import { BookingInline } from '@clndr-pro/react';
     
    export function BookingWidget() {
      return (
        <BookingInline
          slug="intro-call"
          onBooked={(bookingId) => console.log('booked', bookingId)}
        />
      );
    }

    Replace intro-call with your slug. The page itself stays a Server Component; only the widget runs in the browser.

  7. Book a test meeting

    Run npm run dev, open localhost:3000/book, pick a date and a time, and confirm with your own email address. You'll get the confirmation email, and the meeting appears under Meetings.

Make it look like your site

The form ships with plain, unstyled markup. Hand it your own Button, Input and Label and it renders with them:

app/book/booking-widget.tsx
'use client';
 
import { BookingInline } from '@clndr-pro/react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
 
export function BookingWidget() {
  return (
    <BookingInline
      slug="intro-call"
      components={{ Button, Input, Label }}
      classNames={{
        root: 'space-y-6',
        heading: 'text-2xl font-semibold',
        muted: 'text-sm text-muted-foreground',
        slotGrid: 'mt-4 grid grid-cols-3 gap-2',
      }}
    />
  );
}

Styling and slots lists every slot and class name. To design the whole flow yourself (a calendar, grouped time slots, your own form), use the hooks instead: the shadcn booking widget tutorial builds one.

Next