Project Administration
Route prefix: /admin
Files: admin/ directory
The Project Admin section manages the Medplum project settings — users, bots, OAuth clients, access policies, secrets, and sites. These are inherited from the Medplum open-source admin app.
Patients (/admin/patients)​
File: admin/PatientsPage.tsx
Lists all patient accounts in the project using MemberTable filtered to profile-type = Patient. Each row is a ProjectMembership resource whose profile is a Patient. Clicking a row navigates to /admin/members/:membershipId (Edit Membership) — not the patient's FHIR resource directly.
Deleting a Patient​
Patient deletion is handled by the custom endpoint rather than the standard FHIR DELETE. The full delete flow is triggered from the FHIR Browser (/Patient/:id/delete) which calls:
DELETE /fhir/deletePatient/:id
File: packages/server/src/fhir/customHandler/patientHandler.ts
Route registered in: packages/server/src/fhir/customRoutes.ts
Auth: Requires superAdmin or project admin on the authenticated membership — enforced in the route handler before deletePatient is called.
What the endpoint does​
- Validates the patient ID is a valid UUID and the Patient resource exists
- Finds
ProjectMembershiprecords whereprofile = Patient/:idand soft-deletes each one viarepo.deleteResource('ProjectMembership', id)— this revokes login access - Soft-deletes the
Patientresource viarepo.deleteResource('Patient', id)— setsdeleted = true, clearscontent, writes aPatient_Historyentry, removes from search lookup tables, clears the Redis cache entry - Writes an audit log entry (
AuditActions.ADMIN_DELETE_PATIENT) on both success and failure
What is NOT touched​
All clinical FHIR data referencing this patient is left intact:
Observation,Condition,MedicationRequest,Encounter,ProcedureAllergyIntolerance,Immunization,DocumentReference,DiagnosticReportPatientEHRConnectionresources- The
Useraccount record
The User account is intentionally preserved so the patient's identity can be recovered if needed.
Soft delete behaviour (from repo.deleteResource)​
Patient table row → deleted = true, content = '', __version = -1
Patient_History → new history row inserted (deletion recorded)
Search indexes → cleared via deleteFromLookupTables()
Redis cache → cleared via deleteCacheEntry()
Subscriptions → Patient 'delete' event fired to any listening bots
Difference from standard FHIR DELETE (medplum.deleteResource)​
| Standard FHIR DELETE | /fhir/deletePatient/:id | |
|---|---|---|
| Patient record | Soft-deleted | Soft-deleted |
| ProjectMembership | Not touched | Soft-deleted (access revoked) |
| Clinical data | Not touched | Not touched |
| User account | Not touched | Not touched |
| Auth check | Access policy only | Must be project admin or super admin |
| Audit log | Standard AuditEvent | Custom ADMIN_DELETE_PATIENT audit entry |
Users (/admin/users)​
Lists all user accounts that have access to the Admin Portal. Admins can:
- View user details and their project membership
- Edit membership roles and access policies
- Remove users from the project
Edit Membership (/admin/members/:membershipId)​
Edit a specific user's project membership — their role, profile resource, and access policy.
Invite User (/admin/invite)​
Invites a new admin user to the project by email. They receive an invitation link to set their password and join.
Bots (/admin/bots, /admin/bots/new)​
Bots are server-side automation scripts written in TypeScript that run in response to FHIR resource changes (via Subscriptions). Examples: send a notification when a new lab result arrives, auto-populate data fields, run custom business logic.
Create Bot​
Admins can create a new bot by giving it a name and optionally an access policy. The bot code is written and deployed in the Bot Editor (/:resourceType/:id/editor).
OAuth Clients (/admin/clients, /admin/clients/new)​
OAuth clients are applications that are authorized to connect to ChartChat's FHIR API. Each registered client receives a client ID and client secret that it uses to authenticate requests.
Examples:
- The Member Portal is registered as a client.
- Third-party apps connecting via SMART on FHIR need a client registration.
- Backend services or scripts that call the FHIR API programmatically use a client credential.
How to create an OAuth Client​
- Navigate to Admin → OAuth Clients (
/admin/clients). - Click Create new client (bottom right).
- Fill in the form:
| Field | Required | Description |
|---|---|---|
| Name | Yes | Human-readable label for this client (e.g., Member Portal, EHR Sync Service) |
| Description | No | Free-text description of what this client does |
| Redirect URI | No | The URL the OAuth authorization server redirects to after login. Required for user-facing OAuth flows (PKCE / authorization code). Leave blank for machine-to-machine clients. |
| Access Policy | No | Assign an AccessPolicy resource to restrict what this client can read/write via the API. Strongly recommended for all non-admin clients. |
- Click Create Client.
- On success, click Go to new client to open the
ClientApplicationresource. The Details tab shows the generated Client ID (idfield) and Client Secret (secretfield) — copy these now.
If you lose the client secret, you must regenerate it by editing the ClientApplication resource directly (set secret to a new value). The old secret immediately stops working.
Access Policy for clients​
Always assign a narrowly scoped Access Policy to any client that is not an internal admin tool. See Access Policies for how to create one and common policy patterns.
API: POST admin/projects/:projectId/client
Secrets (/admin/secrets)​
Stores key-value secrets accessible to Bots and server-side code. Secrets are encrypted at rest and never exposed to the frontend.
Common uses:
- API keys for external services
- Encryption keys
- Third-party credentials
Project Details (/admin/details)​
Displays and allows editing of the Medplum project's metadata — name, description, and project-level settings.
Sites (/admin/sites)​
Manages the list of authorized origin domains for the project (CORS configuration). Only requests from listed domains are allowed to access the FHIR API.
Member Registration (/admin/members/new)​
Manually registers a new project member with a specific role and access policy — used for creating admin accounts directly rather than via invitation.
Super Admin​
Route: /admin/super
File: admin/SuperAdminPage.tsx
This page is only accessible to users with the superAdmin flag on their account. Regular admin users will see a 403 Forbidden error.
The Super Admin page contains low-level system maintenance tools for managing the FHIR database, search indexes, resource purging, and more. These are powerful operations — use them carefully.
Structure Definitions​
Button: Rebuild StructureDefinitions
StructureDefinition resources contain the metadata about FHIR resource types (field names, types, cardinality, etc.). They come from the FHIR specification, and Medplum adds custom ones for ChartChat's internal types (e.g., PatientEHRConnection, HospitalIntegration, SSOIntegration).
Use this when:
- A new custom resource type has been added to the definitions
- The FHIR spec version has been updated
- Resource type metadata appears incorrect
Calls: POST admin/super/structuredefinitions (async job)
Search Parameters​
Button: Rebuild SearchParameters
SearchParameter resources define what fields can be filtered and sorted in FHIR queries. Rebuilding updates the database with the latest search parameter definitions from the spec and any custom ones.
Use this when:
- A new custom search parameter has been added
- FHIR search on a field is not working as expected
Calls: POST admin/super/searchparameters (async job)
Value Sets​
Button: Rebuild ValueSets
ValueSet resources define allowed coded values for various fields (e.g., condition status codes, allergy severity levels). This button syncs the database with the current FHIR spec value sets.
Calls: POST admin/super/valuesets (async job)
Reindex Resources​
Button: Reindex
When Medplum changes how resources are indexed in the database, existing records need to be reindexed so they are searchable correctly.
| Field | Description |
|---|---|
| Resource Type | The FHIR resource type to reindex (e.g., Observation, Patient) |
| Search Filter | Optional FHIR search filter to limit which records are reindexed (e.g., name=Sam&birthdate=lt2000-01-01) |
| Max Resource Version | Outdated resources (default), All resources, or a specific version number |
Calls: POST admin/super/reindex (async job)
Purge Resources​
Button: Purge
Removes old system-generated resources that accumulate over time and are no longer needed.
| Field | Description |
|---|---|
| Purge Resource Type | AuditEvent or Login |
| Purge Before | Date — all resources older than this date will be deleted |
This permanently deletes records. Use with caution. Always confirm the date range before purging.
Calls: POST admin/super/purge
Remove Bot ID Jobs from Queue​
Button: Remove Jobs by Bot ID
Removes all pending queued jobs for a specific Bot ID. Useful when a bot is misbehaving or needs to be stopped and its queue cleared.
| Field | Description |
|---|---|
| Bot ID | The ID of the Bot resource whose jobs should be removed |
Calls: POST admin/super/removebotidjobsfromqueue
Force Set Password​
Button: Force Set Password
Forcibly sets a new password for any user account by email. This bypasses the normal "Forgot Password" flow.
This applies to the user across all projects, not just the current one. Only use in extreme circumstances — always prefer the standard "Forgot Password" flow first.
| Field | Description |
|---|---|
| The user's email address | |
| Password | The new password to set |
| Project ID | Optional — limits scope to a specific project |
Calls: POST admin/super/setpassword
Database Stats​
Button: Get Database Stats
Queries current table statistics from PostgreSQL — row counts, table sizes, index usage, etc. Results are shown in a modal.
| Field | Description |
|---|---|
| Table Names | Comma-delimited list of table names (e.g., Observation,Observation_History). Leave blank for all tables. |
Calls: POST fhir/R4/$db-stats
Database Invalid Indexes​
Button: Get Database Invalid Indexes
Queries PostgreSQL for any invalid indexes. An invalid index can cause incorrect search results or slow queries. Results are shown in a modal.
Calls: POST fhir/R4/$db-invalid-indexes
Database Schema Drift​
Get Schema Drift — Shows the SQL migration needed to bring the current database schema in line with what the application expects. Results are shown in a modal.
Reconcile Schema Drift — Actually applies the migration shown by Get Schema Drift.
Only run Reconcile Schema Drift if you understand the migration being applied. Always take a database backup first.
Calls:
POST fhir/R4/$db-schema-diffPOST admin/super/reconcile-db-schema-drift(async job)
Reload Cron Resources​
Button: Reload Cron Resources
Clears the entire cron queue and rebuilds all scheduled job schedulers from scratch. Use this if cron bots (bots triggered on a schedule) are not firing correctly.
Calls: POST admin/super/reloadcron (async job)
Database Explain Search​
Runs a PostgreSQL EXPLAIN (or EXPLAIN ANALYZE) on a FHIR search query to show the query execution plan. Used for diagnosing slow queries.
| Field | Description |
|---|---|
| Search | The FHIR search query to explain (e.g., Observation?code=85354-9&_sort=-date&_count=5) |
| Analyze | If checked, runs EXPLAIN ANALYZE — actually executes the query and shows real timings |
| On Behalf Of | Run the query as a specific user's ProjectMembership so access policies are applied. If not set, runs as super admin. |
Results (query plan) are shown in a modal.
Calls: POST fhir/R4/$explain
Async Job Monitor (/admin/super/asyncjob)​
Most Super Admin operations (Rebuild StructureDefinitions, Rebuild SearchParameters, Rebuild ValueSets, Reindex, Reconcile Schema Drift, Reload Cron) are async jobs — they are dispatched to a background worker queue and do not block the UI. Navigate to /admin/super/asyncjob to monitor running and completed jobs, view their progress and outcomes.
Other Super Admin routes​
| Route | Page | Description |
|---|---|---|
/admin/super/asyncjob | SuperAdminAsyncDashboardPage | Monitor and manage running async background jobs |
/admin/super/db | DatabaseToolsPage | Configure PostgreSQL GIN indexes per resource type |
/admin/config | ProjectAdminConfigPage | Project-level configuration — force set password for project-scoped users |
Database Tools (/admin/super/db)​
File: admin/DatabaseToolsPage.tsx
Access: Super admins only
The Database Tools page contains advanced PostgreSQL index maintenance tools. Currently it has one tab: GIN Indexes.
This page is only accessible to users with the superAdmin flag. Regular project admins will see a 403 Forbidden error.
GIN Indexes Tab​
File: admin/db/GINIndexes.tsx
GIN (Generalized Inverted Index) indexes are PostgreSQL index structures used by Medplum to accelerate searches on JSONB and array columns. The GIN Indexes tab has two sections:
Configure GIN Indexes​
Apply fastupdate and gin_pending_list_limit settings to one or more FHIR resource tables in bulk. This dispatches an async job (fhir/R4/$db-configure-indexes).
| Field | Description |
|---|---|
| Table(s) | Multi-select of FHIR resource type tables to update (e.g., Observation, Patient) |
| fastupdate | Controls whether PostgreSQL buffers GIN index updates for faster inserts. Options: unspecified (no change), reset to default (on), off, on |
| gin_pending_list_limit | Max size (kB) of the pending list before a GIN index is cleaned up. Options: unspecified (no change), reset to default, or set a custom value (min 64 kB) |
Click Update to dispatch the configuration job. Results are shown in a modal.
When to use:
- Turn
fastupdate offon tables with frequent searches but infrequent inserts (reduces search latency at the cost of slower inserts). - Raise
gin_pending_list_limiton high-write tables to batch more updates before cleanup.
GIN Index Stats​
Select a table from the dropdown to view all GIN indexes on that table. Clicking any cell shows the full value in a modal.
| Column | Description |
|---|---|
| Schema | PostgreSQL schema name |
| Table | Table name |
| Index | Index name |
| fastupdate | Current fastupdate setting (true/false or default) |
| gin_pending_list_limit | Current pending list limit in kB or default |
| options | Raw index options string |
API: GET fhir/R4/$db-indexes?tableName=<table>
Project Admin Config (/admin/config)​
File: admin/ProjectAdminConfigPage.tsx
Access: Project Administrators (not super admin required)
This page provides project-scoped admin tools. Unlike the Super Admin page, it is accessible to any user with the project admin flag on their account.
Force Set Password (Project-Scoped)​
Sets the password for a specific user within this project only. This is different from the Super Admin Force Set Password, which applies globally across all projects.
| Field | Description |
|---|---|
| The user's email address | |
| Password | The new password to set |
When to use this vs the Super Admin version:
Project Admin Config (/admin/config) | Super Admin (/admin/super) | |
|---|---|---|
| Who can use it | Any project admin | Super admins only |
| Scope | This project only | All projects for that user |
| API | POST admin/projects/setpassword | POST admin/super/setpassword |
| Use case | Routine password resets for users in your project | Emergency global resets — use sparingly |
For routine password issues, always use the Project Admin Config page (/admin/config). Reserve the Super Admin Force Set Password for genuine emergencies where the user has lost access to all projects.