Openfront Hospital

Appointments, intake, patients, clinicians, communications, and clinical operations.

Source · Live site · Catalog

Openfront Hospital is a self-hosted care-booking and hospital-operations application. Visitors browse care services, clinicians, and locations, request an appointment, and complete a one-time intake form. Staff work registration, appointments, intake, care queue, encounters, inpatient beds, clinical fulfillment, claims, communications, and reports from /dashboard/platform.

It is built on Next.js 16, React 19, Keystone 6 (GraphQL), PostgreSQL with Prisma, and Tailwind CSS 4.

Start with Getting started.

Concepts

ConceptMeaning
FacilityThe data boundary. Staff are linked to facilities and see their records only
Provider and ClinicianA bookable directory entry and the care-team record behind it
Visit type (AppointmentType)What patients book: duration, buffers, providers, status
Controlled recordA record changed only through a named workflow mutation
Operation keyIdempotency key sent with each workflow call. Repeating a call with the same key returns the first result
Expected versionOptimistic concurrency. A stale version is rejected with "refresh before retrying"
Transition trailWorkflowTransition and ClinicalAuditLog rows written for every change

Who does what

RoleTypical flagsDaily work
Front deskcanAccessDashboard, canManageAppointments, canManagePatients, canManageIntake, canManageCommunicationsConfirm requests, check in, register, send messages
SchedulercanManageScheduling, canManageProvidersSchedule blocks, visit types
CliniciancanAccessDashboard, canManagePatients and clinical scopeEncounters, orders, results
BillingcanManageBillingCoverage, claims, payments
AdministratorcanAccessAllClinicalData, canManageRoles, canManagePeopleSetup, staff, retention

See Roles and permissions.

Tenancy and access

There is one install-wide HospitalProfile (public name, logo, theme, address, contact). It is a branding record, not an organization boundary.

Facility is the data boundary. Staff users are linked to facilities through Facility.staff. Most read rules let a user see a record when they are on the facility's staff, are the record's provider, or are the patient linked to their user account. A role with canAccessAllClinicalData bypasses those filters and sees every facility.

Role has 19 permission flags: canAccessDashboard, canManageAppointments, canManageScheduling, canManagePatients, canManageProviders, canManageIntake, canManageCommunications, canManageBilling, canManageInventory, canManageIntegrations, canManageDataPortability, canManageRetention, canViewReports, canManageOnboarding, canSeeOtherPeople, canEditOtherPeople, canManagePeople, canManageRoles, and canAccessAllClinicalData. Sensitive fields (patient contact, intake responses, message bodies, audit metadata) are gated by specific flags as well as by facility scope.

Dashboard sidebar visibility is presentation only. Keystone list and field access, plus checks inside each workflow mutation, enforce access.

Models

The schema has 63 lists in features/keystone/models. They fall into two groups.

Master data uses normal Keystone CRUD behind permissions, usually canAccessAllClinicalData:

  • Practice: HospitalProfile, Facility, Department, Ward, Room, Bed (create only; status changes go through workflows), plus FacilityImage, DepartmentImage.
  • Care team and catalog: Provider, Clinician, ClinicianImage, AppointmentType, ProviderScheduleBlock, LabTest.
  • Staff: User, Role.

Controlled records deny or omit GraphQL create, update, and delete. Every write goes through a named workflow mutation that takes an operationKey (idempotency) and usually an expectedVersion (optimistic concurrency). Each transition writes a WorkflowTransition and an audit row, and may queue a ClinicalOutboxEvent.

  • Patients: Patient, PatientIdentifier (masked MRN and external IDs), PatientRegistration (provenance and duplicate resolution), PatientConsent (versioned, superseding chain), PatientPortalAccess.
  • Visits: Appointment, IntakeForm, CareQueueTicket, Encounter, EpisodeClosurePolicy.
  • Clinical: VitalSign, MedicalOrder, Specimen, LabResult (versioned with a correction chain), Prescription, Procedure, CarePlan, ClinicalDocument (sign, amend, entered in error).
  • Inpatient: Admission, BedReservation, BedOccupancyEvent.
  • Revenue: PatientCoverage, InsuranceClaim, ClaimVersion, PatientPayment, PatientRefund.
  • Communications: PatientMessage, CommunicationTemplate, CommunicationPreference.
  • Supplies: InventoryItem, InventoryMovement, ImportBatch.
  • Platform: IntegrationInstallation, MachineCredential, IntegrationDeliveryAttempt, IntegrationWebhookReceipt, ClinicalOutboxEvent, ExternalIdentityLink, RetentionPolicy, RecordHold, RetentionDisposition, ExportJob.
  • Evidence and system: WorkflowTransition, ClinicalAuditLog, PhiReadAudit (written by the GraphQL endpoint when PHI fields are read), PasswordResetRequest, RequestRateLimitBucket.

The repository's docs/schema-map.md lists fields, relationships, and state machines for every list.

Main workflow

  1. Set up: profile, facilities, departments, wards, rooms, beds, staff and roles.
  2. Publish the catalog: providers and clinicians, appointment types (duration, buffers, providers, status bookable / internal / paused), and provider schedule blocks.
  3. Book: the public site calls requestAppointment. That creates or matches a Patient, a requested Appointment, an IntakeForm with a one-time access code, and a PatientConsent. Staff confirm with transitionAppointment.
  4. Intake: the patient opens /intake#accessCode=… and calls submitIntakeForm. Staff review with transitionIntakeReview.
  5. Arrive and treat: arriveAppointment creates a CareQueueTicket; transitionCareQueue calls the patient; openEncounter and transitionEncounter run the encounter. Closing an encounter is checked against the facility's EpisodeClosurePolicy.
  6. Clinical work: vitals, orders, specimens, lab results, prescriptions, procedures, care plans, and clinical documents, each through its own workflow mutation.
  7. Inpatient: reserve a bed, admit, transfer, discharge, then move the bed through cleaning back to available.
  8. Walk-in registration: registerPatient creates the patient, MRN identifier, registration record, and consent, with duplicate review.
  9. Revenue: coverage, claims with immutable claim versions, manual payments and refunds.
  10. Oversight: reports, clinical audit log, and workflow transition trail.

Named GraphQL operations

Custom operations are registered in features/keystone/mutations/index.ts.

AreaOperations
Public storefronthospitalBranding, bookableAppointmentTypes, bookableProviders, availableAppointmentSlots, requestAppointment, intakeFormByAccessCode, submitIntakeForm
AccountsbootstrapHospital, sendUserPasswordResetLink, redeemUserPasswordResetToken, updateActiveUser, redirectToInit
Appointments and schedulingtransitionAppointment, appointmentScheduleConflicts
Registration and consentregisterPatient, recordConsentDecision
IntaketransitionIntakeReview, issueIntakeForm
Care queue and encountersarriveAppointment, transitionCareQueue, openEncounter, transitionEncounter, reviewEpisodePolicy, episodePolicyWorkspace, episodeClosureWorkspace, episodeChildWorkspace, recoverEpisodeClosure
InpatientreserveBed, releaseBedReservation, admitPatient, transitionAdmission, transitionBedReadiness
ClinicalplaceMedicalOrder, transitionMedicalOrder, collectSpecimen, transitionSpecimen, recordLabResult, transitionLabResult, correctLabResult, labResultReadback, recordPrescription, transitionPrescription, scheduleProcedure, transitionProcedure, establishCarePlan, transitionCarePlan, draftClinicalDocument, transitionClinicalDocument, amendClinicalDocument, recordVitalSign, workflowPatientCandidates, workflowEncounterCandidates
BillingrecordPatientCoverage, transitionPatientCoverage, prepareInsuranceClaim, transitionInsuranceClaim, recordPatientPayment, transitionPatientPayment, requestPatientRefund, transitionPatientRefund
CommunicationssendSecurePatientMessage, transitionPatientMessage, registerCommunicationTemplate, reviseCommunicationTemplate, setCommunicationPreference
Inventory and portabilityregisterInventoryItem, transitionInventoryItem, moveInventory, previewInventoryImport, applyInventoryImport, rollbackInventoryImport, requestExportJob, processExportJob
Integrations and retentioninstallIntegration, transitionIntegrationInstallation, issueMachineCredential, revokeMachineCredential, dispatchIntegrationOutbox, dispatchClinicalOutbox, linkExternalIdentity, establishRetentionPolicy, transitionRetentionPolicy, placeRecordHold, releaseRecordHold, recordRetentionDisposition
OnboardingapplyHospitalOnboarding, createOnboardingAppointment

Some of these are used by integrations and scripts rather than a dashboard button, for example transitionPatientPayment, transitionPatientRefund, transitionInventoryItem, reviseCommunicationTemplate, transitionIntegrationInstallation, revokeMachineCredential, transitionRetentionPolicy, releaseRecordHold, transitionClinicalDocument, amendClinicalDocument, and transitionSpecimen. They are reachable through GraphQL with the right role.

These operations record what staff did. They do not make a clinical decision safe or correct; authorized clinicians remain responsible.

Onboarding

When a user with canManageOnboarding signs in, the dashboard offers three templates: Complete Practice Setup, Basic Booking Setup, and Custom Setup (edit the setup JSON before applying). All three call applyHospitalOnboarding, which requires both canManageOnboarding and canAccessAllClinicalData.

The full template seeds the practice "Northstar Family Care": 2 departments (Primary Care, Behavioral Health), 2 facilities (a downtown clinic and a telehealth site), 3 providers and clinicians, 3 appointment types, 5 schedule blocks, 2 patients, 2 appointments, 2 intake forms, and 2 manual phone follow-up messages. Patients are created through registerPatient, appointments through createOnboardingAppointment, intake through issueIntakeForm, and messages through sendSecurePatientMessage, so seeded data follows the same rules as staff-created data.

Onboarding is idempotent: it upserts by stable keys (department code, facility slug, onboarding key, MRN). It returns one-time intake links (/intake#accessCode=…) that expire after 7 days. The links are shown once; only a hash is stored. Onboarding rejects a portalAccess section because no portal invite workflow exists.

Integrations

There is no provider adapter registry. Integration support is a generic, signed webhook layer:

  • Outbound: installIntegration creates an IntegrationInstallation (kind communications, payment, laboratory, pharmacy, or webhook) with an endpoint URL, allowed event types, and an encrypted signing secret. Workflow events land in ClinicalOutboxEvent. A separate worker (npm run worker:outbox) calls POST /api/internal/outbox/dispatch with OUTBOX_WORKER_SECRET, and each event is POSTed as JSON with an x-openfront-signature HMAC header. Endpoints must use HTTPS and resolve to public addresses; INTEGRATION_HOST_ALLOWLIST restricts hosts. Failed deliveries retry and then dead-letter.
  • Inbound: POST /api/integrations/{installationKey}/webhook accepts signed events from a MachineCredential with the webhook:write scope. Supported events are payment.succeeded, payment.failed, refund.succeeded, refund.failed, message.sent, message.delivered, message.failed, claim.acknowledged, and claim.rejected, limited by installation kind. Requests outside a 5-minute window or replayed are rejected.
  • Messages: sendSecurePatientMessage supports sms, email, and phone. Phone messages become manual_required tasks. Email and SMS are queued for an active communications installation; the app does not send email or SMS itself.
  • Payments: PatientPayment methods are cash, check, card_external, and bank_external. Payments are recorded manually; there is no card processor.
  • Exports: requestExportJob builds encrypted patient, appointment, audit, or inventory exports in CSV or JSON, downloaded from /api/exports/{jobId} with a one-time token.
  • Email: SMTP is used only for staff password reset links.
  • AI chat: the dashboard AI chat needs OPENROUTER_API_KEY and OPENROUTER_MODEL. The MCP endpoint it calls (/api/mcp-transport/[transport]) returns 404 in this product, so no MCP tools are exposed.

An installation record does not prove an external EHR, lab, payer, or messaging system is connected.

On this page