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
| Concept | Meaning |
|---|---|
| Facility | The data boundary. Staff are linked to facilities and see their records only |
| Provider and Clinician | A bookable directory entry and the care-team record behind it |
Visit type (AppointmentType) | What patients book: duration, buffers, providers, status |
| Controlled record | A record changed only through a named workflow mutation |
| Operation key | Idempotency key sent with each workflow call. Repeating a call with the same key returns the first result |
| Expected version | Optimistic concurrency. A stale version is rejected with "refresh before retrying" |
| Transition trail | WorkflowTransition and ClinicalAuditLog rows written for every change |
Who does what
| Role | Typical flags | Daily work |
|---|---|---|
| Front desk | canAccessDashboard, canManageAppointments, canManagePatients, canManageIntake, canManageCommunications | Confirm requests, check in, register, send messages |
| Scheduler | canManageScheduling, canManageProviders | Schedule blocks, visit types |
| Clinician | canAccessDashboard, canManagePatients and clinical scope | Encounters, orders, results |
| Billing | canManageBilling | Coverage, claims, payments |
| Administrator | canAccessAllClinicalData, canManageRoles, canManagePeople | Setup, staff, retention |
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), plusFacilityImage,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
- Set up: profile, facilities, departments, wards, rooms, beds, staff and roles.
- Publish the catalog: providers and clinicians, appointment types (duration, buffers, providers, status
bookable/internal/paused), and provider schedule blocks. - Book: the public site calls
requestAppointment. That creates or matches aPatient, arequestedAppointment, anIntakeFormwith a one-time access code, and aPatientConsent. Staff confirm withtransitionAppointment. - Intake: the patient opens
/intake#accessCode=…and callssubmitIntakeForm. Staff review withtransitionIntakeReview. - Arrive and treat:
arriveAppointmentcreates aCareQueueTicket;transitionCareQueuecalls the patient;openEncounterandtransitionEncounterrun the encounter. Closing an encounter is checked against the facility'sEpisodeClosurePolicy. - Clinical work: vitals, orders, specimens, lab results, prescriptions, procedures, care plans, and clinical documents, each through its own workflow mutation.
- Inpatient: reserve a bed, admit, transfer, discharge, then move the bed through cleaning back to available.
- Walk-in registration:
registerPatientcreates the patient, MRN identifier, registration record, and consent, with duplicate review. - Revenue: coverage, claims with immutable claim versions, manual payments and refunds.
- Oversight: reports, clinical audit log, and workflow transition trail.
Named GraphQL operations
Custom operations are registered in features/keystone/mutations/index.ts.
| Area | Operations |
|---|---|
| Public storefront | hospitalBranding, bookableAppointmentTypes, bookableProviders, availableAppointmentSlots, requestAppointment, intakeFormByAccessCode, submitIntakeForm |
| Accounts | bootstrapHospital, sendUserPasswordResetLink, redeemUserPasswordResetToken, updateActiveUser, redirectToInit |
| Appointments and scheduling | transitionAppointment, appointmentScheduleConflicts |
| Registration and consent | registerPatient, recordConsentDecision |
| Intake | transitionIntakeReview, issueIntakeForm |
| Care queue and encounters | arriveAppointment, transitionCareQueue, openEncounter, transitionEncounter, reviewEpisodePolicy, episodePolicyWorkspace, episodeClosureWorkspace, episodeChildWorkspace, recoverEpisodeClosure |
| Inpatient | reserveBed, releaseBedReservation, admitPatient, transitionAdmission, transitionBedReadiness |
| Clinical | placeMedicalOrder, transitionMedicalOrder, collectSpecimen, transitionSpecimen, recordLabResult, transitionLabResult, correctLabResult, labResultReadback, recordPrescription, transitionPrescription, scheduleProcedure, transitionProcedure, establishCarePlan, transitionCarePlan, draftClinicalDocument, transitionClinicalDocument, amendClinicalDocument, recordVitalSign, workflowPatientCandidates, workflowEncounterCandidates |
| Billing | recordPatientCoverage, transitionPatientCoverage, prepareInsuranceClaim, transitionInsuranceClaim, recordPatientPayment, transitionPatientPayment, requestPatientRefund, transitionPatientRefund |
| Communications | sendSecurePatientMessage, transitionPatientMessage, registerCommunicationTemplate, reviseCommunicationTemplate, setCommunicationPreference |
| Inventory and portability | registerInventoryItem, transitionInventoryItem, moveInventory, previewInventoryImport, applyInventoryImport, rollbackInventoryImport, requestExportJob, processExportJob |
| Integrations and retention | installIntegration, transitionIntegrationInstallation, issueMachineCredential, revokeMachineCredential, dispatchIntegrationOutbox, dispatchClinicalOutbox, linkExternalIdentity, establishRetentionPolicy, transitionRetentionPolicy, placeRecordHold, releaseRecordHold, recordRetentionDisposition |
| Onboarding | applyHospitalOnboarding, 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:
installIntegrationcreates anIntegrationInstallation(kindcommunications,payment,laboratory,pharmacy, orwebhook) with an endpoint URL, allowed event types, and an encrypted signing secret. Workflow events land inClinicalOutboxEvent. A separate worker (npm run worker:outbox) callsPOST /api/internal/outbox/dispatchwithOUTBOX_WORKER_SECRET, and each event is POSTed as JSON with anx-openfront-signatureHMAC header. Endpoints must use HTTPS and resolve to public addresses;INTEGRATION_HOST_ALLOWLISTrestricts hosts. Failed deliveries retry and then dead-letter. - Inbound:
POST /api/integrations/{installationKey}/webhookaccepts signed events from aMachineCredentialwith thewebhook:writescope. Supported events arepayment.succeeded,payment.failed,refund.succeeded,refund.failed,message.sent,message.delivered,message.failed,claim.acknowledged, andclaim.rejected, limited by installation kind. Requests outside a 5-minute window or replayed are rejected. - Messages:
sendSecurePatientMessagesupportssms,email, andphone. Phone messages becomemanual_requiredtasks. Email and SMS arequeuedfor an activecommunicationsinstallation; the app does not send email or SMS itself. - Payments:
PatientPaymentmethods arecash,check,card_external, andbank_external. Payments are recorded manually; there is no card processor. - Exports:
requestExportJobbuilds 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_KEYandOPENROUTER_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.