Medplum Elixir
Elixir client for Medplum's FHIR API.
Built for apps that want:
- direct CRUD on FHIR resources
- resource-specific helpers under
Medplum.Resources.* - workflow helpers under
Medplum.Workflows.* - token reuse across requests
- stable
{:error, %Medplum.Error{}}returns - lower-level escape hatches for operations, batch, transaction, binary, upsert, GraphQL, and raw requests
Installation
Add package to mix.exs:
def deps do
[
{:medplum_elixir, "~> 0.2.0"}
]
end
Then fetch deps:
mix deps.get
Quick start
Medplum uses OAuth client credentials. Set env vars first:
export MEDPLUM_BASE_URL="https://api.medplum.com"
export MEDPLUM_CLIENT_ID="..."
export MEDPLUM_CLIENT_SECRET="..."
Create reusable client:
client =
Medplum.new(
base_url: System.fetch_env!("MEDPLUM_BASE_URL"),
client_id: System.fetch_env!("MEDPLUM_CLIENT_ID"),
client_secret: System.fetch_env!("MEDPLUM_CLIENT_SECRET")
)
Read resource:
{:ok, patient} = Medplum.read(client, "Patient", "123")
Search resource:
{:ok, bundle} = Medplum.search(client, "Patient", %{"family" => "Smith"})
Patch resource:
ops = [%{"op" => "replace", "path" => "/active", "value" => true}]
{:ok, patient} = Medplum.patch(client, "Patient", "123", ops)
Stream paged search results:
entries =
client
|> Medplum.stream_search("Patient", %{"family" => "Smith"})
|> Enum.to_list()
Client options
Medplum.new/1 accepts:
base_url- Medplum server URL, trailing slash removed automaticallyclient_id- OAuth client idclient_secret- OAuth client secretfhir_version- defaults to"R4"default_headers- merged into every requestreq_options- forwarded toReqauth_req_options- forwarded only to token requestsretry-false,:safe_transient, or:transientmax_retries- defaults to2token_refresh_skew- defaults to60cache_tokens- defaults totrue
Example with request tuning:
client =
Medplum.new(
base_url: System.fetch_env!("MEDPLUM_BASE_URL"),
client_id: System.fetch_env!("MEDPLUM_CLIENT_ID"),
client_secret: System.fetch_env!("MEDPLUM_CLIENT_SECRET"),
default_headers: [{"x-app-name", "my_app"}],
req_options: [receive_timeout: 15_000],
max_retries: 2,
token_refresh_skew: 60
)
Core API
Main module covers generic FHIR and Medplum operations:
read/3search/3create/3update/4delete/3patch/4request/4operation/5poll_async/3batch/2transaction/2create_binary/3get_binary/2upsert/3graphql/3stream_search/3
Example:
{:ok, bundle} =
Medplum.batch(client, [
%{"request" => %{"method" => "GET", "url" => "Patient/123"}}
])
{:ok, graphql_result} =
Medplum.graphql(client, "query Demo { PatientList { id } }")
Resource helpers
Thin wrappers live under Medplum.Resources.*.
Common modules:
PatientAppointmentEncounterTaskObservationConditionDocumentReferenceServiceRequestDiagnosticReportClaimCoverageOrganizationPractitionerPractitionerRoleScheduleSlotLocationBotSubscription
Example:
alias Medplum.Resources.Patient
alias Medplum.Resources.Task
{:ok, patient} = Patient.get(client, "123")
{:ok, matches} =
Patient.search_by_identifier(client, "http://hospital.example/mrn", "123")
{:ok, tasks} =
Task.search_by_owner(client, "Practitioner/dr-1", %{"status" => "requested"})
Many resource modules also expose identifier-based upsert helpers:
{:ok, patient} =
Patient.upsert(client, %{
"identifier" => [%{"system" => "http://hospital.example/mrn", "value" => "123"}],
"name" => [%{"family" => "Smith"}]
})
Workflow helpers
Higher-level modules live under Medplum.Workflows.*.
Available workflows:
SchedulingBillingAutomationMedicationsCareCoordinationCarePlanningTasksIntakeOrdersCharting
Example:
alias Medplum.Workflows.Automation
alias Medplum.Workflows.Billing
alias Medplum.Workflows.CarePlanning
alias Medplum.Workflows.CareCoordination
alias Medplum.Workflows.Charting
alias Medplum.Workflows.Intake
alias Medplum.Workflows.Medications
alias Medplum.Workflows.Orders
alias Medplum.Workflows.Scheduling
alias Medplum.Workflows.Tasks
{:ok, open_tasks} =
Tasks.list_for_owner(client, "Practitioner/dr-1", %{"status" => "requested"})
{:ok, appointments} =
Scheduling.find_appointments(client, %{
"resourceType" => "Parameters",
"parameter" => [%{"name" => "start", "valueDateTime" => "2026-07-04T09:00:00Z"}]
})
{:ok, service_requests} =
Orders.service_requests_for_subject(client, "Patient/123")
{:ok, claim_pdf} =
Billing.export_claim(client, "claim-123")
{:ok, bot_result} =
Automation.execute_bot(client, "bot-123", %{
"resourceType" => "Patient",
"id" => "123"
})
{:ok, active_meds} =
Medications.active_medication_requests_for_subject(client, "Patient/123")
{:ok, care_goals} =
CarePlanning.active_goals_for_subject(client, "Patient/123")
{:ok, family_contacts} =
CareCoordination.active_family_for_patient(client, "Patient/123")
{:ok, observations} =
Charting.observations_for_patient(client, "Patient/123", %{"category" => "vital-signs"})
{:ok, responses} =
Intake.questionnaire_responses_for_subject(client, "Patient/123")
Error handling
All public functions return either success tuple or %Medplum.Error{}:
case Medplum.read(client, "Patient", "missing-id") do
{:ok, patient} ->
patient
{:error, %Medplum.Error{type: :api_error, status: 404}} ->
nil
{:error, %Medplum.Error{} = error} ->
raise error
end
Error types:
:config_error:auth_failed:request_failed:api_error:invalid_response
Phoenix config
# config/runtime.exs
config :my_app, :medplum,
base_url: System.fetch_env!("MEDPLUM_BASE_URL"),
client_id: System.fetch_env!("MEDPLUM_CLIENT_ID"),
client_secret: System.fetch_env!("MEDPLUM_CLIENT_SECRET")
client =
:my_app
|> Application.fetch_env!(:medplum)
|> Medplum.new()
Docs
- API docs generated with
mix docs - ExDoc extras:
README.mdguides/getting-started.mdguides/resource-and-workflow-helpers.md
Development
Run tests:
mix test
Build docs:
mix docs