Choosing Your Patient Experience

The first decision when building a patient-facing experience on Canvas isn’t technical — it’s about how much of that experience you want to own. Canvas gives you a spectrum: turn on our built-in patient portal as-is, extend it with plugins, or build your own patient app entirely and use Canvas as the clinical backend. This guide helps you pick the path that fits your product, team, and timeline — and points you to the how-to docs once you’ve decided.

The three paths #

 Use the built-in portalExtend the built-in portalBuild your own
Who builds the UICanvasCanvas + your pluginsYou
EffortLowestMediumHighest
Control over look & flowConfigurationConfigure, plus inject widgets, applications, and logicTotal
Patient dataHandled for youHandled for built-in pages; you supply data to your own widgets & applications (Canvas data module, Custom Data, or external sources)You read & write it via Canvas APIs
Best whenYou want a working, compliant portal fastThe built-in portal is close, but you need extra pages, branding, or custom rulesYou have (or want) your own branded app and use Canvas as the source of truth

How to choose #

Choosing a patient experience If you want complete control of the experience with Canvas never exposed to patients, build your own (Path 3). Otherwise, if the built-in portal is enough as-is, use it (Path 1); if it needs a few custom touches, extend it with plugins (Path 2). Yes No Yes No Designing your patient experience Complete control, Canvas not exposed? Build your own app · Path 3 highest effort · full build you have or will build an app Built-in portal enough as-is? Use the built-in portal · Path 1 lowest effort fastest to launch Extend with plugins · Path 2 medium effort portal's close — add a few things

Path 1 — What’s already built into Canvas #

When you sign with Canvas, you already have a full patient portal — no build, no plugins, nothing to stand up. Patients can log in (with their email, phone number, or username, plus a password) and use it from day one, and Canvas hosts it and handles the login, data, and compliance for you.

Out of the box it includes messaging, payments, labs, and a contact page. Other pages — scheduling, My Health, health records, consents, and self-registration — are built in too and ready to switch on whenever you want them.

Everything in this path is configuration only — no plugins or code. You decide which built-in pages appear and set basic branding; that’s the whole effort.

Built-in pages you can turn on or off #

Each page is toggled with a Constance setting, managed by Canvas Support — so contact Support to change them. For a full walkthrough, see Managing the Patient Portal.

SettingPageWhat patients can doDefault
PATIENT_APP_MESSAGINGMessagingSecurely message their care teamTrue
PATIENT_APP_PAYMENTSPaymentView and pay outstanding balancesTrue
PATIENT_APP_LABSLabView their lab resultsTrue
PATIENT_APP_CONTACTContactSee the practice’s contact informationTrue
PATIENT_APP_MY_HEALTHMy HealthReview conditions, medications, allergies, and related clinical infoFalse
PATIENT_APP_RECORDSHealth RecordsAccess and download their health recordsFalse
PATIENT_APP_APPOINTMENTSScheduling flowBook and view appointmentsFalse
PATIENT_APP_CONSENTSConsentsReview and complete consentsFalse
PATIENT_APP_REGISTERRegisterSelf-register from the login pageFalse

Other portal settings:

SettingControlsDefault
ENABLE_PATIENT_PORTALWhole portal on/offTrue
ALLOW_PATIENT_DOCUMENT_UPLOADSPatient document uploads on “My Health”False
PATIENT_APP_USERS_EMAIL_ONLYEmail-only login (access mode, not a page)False
PATIENT_APP_BANNERBanner text on the portal login page""

Branding #

You can put your organization’s identity on the portal without building anything:

  • Logo — your organization’s logo is shown on the login page and throughout the portal. It’s configured on your Canvas organization; until you provide one, the default Canvas logo appears. Work with Canvas to set it.
  • Banner messagePATIENT_APP_BANNER sets a short greeting above the login card.
  • Email sender for patient messages — message and notification emails to patients are sent with your organization’s name as the sender, from the address in your organization’s defaultEmail setting (default do-not-reply@canvasmedical.com). You can change that address; using your own domain may require sender authentication set up with Canvas.

What stays Canvas-driven and isn’t branded per organization:

  • The portal URL — patients sign in at https://<your-instance>.canvasmedical.com/app/.
  • The login flow — the sign-in and verification screens are Canvas’s.
  • Sign-in & verification emails — these are sent from a fixed no-reply@canvasmedical.com (a separate path from the patient-message sender above), so they can’t be rebranded.

Path 2 — Extend the built-in portal with plugins #

The built-in portal is a strong starting point — but it’s rarely exactly what you want. Path 2 is where you make it feel like yours: add the cards, pages, and rules your patients and workflows actually need, while Canvas keeps hosting the portal and handling login, data, and compliance. You never stand up your own app.

Under the hood, every extension is a plugin that reacts to a portal event and returns an effect. As patients move through the portal, your handler can add a widget, render a page, filter appointment slots, and so on.

New to plugins? Start with Your First Plugin, then keep the Patient Portal config hub and the portal events handy. And for inspiration, the community-run Medical Software Foundation library has production-ready portal extensions you can install or learn from — several are linked below.

Here’s what you can do — mix and match as many as you like.

Personalize the landing page with widgets #

The portal home is the first thing patients see after logging in, and it’s the easiest place to add value. Widgets are cards you render there: a “your next appointment” summary, an outstanding-balance nudge, a care-gap reminder (“you’re due for a flu shot”), links to your own resources — anything you can compute for that patient.

You control what each card shows, pulling from the Canvas data module, your plugin’s Custom Data, or an external service.

See Portal Landing Page Widgets and the Custom Landing Page guide for the exact documentation on how to build them. And you don’t have to start from a blank file — there’s a ready-made patient_portal_plugin in our open-source examples repo you can install as-is or use as a reference and starting point.

Shape self-scheduling #

Self-scheduling is powerful, but you’ll usually want guardrails. Filter or reorder the slots a patient can book before they see them — keep new patients on longer visit types, hide same-day slots, steer certain reasons to specific providers, or enforce your booking policies.

See Shape Self-Scheduling · SDK example: patient_portal_search_appointments_slots_plugin.

Real-world example (MSF):

  • urgent-care-self-scheduler — adds a “Need to be seen today?” portal card and a full self-booking flow that offers only credentialed providers, blocks duplicate bookings, and pre-populates the visit note. A good end-to-end model.

Tailor appointment cards #

Fine-tune the actions on each appointment. Hide Cancel or Reschedule when your policy says so (for example, no reschedule within 24 hours of the visit), or add a telehealth join link to virtual appointments — decided per appointment.

See Customize Appointment Cards.

Real-world example (MSF):

  • portal_disable_cancel_appts — answers the “can this appointment be canceled?” check with no, removing the self-service Cancel action so cancellations route through staff.

Surface forms and react to responses #

Meet patients with the right questionnaire at the right moment — an intake packet before a first visit, a PHQ-9 for a behavioral-health appointment, a post-visit survey — and act on what they submit. Forms are surfaced to the patient when they log in to the portal: you choose which ones appear, then react to responses to store the answers or trigger follow-up. Responses are saved as Interviews, and you can even generate a Questionnaire command in the patient’s note.

See the Patient Portal effects → Forms for the technical reference.

Automate login and activation #

Getting patients into the portal doesn’t have to be manual. Automatically invite patients to activate their account when they’re ready — for example, once you’ve confirmed a good email or phone — instead of having staff send invitations one at a time. You can also keep a patient’s portal login in sync when their email or phone changes, and verify a contact point before you rely on it to invite them or send messages.

See User Login for automating invitations and keeping the login in sync, and Send Contact Verification for confirming a contact point first.

Control the navigation layout #

Decide which items appear in the portal navigation and in what order — and do it conditionally per patient. Surface a page only to patients in a certain program, reorder the menu so what matters most comes first, or hide built-in items you don’t use. The navigation isn’t fixed; you compute it for each patient every time the portal loads.

See Configure Portal Menu Items.

Add your own pages #

Need more than a card? Add entire patient-facing pages with an Application scoped to the patient portal — a program dashboard, an intake wizard, a financial-assistance flow, an education library. The page lives inside the portal (same URL, same login, same navigation), but its UI and logic are entirely yours.

This is also how you replace a built-in page you don’t love. Don’t like how the stock My Health page presents conditions, medications, and allergies? Build your own version exactly the way you want it, then turn off the built-in page with its Path 1 toggle (PATIENT_APP_MY_HEALTH) so patients only ever see yours. The same works for any built-in section — keep the stock pages that suit you and swap out the ones that don’t, one at a time.

See the Applications handler and Configure Portal Menu Items for surfacing your page in the portal navigation · SDK examples: example_patient_portal_page, patient_app_schedule.

Real-world examples (MSF):

  • patient-portal-profile — adds a read-only My Profile page (demographics, care team, preferred pharmacy).
  • portal-content — builds richer My Records and My Profile pages (visit notes, labs, imaging, letters, insurance) with optional review-gating. A great model for replacing the stock pages with your own.
  • patient-portal-forms — ships a whole forms experience as its own portal page: staff assign questionnaires (PHQ-9, GAD-7, ROS…) that patients complete in the portal, with responses posting back to the chart.

Missing something? Ask before you rebuild #

Customizing the portal puts you in the best position to see what’s missing. If you hit something plugins can’t do in the portal yet, requesting a new SDK capability is often a better move than jumping to building your own (Path 3) — it can unlock what you need inside the built-in portal, so you keep everything Canvas handles for you (login, data, compliance) instead of rebuilding it. Many of today’s patient-portal capabilities exist because customers asked for them.


Path 3 — Build your own patient app on Canvas data #

If you want a fully branded app — your own UI, your own login — build it as an external application and use Canvas as the clinical source of truth. You move data between your app and Canvas in three complementary ways; most portals use a combination, because each fits a different job and has its own authentication model.

How a customer-owned portal talks to Canvas Your app calls into Canvas via the FHIR API (OAuth2) and SimpleAPI (API key) to read and write data; Canvas calls out to your app via event-driven webhooks, which your app authenticates with a shared secret. Your patient portal your UI + your login Canvas clinical source of truth FHIR API OAuth2 · standard resources (read/write) SimpleAPI API key · custom endpoints (read/write) Events → webhook shared secret · Canvas pushes changes → into Canvas: Canvas authenticates you  ·  ← out to you: your app verifies Canvas's secret

A typical combination:

  • FHIR API for the bulk of standard reads and writes (patients, appointments, conditions, coverage…).
  • SimpleAPI where you need something FHIR doesn’t expose, a tailored/bundled response, or custom data — your app hits one plugin endpoint instead of stitching several calls.
  • Events → webhooks to keep your app in sync as things change in Canvas, instead of polling.

FHIR API — read & write standard Canvas data directly #

Best for: standard resources (Patient, Appointment, Condition, Coverage, etc.) read or written straight from your app.

Auth (your app → Canvas): Canvas issues OAuth2 tokens with SMART on FHIR scopes. For a portal where a patient logs in and must only see their own record, use a patient-scoped token — it’s locked to a single patient and Canvas enforces it server-side, so any read or write touching another patient is rejected. See Customer Authentication for the full flow, scopes, and the user/ and system/ contexts.

See the FHIR API reference and Auth best practices.

Examples:

  • canvas_fhir_client (SDK) — a Python client that authenticates and reads/writes Canvas FHIR resources.
  • cms-access-fhir-client (MSF) — FHIR-client mechanics in action (OAuth plus a full request/response exchange), here integrating with the external CMS ACCESS FHIR APIs.

SimpleAPI — build the exact endpoints your app wishes Canvas had #

Where FHIR gives you standard resources, SimpleAPI lets you define your own — you own the URL, the request, and the JSON response shape, all running inside a Canvas plugin. Instead of bending your app to fit a generic API, you build the endpoint your app actually wants and call it directly.

New to the idea? Read the announcement: Introducing Custom API Endpoints with the Canvas SDK.

This is where SimpleAPI shines:

  • Shape the payload to your UI. Return exactly the fields a screen needs, already assembled — no over-fetching, no reshaping FHIR bundles on the client.
  • Collapse many calls into one. Bundle related reads and writes into a single round-trip, so a screen that would take five FHIR calls takes one. Fewer requests, simpler client code, faster load.
  • Reach data FHIR doesn’t expose. Surface your plugin’s own Custom Data, computed values, or anything else in the data module.
  • Run your logic server-side, next to the data. Validation, orchestration, and business rules live in the plugin — close to Canvas and behind your key — instead of scattered across your app.

Reads come from the data module; writes go through Effects and Commands (the SDK data models are read-only).

Auth (your app → Canvas): an API key — declare a secret in the plugin and send it in the Authorization header (APIKeyAuthMixin). Simple, and the right choice for an external caller.

See SimpleAPI HTTP handlers.

SDK examples:

Real-world examples (MSF):

  • lab-result-api — one GET /lab-result/<id> returns a lab report with its patient, originating order, ordering provider, and nested result values assembled into a single response. A textbook “collapse many calls into one, shaped to your UI” endpoint.
  • cpt-billing-api — a write endpoint that adds CPT billing line items to a note from an external caller.
  • note-command-api — read notes with enhanced command data, and create notes via POST /create-note with flexible identifier lookup.
  • custom-observation-management — an API-key-authed read/write API over a clinical resource: GET a single observation, a filtered list, or the available filters, and POST to record a new one.

Events → webhooks — Canvas pushes changes to your app the moment they happen #

Instead of your app constantly polling Canvas asking “anything new?”, let Canvas tell you. A plugin subscribes to the events you care about — an appointment booked, a task created, a patient updated — and POSTs to your endpoint the instant it happens.

Why reach for webhooks:

  • Real-time sync, no polling. Keep an external system current — a billing platform, a CRM, a data warehouse, your own app’s database — the moment data changes, instead of nightly jobs that are always a little stale. For example, a patient gives the front desk an updated address; a webhook pushes that change to your app so it’s already reflected the next time they log in to your portal.
  • Trigger downstream workflows. Fire an appointment reminder, page a care team, kick off a prior-auth, or open a ticket in your system as soon as the triggering event lands.
  • Fan out from one event. A single Canvas change can notify several systems at once, each with the payload it needs.
  • Lower latency and load. You react in seconds and skip the wasted requests — and the changes missed between polls — that polling brings.

Auth (Canvas → your app — reversed): you secure your receiving endpoint. The plugin sends a shared secret/signature (stored as a plugin secret) that your app verifies; no Canvas token is involved on the inbound call.

See the SDK Events reference, the HTTP Request effect, and the Creating Webhooks guide.

SDK examples:

Real-world examples (MSF):

  • appointment-sync-webhook — POSTs appointment lifecycle events (created, cancelled, no-show) to an external system as they happen.
  • task_webhook_notification — sends a customizable payload to your endpoint whenever a task is created or updated.
  • gcal_sync — near-real-time two-way sync between Canvas appointments and Google Calendar, with Canvas as the system of record.

Still deciding? #

  • Want a portal live quickly with minimal effort → Path 1, and contact Support to set the page toggles.
  • Happy with the built-in portal but need a few custom touches → Path 2; skim the Patient Portal config hub.
  • Building your own branded experience → Path 3; start with the FHIR API and add SimpleAPI and webhooks as needed.