npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@timum/booking

v1.20.3

Published

## Introduction

Readme

timum booking

Introduction

timum BookingJs is a fully customizable appointment booking frontend app, intended to be integrated into every web page or web app or mobile app. It is ready to use out-of-the-box. And it provides the ability to customize it. This documentation guides you through all the customization capabilities.

Content

Features

  • Real time availability and booking
  • Booking requests for public appointments - approval required
  • Multiple participant appointments
  • Visitor legitimation - only invited visitors see the booking widget on public pages, listings, exposés
  • Visitor identification - via customer ID from CRM
  • Show/hide fully booked appointments option
  • Prebooking period option - prevents booking if appointment is too soon from now
  • Double booking prevention
  • Cancelling appointment by customer with proper authentication
  • Multiple instances on the same page - display multiple booking widgets without conflicts
  • Fully customizable look and feel with mui theming (available with professional plans). This includes font customization; see Advanced Customisation → Font loading for details.
  • Customizable wording across languages (availabe with professional plans)
  • Dynamic definition of input fields with localized labels and validation (available with professional plans)
    • Therefore, ability to request additional information from customers during booking process
  • Support for multiple languages (English, Italian, Spanish, French and German included) - add your own language
  • Callbacks for custom code execution and event response (e.g. booking created, appointments loaded, booking canceled)

How to Integrate

These are some minimal examples. BookingJs is highly customizable. An overview over all configuration options can be found in Advanced Customisation

In a Script Tag

Add the following code to your webpage:

<div id="bookingjs" style="margin: 32px"></div>
<script type="module">
  import * as timum from 'https://cdn.timum.de/bookingjs/1/booking.js';

  timum.init({ ref: 'booking-widget-demo-resource@timum' });
</script>

A working fiddle can be found here.

Alternatively, you can add ref to your url as a parameter. This allows you to omit ref in the javascript portion. Like this:

https://your.website.nic?ref=<enter resource reference here>

<div id="bookingjs" style="margin: 32px"></div>
<script type="module">
  import * as timum from 'https://cdn.timum.de/bookingjs/1/booking.js';

  timum.init(); //<- no need for the reference here
</script>

As ESM import

  1. Add timum booking to your project with yarn add @timum/booking or use npm with npm install @timum/booking
  2. Add <div id="bookingjs" style="margin: 32px"></div> where you want timum to be displayed (the margin is just a suggestion, not mandatory).
  3. Add the following code to one of your .js files:
import { init } from '@timum/booking';

init({ ref: 'booking-widget-demo-resource@timum' }); // <- the ref used here is just an example. Use a reference to one of your own resources.

A example project can be found here

As React component import

  1. Add timum booking to your project with yarn add @timum/booking or use npm with npm install @timum/booking
  2. Add the following code anywhere in your jsx:
import { TimumBooking } from '@timum/booking';

// other code

<TimumBooking
  appConfig={{
    ref: 'booking-widget-demo-resource@timum',
    // other options
  }}
/>;

// other code

Multiple Instances

TimumBooking fully supports displaying multiple booking widgets on the same page. Each instance is automatically assigned a unique iframe ID and maintains its own isolated configuration, preventing any conflicts between instances. This is particularly useful when you need to display booking options for different resources or with different configurations on the same page.

import { TimumBooking } from '@timum/booking';

// Display multiple booking widgets for different resources
<div>
  <h2>Resource A Bookings</h2>
  <TimumBooking
    appConfig={{
      ref: 'resource-a@timum',
      height: '500px',
    }}
  />

  <h2>Resource B Bookings</h2>
  <TimumBooking
    appConfig={{
      ref: 'resource-b@timum',
      height: '500px',
    }}
  />
</div>;

Each instance:

  • Gets a unique iframe ID (e.g., timum_booking_iframe_0, timum_booking_iframe_1)
  • Maintains its own configuration separately from other instances
  • Can have different props, themes, and configurations
  • Receives configuration updates and postMessages independently
  • Is properly cleaned up when unmounted, preventing memory leaks

Entity Referencing

As shown in How to Integrate, there are two ways in which to provide bookingjs with the necessary references to retrieve public appointments from the backend.

Additionally, there are three different kinds of references you can provide, each having their own implications concerning the delivered appointments or whether the booking frontend is shown at all.

Channel Reference

Resource channels are entities in Timum, each storing a configuration that changes the booking experience for customers. Each individual resource has four channels that users can distribute to their customers through a link. To use these channels in an embed scenario, provide a channel reference in the ref property (either in the URL or as part of the config). In this case, nothing else is required.

Resource Reference

A reference that refers to a plain resource, where the channel is unknown. The default channel (and its corresponding configuration) is used instead.

Resource Reference with ChannelKey

If you don't have a channel reference but still want to use a different channel than the default one, you can provide a combination of resource reference and channelKey to uniquely identify the channel (and resource) to be used.

The following table gives an overview of the different permutations and how bookingjs behaves when encountering each of them:

| url RESref OR CHref | timum.init RESref OR CHref | timum.init chKey | show OR hide | | :-----------------: | :------------------------: | :--------------: | :----------------------------: | | - | - | ignored | HIDE | | ResourceReference | ignored | YES | show widget for channelKey | | ResourceReference | ignored | - | show default Channel | | - | ResourceReference | - | show default Channel | | - | ResourceReference | YES | show widget for channelKey | | ChannelReference | ignored | ignored | show Channel | | - | ChannelReference | ignored | show Channel |

  • RESref: short for resource reference.
  • CHref: short for channel reference.

Example
  • First row: If neither a resource reference nor a channel reference is provided, not in the URL nor in the config object, any given channel key is ignored, and bookingjs is hidden.

  • Third row: If a resource reference is given in the URL and in the config object, and no channelKey is provided, the ref in the URL takes priority. Since it is a resource reference and no channel key is provided, the default channel is used.

  • Fifth row: If a resource reference and a channelKey are provided in the config object, and there is nothing in the URL, the config object is used. BookingJs gets displayed using the channel uniquely identified by the resource reference and the channelKey.

Customer pre-identification

BookingJs can identify customers automatically if configured correctly. This means they don't need to input their personal data prior to booking.

Identification via pDataId

Detection method: Is there a valid pDataId paired with a supported pDataPlatform (platform) in parent url matching an entity (contact, customer) in given CRM? General behaviour: If there is, then visitor is pre identified. If not: provide standard booking dialog. See appConfig for more information about pData.

Identification via Logged-in User

Even customers can register at timum and become registered and logged-in users. If the booking system identifies a logged-in user, the booking dialog does not provide personal data input fields but informs the visitor that they are identified.

Note that if the customer is logged in to timum and there is a pData config/url param as well then the auth cookie takes priority.


Advanced usage

timum booking has a lot of customisation options.

The init accepts the following arguements in the order listed here. The following table gives a rough overview over each.

| Arguments | Description | | --------- | -------------------------------------------------------------------------------------------------- | | appConfig | object, containing various options like callbacks, localiation, input fields etc. | | muiTheme | object, mui theme allowing you to change the look and feel of timum. Requires a professional plan. | | fcConfig | object, only used if you use full calendar as a booking frontend (settable in appConfig). |

muiTheme

timum uses mui components for all frontends. The muiTheme object allows global design changes. See here for a general overview. See here for theme related documentation.

When you open the file config.js in this example, you can find an elaborate custom configuration which includes a redesign of the standard timum theme.

Needs professional plan.

Fonts

You can set global or per-component fonts via the MUI theme (e.g., typography.fontFamily or component styleOverrides). BookingJS can also load the corresponding font files for you; see Font Loading for how to declare font resources in the theme and control the loading strategy. Requires a professional plan for custom theming.

fcConfig

FullCalendar can be set as one of the frontends in appConfig. See here for a full list of configuration options.

Note that since fullCalendar isn't mui based, it does not consider the muiTheme object.

There are also some options specific to bookingjs. In addition to the configuration options of fullcalendar the following options are also available (all of them are optional):

appConfig

This object's options directly affect timum's behaviour or allow you to react to it.

Font Loading

BookingJS supports two font-loading strategies, so you can either reuse an existing remote CSS (e.g., Google Fonts) or reference font files directly (no extra CSS round-trip). The widget detects your font declarations inside the MUI theme and loads fonts accordingly. This complements theming (muiTheme) and lets you apply custom fonts across the UI. Requires a professional plan for custom theming. See the theming entry points described under Advanced usage (init accepts appConfig, muiTheme, fcConfig) and the integration methods in How to Integrate (Script Tag / ESM / React) .

  • Where to declare fonts in the theme

    • Inline faces: add fontFace (single) or fontFaces (array) anywhere in your theme object (e.g., top-level, typography, or component defaultProps/styleOverrides).
    • Link CSS: add fontSource (string or string[]) anywhere in your theme. The widget will inject for each unique URL (loading duplicates is prevented).
  • Applying the font

    • Set typography.fontFamily to your desired stack, or override per component (muiTheme → components.*.styleOverrides.root.fontFamily). BookingJS will load the faces you declared and use them if referenced in the theme (subject to your professional plan).
Examples
  1. Inline variable font (preferred for performance)
// passed as the "muiTheme" argument to init
<div id="bookingjs" style="margin: 32px"></div>
<script type="module">
  import * as timum from 'https://cdn.timum.de/bookingjs/1/booking.js';

  timum.init(
    { ref: 'booking-widget-demo-resource@timum' }, // appConfig
    {
      // other theme props...

      typography: {
        fontFamily:
          '"My Font", Roboto, Helvetica, Arial',
        // BookingJS will load these files directly via @font-face rules
        fontFaces: [
          {
            family: 'My Font',
            style: 'normal',
            weight: '100 900',   // variable axis range or single value like '400'
            display: 'swap',
            src: [
              { local: 'Inter' },
              { url: '/fonts/inter-var.woff2', format: 'woff2' },
            ],
          },
          {
            family: 'My Font',
            style: 'italic',
            weight: '100 900',
            display: 'swap',
            src: [
              { local: 'Inter Italic' },
              { url: '/fonts/inter-italic-var.woff2', format: 'woff2' },
            ],
          },
        ],
      },

      // other theme props...
    }
  );
</script>
  1. Link-based CSS (reuse existing CSS bundles)
// passed as the "muiTheme" argument to init
<div id="bookingjs" style="margin: 32px"></div>
<script type="module">
  import * as timum from 'https://cdn.timum.de/bookingjs/1/booking.js';

  timum.init(
    { ref: 'booking-widget-demo-resource@timum' }, // appConfig
    {
      // other theme props...

      typography: {
        fontFamily:
          '"Roboto", "Helvetica", "Arial", sans-serif',
        // BookingJS will inject <link rel="stylesheet"> for these URLs
        // Note: this is just an example. We host our fonts on our own servers
        fontSource: [
            'https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap',
        ],
      },

      // other theme props...
    }
  );
</script>

Notes and best practices

  • Use WOFF2 where possible (smaller, widely supported).
  • Prefer variable fonts (weight: "100 900") instead of many static weights.
  • Use display: 'swap' to avoid FOIT.
  • If fonts are hosted on a different domain, ensure proper CORS headers so the browser can load them.
  • You can still use per-component overrides to mix families (e.g., Sora for Buttons only) by setting components.MuiButton.styleOverrides.root.fontFamily; just ensure the face is declared under fontFaces or provided via fontSource as shown above.

How to use callbacks

The callbacks object may contain any of the following functions.

Booking related
  • openedBookingPage
  • closedBookingPage,
  • createBookingStarted
  • createBookingSuccessful
  • createBookingFailed

All Booking related callbacks receive a single obejct as argument containing the following properties:

  • a timeslot looking like this:
       {
          start: luxon Datetime object. Internal state. "2023-01-27T09:05:00.000Z",
          end: luxon Datetime object. Internal state. "2023-01-27T09:35:00.000Z",
          timeslot_uuid: uuid of the booked timeslot. e.g. "82ec5220-9d55-11ed-8617-e4a7a0ca32e8",
          product_uuid: uuid of the booked product e.g. "92867f70-4836-11e5-bc04-021a52c25043",
          product_name: string. e.g. "Meeting",
          resource_name: string. e.g. "Booking Widget DEMO",
          capacity: number e.g. 1,
          capacity_left: number e.g. 1,
          kind: either "models.Bookable" or "models.LotAppointment",
          untouchedStart: ISO String e.g: "2023-01-27T09:05:00+01:00" as the server sent it,
          untouchedEnd: ISO String e.g: "2023-01-27T09:35:00+01:00" as the server sent it
          }
  • a data object looking like this:
 {
   firstName: 'string',
   lastName: 'string',
   email: 'string',
   agbs: 'bool'

   // plus whatever optional and/or custom fields you yourself have defined.
   // -> so the makeup of this object changes depending on your settings.
 }

Additionally, createBookingFailed and createBookingSuccessful also have a RTKQ response object in their respective argument. See here for a reference.

Cancelation Related
  • openedCancelPage
  • closedCancelPage
  • cancelationStarted
  • cancelationSuccessful
  • cancelationFailed

All cancelation related callbacks receive a single obejct as argument containing the same properties as described in Booking related

Additionally, cancelationSuccessful and cancelationFailed also have a RTKQ response object in their respecive argument. See here for a reference.

Dialog related

In addition to the dialog related callbacks already mentioned in Cancelation Related and Booking related, there are callbacks for all other dialogs as well.

  • openedProductSelection
  • openedResourceSelection
  • openedConfirmationPage
  • closedProductSelection
  • closedResourceSelection
  • closedConfirmationPage

These do not receive any parameters.

Data Fetching Related

In order to display public appointments, resource and calendar information timum sends several requests.

  • fetchingPublicDataSucceeded Gets passed in a single object looking like this:

      {
        contact: {
          name: 'string',
          email: 'string',
          mobile: 'string',
          phone: 'string',
        },
        resource: {
          uuid: 'string',
          name: 'string', //<- 80 chars, max length of names in timum
          description: 'string',
          msgHelpText: 'string',
          type: 'string',
          url: 'string',
          imgUrl: 'string',
        },
        provider: {
          name: 'string',
          description: 'string',
          isThemingAllowed: 'boolean',
          isLocalisationAllowed: 'boolean',
          areCustomFieldsAllowed: 'boolean'
        },
        channel: {
          bookingProcess: 'string' // One of 'IMMEDIATE' or 'REQUESTED',
        },
      }
  • fetchingPublicDataFailed receives a single RTKQ error object. See here for a reference.

  • fetchingProductsSucceeded Gets passed in a single object looking like this:

    {
      products: [
        {
          description: string,
          minDuration: number,
          name: string,
          uuid,
        },
        //...
      ];
    }
  • fetchingProductsFailed receives a single RTKQ error object. See here for a reference.

  • fetchingBookablesSucceeded Gets passed in a single object looking like this:

      {
        2023-02-06: [ //<- this obviously changes depending on when appointments are available.
          0: {
              timeslot_uuid: uuid of the booked timeslot,
              product_uuid: uuid of the booked product,
              product_name: string,
              resource_name: string,
              capacity: number,
              capacity_left: number,
              kind: one of "models.Bookable" or "models.LotAppointment",
              start: ISO String e.g: "2023-02-06T09:05:00+01:00",
              end: ISO String e.g: "2023-02-06T09:35:00+01:00"
          },
          //... more appointments ...
        ],
        //... more dates ...
      }
    • fetchingBookablesFailed receives a single RTKQ error object. See here for a reference.

Localisation

timum booking has the ability to change all of its text. It comes with English, Italian, Spanish, French and German pre-installed, but you can add additional translations as well. You can also change the text for these languages to your preference. You don't have to redefine the entire localization object; you can just add or change the specific texts you want. Any missing texts will be taken from the default. The code snippet provided shows the complete localization object and the standard text for both English and German. For each language, it includes text for different parts of the booking process such as product selection, appointment booking, appointment request, appointment cancellation, form fields, and more.

localization: {
  de: {
    resource_selection_headline: 'Resource wählen',
    product_selection_headline: 'Terminart wählen',
    booked_successfully_header: 'Termin gebucht',
    booked_successfully_message:
      'Sie erhalten eine E-Mail mit den Termindetails an {{mail}}',
    requested_successfully_header: 'Termin angefragt',
    requested_successfully_message:
      'Sie erhalten eine E-Mail mit den Termindetails an {{mail}}. Sie werden unter der gleichen Adresse benachrichtigt, sobald Ihre Anfrage bearbeitet wurde.',
    submit_button_book: 'Buchen',
    submit_button_request: 'Verbindlich Anfragen',

    noEventsMessage:
      'Zur Zeit sind leider keine buchbaren Termine verfügbar.',
    appoinment_at_capacity: 'belegt',
    add_to_calendar_btn: 'Zu Kalender hinzufügen',
    until_reservation_expiration:
      '{{expiration}} bis zum Ablauf der Reservierung',
    reservation_expired: 'Reservierung abgelaufen.',
    identified_customer_hint:
      'Sie wurden mit persönlichem Link eingeladen und können direkt Ihren Termin buchen.',
    reservation_failed: {
      title: 'Hohe Nachfrage',
      mesage:
        'Dieser Termin wurde gerade durch jemanden reserviert. Bitte wählen' +
        'Sie einen anderen Termin. Oder schauen Sie später noch einmal, ob' +
        'ein Termin frei wird.',
    },
    cancellation: {
      cancelation_successfull_message: 'Termin erfolgreich abgesagt',
      cancellable_appointment_highlight: 'Mein Termin',
      submit_button_cancel: 'Absagen',
      cancel_appointment_header: 'Ihr Termin',
      message_label: 'Nachricht zur Terminabsage',
    },
    validation: {
      field_required: 'Notwendig',
      privacy_field_required:
        'Sie müssen die Datenschutzbestimmungen akzeptieren bevor Sie buchen können.',
      email_field_must_be_valid: 'Geben Sie eine valide E-Mail Adresse ein',
    },
    fields: { // field labels are markdown capable. That's why you can use basic html as well.
      firstName: 'Vorname',
      lastName: 'Nachname',
      name: 'Name',
      email: 'E-Mail',
      mobile: 'Mobil',
      message: 'Ihre Nachricht',
      accept_timum_privacy:
            '<a href="https://info.timum.de/datenschutz" target="_blank">Datenschutzbestimmungen</a> gelesen und akzeptiert',

    },
  },
  // the same for english.
  en: {
    resource_selection_headline: 'Choose Resource',
    product_selection_headline: 'Choose Product',
    booked_successfully_header: 'Appoinment Booked',
    booked_successfully_message:
      'You will receive an email with appointment details to {{mail}}',
    requested_successfully_header: 'Appointment Requested',
    requested_successfully_message:
      'You will receive an email with appointment details to {{mail}}. You will be notified at the same address once your request has been processed.',
    submit_button_book: 'Book',
    submit_button_request: 'Request',

    noEventsMessage:
      'Unfortunately, there are no bookable dates available at the moment.',
    appoinment_at_capacity: 'fully booked',
    add_to_calendar_btn: 'Add to Calendar',
    until_reservation_expiration:
      '{{expiration}} until reservation expiration.',
    reservation_expired: 'Reservation expired.',
    identified_customer_hint:
      'You have been invited with a personal link and can book your appointment directly.',
    reservation_failed: {
      title: 'High Demand',
      mesage:
        'This appointment has just been reserved by someone. Please choose another appointment. Or check back later to see if an appointment becomes available.',
    },
    cancellation: {
      cancelation_successfull_message: 'Appointment canceled sucessfully.',
      cancellable_appointment_highlight: 'My Appointment',
      submit_button_cancel: 'Cancel',
      cancel_appointment_header: 'Your Appointment',
      message_label: 'You may enter a reason here.',
    },
    validation: {
      field_required: 'Required',
      privacy_field_required:
        'You must accept the privacy policy prior to booking.',
      email_field_must_be_valid: 'Enter a valid email address',
    },
    fields: { // field labels are markdown capable. That's why you can use basic html as well.
      firstName: 'First name',
      lastName: 'Last name',
      name: 'name',
      email: 'E-mail',
      mobile: 'Mobile',
      message: 'Your Message',
      accept_timum_privacy:
            '<a href="https://info.timum.de/datenschutz" target="_blank">Privacy policy</a> read and accepted.',

    },
  },
},

Booking Form Fields

You can customize the information you request from customers before booking by defining fields. The minimum required fields for timum are firstName, lastName, email, and agbs. By default, timum also requests mobile and message fields, but you can add your own fields as well.

Fields, including custom ones, support validation and localization. Validation is realized using the yup library, which is included with timum. You can import it using the following code:

import { yup } from '@timum/booking';

Localization is achieved by using the title property in the field definition. You can specify a translation key and add the corresponding translation to the localization object. The same process can be applied to custom field validations.

The standard configuration can be overridden except for the aforementioned, required fields. The following is the standard configuration:

fields: {
  firstName: {
    index: 0,
    title: 'fields.firstName',
    validation: yup.string().required('validation.field_required'), // <- compare with key in localisation
  },
  lastName: {
    index: 1,
    title: 'fields.lastName',
    validation: yup.string().required('validation.field_required'),
  },
  email: {
    index: 2,
    title: 'fields.email',
    format: 'email',
    type: 'text',
    validation: yup
      .string()
      .email('validation.email_field_must_be_valid')
      .required('validation.field_required'),
  },
  mobile: {
    index: 3,
    title: 'fields.mobile',
    type: 'phoneNumber',
    isRequired: false,
    defaultCountry: 'DE',
    preferredCountries: ['DE', 'CH', 'AT'],
    // validation: is in built and ignored
  },
  message: {
    index: 7,
    title: 'fields.message',
    type: 'textarea',
    validation: yup
      .string()
      .max(1024),
    limit: 1024,
  },
  agbs: {
    index: 8,
    title: 'fields.accept_timum_privacy',
    type: 'checkbox',
    validation: yup
      .boolean()
      .required('validation.field_required')
      .test(
        'privacyAccepted',
        'validation.privacy_field_required',
        (value) => value === true,
      ),
  },
  locale: {
    index: 9,
    preventRendering: true,
  },
},

Custom Fields

If the values retrieved via custom fields need to be transferred to timum backend as additional booking information, set the parameter sendCustomValuesInMessage. With this, the values of custom fields are attached to the customer message. They will be visible to the customer as well in booking confirmation mailings.

sendCustomValuesInMessage: true, // necessary to transmit custom field values as part of the customer message

Anatomy of a custom booking form field:

<fieldName> : {

Depending on the type there are additional properties you can/must specify:

  • Type text:

    • format: string; the native input field's 'type' (e.g. 'email', 'number', etc.).
  • Type phoneNumber:

    • does NOT support validation. Phone number validation is complex, so timum handles it for you. This does mean that field validation localisation is currently not supported for fields of this type. This will be fixed in a future update.
    • isRequired: boolean; if true, this field must be filled with a valid number
    • defaultCountry: string, denotes the country code which is preselected when first rendering the component. Default is 'DE',
    • preferredCountries: list of strings; denotes which countries are displayed first in the country drop down. Defaults are ['DE', 'CH', 'AT'],
  • Type textArea:

    • limit: number; sets the maximum number of characters customers can enter. Standard is 1024 characters. Setting this to a higher number leads to error.
  • Type checkbox:

    • no special properties.
  • Type select:

    • options: array of objects with structure { title: string, key: string }. The title is displayed and the key is passed in the data object to callbacks.

Examples

In this section you find examples of common patterns.

Customizing Fonts in timum's BookingJS

This guide demonstrates how to override the default font for the entire BookingJS widget and how to set specific fonts for individual UI components.

The example below shows how to set a new default font (Ysabeau Office) and then apply different fonts (Tangerine) to MuiButton and MuiLink components.

View Live Example on jsFiddle

<div id="bookingjs" style="margin: 32px"></div>
<script type="module">
  import * as timum from 'https://cdn.timum.de/bookingjs/1/booking.js';

  // --- 1. Initialize BookingJS with Resource Reference and Configuration ---
  timum.init(
    {
      // Your resource reference
      ref: 'booking-widget-demo-resource@timum',
    },
    {
      // --- Typography Configuration ---
      typography: {
        // **Default font** for the entire widget: A comma-separated list of font families.
        fontFamily: '"Ysabeau Office", Roboto, Helvetica, Arial',

        // **Font sources** to load via <link rel="stylesheet">.
        fontSource: [
          'https://fonts.googleapis.com/css2?family=Tangerine:wght@400;700&family=Ysabeau+Office:ital,wght@0,1..1000;1,1..1000&display=swap',
        ],
      },

      // --- Component-Specific Font Overrides ---
      // This section uses the underlying MUI (Material-UI) component names.
      components: {
        // Override the font for all MUI Buttons
        MuiButton: {
          styleOverrides: {
            root: {
              fontFamily: '"Tangerine", Roboto, Helvetica, Arial',
            },
          },
        },
        // Override the font for all MUI Links
        MuiLink: {
          styleOverrides: {
            root: {
              fontFamily: '"Tangerine", Roboto, Helvetica, Arial',
            },
          },
        },
      },
    },
  );
</script>

Restrict BookingJS Access to Invited Customers

You can configure BookingJS to only display the booking interface for customers who have been explicitly invited via a unique link. This provides a layer of access control, ensuring that only qualified individuals (e.g., registered clients, specific leads) can proceed with a booking.

How it Works

This approach relies on URL query parameters to authenticate the user and passes these parameters into the timum.init configuration.

  1. Invitation Link: You generate unique invitation links containing a ref (resource identifier) and an invitee (customer identifier).
    • Example Link: https://www.your-site.com/immo?ref=[uuid]&invitee=[addressId]
  2. Validation: The embedded JavaScript checks for the presence of these parameters. If they are missing, the widget is not initialized.
  3. Authentication: The invitee ID is passed to BookingJS via the pData object, allowing the backend to verify the customer against your platform data (e.g., onOffice, FlowFact, Immosolve).

Implementation

The following code demonstrates how to extract the necessary parameters from the URL and pass them to timum.init only when both are present.

<div id="bookingjs" style="margin: 32px"></div>
<script type="module">
  import * as timum from 'https://cdn.timum.de/bookingjs/1/booking.js';

  // Function to hide the element
  const hideBookingJs = () => {
    const el = document.getElementById('bookingjs');
    if (!el) return;

    // Hide the element if initialized but fails to find bookables
    el.style.display = 'none';
  };

  // --- 1. Extract Parameters from the URL ---
  const url = new URL(window.location.href);
  const ref = url.searchParams.get('ref');
  const inviteeId = url.searchParams.get('invitee');

  // Stop execution if required invitation parameters are missing
  if (!ref || !inviteeId) {
    // Optionally, hide the empty container immediately if you want to be certain
    document.getElementById('bookingjs')?.style.display = 'none';
    return;
  }

  // --- 2. Initialize BookingJS with Customer Data ---
  timum.init({
    // Use the dynamic resource 'ref' from the URL
    ref: ref,

    // Pass the customer's identifier via pData for verification
    pData: {
      // Use the platform you are integrating with (e.g., 'onoffice')
      platform: 'onoffice',

      // 'personId' is the required key for onOffice/addressId matching
      personId: inviteeId,
    },

    // --- 3. Optional Callbacks ---
    callback: {
      // If data fetching succeeds but returns no available slots for the user
      fetchingBookablesSucceeded: (bookables) => {
        const hasBookables = bookables && Object.values(bookables).length > 0;
        if (!hasBookables) {
          hideBookingJs();
        }
      },

      // If data fetching fails (e.g., server error)
      fetchingBookablesFailed: () => {
        hideBookingJs();
      },
    },
  });
</script>

Note on pData

The key used within the pData object (e.g., personId) must match the expected parameter name for your connected platform (onOffice, FlowFact, etc.) to correctly authenticate the user. Consult the platform-specific integration guide for the correct parameter name. (See also: pData Configuration

Conditionally Hide BookingJS When No Bookables Are Available

When the BookingJS widget loads, you may wish to automatically hide the widget container if no bookable resources are found or if the data retrieval fails.

You might want to hide the widget to:

  • Improve UX: Avoid showing customers an empty element or informing them about a service they cannot currently book.
  • Reduce Clutter: Keep your website clean by removing elements that serve no purpose at the moment.
  • Display a Custom Message: Use the space to show your own fallback message (e.g., "All services are fully booked! Check back soon.").

The timum.init function provides callback hooks that fire after the widget attempts to fetch resources.

The following snippet demonstrates how to hide the main bookingjs container by checking if the bookables object is empty or if the fetch operation failed entirely.

<div id="bookingjs" style="margin: 32px"></div>
<script type="module">
  import * as timum from 'https://cdn.timum.de/bookingjs/1/booking.js';

  // Function to hide the element by setting its display style
  const hideBookingJs = () => {
    const el = document.getElementById('bookingjs');
    if (!el) return; // Exit if the element wasn't found

    //Set the style to hide element
    el.style.display = 'none';

  };

  timum.init({
    ref: 'booking-widget-demo-resource@timum',
    callback: {
      // 1. Check if the fetch succeeded but returned no bookables
      fetchingBookablesSucceeded: (bookables) => {
        // Use optional chaining for safety, then check the number of items
        const hasBookables = bookables && Object.values(bookables).length > 0;

        if (!hasBookables) {
          hideBookingJs();
        }
      },

      // 2. Hide the widget if the fetch failed (e.g., server error)
      fetchingBookablesFailed: () => {
        hideBookingJs();
      },

      // NOTE: Consider adding other related callbacks if needed.
    },
  });
</script>

Custom Field Example

Here is an example tying all of the aforementioned concepts together. And here is a fiddle containing the very example detailed in this chapter.

The Scenario

It is necessary to determine the gender of customers for a specific purpose. The mobile and message fields are not necessary and will be removed. The new gender field must be filled, therefore a validation check is required to ensure it is completed. Both the name of the gender field and the validation messages must be translated into different languages.

The Implementation

This is the base configuration. As it uses the standard field configuration shown in Booking form fields :

<div id="bookingjs" style="margin: 32px"></div>
<script type="module">
  import * as timum from 'https://cdn.timum.de/bookingjs/1/booking.js';

  timum.init({ ref: 'booking-widget-demo-resource@timum' });
</script>

Let's add the necessary changes:

<div id="bookingjs" style="margin: 32px"></div>
<script type="module">
  import * as timum from 'https://cdn.timum.de/bookingjs/1/booking.js';

  timum.init({
    ref: 'booking-widget-demo-resource@timum',

    fields: {
      // set these fields to undefined explicitly in order to remove them
      message: undefined,
      mobile: undefined,
      firstName: {
        // index defines the order in which the fields get displayed
        index: 0,
        title: 'fields.firstName',
        validation: yup.string().required('validation.field_required'), // <- compare with key in localisation
      },
      lastName: {
        index: 1,
        title: 'fields.lastName',
        validation: yup.string().required('validation.field_required'),
      },
      email: {
        index: 2,
        title: 'fields.email',
        format: 'email',
        type: 'text',
        validation: yup
          .string()
          .email('validation.email_field_must_be_valid')
          .required('validation.field_required'),
      },
      gender: {
        index: 3,
        // new language key
        title: 'fields.gender.title',
        // best for a limited number of predefined choices
        type: 'select',
        // could use 'validation.field_required' but
        // for this example let's create a new  key
        validation: yup.string().required('validation.gender_field_required'),
        options: [
          { key: 'm', title: 'fields.gender.male' },
          { key: 'w', title: 'fields.gender.female' },
          { key: 'd', title: 'fields.gender.other' },
        ],
      },
      agbs: {
        index: 4,
        title: 'Datenschutzbestimmungen gelesen und akzeptiert.',
        type: 'checkbox',
        validation: yup
          .boolean()
          .required('validation.field_required')
          .test(
            'privacyAccepted',
            'validation.privacy_field_required',
            (value) => value === true,
          ),
      },
    },
    sendCustomValuesInMessage: true, // necessary to transmit custom field values as part of the customer message

    // now we need to add the new translation keys and their translations.

    localization: {
      de: {
        validation: {
          gender_field_required: 'Bitte wählen.',
        },
        fields: {
          gender: {
            title: 'Geschlecht',
            male: 'Männlich',
            female: 'Weiblich',
            other: 'Divers',
          },
        },
      },
      en: {
        validation: {
          gender_field_required: 'Please select an option.',
        },
        fields: {
          gender: {
            title: 'Gender',
            male: 'Male',
            female: 'Female',
            other: 'Others',
          },
        },
      },
    },

    // the following callback consumes the customers input, allowing you to act on
    //the entered gender value.
    callback: {
      createBookingStarted: ({ data }) => {
        console.log(data.gender);
      },
    },
  });
</script>