Medplum Monthly Update - September 2026
PlumCon 2026 happened on September 3 in San Francisco. Thank you to everyone who spoke, demoed, and came. It was the largest gathering of people building on Medplum yet, and the community demos are posted on the lineup page.
September brought 153 commits from 21 contributors, with six patch releases, v5.1.37 through v5.1.42.
Scheduling gained rescheduling, overbooking, either/or participant search, and a workspace for configuring visit types. A new Cron resource decouples a Bot's schedule from the Bot itself. The Provider App grew a full billing setup flow, and a long list of identity and session hardening landed on the server. Two new long-form posts went up: the Awell Panels case study and How Medplum thinks about FHIR.
All of this continues to drive forward our 2026 roadmap priorities.
Features
Scheduling
Scheduling was again the largest area of the month. August built the booking flow; September made the scheduling workspace handle the situations a real front desk hits every day:
- Rescheduling — A new
Appointment/$rescheduleoperation moves an existing appointment to a new time or a new set of schedules in a single transaction: it releases the slots the appointment holds, validates the new time, creates the new slots, and updates the appointment in place. The reschedule form in Storybook shows the flow. Alongside it,Appointment/$findaccepts anignore-appointmentparameter that computes availability as if the named appointment did not exist. Without that, an 11am visit with Dr. Smith in room one could never find 11am with Dr. Smith in room two, because Dr. Smith was busy at 11am with the very appointment being moved - Overbooking — A
slotCapacityparameter on the scheduling parameters extension sets how many appointments may share a time. Each booked slot is stamped with the capacity it was created under, and a new booking is admitted only while every overlapping booking stays under its own capacity, so the strictest rule wins. A capacity of one remains exclusive, and slots booked before this change read back as capacity one - Either/or participants — The booking form's participant fields previously intersected every selected actor's schedule, which made "provider A and B" easy and "provider A or B" impossible without two searches. An Add another control (Storybook) now expresses alternatives, so a search can ask for either room C or D, with device E, with provider A, and with either B or C, in one
$find - Visit type configuration — A
SchedulingConfigWorkspacein@medplum/react-schedulinglists every visit type and opens the selected one for editing in place. Practice admins had been hand-editingHealthcareServiceresources to do this. ASchedulingParametersEditoredits duration, buffers, alignment, capacity, and time zones at either level, the service's own parameters or a schedule's override for one service, and the weekly hours editor was restructured so a parent form can hold its state and save once
- Prior authorization fields — A visit type can declare that it requires prior authorization through a
requires-prior-authcoding onHealthcareService.eligibility. Booking such a visit adds procedure codes, diagnosis codes, and a medical-necessity confirmation to the booking form, with custom values allowed in the code fields
- Booking outside the rules — A
canBypassSchedulingRulesprop on the workspace and booking forms adds a Date & time and Minutes field above the offered times (Storybook). The time finder is right for almost everyone, but a scheduler squeezing in an urgent patient or running a visit long needs a way to place a visit the rules would refuse - More days at once — The time finder has a Show more days button that loads two additional days at a time, requesting only days not already answered so results on screen stay put. The results column scrolls on its own so a long list does not push the form off screen
- Appointment details and cancellation — Selecting an appointment on the calendar opens its details in the side pane, with a cancel action.
Appointment/$canceltakes an optional structuredcancelationReason, the form also accepts free text, and cancelled appointments no longer render on the calendar
- Calendar filters — The workspace calendar filters by location and visit type using a typeahead, which is what makes it usable for installs with many of each. Calendar colors stay stable across filter changes
- Time zones — When offered times or viewed schedules are in a different time zone than the viewer's, the workspace says so, in the slot labels and in a notice above the calendar (Storybook). On the server,
$findno longer fails when an actor's resource cannot be read under the caller's access policy if a time zone is available from the service or schedule
- Inactive services and schedules — A
HealthcareServiceorSchedulemarkedactive: falseis rejected by$find,$book,$hold, and$reschedule.$cancelis deliberately unaffected, so appointments booked before a deactivation remain cancelable, and$reschedulecan move an appointment from an inactive schedule to an active one - Appointment site — The site chosen in the booking form is now stored on the appointment as a
Locationreference insupportingInformation, readable throughgetAppointmentSitein@medplum/core - Smaller changes — Participants are named from the loaded resource rather than a reference display that may be absent, the UI says "actor type" instead of "role" to avoid collision with
PractitionerRole, more calendar statuses are styled so callers decide what to show, the scheduling parameter helpers in@medplum/corework on bothHealthcareServiceandSchedule, and operation-created appointments are stamped with the server version
There is also a new scheduling decision guide with discovery questions and a scoping matrix (Everett Williams).
Provider App
David Yanez and
Finn Bergquist
The Provider App billing surface introduced in August became a complete setup flow (roadmap):
- Billing providers — A billing providers tab lists and edits the organizations and practitioners claims are rendered by. Saving a practitioner writes NPI and taxonomy and either points the active
PractitionerRoleat a billing organization or clears it so the practitioner bills individually. Individual billers with an incomplete address are flagged. An existingOrganizationcan be added as a billing provider rather than re-entered - Live registration check — As an NPI is typed, the app looks it up against the claims processor and shows whether the provider is registered, unregistered, or the check failed, and the save button waits on the result. Existing providers take an edit path rather than a duplicate create. Provider contracts are checked too, so a claim is not built against a provider with no effective contract
- Billing in the visit — Visit details show the billing organization, the encounter eligibility check lets the user pick the provider, and the eligibility request inbox was reworked. The get-started flow now includes an initial visit and billing setup
- Diagnoses from lab orders — When lab orders are submitted during an encounter, the diagnoses on the order are added to the encounter as conditions, so the chart and the claim agree without re-entry
- Encounter review — A
ClinicalImpressionis created on review if the encounter does not have one, which matters for encounters created outside the Provider App - Lab results display — Leaf observations whose value is
DNR(Do Not Report) are suppressed inDiagnosticReportDisplay, andNoteDisplaypreserves line breaks and renders in monospace, so a microbiology antibiogram delivered as a pre-formatted note keeps its columns (Finn Bergquist) - Polish — Loading states in the timeline and patient summary, an encounter title overlap fix, and audio attachments play inline in the Medplum App (Andy Stoneman)
PlanDefinition/$apply— Prefers the active version of a canonical definition when several versions exist
Bots and Automation
- The
Cronresource — A Bot's schedule used to live on the Bot itself, which tied one Bot to one schedule, gave no way to pass input to a scheduled run, and no way to run under an identity other than the Bot's own. The newCronresource owns the schedule instead:onBehalfOfnames theProjectMembershipthe run assumes,parametercarries input, andendTimebounds it. The same Bot can run on several schedules under different access policies, and a Bot in a linked project can be scheduled by each customer project that links to it while running with that project's permissions. Invalid cron strings and unreadable references are rejected on write rather than becoming jobs that silently never fire. The cron guide covers the details - Raw webhook bodies — Public webhook Bots received parsed JSON, which discards the exact bytes a provider such as a payment processor signs. An opt-in
Bot.rawBodysetting delivers the original request text inevent.inputso signatures can be verified. Existing Bots are unchanged. See consuming webhooks - Bot input storage — A
storeBotInputserver config flag controls whether each invocation's input is written to binary storage. It defaults on, so nothing changes unless a deployment turns it off
Platform and API
Cody Ebberson,
Noah Silas, and
Matt Willer
- Customizable
CapabilityStatement— Medplum supports all of FHIR R4, but an implementer may expose a subset: a consumer-facing endpoint that does not accept patient-entered data, or a deployment limited to USCDI concepts./fhir/R4/metadataadvertised everything unconditionally. The generated statement can now be adjusted through server configuration, so a deployment states what it supports while still receiving updates as Medplum adds resource types, search parameters, and profiles - Request correlation — A new
X-Medplum-Log-Tagrequest header attaches up to 128 characters of caller-supplied context to server logs, so an integration authenticating through one machine-to-machine client can still correlate requests back to its end users. Trace IDs were aligned with W3C Trace Context, responses now carryX-Request-IdandX-Trace-Id, and outboundtraceparentheaders are constructed properly. Both are covered in log streaming - Project-scoped URLs — WebSocket subscriptions and DICOMweb are reachable under
/projects/{projectId}/, matching the rest of the API. AMedplumClientconfigured with a project-scoped base URL previously could not open a subscription socket - Expunge tombstones — After
$expungehard-deletes a resource and its history, a tombstone row tagged with the ISO 21089destroylifecycle code is written to the history table, so a permanent erasure leaves a record that it happened (Karl Pietrzak) - Email verification — The Google sign-in path sends the verification email and honors Google's
email_verifiedclaim, and external and token-exchange sign-ins verify automatically; existing unverified Google users are upgraded in place (Andy Stoneman) - Medication cart contents — Vendor-neutral types and a
getCarthook read a patient's e-prescribe cart as the prescribing system holds it, reconciled against local draftMedicationRequests. Counting drafts alone was a lower bound: it missed lines staged through another UI and could not confirm whether a failed removal left a line behind (Oleg Rocklin) - Lab orders — An order-time override for the physician-level performing lab account number, mirroring the existing practice-level override, so a provider with several accounts at one lab picks between them per order (Finn Bergquist)
- Smaller changes — A
Login.projectsearch parameter (Philip Knott), askipEmailoption on$update-user-email(Reshma Khilnani),_id:missinghandled correctly (Matt Long),ValueSet/$expanduses supplied designations,POST /admin/projects/:id/clientno longer lets a request body dropmeta.projectand orphan the new client, settledValueSetprobe results stay cached across unmount in@medplum/react-hooks, and auseStabilizedCallbackhook replaces several ad hoc ref patterns
AI
- Newer models and reasoning effort — The
$aioperation and Spaces can use the latest OpenAI models, and callers can set the reasoning effort per request. Spaces exposes the model choice in its settings
Revenue Cycle and Billing
The Provider App work above rides on a set of revenue cycle capabilities in the claims integrations:
- Billing setup operations — Bots that take FHIR inputs and return FHIR resources for the payer directory, provider creation and editing, provider listing, and contract lookup. The payer directory moved to the processor's newer API and returns richer metadata as extensions: payer category, eligibility and remittance payer IDs, alternate names, and per-capability support. There is a payer directory lookup guide
- Retry-safe claim submission — An interrupted submission can be safely retried: the payload is checked against the stored
Claim, identifier writeback is version-checked, and a single conditionalClaimResponseis written. Telehealth encounters now submit with the correct place of service rather than defaulting to an office visit, and a NUCC taxonomy is no longer required on the rendering practitioner - Self-pay — A self-pay coverage profile is accepted on claims alongside insurance coverage
- Patient balances and payments — A shared patient accounts-receivable sync writes a normalized patient-balance summary onto the claim's
ClaimResponseplus an itemizationDocumentReference, and an idempotent patient payment posting operation records a payment collected in a payment processor exactly once, keyed on the processor's payment ID - Caller policies — An installer creates one tagged
AccessPolicyper shared billing capability in a customer project, replacing hand-written policy entries that broke whenever the shared code changed - Eligibility — Eligibility checks accept a
PractitionerorPractitionerRoleas the provider, not only anOrganization, across both supported processors - Clearinghouse — A
CodeSystemfor X12 service type codes,Claim.facilitymapped to the service facility location on submission, and claim mode now follows the API key rather than a separate test-mode flag that the clearinghouse rejected
E-Prescribing
- Cart contents — A
$get-cartoperation returns the current contents of a patient's medication cart from the prescribing system, so the cart badge reflects what the prescriber will actually see at signing - Deletions — A prescription deleted in the prescribing system previously reached Medplum only when someone re-ran patient sync. Deletion events are now handled when they arrive, across every envelope shape the vendor might post
- Concurrency — Two patient-sync runs racing (a second tab, a strict-mode double mount) could each create a remote patient, with the later write orphaning the first. A version-checked claim on the
Patientensures exactly one run creates the remote record; an existing remote ID is never replaced - Pharmacies — Backend callers can search pharmacies with the same admin fallback drug search uses, shared pharmacy records are created conditionally instead of duplicated, and a patient's preferred pharmacy can be removed or replaced in both systems. The pharmacy workflow docs were expanded to match
- Smaller changes — A per-project iframe template via a project secret, drug search returns marketed formulations even after one format is saved, drug interaction checks exit cleanly when there are no current medications, drug description length is capped below the NCPDP limit, and a supervising prescriber operation with its own guide for the other supported prescribing vendor
Labs, Terminology, and Integrations
Finn Bergquist,
Matt Willer, and
Ian Plunkett
- Lab ordering iframe — The lab network's hosted ordering interface can be launched from Medplum through a type-level operation that matches the patient on demographics and returns an authenticated URL. The iframe guide covers prerequisites, the SDK call, callback behavior, and troubleshooting, and distinguishes it from the results integration
- HIE groundwork — Shared connection, installation, subscription, and deployment helpers were extracted from the lab ordering code so the health information exchange work builds on the same layer without entangling the two (Darren Eam)
- Terminology — SNOMED CT Canadian Edition import, and an ICD-10-CM to CIM-10-FR crosswalk importer that loads the French PMSI classification mapping as a
ConceptMapqueryable through$translate. A new guide explains filtering large code systems into usableValueSets - Log streaming — A project's logs can stream to more than one destination (Matt Long)
Enterprise: Scale, Database, and Self-Hosting
Matt Long,
Matt Willer, and
Karl Pietrzak
Enterprise scale work continued the sharding groundwork from August and tightened the database and operations paths:
- Sharding preparation — Background job payloads now carry a
JobTargetdescribing the shard their repository should connect to, and the global system repository is meaningfully distinct from a shard repository: touching a project-scoped resource type through the global one logs today and will throw once the remaining call sites are cleaned up. Production behavior is unchanged - Indexes — Scoped indexes are used more widely, augmented
Taskindexes and asubject, dateindex onObservationspeed up common chart queries, schema drift detection was fixed, and invalid indexes can be dropped in any schema includingpg_toast - Bulk export memory — Two memory leaks in bulk export were fixed: an unnecessary de-duplication set when cursor pagination already de-duplicates, and backpressure listeners that were never cleaned up
- Operations — A server config setting controls write-through cache behavior, a Super Admin schema version override, a copy-to-clipboard button on the DB stats output, and reentrant async batch processing by default with sync and async batches distinguished in telemetry
- Health check recovery —
/healthcheckreserves a database connection and reuses it so a saturated pool does not fail the check. If that connection dropped, every later check returned 500 until restart, and a load balancer would take the instance out of rotation while the server itself kept working. It now recovers (Cody Ebberson) - Managed Postgres — Schema migration v117 no longer tries to create the
pgstattupleextension, which blocked upgrades on managed Postgres without superuser privileges Enterpriseresource — A newEnterpriseresource type represents a group of projects managed together (Maddy Li)- Email — An incomplete project SMTP configuration returns 400 instead of 500 (Darren Eam), the SMTP options were clarified in the docs, and the environment variable for requiring a verified email before project creation now works (Cody Ebberson)
- Agent and subscriptions — The Agent upgrader names its log at spawn time rather than module load, so each upgrade attempt leaves its own evidence instead of overwriting the last. WebSocket subscription notifications now honor
AccessPolicy.hiddenFields, which every other read path already applied (Derrick Farris)
Compliance
Cody Ebberson,
Matt Willer, and
Matt Long
Compliance work this month was concentrated on identity, sessions, and the evidence trail:
- Backup and recovery — A new page describing how Medplum hosted handles database high availability, backups, and recovery. All of it is in the SOC 2 report, but that document is dense, and these were the questions we kept getting asked
- Malware scanning — A
Binary/$scanoperation returns the AWS GuardDuty malware scan result for a stored object as anOperationOutcome, requesting an on-demand scan when no result exists. It works with automatic scanning off and never re-scans an object that already has a verdict (Derrick Farris) - Security request expiry — The
UserSecurityRequestresources behind password reset, invite, and email verification had no expiration and were not invalidated by a newer request, so a reset link in a compromised mailbox stayed redeemable indefinitely. Requests now carryexpiresAt, supersede earlier requests of the same type, and cannot be redeemed twice - External identity providers — The
/auth/externalcallback verifies theid_tokensignature when the provider publishes a JWKS, matching what the token-exchange path already did, with regression coverage that external login is scoped to the client's project. See external identity providers - One
Loginper authorization —/oauth2/authorizereused an existingLoginwhen it found one, which forked a session onto a single resource and let concurrent authorizations clobber each other's PKCE challenge and nonce. Each authorization is now its own grant with fresh per-request state and a fresh refresh secret - Token checks — Stronger checks around ID tokens,
patient/scopes rejected when no patient context is present, the introspection query filter updated, and a test suite that exercises the refresh token grant against real signature verification rather than a mocked one, covering forged tokens, token-type confusion, and cross-project redemption - Access gaps closed — An ordinary member of a linked project could obtain a signed upload URL for another project's
Binaryobjects through$presigned-url; that is closed. MFA verification rate limits were tightened. The$aioperation uses the server's safe fetch for outbound requests - Audit trail —
AuditEventrecords the references returned by a search, system writes are logged, and the$expungetombstone described above gives permanent deletion a durable record - Supply chain — npm installs and Docker base images are hash-pinned, CI workflows declare explicit read-only token permissions, and PR vouching gained a labeled escape hatch, clearing the actionable OpenSSF Scorecard alerts (Derrick Farris)
- Edge protection — The anonymous IP list managed rule was added to the WAF block list for Medplum hosted
- C-CDA conformance — Medication dose quantities in an imported C-CDA now carry the document's value and unit instead of a hard-coded unit, verified against the ONC sample document (contributed by Jack Przybyl)
Documentation
Everett Williams,
Cody Ebberson, and
Darren Eam
Provider and platform
- Scheduling decision guide — Discovery questions, a scoping matrix, and implementation guidance aligned with current scheduling behavior. The workflow guides were reordered into the sequence a build actually follows: intake, scheduling, charting, e-prescribe, referrals, messaging, RCM, data migration, access control. The example
AGENTS.mdlicensure rule was corrected:PractitionerRoleis organized by service line, can reference multiple jurisdictions, and does not itself enforce booking eligibility - Data migration — A decision guide and four new pages on adoption strategy, mapping governance, validation and reconciliation, and testing and acceptance, with the planning, pipeline, and cutover guidance expanded and the examples aligned around one FHIR R4 data flow
- Backup and recovery — Described under Compliance above; the compliance sidebar was reordered alongside it
- Cron jobs — The
Cronresource, including running a shared Bot from a linked project (Derrick Farris) - Reference updates — Consuming webhooks covers
rawBody, log streaming covers the log tag and request and trace IDs, server config covers the new flags, subscriptions notes the limit of 1,000 active subscriptions evaluated per project (Finn Bergquist), and the password reset,$expunge, and$update-user-emailpages reflect this month's changes - Filtering large code systems — How to build a
ValueSetthat narrows a largeCodeSystemto what a form needs (Finn Bergquist) - Azure Blob Storage — Configuring
binaryStoragefor Azure, managed-identity permissions, and the distinction between runtime binary storage and frontend CDN storage (contributed by zhv50) - Contributing — The README contributing section was refreshed (Matt Willer)
Integrations
- Lab ordering iframe — The hosted ordering interface guide described above, linked from the lab integration overview, ordering, and results pages
- Payer directory lookup — Searching the payer directory and using the result in claims and eligibility checks (David Yanez)
- E-prescribe — Pharmacy workflows and the medication cart contents operation, plus a supervising prescriber guide and an updated sandbox eligibility example for the clearinghouse
Bug Fixes
- Busy
Slots that carry aserviceTypenow block only that service when computing availability, as the documentation always said (contributed by Nick Catalano) - C-CDA medication
doseQuantityvalue and unit are mapped from the document rather than a hard-coded[IU](contributed by Jack Przybyl) MEDPLUM_AUTO_DOWNLOAD_ENABLED=falseis parsed as a boolean, so the download worker can actually be disabled from the environment (contributed by Joshua Kelly)- Two Foomedical home page links pointed at routes that did not exist and rendered a blank page (contributed by Krishna More)
- Line numbers in the blame code block were invisible in dark mode (contributed by Aditya Mitra)
Videos
New videos published this month, all on the Medplum YouTube channel.
- Awell Panels - Patient worklists that update themselves: a walkthrough of Panels, the worklist surface from the Awell case study
- Awell Panels - build a Panel from scratch: defining a patient population, configuring how a care team works it, and wiring what happens next
- Pelairo LIS built on Medplum: a modern laboratory information system for pathology labs, from the PlumCon community demos
From the Blog
Longer-form writing published this month:
Awell Panels: Worklists That Update Themselves by Thomas Vande Casteele — How Awell built Panels on self-hosted Medplum and took post-discharge follow-up from two hospitals to 180 facilities in nine months, with a walkthrough and a build-from-scratch demo
How Medplum thinks about FHIR by Maddy Li — Why Medplum treats FHIR as the transactional system of record rather than a read-mostly interface bolted onto proprietary tables
Releases
- v5.1.37 — September 4
- v5.1.38 — September 14
- v5.1.39 — September 15
- v5.1.40 — September 23
- v5.1.41 — September 24
- v5.1.42 — September 25
Looking Ahead
Much of what is in this update was demoed live at PlumCon, and the scheduling work in particular is close to the point where a practice can configure and run its calendar without touching raw FHIR. Expect that to round out in October.
We are also hiring a Head of Security and a Security Engineer. If the Compliance section above reads like the kind of work you want to own, we would like to talk.
Join us on Discord to share feedback or follow along on GitHub. To learn more about anything in this update, or to talk through how it applies to what you are building, contact us at hello@medplum.com.
