Developers

Below you'll find information, guides and tutorials helping you get flying with a custom Timekit integration in no time.

Advanced configuration

Booking.js is made for various use-cases, so it's really extensible and customizable. We augment all the intrinsic options so you can overwrite them as needed, e.g. Timekit availability options or fullcalendar settings.

Timekit works best with projects. A project in Timekit is a configuration of a specific booking experience. Usually, a project will correspond to an existing process already present in your organization.

You can create projects from your admin interface and reference them through your Booking Widget configuration. You can even modify them further if that is what you need. If you choose not use a project as your starting point, be sure to configure the necessary keys or your Booking Widget will be hard to use. Read more about projects here.

Before you continue!

Again, as stated above, please consider using projects to persist configuration with us. They are easy to create and manage both in our admin and through our API.

Example

{

  // Required

  app_key:                      '',   // Your Timekit client-side key (App Widget Key)

  // Optional

  project_id:                   '',   // Reference a project where you want to pull settings from and connect bookings to
  el:                           '#bookingjs', // Which element should we the library load into
  autoload:                     true, // Auto initialization if a windo.timekitBookingConfig variable is found
  debug:                        false, // Enable debugging mode to output useful state/step data in the console
  disable_confirm_page:         false, // Disable the confirmation page and use the "clickTimeslot" callback to receive selected timeslot

  // Manual configuration (not needed when supplying project_id)

  resources:                    [],   // ID's of the resources that are available as par of this project
  availability:                 { ... }, // Configure how availability should be queried. (see below) 
  availability_constraints:     [ ... ], // Configure any cross-resource constraints. (see below)
  booking:                      { ... }, // Configure how bookings should be made. (see below)
  reminders:                    [ ... ], // Reminder settings specific to this project. (see below)
  customer_fields:              { ... }, // Configure which inputs the customer can add to the booking. (see below)
  ui:                           { ... }, // UI specific configuration used by our booking.js widget (v2) reference coming.
  fullcalendar:                 { ... }, // Configure FullCalendar options. (see below)
  callbacks:                    { ... } // Register callbacks on events. (see below)

}

Structure follows projects

Whether you choose to override project settings or supply all configuration manually, do know that the naming and content of the configuration parameters (e.g. resources, availability_constraints, customer_fields etc) follow the exact same naming conventions as used in projects. See the Create Project endpoint for reference.

Availability

A central feature of Timekit is searching for availability of your resources. Availability are timeslots that your resources are open for booking i.e. spots where they are not already booked or blocked.

We support different modes/types of availability: mutual, roundrobin_random and roundrobin_prioritized. You can retrieve availability for a single resource or multiple at the same time.

Constraints are used to further limit the search space, with the most common use case being opening hours. Here is a typical setup used for half an hour timeslots with random resource selection:

availability: {
 mode: 'roundrobin_random',
 length: '30 minutes',
 from: '-1 hour',
 to: '4 weeks',
 buffer: '15 minutes',
 ignore_all_day_events: false
}

Find details on each key by looking at our availability endpoint.

Availability Constraints

The most typical use-case for availability_constraints is setting opening hours and/or working hours. You can do this with two different kinds of constraints; blocking and allowing. "Allowing" constraints implicitly blocks everything else, so for instance the constraint AllowDay, with the day set to monday, will block all the other days in the week. This means that you do not need to explicitly block periods that are implicitly being blocked with an "allowing" constraint. So if you are using the allow_weekdays constraint, you don't need to also supply the block_weekends constraint.

You can combine constraints to fit your use-case. As mentioned the most common use case is setting opening hours. With opening hours Monday to Friday 9am to 5pm this would look like this:

availability_constraints: [
  { allow_day_and_time: { day: 'monday', start: 9, end: 17 } },
  { allow_day_and_time: { day: 'tuesday', start: 9, end: 17 } }
],

Find details on each key by looking at our availability_constraints endpoint.

Booking

Setting a booking configuration helps you define the way a booking is handled and presented. A typical scenario will use the graph type instant, so no confirmation is needed by the recipient. You can see how all of Timekit's graphs work right here.

booking: {
  graph: 'instant', // what type of flow? instant confirms the booking immediately
  what: 'TBD',
  where: 'West End'
}

Find details on each key by looking at our booking endpoint.

Reminders

The easiest way to send reminders emails before or after a booking is to use the built-in reminder emails.

Setting up an email-reminder to be send to the owner 15 minutes before a booking takes place looks like this:

reminders: [
  {
    type: 'email',
    settings: { 
      recipient: 'owner', 
      subject: 'Sales call in 15 mins!'
    },
    when: {
      type: 'before', 
      unit: 'mins', 
      time: 15
    }
  }
]

Find details on each key by looking at our booking endpoint here.

Customer Fields

You can customize the booking form fields and their settings in customer_fields. Only the name, and email are required by default (for the event creation to work properly).

Customer fields are flexible and highly customizable. You can create your own fields with different input types to collect all sorts of customer info. To add a new field, simply define a new object in customer_fields object with the slug being an unique key.

Here's an example of changing the name of the email field and adding a new department dropdown field:

customer_fields: {
  email: {
    title: 'Your e-mail'
  },
  department: {
    title: 'Your department',
    required: true,
    format: 'select',
    enum: ['Design', 'Development', 'Sales']
  }
}

Here's a complete list of all the settings you can set per field:

Field key
Description

title

The name of the field as shown in the form

required

Boolean, defines whether the field is required or optional

prefilled

Allows you to prefill the value in form. Set to false to disable.

readonly

Allows you lock the field from customer input in the form. Set to false to disable.

format

Defines the field type and how it should be rendered in the form. See table below.

enum

Only used by fields that have "format" set to "select" (dropdown). Supply an array of possible values.

Here's the possible values for the format key:

Format value
Description

text

Standard text field. This is the default value if the "format" key is omitted entirely.

textarea

Multi-line text field

email

Text field with email validation

tel

Text field with phone number validation

number

Text field with number/integer validation

checkbox

A checkbox that allows the customer to tick on/off (saves result as true/false)

select

Dropdown as a select field with multiple options to choose from. Declare possible options in the "enum" key (see above)

If you're collecting user information before loading the widget, it can be useful to inject it into the form by setting the "prefilled" keys - just pass in the values and they will be set upon load. Combine it with "readonly" to lock the fields for user input.

Pre-filling input fields article

Learn how to prefill the fields through URL parameters or with dynamic values in our Help Center article

Here's a full example:

customer_fields: {
  name: {
    title: 'Full name',
    prefilled: false,
    readonly: false
  },
  email: {
    title: 'E-mail',
    prefilled: 'marty.mcfly@timekit.io',
    readonly: true
  },
  comment: {
    title: 'Comment',
    prefilled: false,
    required: true,
    readonly: false,
    format: 'textarea'
  },
  phone: {
    title: 'Phone number',
    prefilled: false,
    required: false,
    readonly: false,
    format: 'tel'
  },
  voip: {
    title: 'Skype username',
    prefilled: false,
    required: false,
    readonly: false
  },
  location: {
    title: 'Location',
    prefilled: false,
    required: false,
    readonly: false
  }
}

See our customer fields example on GitHub.

UI

Changing the appearance of the widget can be achieved by configuring the widget ui (User Interface).

ui: {
  display_name: 'Martys Timetravel Service',
  avatar: 'http://via.placeholder.com/100x100',
  availability_view: 'agendaWeek',
  timezone: null,
  show_credits: true,
  show_timezone_helper: true,
  time_date_format: '12h-mdy-sun',
  localization: {
    allocated_resource_prefix: 'for',
    submit_button: 'Book it now!',
    success_message: 'Thanks for booking!'
  }
}

Here's a few articles on how to achieve specific tasks:

Full Calendar

The Booking Widget is built on top of Full Calendar. Setting a fullcalendar configuration will allow you to override all the internal FullCalendar settings.

Below is an example of us changing a few things:

fullcalendar: {
  header: {
    left:       '',
    center:     '',
    right:      'today, prev, next'
  },
  views: {
    agenda: {
      displayEventEnd: false
    }
  },
  allDaySlot:   false,
  scrollTime:   '08:00:00',
  defaultView:  sizing.view,     // Inserted dynamically based on the current width of the widget
  height:       sizing.height,   // Inserted dynamically based on the current width of the widget
  eventClick:   function(event), // Handled internally in Booking.js (overwrite if you want to replace the booking page)
  windowResize: function(view)   // Handled internally in Booking.js (overwrite if you want to disable or change automatic resizing)
}

Callbacks

With callbacks, you can hook into events happening throughout the user flow and perform asynchronous events. This is especially powerful for saving user data to your CRM system or redirect users to a payment gateway after booking is finished.

Inspect the source code to learn more about in which order callbacks are fired. Below is a complete list of all callbacks available:

callbacks: {
  fetchAvailabilityStarted:    function(args) {},
  fetchAvailabilitySuccessful: function(response) {},
  fetchAvailabilityFailed:     function(response) {},
  createBookingStarted:        function(args) {},
  createBookingSuccessful:     function(response) {},
  createBookingFailed:         function(response) {},
  getUserTimezoneStarted:      function(args) {},
  getUserTimezoneSuccessful:   function(response) {},
  getUserTimezoneFailed:       function(response) {},
  fullCalendarInitialized:     function() {},
  renderCompleted:             function() {},
  showBookingPage:             function(event) {},
  closeBookingPage:            function() {},
  submitBookingForm:           function(values) {},
  errorTriggered:              function(message) {}
}

Methods

After you instantiated the widget, you can control it with the following methods:

var widget = new TimekitBooking();
widget.init(config);          // Initializes the widget with the given config
widget.render();              // Re-renders the widget with it's instance config
widget.setConfig(config);     // Push a new config into it (call render() afterwards)
widget.getConfig();           // Returns the current config
widget.destroy();             // Cleans the DOM element and empty config
widget.fullCalendar(action);  // Direct access to FullCalendar's own method (for advanced use)

Instantiation Methods

Only available when your using the instantiation approach and not autoload