Skip to main content

Defining Availability

Beta

Medplum Scheduling APIs are currently in beta.

This guide covers how to configure availability using the SchedulingParameters extension — at both the actor level (per Schedule) and the service type level (via HealthcareService). It covers scheduling constraints, field-level inheritance, override behavior, timezone handling, and multi-resource scheduling patterns.

Parameters may be defined on a HealthcareService and shared amongst all Schedules that book that type of appointment. Each Schedule may also override these parameters to define behaviors specific to that schedule as needed.

A few constraints trip people up most often — see Common Pitfalls for a quick reference before you start.

The diagram below shows how availability can be defined at both

The Scheduling Parameters Extension

All scheduling constraints are managed through a single consolidated extension: SchedulingParameters. This extension can appear on both HealthcareService (for shared configuration) and Schedule.

To use scheduling APIs for a Schedule and HealthcareService, at least one of them must define the duration attribute (used to set how long the scheduled appointment will last). There must be a timezone attribute, which may also be defined on the Schedule's actor. (See Timezone Resolution)

When using scheduling APIs to interact with multiple Schedule resources at once, they must be configured with matching duration, alignmentInterval, alignmentTimezone, and alignmentOffset parameters. For this reason, Medplum recommends that these parameters only be set on HealthcareService resources.

Extension Fields

UrlTypeDefault ValueDescriptionHealthcareService usage notesSchedule usage notes
durationDurationnoneDetermines how long the time increments for a Slot areRecommended to prefer setting this on HealthcareService
timezoneCodenoneSpecifies the timezone (IANA timezone identifier, e.g., America/New_York) for interpreting availability. When not set, falls back to the Schedule.actor's timezone.
bufferBeforeDuration0 minutes (no buffer needed)Sets prep-time needed before appointment start. It must be free at booking time, and will be reserved with a Slot.
bufferAfterDuration0 minutes (no buffer needed)Sets cleanup time needed after appointment end. It must be free at booking time, and will be reserved with a Slot.
alignmentIntervalDuration60 minutes (appointments start on-the-hour)Start times must align to this interval (e.g., every 15 minutes)Recommended to prefer setting this on HealthcareService
alignmentOffsetDuration0 minutesShifts allowed start times by this offset (e.g., with a 15-minute alignmentInterval and a 5-minute alignmentOffset, valid starts are :05, :20, :35, :50)Recommended to prefer setting this on HealthcareService
alignmentTimezoneCode'Etc/UTC'Anchors the alignment grid to local midnight of the given timezone, keeping start times stable across DST transitions.Recommended to prefer setting this on HealthcareService
serviceReference(HealthcareService)nonePointer to the HealthcareService that these parameters should override.Not permitted
availabilityNested ExtensionAlways availableWeekly recurring availability windows. When set, appointments must fit inside these windows.Not permitted (use HealthcareService.availableTime instead)
Example of the SchedulingParameters extension on a HealthcareService
{
url: 'https://medplum.com/fhir/StructureDefinition/SchedulingParameters',
// set duration / alignmentInterval / alignmentTimezone HERE; all schedules booked together must match on these
extension: [
// Recommended: duration determines how long the time increments for a Slot are.
// If not set here, must be defined on all related Schedules. To book on multiple
// schedules at once, they must all match in this dimension.
{
url: 'duration',
valueDuration: { value: 1, unit: 'h' },
},

// Recommended: Time alignment interval (appointment start time boundaries)
// To book on multiple schedules at once, they must all match in this dimension.
{
url: 'alignmentInterval',
valueDuration: { value: 15, unit: 'min', system: 'http://unitsofmeasure.org', code: 'min' },
},

// Recommended: Time alignment offset (shift from interval boundaries)
// To book on multiple schedules at once, they must all match in this dimension.
{
url: 'alignmentOffset',
valueDuration: { value: 0, unit: 'min', system: 'http://unitsofmeasure.org', code: 'min' },
},

// Recommended: Timezone for anchoring the alignment grid to local midnight
// To book on multiple schedules at once, they must all match in this dimension.
// This parameter is Independent of `timezone`, which controls availability window interpretation
{
url: 'alignmentTimezone',
valueCode: 'America/New_York',
},

// Optional: specify time zone for availability interpretation
// Falls back to Schedule's actor time zone if not specified
{
url: 'timezone',
valueCode: 'America/Los_Angeles',
},

// Optional: Buffer time required before appointment
{
url: 'bufferBefore',
valueDuration: { value: 15, unit: 'min', system: 'http://unitsofmeasure.org', code: 'min' },
},

// Optional: Buffer time required after appointment
{
url: 'bufferAfter',
valueDuration: { value: 10, unit: 'min', system: 'http://unitsofmeasure.org', code: 'min' },
},
],
};
Example of the SchedulingParameters extension on a Schedule
{
url: 'https://medplum.com/fhir/StructureDefinition/SchedulingParameters',
extension: [
// Required on Schedule: you must specify what type of appointment these parameters apply to
{
url: 'service',
valueReference: {
reference: 'HealthcareService/5d02acfd-fbe8-4537-84e4-31f5116be105',
display: 'Bariatric Surgery',
},
},

// Optional: specify time zone for availability interpretation
// Falls back to Schedule's actor time zone if not specified
{
url: 'timezone',
valueCode: 'America/Los_Angeles',
},

// Optional: duration determines how long the time increments for a Slot are.
// If not set here, must be defined on the related HealthcareService
{
url: 'duration',
valueDuration: { value: 1, unit: 'h' },
},

// Recurring availability (Schedule only)
{
url: 'availability',
extension: [
{
url: 'availableTime',
extension: [
{ url: 'daysOfWeek', valueCode: 'mon' },
{ url: 'daysOfWeek', valueCode: 'wed' },
{ url: 'daysOfWeek', valueCode: 'fri' },
{ url: 'availableStartTime', valueTime: '09:00:00' },
{ url: 'availableEndTime', valueTime: '17:00:00' },
],
},
],
},

// Buffer time before appointment
{
url: 'bufferBefore',
valueDuration: { value: 15, unit: 'min', system: 'http://unitsofmeasure.org', code: 'min' },
},

// Buffer time after appointment
{
url: 'bufferAfter',
valueDuration: { value: 10, unit: 'min', system: 'http://unitsofmeasure.org', code: 'min' },
},

// Time alignment interval (appointment start time boundaries)
// Recommended to be set on `HealthcareService` and inherited: to book multiple schedules at once,
// they must all match in this dimension.
{
url: 'alignmentInterval',
valueDuration: { value: 15, unit: 'min', system: 'http://unitsofmeasure.org', code: 'min' },
},

// Time alignment offset (shift from interval boundaries)
// Recommended to be set on `HealthcareService` and inherited: to book multiple schedules at once,
// they must all match in this dimension.
{
url: 'alignmentOffset',
valueDuration: { value: 0, unit: 'min', system: 'http://unitsofmeasure.org', code: 'min' },
},

// Timezone for anchoring the alignment grid to local midnight
// Recommended to be set on `HealthcareService` and inherited: to book multiple schedules at once,
// they must all match in this dimension.
{
url: 'alignmentTimezone',
valueCode: 'America/New_York',
},
],
};

Alignment grid

Medplum Scheduling APIs generate possible appointments by projecting a repeating daily grid. These parameters control that grid:

ParameterDescriptionDefault
alignmentIntervalHow frequently slot start times occur60 minutes
alignmentOffsetShifts slot start times by this amount0 minutes
alignmentTimezoneWhat timezone the grid is anchored toEtc/UTC

alignmentInterval

Sets how frequently appointments may begin. For back-to-back scheduling without gaps, set this value to match the duration parameter.

alignmentOffset

Example: to align your appointments starting at 9:15, 10:15, ..., set alignmentOffset to 15 minutes (with a 60-minute alignmentInterval).

alignmentTimezone

When clocks change for DST, slots appear to shift by an hour in local time — for example, a 9:00am slot may appear at 8:00am or 10:00am. Setting alignmentTimezone anchors the grid to local midnight instead, keeping slot times consistent year-round.

Relationship to timezone: The two fields serve distinct purposes and can be set independently:

  • timezone — which timezone to use when reading availableStartTime/availableEndTime values
  • alignmentTimezone — which timezone's midnight to use as the alignment grid anchor

The rare case in which they differ: a provider whose availability hours and appointment grid are managed in different timezones.

Actor Level Availability

Actor-level availability is defined per Schedule and is resolved field-by-field against the service-level parameters on the HealthcareService:

  • For each parameter (duration, buffers, alignment, and availability), the value set on the Schedule overrides the HealthcareService value for that field; any field the Schedule does not set is inherited from the service. This is an override, not a merge — a Schedule that sets its own availability fully replaces the service's availableTime, rather than narrowing it. See Override Behavior for the full resolution order.
  • A separate intersection applies only when booking across multiple Schedules at once (e.g. surgeon + room + anesthesiologist): a time is offered only when every required Schedule is available. That intersection is between Schedules — not between a Schedule and its HealthcareService.

The Concept of Implicit Availability

Medplum's scheduling model uses implicit availability: time is assumed to be free by default. You define availability rules using extensions that specify when resources are available based on recurring patterns. Slot resources are only used for explicit overrides—either to mark time as busy (when an appointment is booked) or to block out unavailable time.

This approach avoids the need to pre-generate thousands of Slot resources for every possible time slot. Instead, the system calculates available windows dynamically based on the availability rules you define.

The Schedule Resource

The Schedule resource is the foundation for defining actor-level availability for a provider, location, or device.

The Schedule resource should define the service types that it is capable of acting on in its serviceType attribute. To use Medplum Scheduling APIs, this should include the extension https://medplum.com/fhir/service-type-reference holding a reference to the matching HealthcareService.

Here is an example of a Schedule resource that defines availability for a Practitioner.

{
resourceType: 'Schedule',
id: 'dr-smith-schedule',
// Schedule has no 'name' field in R4 - use comment for a label
comment: "Dr. Smith's Office Visit availability",
// Practitioner or PractitionerRole; the actor must carry a timezone extension
actor: [{ reference: 'Practitioner/dr-smith' }],
// `serviceType` links this schedule to the HealthcareService resources that it is permitted to use for scheduling
serviceType: [
{
text: 'Office Visit',
coding: [{ code: 'office-visit' }],
extension: [
{
url: 'https://medplum.com/fhir/service-type-reference',
valueReference: {
reference: 'HealthcareService/23c3f1cc-4f55-4990-9775-511b02487e7e',
display: 'Office Visit',
},
},
],
},
],
extension: [
// This extension applies custom recurring availability when this schedule is
// used with the "Office Visit" service type.
{
url: 'https://medplum.com/fhir/StructureDefinition/SchedulingParameters',
extension: [
{
url: 'service',
valueReference: {
reference: 'HealthcareService/23c3f1cc-4f55-4990-9775-511b02487e7e',
display: 'Office Visit',
},
},
{
url: 'availability',
extension: [
{
url: 'availableTime',
extension: [
{ url: 'availableStartTime', valueTime: '09:00:00' },
{ url: 'availableEndTime', valueTime: '17:00:00' },
{ url: 'daysOfWeek', valueCode: 'mon' },
{ url: 'daysOfWeek', valueCode: 'tue' },
{ url: 'daysOfWeek', valueCode: 'wed' },
{ url: 'daysOfWeek', valueCode: 'thu' },
{ url: 'daysOfWeek', valueCode: 'fri' },
],
},
],
},
],
},
],
};
Schedule has no name element

In FHIR R4, Schedule has no name element. This is easy to trip over because Organization, HealthcareService, and Location all do have name. Sending name on a Schedule fails validation with Invalid additional property "name".

Instead, use comment — a free-text field that's a good place for a human-readable description or label (for example, "Dr. Smith's Office Visit availability").

Choosing the actor: Practitioner vs PractitionerRole

Schedule.actor may reference a Practitioner, PractitionerRole, Location, or Device. When the actor is a person:

  • Use Practitioner when availability is for the individual regardless of role or location.
  • Use PractitionerRole when availability is specific to a role, organization, or location binding (for example, when licensure varies by state — see state-by-state licensure).

availability Extension

The availability sub-extension mirrors the FHIR R5+ Availability datatype shape. It is encoded using nested R4 extensions (because R4 does not have a native Availability data type). This is close to the R4 definition of HealthcareService.availabileTime, which is another possible source of scheduling availability data. If this sub-extension is not present, availability is constrained only by the presence of existing Slot resources for the schedule.

Sub-extensionTypeDescriptionRepeatable
availableTime(nested)One entry per availability windowYes
daysOfWeekvalueCodeOne entry per day (monsun)Yes
allDayvalueBooleanIf true, window spans the full dayNo
availableStartTimevalueTimeOpening time (not allowed when allDay is present)No
availableEndTimevalueTimeClosing time (not allowed when allDay is present)No
notAvailableTime(nested)Typed for future use; not yet processedYes
{
resourceType: 'Schedule',
id: 'dr-smith-schedule',
comment: "Dr. Smith's Office Visit availability",
actor: [{ reference: 'Practitioner/dr-smith' }],
serviceType: [
{
text: 'Office Visit',
coding: [{ code: 'office-visit' }],
extension: [
{
url: 'https://medplum.com/fhir/service-type-reference',
valueReference: {
reference: 'HealthcareService/23c3f1cc-4f55-4990-9775-511b02487e7e',
display: 'Office Visit',
},
},
],
},
],
extension: [
{
url: 'https://medplum.com/fhir/StructureDefinition/SchedulingParameters',
extension: [
{
url: 'service',
valueReference: {
reference: 'HealthcareService/23c3f1cc-4f55-4990-9775-511b02487e7e',
display: 'Office Visit',
},
},
{
url: 'duration',
valueDuration: { value: 1, unit: 'h' },
},
{
url: 'availability',
extension: [
{
url: 'availableTime',
extension: [
{ url: 'daysOfWeek', valueCode: 'mon' },
{ url: 'daysOfWeek', valueCode: 'tue' },
{ url: 'daysOfWeek', valueCode: 'wed' },
{ url: 'daysOfWeek', valueCode: 'thu' },
{ url: 'daysOfWeek', valueCode: 'fri' },
{ url: 'availableStartTime', valueTime: '09:00:00' },
{ url: 'availableEndTime', valueTime: '17:00:00' },
],
},
],
},
],
},
],
};

Service Level Availability

Service Types and HealthcareService

A HealthcareService gives a mechanism to define common scheduling parameters for an appointment type, which can then be used by multiple Practitioner's Schedules. This allows you to define standard appointment durations, buffer times, and grid alignment settings once and apply them across multiple providers.

In Medplum scheduling, one HealthcareService represents one bookable appointment type (e.g. "Office Visit", "New Patient Visit", "Bariatric Surgery"). It is the place where the shared SchedulingParameters extension lives, so the same duration, buffers, and alignment apply everywhere the type is booked.

Coding a HealthcareService

A HealthcareService carries several CodeableConcept fields. The most relevant for scheduling is type (the specific appointment/service type that the Schedule.serviceType matches against). The full set:

FieldMeaningConventional code system
categoryBroad category of serviceHL7 service-category
typeThe specific service / appointment typeHL7 service-type, or SNOMED CT procedure codes
specialtyClinical specialties handled by the siteSNOMED CT practice-setting codes (c80-practice-codes)

These bindings are example/preferred, not required — FHIR does not force a particular terminology here. You can use a code from the HL7 service-type value set, a SNOMED CT code (as the surgical examples below do), or your own local system (as the http://example.org/appointment-types examples below do). What matters is that the same code/system you put on HealthcareService.type is what the Schedule.serviceType declares and what $find is queried with.

LOINC is not used here

LOINC codes describe observations, lab tests, and documents — not services. Use the HL7 service-type/service-category systems or SNOMED CT for a HealthcareService, not LOINC.

For a Schedule to use the HealthcareService's scheduling parameters, the Schedule.serviceType must include a reference to the HealthcareService in its extensions.

{
resourceType: 'HealthcareService',
id: '23c3f1cc-4f55-4990-9775-511b02487e7e',
type: [
{
text: 'Office Visit',
coding: [{ system: 'http://example.org/appointment-types', code: 'office-visit' }],
},
],
availableTime: [
{
daysOfWeek: ['mon', 'tue', 'wed', 'thu', 'fri'],
availableStartTime: '09:00:00',
availableEndTime: '17:00:00',
},
],
extension: [
{
url: 'https://medplum.com/fhir/StructureDefinition/SchedulingParameters',
// set duration / alignmentInterval / alignmentTimezone HERE; all schedules booked together must match on these
extension: [
{
url: 'duration',
valueDuration: { value: 1, unit: 'h' },
},
],
},
],
};
{
resourceType: 'Schedule',
id: 'dr-smith-schedule',
comment: "Dr. Smith's Office Visit availability",
actor: [{ reference: 'Practitioner/dr-smith' }],
serviceType: [
{
text: 'Office Visit',
coding: [{ code: 'office-visit' }],
extension: [
{
url: 'https://medplum.com/fhir/service-type-reference',
valueReference: {
reference: 'HealthcareService/23c3f1cc-4f55-4990-9775-511b02487e7e',
display: 'Office Visit',
},
},
],
},
],
};

Override Behavior

A Practitioner's Schedule can override individual scheduling parameters for a specific service type by adding a SchedulingParameters extension that references that service. Only the fields explicitly set on the Schedule override the HealthcareService defaults — all other fields are inherited.

This means you only need to specify what differs. For example, to restrict availability to Tuesday and Thursday mornings while keeping all other parameters (duration, buffers, alignment) from the HealthcareService:

{
resourceType: 'Schedule',
id: 'dr-chen-schedule',
active: true,
comment: 'Dr. Chen - New Patient Visit (Tue/Thu mornings only)',
actor: [{ reference: 'Practitioner/dr-chen' }],
serviceType: [
{
coding: [{ code: 'new-patient-visit' }],
extension: [
{
url: 'https://medplum.com/fhir/service-type-reference',
valueReference: {
reference: 'HealthcareService/f44bbf25-bf57-4263-8f10-be060cc91672',
display: 'New Patient Visit',
},
},
],
},
],
extension: [
{
url: 'https://medplum.com/fhir/StructureDefinition/SchedulingParameters',
extension: [
{
// required: identifies which HealthcareService these parameters override
url: 'service',
valueReference: {
reference: 'HealthcareService/f44bbf25-bf57-4263-8f10-be060cc91672',
display: 'New Patient Visit',
},
},
{
// overridden here; duration, buffers, and alignment are inherited from HealthcareService
url: 'availability',
extension: [
{
url: 'availableTime',
extension: [
{ url: 'daysOfWeek', valueCode: 'tue' },
{ url: 'daysOfWeek', valueCode: 'thu' },
{ url: 'availableStartTime', valueTime: '09:00:00' },
{ url: 'availableEndTime', valueTime: '13:00:00' },
],
},
],
},
],
},
],
};

Field-level inheritance: When a Schedule has a SchedulingParameters extension for a service, each field is resolved independently using this priority order (highest to lowest):

  1. The field value from the Schedule's SchedulingParameters extension for that service
  2. The field value from the HealthcareService's SchedulingParameters extension
  3. The system default (0 for buffers and offset; 60 minutes for alignment interval; always-available for availability)
  4. Per-actor timezone information (via Schedule.actor; only for timezone attribute)

If a Schedule has no SchedulingParameters extension at all, all parameters are inherited from this chain.

Blocking Time by Service Type

Here is an example of a Slot resource that blocks time for a specific service type.

{
resourceType: 'Slot',
schedule: { reference: 'Schedule/dr-johnson-schedule' },
status: 'busy-unavailable',
start: '2025-12-24T08:00:00Z',
end: '2025-12-27T07:59:59Z',
comment: 'Holiday vacation',
serviceType: [{ coding: [{ code: 'office-visit' }] }],
};
  • With serviceType: Blocks only that specific service
  • Without serviceType: Blocks all services

Timezone Resolution

Timezone per Scheduling Parameters Entry

The timezone parameter allows you to specify different timezones for different service types within the same Schedule. This is useful when a provider needs to define availability in different timezones for different services (e.g., a doctor who provides cardiac surgery where they might travel to in one time zone and call center availability in another time zone).

Fallback Logic: If no time zone is specified in the scheduling-parameters extension, then the availability will be interpreted in the time zone defined on the Schedule's actor reference (Practitioner, Location, or Device). It looks for the FHIR sanctioned time zone extension:

Adding a Timezone to an Actor

There is no native timezone field on Practitioner, Location, or Device, so you must add it via the FHIR timezone extension:

{
resourceType: 'Practitioner',
// ...
extension: [
{
url: 'http://hl7.org/fhir/StructureDefinition/timezone',
valueCode: 'America/Los_Angeles',
},
],
};

Timezone Resolution Order:

  1. If timezone is specified in the scheduling-parameters extension of a Schedule resource, use that time zone
  2. If timezone is specified in the scheduling-parameters extension of a HealthcareService resource, use that time zone
  3. Otherwise, fall back to the time zone defined on the Schedule's actor reference (Practitioner, PractitionerRole, Location, or Device)

Important Notes:

  • The time zone value should be an IANA time zone identifier (e.g., America/New_York, America/Los_Angeles, America/Miami)
  • When timezone is specified, all Time values in the availability extension are interpreted in that time zone

Here is an example of a Schedule with multiple service types, each with its own time zone:

{
resourceType: 'Schedule',
id: 'dr-smith-schedule',
comment: 'Dr. Smith - Cardiac Surgery (PT) and Call Center (ET)',
actor: [{ reference: 'Practitioner/dr-smith' }],
serviceType: [
{
text: 'Cardiac Surgery',
coding: [{ code: 'cardiac-surgery' }],
},
{
text: 'Call Center Availability',
coding: [{ code: 'call-center-availability' }],
},
],
extension: [
{
url: 'https://medplum.com/fhir/StructureDefinition/SchedulingParameters',
extension: [
{
url: 'service',
valueReference: {
reference: 'HealthcareService/a8f88a98-2578-4644-b408-7ba73f104298',
display: 'Cardiac Surgery',
},
},
{
url: 'timezone',
valueCode: 'America/Los_Angeles',
},
{
url: 'duration',
valueDuration: { value: 1, unit: 'h' },
},
{
url: 'availability',
extension: [
{
url: 'availableTime',
extension: [
{ url: 'daysOfWeek', valueCode: 'mon' },
{ url: 'daysOfWeek', valueCode: 'tue' },
{ url: 'daysOfWeek', valueCode: 'wed' },
{ url: 'availableStartTime', valueTime: '11:00:00' }, // Interpreted in America/Los_Angeles
{ url: 'availableEndTime', valueTime: '15:00:00' }, // Interpreted in America/Los_Angeles
],
},
],
},
],
},
{
url: 'https://medplum.com/fhir/StructureDefinition/SchedulingParameters',
extension: [
{
url: 'timezone',
valueCode: 'America/New_York',
},
{
url: 'service',
valueReference: {
reference: 'HealthcareService/0dbe6bf1-40b8-4204-a406-f78b5a0e59d0',
display: 'Call Center Availability',
},
},
{
url: 'duration',
valueDuration: { value: 1, unit: 'h' },
},
{
url: 'availability',
extension: [
{
url: 'availableTime',
extension: [
{ url: 'daysOfWeek', valueCode: 'mon' },
{ url: 'daysOfWeek', valueCode: 'tue' },
{ url: 'daysOfWeek', valueCode: 'wed' },
{ url: 'availableStartTime', valueTime: '09:00:00' }, // Interpreted in America/New_York
{ url: 'availableEndTime', valueTime: '17:00:00' }, // Interpreted in America/New_York
],
},
],
},
],
},
],
};

In this example:

  • Cardiac surgery availability is defined in America/Los_Angeles time zone (Mon-Wed 11am-3pm America/Los Angeles)
  • Call Center availability is defined in America/New_York time zone (Mon-Wed 9am-5pm Eastern)
  • Each service type's availability times are interpreted independently based on their respective timezones

Examples

Example 1: Simple Primary Care Office with Appointment Type Defaults

This example shows how to define availability for a simple primary care office where Practitioner's Schedules inherit default scheduling parameters from an HealthcareService.

HealthcareService: Office Visit Defaults

This HealthcareService defines default scheduling parameters for a 30-minute office visit with 5-minute buffers and 15-minute alignment intervals.

{
resourceType: 'HealthcareService',
id: 'office-visit',
type: [
{
text: 'Office Visit',
coding: [{ system: 'http://example.org/appointment-types', code: 'office-visit', display: 'Office Visit' }],
},
],
extension: [
{
url: 'https://medplum.com/fhir/StructureDefinition/SchedulingParameters',
extension: [
{
url: 'duration',
valueDuration: { value: 30, unit: 'min' },
},
{ url: 'bufferBefore', valueDuration: { value: 5, unit: 'min' } },
{ url: 'bufferAfter', valueDuration: { value: 5, unit: 'min' } },
{ url: 'alignmentInterval', valueDuration: { value: 15, unit: 'min' } },
{ url: 'alignmentOffset', valueDuration: { value: 0, unit: 'min' } },
],
},
],
};
Schedule: Practitioner's Schedule without Overrides

This Schedule shows Dr. Johnson's availability (Mon-Fri 9am-5pm) that inherits all default parameters from the HealthcareService without any service-specific overrides.

{
resourceType: 'Schedule',
id: 'dr-johnson-schedule',
active: true,
comment: "Dr. Sarah Johnson's Office Visit availability",
actor: [
{
reference: 'Practitioner/dr-johnson',
display: 'Dr. Sarah Johnson',
},
],
planningHorizon: {
start: '2025-01-01T00:00:00Z',
end: '2025-12-31T23:59:59Z',
},
serviceType: [
// This entry will allow using the office-visit shared HealthcareService definitions
{
text: 'Office Visit',
coding: [{ code: 'office-visit' }],
extension: [
{
url: 'https://medplum.com/fhir/service-type-reference',
valueReference: {
reference: 'HealthcareService/office-visit',
display: 'Office Visit',
},
},
],
},
],
// No SchedulingParameters extension: everything is inherited from the HealthcareService
};

Result: Dr. Johnson's schedule inherits all the default parameters from the HealthcareService for an office visit:

  • $find called with service-type=office-visit: For office visits, available to start every 15 minutes (:00, :15, :30, :45) with 5-minute buffers [from HealthcareService]

Example 2: Multi-Service Provider with Multiple Appointment Types and Overrides

A provider who offers different appointment types with varying availability and constraints. Overrides the default scheduling parameters for new patient visits.

HealthcareService: New Patient Visit

This HealthcareService defines a 60-minute new patient visit with 15-minute buffers and 30-minute alignment intervals.

{
resourceType: 'HealthcareService',
id: 'new-patient-visit',
type: [
{
text: 'New Patient Visit',
coding: [
{ system: 'http://example.org/appointment-types', code: 'new-patient-visit', display: 'New Patient Visit' },
],
},
],
extension: [
{
url: 'https://medplum.com/fhir/StructureDefinition/SchedulingParameters',
extension: [
{ url: 'duration', valueDuration: { value: 1, unit: 'h' } },
{ url: 'bufferBefore', valueDuration: { value: 15, unit: 'min' } },
{ url: 'bufferAfter', valueDuration: { value: 15, unit: 'min' } },
{ url: 'alignmentInterval', valueDuration: { value: 30, unit: 'min' } },
],
},
],
};
HealthcareService: Follow-up Visit

This HealthcareService defines a 20-minute follow-up visit with 5-minute buffers and 10-minute alignment intervals for more frequent scheduling.

It defines default availability of Monday-Friday, 9am-5pm.

{
resourceType: 'HealthcareService',
id: 'follow-up-visit',
type: [
{
text: 'Follow-up Visit',
coding: [{ system: 'http://example.org/appointment-types', code: 'follow-up', display: 'Follow-up Visit' }],
},
],
availableTime: [
{
daysOfWeek: ['mon', 'tue', 'wed', 'thu', 'fri'],
availableStartTime: '09:00:00',
availableEndTime: '17:00:00',
},
],
extension: [
{
url: 'https://medplum.com/fhir/StructureDefinition/SchedulingParameters',
extension: [
{ url: 'duration', valueDuration: { value: 20, unit: 'min' } },
{ url: 'bufferBefore', valueDuration: { value: 5, unit: 'min' } },
{ url: 'bufferAfter', valueDuration: { value: 5, unit: 'min' } },
{ url: 'alignmentInterval', valueDuration: { value: 10, unit: 'min' } },
],
},
],
};
Schedule: Multi-Service with Overrides

This schedule declares in its serviceType array that it can be booked for New Patient visits and Follow-Up visits.

This Schedule uses the shared availability from the "Follow-Up" service (Mon-Fri 9am-5pm). It overrides "New Patient Visit" appointment type to only be available on Tuesday and Thursday mornings (9am-1pm).

{
resourceType: 'Schedule',
id: 'dr-chen-schedule',
active: true,
comment: 'Dr. Chen - New Patient and Follow-up Visits',
actor: [{ reference: 'PractitionerRole/dr-chen' }],
planningHorizon: {
start: '2025-01-01T00:00:00Z',
end: '2025-12-31T23:59:59Z',
},
serviceType: [
{
text: 'New Patient Visit',
coding: [{ system: 'http://example.org/appointment-types', code: 'new-patient-visit' }],
extension: [
{
url: 'https://medplum.com/fhir/service-type-reference',
valueReference: {
reference: 'HealthcareService/new-patient-visit',
display: 'New Patient Visit',
},
},
],
},
{
text: 'Follow-up Visit',
coding: [{ system: 'http://example.org/appointment-types', code: 'follow-up' }],
extension: [
{
url: 'https://medplum.com/fhir/service-type-reference',
valueReference: {
reference: 'HealthcareService/follow-up',
display: 'Follow-up Visit',
},
},
],
},
],
extension: [
// New patient visits only on Tuesday and Thursday mornings
{
url: 'https://medplum.com/fhir/StructureDefinition/SchedulingParameters',
extension: [
{
url: 'service',
valueReference: {
reference: 'HealthcareService/new-patient-visit',
display: 'New Patient Visit',
},
},
{
url: 'availability',
extension: [
{
url: 'availableTime',
extension: [
{ url: 'daysOfWeek', valueCode: 'tue' },
{ url: 'daysOfWeek', valueCode: 'thu' },
{ url: 'availableStartTime', valueTime: '09:00:00' },
{ url: 'availableEndTime', valueTime: '13:00:00' },
],
},
],
},
],
},
],
};

Result:

  • New patient visits (ie. $find with the "New patient visit" HealthcareService): Tue/Thu 9am-1pm only, 60 minutes, can start every 30 minutes, 15-min buffers
  • Follow-ups (ie. $find called with the "Follow-up visit" HealthcareService): Mon-Fri 9am-5pm, 20 minutes, can start every 10 minutes, 5-min buffers

Example 3: Location-Specific Complex Surgical Scheduling

A bariatric surgery requiring surgeon, OR room, and anesthesiologist coordination.

HealthcareService: Bariatric Surgery

This HealthcareService defines scheduling for a 120-minute surgical procedure with 45/30-minute buffers.

{
resourceType: 'HealthcareService',
id: 'bariatric-surgery',
type: [
{
coding: [{ system: 'http://snomed.info/sct', code: '287809009', display: 'Bariatric Surgery' }],
},
],
extension: [
{
url: 'https://medplum.com/fhir/StructureDefinition/SchedulingParameters',
extension: [
{ url: 'duration', valueDuration: { value: 120, unit: 'min' } },
{ url: 'bufferBefore', valueDuration: { value: 45, unit: 'min' } },
{ url: 'bufferAfter', valueDuration: { value: 30, unit: 'min' } },
{ url: 'alignmentInterval', valueDuration: { value: 30, unit: 'min' } },
],
},
],
};
Schedule: Surgeon Availability

This Schedule shows Dr. Martinez's availability for bariatric surgeries, limited to Tuesday and Thursday mornings (8am-4pm).

{
resourceType: 'Schedule',
id: 'surgeon-martinez-schedule',
active: true,
comment: 'Dr. Maria Martinez (Surgeon) - Bariatric Surgery',
serviceType: [
{
coding: [{ system: 'http://snomed.info/sct', code: '287809009' }],
extension: [
{
url: 'https://medplum.com/fhir/service-type-reference',
valueReference: { reference: 'HealthcareService/bariatric-surgery' },
},
],
},
],
actor: [
{
reference: 'Practitioner/surgeon-martinez',
display: 'Dr. Maria Martinez - Bariatric Surgeon',
},
],
extension: [
{
url: 'https://medplum.com/fhir/StructureDefinition/SchedulingParameters',
extension: [
{
url: 'service',
valueReference: { reference: 'HealthcareService/bariatric-surgery' },
},
{
url: 'availability',
extension: [
{
url: 'availableTime',
extension: [
{ url: 'daysOfWeek', valueCode: 'tue' },
{ url: 'daysOfWeek', valueCode: 'thu' },
{ url: 'availableStartTime', valueTime: '08:00:00' },
{ url: 'availableEndTime', valueTime: '16:00:00' },
],
},
],
},
],
},
],
};
Schedule: Operating Room Availability

This Schedule shows Operating Room 3's availability for surgical procedures, available weekdays 7am-7pm with extended 12-hour blocks.

{
resourceType: 'Schedule',
id: 'or-3-schedule',
active: true,
comment: 'Operating Room 3 - Bariatric Surgery',
actor: [
{
reference: 'Location/or-3',
display: 'Operating Room 3',
},
],
serviceType: [
{
coding: [{ system: 'http://snomed.info/sct', code: '287809009' }],
extension: [
{
url: 'https://medplum.com/fhir/service-type-reference',
valueReference: { reference: 'HealthcareService/bariatric-surgery' },
},
],
},
],
extension: [
{
url: 'https://medplum.com/fhir/StructureDefinition/SchedulingParameters',
extension: [
{
url: 'service',
valueReference: { reference: 'HealthcareService/bariatric-surgery' },
},
{
url: 'availability',
extension: [
{
url: 'availableTime',
extension: [
{ url: 'daysOfWeek', valueCode: 'mon' },
{ url: 'daysOfWeek', valueCode: 'tue' },
{ url: 'daysOfWeek', valueCode: 'wed' },
{ url: 'daysOfWeek', valueCode: 'thu' },
{ url: 'daysOfWeek', valueCode: 'fri' },
{ url: 'availableStartTime', valueTime: '07:00:00' },
{ url: 'availableEndTime', valueTime: '19:00:00' },
],
},
],
},
],
},
],
};
Schedule: Anesthesiologist Availability

This Schedule shows Dr. Kim's availability for surgical procedures, covering weekdays 7am-5pm (10-hour blocks).

{
resourceType: 'Schedule',
id: 'anesthesiologist-kim-schedule',
active: true,
comment: 'Dr. James Kim (Anesthesiologist) - Bariatric Surgery',
serviceType: [
{
coding: [{ system: 'http://snomed.info/sct', code: '287809009' }],
extension: [
{
url: 'https://medplum.com/fhir/service-type-reference',
valueReference: { reference: 'HealthcareService/bariatric-surgery' },
},
],
},
],
actor: [
{
reference: 'Practitioner/anesthesiologist-kim',
display: 'Dr. James Kim - Anesthesiologist',
},
],
extension: [
{
url: 'https://medplum.com/fhir/StructureDefinition/SchedulingParameters',
extension: [
{
url: 'service',
valueReference: { reference: 'HealthcareService/bariatric-surgery' },
},
{
url: 'availability',
extension: [
{
url: 'availableTime',
extension: [
{ url: 'daysOfWeek', valueCode: 'mon' },
{ url: 'daysOfWeek', valueCode: 'tue' },
{ url: 'daysOfWeek', valueCode: 'wed' },
{ url: 'daysOfWeek', valueCode: 'thu' },
{ url: 'daysOfWeek', valueCode: 'fri' },
{ url: 'availableStartTime', valueTime: '07:00:00' },
{ url: 'availableEndTime', valueTime: '17:00:00' },
],
},
],
},
],
},
],
};

Result: When booking a bariatric surgery, the system queries all three schedules, calculates the intersection of availability, and creates atomic transaction bundles to book all required resources simultaneously.

Example 4: Canonical Seed Bundle — One Service, Multiple Practitioners and Rooms

This is a complete, uploadable seed bundle for the common case of a single service (here, a "Surgical Procedure") that is bookable across multiple practitioners and rooms. It is the recommended starting point for configuring a multi-schedule setup.

It demonstrates the patterns from Common Pitfalls:

  • type: transaction so a single bad entry rolls back the whole bundle (see FHIR Batch Requests).
  • urn:uuid fullUrls so resources can cross-reference each other before they have server-assigned IDs.
  • comment, not name, on each Schedule.
  • Practitioner/Location actors each carrying a timezone extension.
  • ifNoneExist on the Organization and HealthcareService so re-running the seed is idempotent for those resources.

Each Schedule sets only its own availability and inherits duration, buffers, and alignment from the HealthcareService. Add more practitioners or rooms by duplicating the Practitioner/Location + Schedule pair.

note

The service code below uses a placeholder http://example.org/appointment-types system. Replace it with a real SNOMED CT or CPT code before using this in production.

{
resourceType: 'Bundle',
// type: transaction so a bad entry rolls back instead of silently partial-committing
type: 'transaction',
entry: [
{
fullUrl: 'urn:uuid:caae036f-8872-4d5c-ac2b-bbb37df5f08e',
resource: {
resourceType: 'Organization',
name: 'Bayview Surgery Center',
identifier: [{ system: 'http://example.org/organizations', value: 'bayview-surgery-center' }],
},
request: {
method: 'POST',
url: 'Organization',
// ifNoneExist makes re-running the seed idempotent (conditional create)
ifNoneExist: 'identifier=http://example.org/organizations|bayview-surgery-center',
},
},
{
fullUrl: 'urn:uuid:fe101ba5-44f9-45e9-803c-201605a5dde3',
resource: {
resourceType: 'HealthcareService',
providedBy: { reference: 'urn:uuid:caae036f-8872-4d5c-ac2b-bbb37df5f08e', display: 'Bayview Surgery Center' },
name: 'Surgical Procedure',
type: [
{
text: 'Surgical Procedure',
coding: [
{
system: 'http://example.org/appointment-types',
code: 'surgical-procedure',
display: 'Surgical Procedure',
},
],
},
],
extension: [
{
url: 'https://medplum.com/fhir/StructureDefinition/SchedulingParameters',
// set duration / alignmentInterval / alignmentTimezone HERE; all schedules booked together must match on these
extension: [
{
url: 'duration',
valueDuration: { value: 120, unit: 'min', system: 'http://unitsofmeasure.org', code: 'min' },
},
{
url: 'bufferBefore',
valueDuration: { value: 30, unit: 'min', system: 'http://unitsofmeasure.org', code: 'min' },
},
{
url: 'bufferAfter',
valueDuration: { value: 30, unit: 'min', system: 'http://unitsofmeasure.org', code: 'min' },
},
{
url: 'alignmentInterval',
valueDuration: { value: 30, unit: 'min', system: 'http://unitsofmeasure.org', code: 'min' },
},
{ url: 'alignmentTimezone', valueCode: 'America/Los_Angeles' },
],
},
],
identifier: [{ system: 'http://example.org/serviceTypes', value: 'surgical-procedure' }],
},
request: {
method: 'POST',
url: 'HealthcareService',
ifNoneExist: 'identifier=http://example.org/serviceTypes|surgical-procedure',
},
},
{
fullUrl: 'urn:uuid:39ff8f6a-5557-496a-b5cd-05f61899d223',
resource: {
resourceType: 'Location',
name: 'OR-1',
mode: 'instance',
extension: [{ url: 'http://hl7.org/fhir/StructureDefinition/timezone', valueCode: 'America/Los_Angeles' }],
},
request: { method: 'POST', url: 'Location' },
},
{
fullUrl: 'urn:uuid:a152fbf8-aef5-450f-9868-0819bb06a1cd',
resource: {
resourceType: 'Practitioner',
name: [{ given: ['Maria'], family: 'Martinez', prefix: ['Dr.'] }],
extension: [{ url: 'http://hl7.org/fhir/StructureDefinition/timezone', valueCode: 'America/Los_Angeles' }],
},
request: { method: 'POST', url: 'Practitioner' },
},
{
resource: {
resourceType: 'Schedule',
active: true,
// Schedule has no 'name' field in R4 - use comment for a label
comment: 'Dr. Martinez (Surgeon) - Surgical Procedure',
actor: [{ reference: 'urn:uuid:a152fbf8-aef5-450f-9868-0819bb06a1cd', display: 'Dr. Maria Martinez' }],
serviceType: [
{
text: 'Surgical Procedure',
coding: [{ system: 'http://example.org/appointment-types', code: 'surgical-procedure' }],
extension: [
{
url: 'https://medplum.com/fhir/service-type-reference',
valueReference: {
reference: 'urn:uuid:fe101ba5-44f9-45e9-803c-201605a5dde3',
display: 'Surgical Procedure',
},
},
],
},
],
extension: [
{
url: 'https://medplum.com/fhir/StructureDefinition/SchedulingParameters',
extension: [
{
url: 'service',
valueReference: {
reference: 'urn:uuid:fe101ba5-44f9-45e9-803c-201605a5dde3',
display: 'Surgical Procedure',
},
},
{
url: 'availability',
extension: [
{
url: 'availableTime',
extension: [
{ url: 'daysOfWeek', valueCode: 'tue' },
{ url: 'daysOfWeek', valueCode: 'thu' },
{ url: 'availableStartTime', valueTime: '08:00:00' },
{ url: 'availableEndTime', valueTime: '16:00:00' },
],
},
],
},
],
},
],
},
request: { method: 'POST', url: 'Schedule' },
},
{
resource: {
resourceType: 'Schedule',
active: true,
comment: 'OR-1 - Surgical Procedure',
actor: [{ reference: 'urn:uuid:39ff8f6a-5557-496a-b5cd-05f61899d223', display: 'OR-1' }],
serviceType: [
{
text: 'Surgical Procedure',
coding: [{ system: 'http://example.org/appointment-types', code: 'surgical-procedure' }],
extension: [
{
url: 'https://medplum.com/fhir/service-type-reference',
valueReference: {
reference: 'urn:uuid:fe101ba5-44f9-45e9-803c-201605a5dde3',
display: 'Surgical Procedure',
},
},
],
},
],
extension: [
{
url: 'https://medplum.com/fhir/StructureDefinition/SchedulingParameters',
extension: [
{
url: 'service',
valueReference: {
reference: 'urn:uuid:fe101ba5-44f9-45e9-803c-201605a5dde3',
display: 'Surgical Procedure',
},
},
{
url: 'availability',
extension: [
{
url: 'availableTime',
extension: [
{ url: 'daysOfWeek', valueCode: 'mon' },
{ url: 'daysOfWeek', valueCode: 'tue' },
{ url: 'daysOfWeek', valueCode: 'wed' },
{ url: 'daysOfWeek', valueCode: 'thu' },
{ url: 'daysOfWeek', valueCode: 'fri' },
{ url: 'availableStartTime', valueTime: '07:00:00' },
{ url: 'availableEndTime', valueTime: '19:00:00' },
],
},
],
},
],
},
],
},
request: { method: 'POST', url: 'Schedule' },
},
],
};

Location Modeling

Location Hierarchy Pattern

Organization (Surgery Center)
└─ Location (Building) [mode=kind]
├─ Location (Operating Rooms) [mode=kind, type=OR]
│ ├─ Location (OR-1) [mode=instance]
│ ├─ Location (OR-2) [mode=instance]
│ └─ Location (OR-3) [mode=instance]
└─ Location (Recovery Rooms) [mode=kind, type=RR]
├─ Location (Recovery-A) [mode=instance]
└─ Location (Recovery-B) [mode=instance]

Specific vs. "Any Available" Room

Specific room required:

  • Query Schedule?actor=Location/or-3

Any OR room acceptable:

  • Query: Schedule?actor:Location.partof:Location.type=OR

Best Practices

1. Set Defaults on HealthcareService, Override Only What Differs on Schedule

Define duration, buffers, and alignment once on the HealthcareService. Only add a SchedulingParameters extension to a Schedule when that actor's availability or parameters differ from the service defaults. Omit any field that should be inherited.

2. Minimize Pre-Generated Slots

Only create Slot resources for:

  • Booked appointments (status: busy)
  • Blocked time (status: busy-unavailable)

Let $find calculate available windows dynamically.

Common Pitfalls

A few constraints trip people up most often when configuring availability:

  • Schedule has no name element in FHIR R4. Use comment for a human-readable label — sending name fails validation with Invalid additional property "name". See The Schedule Resource.
  • A Schedule's parameters override, they don't merge. Setting availability on a Schedule fully replaces the service's availableTime rather than narrowing it. See Override Behavior.
  • To book across multiple schedules at once, duration, alignmentInterval, alignmentOffset, and alignmentTimezone must match across them. Prefer setting these only on the HealthcareService.

Beta Status

The Scheduling API is under active development. This beta release of the scheduling API is expected to gain additional capabilities.

  • bookingLimit - An upcoming scheduling parameter that will allow you to express how often a given service type may be added to a schedule. This is not yet implemented.