Dexcom CGM Viewer | Canvas Medical
admir-vicert/dexcom-cgm-viewer
Chart Application
You can also View the extension on GitHub
dexcom_cgm_viewer
What it does
dexcom_cgm_viewer is a Canvas plugin that brings a patient's Dexcom continuous glucose monitor (CGM) data into the chart drawer. Staff can generate a one-tap connection link, deliver it to the patient over email (via SendGrid), the Canvas patient portal, or a copyable URL, and — once the patient authorizes — view latest glucose, trend arrow, time-in-range, and a glucose chart for the last 7 / 14 / 30 / 90 days. Sync is on-demand through a Dexcom Developer API v3 client; there is no nightly cron.
Problem it solves
CGM data lives in Dexcom's cloud and traditionally requires staff to either pull patient-shared reports manually or wait on a separate vendor integration. This plugin lets a clinician connect a patient to Dexcom from inside the chart in under a minute and review glucose trends without leaving Canvas — replacing the manual PDF-report workflow common in endocrinology, primary care, and remote-monitoring programs.
Who it's for
- Endocrinology practices managing type 1 and insulin-dependent type 2 diabetes.
- Primary care clinicians running diabetes-management programs.
- Remote patient monitoring (RPM) and chronic-care-management (CCM) teams who already bill 99453 / 99454 / 99457 and need CGM data surfaced inside the EHR.
- Any care team that wants Dexcom data in-chart without a separate reporting portal.
Screenshots
The plugin tile inside the patient chart:
After connecting the patient and running a sync — latest glucose, trend arrow, time-in-range, and the glucose chart:
How to install
canvas install dexcom_cgm_viewer --host <your-canvas-host>
After installing, set the required plugin secrets (see Configuration options).
Configuration options
The plugin declares its configuration as manifest variables. Values flagged sensitive are hidden in the Admin UI and canvas config list; non-sensitive values are shown in plaintext for verification. (Requires Canvas ≥ 1.305.0 and canvas CLI ≥ 0.158.0 for the variables schema.)
Five required variables:
| Variable | Sensitive | Notes |
|---|---|---|
DEXCOM_CLIENT_ID |
no | From Dexcom developer registration |
DEXCOM_CLIENT_SECRET |
yes | From Dexcom developer registration |
DEXCOM_REDIRECT_URI |
no | Pre-registered with Dexcom; e.g. https://<canvas-host>/plugin-io/api/dexcom_cgm_viewer/callback |
DEXCOM_ENVIRONMENT |
no | sandbox or production |
DEXCOM_MAGIC_LINK_SECRET |
yes | High-entropy string (≥ 32 bytes) used to HMAC-SHA256 sign magic-link tokens |
Do not set a
namespace_read_write_access_key. This plugin owns itscanvas__dexcom_cgm_viewercustom-data namespace, so Canvas generates and manages that key automatically at install time. Setting it yourself overwrites the value Canvas generated, which breaks the plugin's access to its own tables — the chart panel then loads blank. (You only ever supply a namespace access key when a plugin reads a namespace it does not own.)
Two optional secrets enable email delivery via SendGrid. When unset, the link is still generated and offered as a copyable value plus a Canvas portal message:
| Variable | Sensitive | Notes |
|---|---|---|
SENDGRID_API_KEY |
yes | SendGrid API key with Mail Send permission |
SENDGRID_FROM_EMAIL |
no | A SendGrid-verified sender address |
Set them at install time — pass sensitive values with --secret and non-sensitive ones with --variable:
canvas install dexcom_cgm_viewer --host <your-canvas-host> \
--variable "DEXCOM_CLIENT_ID=..." \
--secret "DEXCOM_CLIENT_SECRET=..." \
--variable "DEXCOM_REDIRECT_URI=https://<your-canvas-host>/plugin-io/api/dexcom_cgm_viewer/callback" \
--variable "DEXCOM_ENVIRONMENT=sandbox" \
--secret "DEXCOM_MAGIC_LINK_SECRET=$(python -c 'import secrets;print(secrets.token_urlsafe(48))')" \
--secret "SENDGRID_API_KEY=..." \
--variable "SENDGRID_FROM_EMAIL=..."
Note on token storage: OAuth tokens are stored unencrypted in the plugin's namespaced custom-data tables. The Canvas plugin sandbox blocks the
cryptographylibrary, so application-layer encryption-at-rest cannot be added until Canvas exposes an encrypted-field type. Tokens are scoped per patient, never returned by any plugin API, and refresh tokens rotate on every refresh.
Architecture
DexcomChartApp (patient-scoped Application)
│
▼
DexcomChartAPI ──► StaffSessionAuthMixin
│
▼
Canvas custom data:
• dexcom_oauth_tokens (OAuth tokens, single-use refresh rotation)
• dexcom_sync_state (sync watermark, link-pending, errors)
• dexcom_egvs (5-min readings, 90-day rolling retention)
• dexcom_summaries (daily aggregates, indefinite retention)
DexcomOAuthAPI ──► JWT-authenticated /connect & /callback
Routes
All routes are under /plugin-io/api/dexcom_cgm_viewer/.
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | / |
Staff session | Render chart-drawer shell |
| GET | /data?patient_id=&range= |
Staff session | View-model JSON |
| POST | /sync?patient_id=&range= |
Staff session | Manual pull (chunked into 30-day windows) |
| POST | /send-link?patient_id=&channels= |
Staff session | Mint link, deliver over selected channels |
| POST | /disconnect?patient_id= |
Staff session | Purge tokens + cached data |
| GET | /diagnose?patient_id= |
Staff session | Read-only contact-point state (messaging triage) |
| GET | /connect?token=<jwt> |
JWT | Patient-facing redirect to Dexcom OAuth |
| GET | /callback?code=&state= |
OAuth state | Exchange code, store tokens |
UAT setup
End-to-end walkthrough for a tester: register Dexcom credentials, set the plugin secrets, prep a test patient, and run the connect → sync → disconnect flow.
1. Dexcom developer credentials
- Sign in at https://developer.dexcom.com/ and create an account if needed.
- Create a new app under My Apps → Add App.
- For the OAuth Redirect URI, register exactly:
https://<your-canvas-host>/plugin-io/api/dexcom_cgm_viewer/callback
Any mismatch (trailing slash, wrong host, http vs https) makes every patient OAuth attempt fail. 4. Pick Sandbox to start — switching to Production requires Dexcom's partner-program approval. 5. Copy the Client ID and Client Secret.
2. Generate the magic-link secret
python -c "import secrets; print(secrets.token_urlsafe(48))"
3. (Optional) SendGrid setup for email delivery
Skip this if you only need to test the Canvas-portal channel and the copyable link.
- Sign in at https://sendgrid.com/ and create an account.
- Settings → API Keys → Create — give the key only the Mail Send permission.
- Settings → Sender Authentication → Single Sender Verification — add and verify a sender email.
- Copy the API key and the verified sender address into the
SENDGRID_API_KEYandSENDGRID_FROM_EMAILsecrets.
4. Prepare a test patient
For portal-message or email delivery to work, the patient needs a messageable channel:
- Open the patient chart → patient profile drawer.
- Under Contact info / Telecom, add an email contact point with:
- System: Email
- Value: an inbox you can actually check
- Has consent: true
- Opted out: false
- State: Active
- Save.
If the patient has none of those, link generation still works — staff just copy the link manually.
5. End-to-end test
Open the test patient's chart → expand the Plugins tab → click Dexcom CGM.
Disconnected state: tick Send via email and/or Send via patient portal → click Generate connection link. The toast tells you which channels delivered. A copyable link appears in the panel.
Connect: open the link in an incognito window (or on your phone). It redirects to Dexcom's hosted login. Use one of the sandbox test users:
User7(G7) — recommended, current-generation dataUser4(G6 receiver),User6(G6 mobile),User8(ONE+)
Sandbox data repeats every 10 days.
Accept the HIPAA authorization. You'll land on the "You're connected" page.
Back in the chart drawer, click Check for connection (or refresh) — the UI flips to the connected state.
Sync now — the first sync pulls a 14-day window (default range) and shows a loading banner. Subsequent range chips (7d / 14d / 30d / 90d) re-read from the local database without calling Dexcom.
First-sync timing: a 90-day range is chunked into three 30-day requests under the hood (Dexcom v3 limit) — expect 10–30 seconds.
Disconnect — confirms a prompt, then purges tokens and cached glucose data. The UI returns to the disconnected state.
6. Troubleshooting
| Symptom | Where to look |
|---|---|
| Email not received | canvas logs --host <host> --since 5m --level ERROR — look for SendGrid 4xx/5xx |
| Portal message never appeared | Hit https://<host>/plugin-io/api/dexcom_cgm_viewer/diagnose?patient_id=<UUID> while logged in as staff — confirms the patient has a verified messaging channel |
| Dexcom OAuth bounces back to an error | Check that the registered redirect URI matches DEXCOM_REDIRECT_URI exactly |
| Sync fails with "sync timed out" | Logs will show whether Dexcom returned 401 (refresh issue) or 4xx (date-range issue) |
| Patient ID lookup fails | Patient IDs are UUIDs — grab from the ?patient_id= in any working /data request in DevTools, not from the chart UI |
Local development
uv sync
uv run pytest --cov=dexcom_cgm_viewer --cov-report=term-missing
uv run mypy dexcom_cgm_viewer
License
Released under the MIT License.
Info
This plugin was developed and contributed by Vicert.