Skip to content
SuperScheduler

Core conceptsApplies toLite and Pro

Resources, events and intervals

Rows are ResourceData objects with an id and a name; bars are EventData objects with id, text, start, end and the resource id they belong to. Ids are strings or numbers compared strictly, so 1 and '1' differ. Intervals are half-open, [start, end), and dates are civil wall-clock values written as ISO strings with seconds; the library never converts time zones. Add your own fields with EventData<YourFields> and narrow them when they come back from the control.

Verified against v0.1.0 · reviewed October 7, 2026.md

SuperScheduler draws two arrays: resources, the rows, and events, the bars on those rows. Both are plain objects you create from your own data. Getting four rules right avoids nearly every "my event does not show up" problem: ids are strictly typed, intervals exclude their end, date strings include seconds, and times are wall-clock values without a time zone.

The rules on this page apply to both editions unless a section says otherwise. Lite accepts a subset of the fields; see Quick start with Lite.

Resources

A resource needs an id and a name. In Pro, ResourceData is open: you can keep your own fields (floor, kind, capacity) on the same object and read them back in callbacks and rendering hooks.

Fields you will use most in Pro:

FieldPurpose
idString or number; unique among resources
nameRow header text
backColor, cssClass, html, toolTipRow header appearance (html is trusted markup)
minHeight, eventHeightRow geometry for this row only
cellsDisabledEvery cell of the row rejects drops and selections
columnsCells for extra row header columns (with rowHeaderColumns)
children, expandedA resource tree (with treeEnabled)
frozen'top' or 'bottom': the row stays visible while scrolling

Resource trees

To group rows, nest resources in children and set treeEnabled on the scheduler. Without treeEnabled, children are ignored and the list stays flat. A parent starts collapsed unless its expanded field is true. Parents can hold events like any row; set treePreventParentUsage to keep them as pure group headers. Trees, row columns and row selection are covered in Trees, columns and selection. Lite accepts flat lists only.

Events

An event needs id, text, start, end and, to appear on a row, resource. Optional fields change its appearance and behavior:

FieldPurpose
backColor, fontColor, borderColor, barColorColors of the bar, its text, border and duration bar
cssClassClasses for your own CSS
htmlContent as trusted HTML (escape user data with SuperScheduler.Util.escapeHtml)
toolTip, bubbleHtmlNative tooltip, or hover bubble content
moveDisabled, resizeDisabledLock this event against moving or resizing
moveHDisabled, moveVDisabledAllow moving only between rows, or only in time
clickDisabled, deleteDisabledOpt this event out of clicks or deletion
tagsAny value for your own use

Every field in this table is Pro only except backColor, fontColor, cssClass, toolTip and tags, which Lite accepts too.

Ids are strings or numbers, compared strictly

ResourceId and EventId are string | number, and comparisons use the value and its type. The number 101 and the string '101' are different ids. An event with resource: '101' is not drawn on a row whose id is 101, and control.events.find('7') does not find the event with id 7.

This matters most when data comes from several sources: a database driver may return numeric room ids while a form or a URL gives strings. Normalize ids at the boundary where data enters your application, and keep one type per kind of id.

Intervals are half-open

An event occupies [start, end): the start instant belongs to it, the end instant does not. Three consequences:

  • Back-to-back events do not overlap. A stay ending at 11:00 and the next one starting at 11:00 on the same room are compatible, also when overlaps are refused.
  • A date-only end is the first free day. start: '2026-10-02', end: '2026-10-05' covers 2, 3 and 4 October. To show the 5th as well, the end is '2026-10-06'.
  • Durations are plain differences. end - start is the duration, with no "plus one day" adjustments.

Your backend should use the same rule. Two intervals overlap when a.start < b.end && b.start < a.end; a date range query for what is visible between from and to is start < to AND end > from.

In Pro, eventEndSpec: 'Date' switches to inclusive date-only ends for day-based plannings: an event ending on '2026-10-05' then covers the 5th. The library converts the value internally and gives it back in the same convention. Use one convention per scheduler.

Date strings

start, end and every date option accept a SuperScheduler.Date or an ISO 8601 string:

InputAcceptedRead as
'2026-10-02'yesMidnight at the start of 2 October
'2026-10-02T14:00:00'yes14:00
'2026-10-02 14:00:00'yes14:00 (a space instead of T)
'2026-10-02T14:00:00.250'yesWith milliseconds
'2026-10-02T14:00'no, throwsSeconds are required
'2026-10-02T14:00:00+02:00'yes, with care12:00: an offset converts the value to UTC wall-clock time
new Date()noA native Date does not type-check as start or end

Two rules prevent most surprises: always include seconds, and do not send offsets or Z unless you want the UTC wall clock. Format values as yyyy-MM-ddTHH:mm:ss in the time zone of the place being planned.

Civil time, no time zones

SuperScheduler works with civil (wall-clock) date-times: 2026-10-25T02:30:00 is "half past two on the 25th" exactly as written, with no time zone and no daylight-saving shifts. The scheduler displays what you give it and gives back values in the same form.

That is what a planning board needs: a hotel in Madrid shows check-in at 14:00 local time for every user, wherever their browser is. It also means the conversions are yours:

  • If your backend stores instants (UTC timestamps), convert them to the wall clock of the resource's location before passing them, and back to instants when saving.
  • If different resources sit in different time zones, decide which wall clock the view shows; the scheduler has a single time axis.
  • Recurring events (every Monday at 9:00) must reach the scheduler as concrete occurrences that your application expanded.

Custom fields with EventData<T>

Your events usually carry more than a label: a guest code, a status, a price. In Pro, EventData has no index signature, so an object literal with extra properties fails TypeScript's excess property check. Declare your fields once and use the generic, SuperScheduler.EventData<YourFields>:

src/bookings.tsts
import type { SuperScheduler } from 'super-scheduler'

/** Fields your application adds to every booking. */
export interface BookingFields {
  guestCode: string
  status: 'tentative' | 'confirmed' | 'checkedIn'
  adults: number
}

export type BookingEvent = SuperScheduler.EventData<BookingFields>

// ResourceData accepts extra properties: keep your own row fields next to id and name.
export const rooms: SuperScheduler.ResourceData[] = [
  { id: 101, name: 'Room 101', floor: 1, kind: 'double' },
  { id: 102, name: 'Room 102', floor: 1, kind: 'suite' },
]

// EventData has no index signature: type the array so literals may carry custom fields.
export const bookings: BookingEvent[] = [
  {
    id: 'bk-1042',
    resource: 101, // the same type as the room id: 101 and '101' are different ids
    start: '2026-10-02T14:00:00',
    end: '2026-10-05T11:00:00',
    text: 'Booking 1042',
    guestCode: 'G-1042',
    status: 'confirmed',
    adults: 2,
  },
]

const STATUSES: ReadonlySet<string> = new Set(['tentative', 'confirmed', 'checkedIn'])

/**
 * Objects that come back from the control (handler arguments, onEventsChange) are typed as plain
 * EventData. Narrow them instead of casting, so a malformed object is caught where it appears.
 */
export function isBooking(data: SuperScheduler.EventData): data is BookingEvent {
  return (
    'guestCode' in data &&
    typeof data.guestCode === 'string' &&
    'status' in data &&
    typeof data.status === 'string' &&
    STATUSES.has(data.status) &&
    'adults' in data &&
    typeof data.adults === 'number'
  )
}

Data coming back from the control is typed as plain EventData: handler arguments, onEventsChange, control.events.list. The library keeps your fields, but TypeScript cannot know they are there. Narrow with a guard like isBooking instead of casting; a guard also catches objects your own code built incorrectly. Rendering hooks receive an open copy (args.data in onBeforeEventRender), so you can read fields directly there, but a guard keeps the types exact:

src/TypedPlanning.tsxtsx
import { useMemo } from 'react'
import { SuperScheduler, SuperSchedulerComponent } from 'super-scheduler'
import type { SchedulerProps } from 'super-scheduler'
import { bookings, isBooking, rooms } from './typed-data'

export function TypedPlanning({ onOpen }: { onOpen: (guestCode: string) => void }) {
  const owned = useMemo(() => bookings.slice(), [])

  const config = useMemo<SchedulerProps>(
    () => ({
      startDate: '2026-10-01',
      days: 31,
      scale: 'Day',
      onBeforeEventRender: (args) => {
        // args.data is a per-render copy: start and end are always SuperScheduler.Date here.
        if (!isBooking(args.data)) return
        const nights = Math.round(
          (args.data.end.getTime() - args.data.start.getTime()) / 86_400_000,
        )
        // html is trusted markup: escape every value that came from users.
        const code = SuperScheduler.Util.escapeHtml(args.data.guestCode)
        args.data.html = `${code} · ${nights} night${nights === 1 ? '' : 's'}`
        args.data.cssClass = `booking booking--${args.data.status}`
      },
      onEventClick: (args) => {
        // args.e is a wrapper: id(), start(), text() are methods; data is the raw object.
        const data = args.e.data
        if (isBooking(data)) onOpen(data.guestCode)
      },
    }),
    [onOpen],
  )

  return <SuperSchedulerComponent {...config} resources={rooms} events={owned} />
}

You should see each booking labeled with its guest code and number of nights, and onOpen called with the guest code when you click it.

Lite's EventData is not generic and accepts no extra fields in TypeScript: keep application data in tags, which onEventClick hands back in e.data.tags.

Values after a drag or a resize

When a user moves or resizes an event in Pro, the library does not edit your object. It replaces it with a new object, { ...old, start, end, resource }, in which start and end are SuperScheduler.Date instances. Events nobody touched keep the strings you passed. Code that reads events must therefore accept both forms:

src/event-dates.tsts
import { SuperScheduler } from 'super-scheduler'

/**
 * After a drag or a resize, the committed event holds SuperScheduler.Date values in start and end;
 * events nobody touched keep the strings you passed. Normalize both to one canonical string.
 */
export function toIso(value: SuperScheduler.DateInput): string {
  // `value` is `yyyy-MM-ddTHH:mm:ss` (plus `.fff` when milliseconds are not zero).
  return typeof value === 'string' ? new SuperScheduler.Date(value).value : value.value
}

/** The part of an event your backend stores. */
export function toSavePayload(event: SuperScheduler.EventData) {
  if (event.resource === undefined) throw new Error(`Event ${String(event.id)} has no resource`)
  return {
    id: String(event.id),
    resource: String(event.resource),
    start: toIso(event.start),
    end: toIso(event.end),
  }
}

/** Half-open overlap, the rule the scheduler applies: touching intervals do not overlap. */
export function overlaps(a: SuperScheduler.EventData, b: SuperScheduler.EventData): boolean {
  const date = (value: SuperScheduler.DateInput) => new SuperScheduler.Date(value)
  return SuperScheduler.Util.overlaps(date(a.start), date(a.end), date(b.start), date(b.end))
}

A few details help when you persist or compare these values:

  • JSON.stringify writes a SuperScheduler.Date as yyyy-MM-ddTHH:mm:ss, so a whole event object serializes to valid ISO strings.
  • String(date) and date.value give the same text; date.toString('d MMM HH:mm', 'en-us') formats with a pattern and a locale.
  • Compare dates with a.equals(b) or by getTime(). === compares object identity.
  • getDay() is the day of the month (1 to 31), unlike the native Date. The weekday is getDayOfWeek() (0 is Sunday) or dayOfWeekISO() (1 is Monday).
  • Test the type with value instanceof SuperScheduler.Date, never with the constructor name. In Lite, use Lite's own SuperScheduler.Date; when both editions are installed, exchange ISO strings between them.

Fleet rental planningA compact is grounded on pickup day. Hand its rental to another car, keep the cleaning slot and see where the fleet runs out. Lab instrument bookingBook an instrument and its calibration comes with it. Clear a session out of a service visit, then stretch your run.

Next steps