Storing Metadata in Healthie

Storing Metadata in Healthie

Metadata is a flexible field available on many Healthie resources that lets you attach your own structured data to a record without changing your data model or standing up a separate mapping table. It is the standard way to carry external identifiers, integration state, and system-of-origin context alongside the Healthie records they belong to.

This article covers where metadata is supported, how to write and read it, what the limits are, and when to use something else instead.

When to use metadata

Metadata is a good fit when you need to:

  • Store the ID of a matching record in your own system, a CRM, a billing platform, or a data warehouse
  • Record where a record came from, such as a specific intake funnel, campaign, partner, or migration batch
  • Carry integration state that your application needs but Healthie does not model, such as a sync timestamp or a processing status
  • Tag records for downstream reporting in your warehouse

Metadata is not a good fit for clinical data, anything a provider needs to read in the Healthie UI, or anything you need to search or filter on. See When to use something else below.

Where metadata is supported

Metadata is available across scheduling, charting, billing, and patient records. The field is typed as either String  or JSON  depending on the resource, and character limits vary.

Patients and providers

Resource Write with Type Limit
User (patient) createClient , updateClient String 128,000
Organization member updateOrganizationMember String 10,000

Scheduling

Resource Write with Type Limit
Appointment createAppointment , updateAppointment JSON No documented limit
Appointment type createAppointmentType , updateAppointmentType String 128,000, staff and providers only

Charting and forms

Resource Write with Type Limit
Custom module form (form template) createCustomModuleForm , updateCustomModuleForm String 10,000
Custom module (form field) createCustomModule , updateCustomModule String 128,000
Form answer group (completed form) createFormAnswerGroup , updateFormAnswerGroup String 128,000, staff and providers only
Form answer FormAnswerInput String 128,000
Requested form completion createRequestedFormCompletion String 128,000, staff and providers only
Smart phrase createSmartPhrase , updateSmartPhrase JSON 50 keys, 2,000 characters total

Clinical records

Resource Write with Type Limit
Allergy or sensitivity createAllergySensitivity , updateAllergySensitivity JSON 50 keys, 2,000 characters total
Medication createMedication , updateMedication JSON 50 keys, 2,000 characters total
Diagnosis DiagnosesInput JSON 50 keys, 2,000 characters total

Billing and insurance

Resource Write with Type Limit
Billing item createBillingItem , updateBillingItem String 128,000
Requested payment createRequestedPayment , updateRequestedPayment JSON 2,000
Policy updatePolicy , UserPolicyInput , ClientPolicyInput JSON 2,000
Insurance plan createInsurancePlan , updateInsurancePlan JSON 2,000
Benefit BenefitInput JSON 2,000

Everything else

Resource Write with Type Limit
Task createTask , updateTask JSON 2,000
Conversation createConversation , updateConversation JSON 2,000
Document createDocument , updateDocument String 128,000, staff and providers only
Referral createReferral , updateReferral String 128,000
Referring physician createReferringPhysician , updateReferringPhysician String 128,000

A small number of resources document a different limit on the read field than on the write input. Before you build against a limit, confirm it on the reference page for the exact input type you are using.

Writing metadata

String fields

For resources where metadata  is typed as String , serialize your JSON object before sending it. In JavaScript, that means JSON.stringify(...) .


graphql
mutation createClient($input: createClientInput) {
  createClient(input: $input) {
    user {
      id
      metadata
    }
    messages {
      field
      message
    }
  }
}

json
{
  "input": {
    "first_name": "Jordan",
    "last_name": "Reyes",
    "email": "jordan.reyes@example.com",
    "dietitian_id": "12345",
    "metadata": "{\"crm_id\":\"cus_8823\",\"source\":\"partner_referral\",\"synced_at\":\"2026-08-06T14:22:00Z\"}"
  }
}

JSON fields

For resources where metadata  is typed as JSON , send the object directly. No string escaping is needed.


graphql
mutation updateAppointment($input: updateAppointmentInput) {
  updateAppointment(input: $input) {
    appointment {
      id
      metadata
    }
    messages {
      field
      message
    }
  }
}

json
{
  "input": {
    "id": "98765",
    "metadata": {
      "scheduling_source": "member_app",
      "campaign_id": "q3_reengagement",
      "external_visit_id": "visit_44219"
    }
  }
}

Updates replace, they do not merge

Writing metadata  overwrites the entire stored value. There is no partial update. If you need to change one key, read the current metadata, merge your change in your own code, and write the full object back. If two systems write metadata on the same record, coordinate ownership of keys or you will lose data.

Reading metadata

Request metadata  like any other field. It is returned in the same form it is stored, so String  fields come back as a serialized string that your client needs to parse.


graphql
query user($id: ID) {
  user(id: $id) {
    id
    first_name
    last_name
    metadata
  }
}

Metadata is also available on list queries, so you can pull it in bulk alongside the records themselves.


graphql
query users($page_size: Int, $offset: Int) {
  users(page_size: $page_size, offset: $offset) {
    id
    metadata
  }
}

Limitations to plan around

Metadata is not searchable or filterable. No query accepts metadata as a filter argument. You cannot ask Healthie for "the patient whose crm_id  is cus_8823 ." If you need lookup by an external identifier, use one of the alternatives below or maintain the mapping on your side.

Metadata is not visible in the Healthie UI. Providers and staff will not see it in the patient chart or anywhere else in the product. It exists for your integration, not for clinical users.

Webhooks do not carry metadata. Healthie sends thin payloads containing the resource ID, resource type, and event type. Fetch the record through the API after receiving the webhook if you need its metadata.

Some fields are permissioned. Metadata on appointment types, documents, form answer groups, and requested form completions is only accessible to staff and providers. Client-scoped API keys will not return it.

Sandbox does not allow PHI. Do not put protected health information in metadata in the Sandbox environment. In Production, treat metadata as you would any other field holding patient data.

When to use something else

If you need to Use
Look up a patient by an ID from your system record_identifier  or additional_record_identifier  on User. Both are searchable through the keywords  argument on the users  query.
Relate a form template, form field, completed form, or journal entry to a third-party object external_id  and external_id_type , available on CustomModule , CustomModuleForm , FormAnswerGroup , and Entry .
Capture clinical or intake information A custom module on a charting form, so the data is visible to providers, chartable, and reportable.
Segment or filter patients in the UI Tags or user groups.

Best practices

  • Namespace your keys. Prefix keys with the owning system, such as crm_id  or warehouse_synced_at , so it stays obvious where a value came from when a second integration is added.
  • Keep values flat and small. Short key-value pairs are easier to reason about and stay well inside the limits. Several resources cap metadata at 2,000 characters, which is smaller than teams expect.
  • Validate length before you write. Exceeding the limit returns a validation error in the messages  field on the mutation payload. Check messages  on every write.
  • Do not use metadata as a source of truth. It is a place to carry references and integration state, not a substitute for storing the record properly in Healthie or in your own database.
  • Document your schema internally. Metadata is untyped by design. Without a documented key convention, it becomes unreadable within a few quarters.

Guides

Schema reference

Related help articles

Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.