Tutorial — Build your first widget
What you'll build
By the end of this tutorial you'll have a Hello-Patient widget rendering in the patient detail sidebar of your sandbox tenant. It reads the current patient via the App Bridge and renders their display name.
Prerequisites
- Node.js 20+
- An OAuth client ID issued by your tenant administrator
- A configured home tenant and sandbox tenant
If any of those aren't in place yet, work through Getting Started first, then come back here.
Step 1: Scaffold
Goal: get a working app skeleton on disk.
orion init hello-patient --client-id <your-client-id>
cd hello-patient
npm installThe scaffold writes a TypeScript + React + Vite project with two files you'll touch most often:
orion-app.json— the manifest (slug, name, version, scopes, extensions)src/main.tsx— the iframe entrypoint that boots the App Bridge
It also writes orion.config.ts for CLI configuration (clientId, host, sandbox URL, tenant UUID, app slug) and a starter dist/ ignore. You won't need to edit those for this tutorial.
Step 2: Configure tenants
Goal: point the CLI at your tenants and authenticate.
Set the home tenant — the one that hosts the marketplace and is the target for orion publish:
orion config hostSet the sandbox tenant — the one orion dev tunnels into for live development:
orion config sandboxThen sign in:
orion loginA browser opens to the OAuth authorize page. Approve it and the CLI stores your tokens locally.
Step 3: Generate the widget
Goal: register a widget extension in the manifest and scaffold its component file.
orion generate widget hello-patient --mount-point patient-detail-sidebarThis adds an entry to the extensions.widgets[] array in orion-app.json:
{
"extensions": {
"widgets": [
{
"id": "hello-patient",
"title": "Hello Patient",
"mountPoint": "patient-detail-sidebar",
"component": "HelloPatient"
}
]
}
}It also scaffolds the component into something like src/widgets/HelloPatient.tsx. The exact file path is printed by the command — open the file it shows you.
patient-detail-sidebar is one of the available widget mount points. See the manifest schema for the full set of supported slots.
Step 4: Edit the component
Goal: read the current patient from the App Bridge and render their name.
Replace the body of the generated widget with:
import { usePatient } from '@orion-ehr/app-bridge';
export default function HelloPatient() {
const patient = usePatient();
if (!patient) return <p>No patient in context.</p>;
const name = `${patient.firstName ?? ''} ${patient.lastName ?? ''}`.trim();
return <p>Hello, {name || 'there'}.</p>;
}usePatient() returns the live PatientStub from the App Bridge — { id, uuid, firstName, lastName } — or null when the user isn't in patient context. The full hook surface (theme, encounter, user, toasts, navigation) is documented in the @orion-ehr/app-bridge README.
Step 5: Run dev
Goal: see your widget render live in the sandbox tenant.
orion devVite starts locally. The CLI opens a Cloudflare tunnel and registers it with your sandbox tenant so the host EMR can iframe your in-progress build. The terminal prints the tunnel URL once it's live.
In a browser, sign into your sandbox tenant, open any patient chart, and look at the patient detail sidebar. Your widget mounts there and displays the patient's name. Edits to HelloPatient.tsx hot-reload inside the iframe.
Step 6: Validate
Goal: catch manifest, bundle, and scope issues before you publish.
orion validateA clean run reports no errors. If validation fails, the CLI prints which file and which manifest field needs attention. Fix and re-run until it passes — orion publish re-runs validate internally, so this just gives you faster feedback during development.
Step 7: Build and publish
Goal: ship a versioned bundle to the marketplace for review.
orion build
orion publishpublish re-runs validation, archives dist/ and your source, prompts for a changelog, and submits the bundle for marketplace review.
A few things to know:
- The manifest
versionis semver and must change between publishes. Bump it before re-publishing or the upload is rejected. - The changelog prompt is required; this is what reviewers see first.
- After upload, the version enters a review queue. It does not appear in the marketplace until a reviewer approves it.
Step 8: Watch the review
Goal: track review status and read reviewer feedback.
List your published versions and their states:
orion versions listOnce a reviewer posts feedback or makes a decision, read the full thread:
orion versions show 1.0.0If the reviewer asks for changes, fix the code, bump the version, and run orion publish again.
What to try next
- Development commands — full reference for
orion dev,orion generate, and friends. - Manifest reference — every field in
orion-app.jsonexplained. - Wire up FHIR — once context-only widgets feel comfortable, add SMART-on-FHIR REST calls for clinical data. See your tenant's broader Orion developer documentation for the FHIR surface.