meta.accounts guidance
# support
d
I discovered this while getting patient provider chat subscriptions working in our app. Websockets were working just fine on the provider side but they weren't on the patient side where no messages were coming through. I found this message in the log
"Subscription suppressed due to mismatched accounts"
which when I added
{ meta: { accounts: [{ reference: 'Patient/:id' }] } }
to the provider side Communication creating the messages came through. All of these Communication records all had the patient as the subject and shared a single base
part-of
and I'm able to load them via http just fine, so I'm a bit confused as to why this is required to get websocket subscriptions working. Any guidance on how and when to use
meta.accounts
would be greatly appreciated
r
Hi Doug! Here's what I think is happening. Your websocket subscription has meta.accounts set (inherited from the Patient profile) Your Communication resources don't have matching meta.accounts The subscription filtering logic requires at least one matching account, so subscriptions are suppressed Subscriptions inherit meta.accounts from the user's profile (Patient/Practitioner) when created Resources should inherit meta.accounts from their Patient subject, but only if: The Patient has meta.accounts set at the time the resource is created The resource has a proper subject reference If Communications were created before the Patient's accounts were set, they won't have accounts Use the
$set-accounts
operation with propagate: true to update all resources in the patient compartment:
d
Hi Reshma, Yes that is exactly what is happening. The Communication resources have the patient in the compartment properly but do not have the patient in meta.accounts. This is happening when a Pracitioner is sending a message to a Patient, and this is when the subscription is most useful. It looks like none of our Patient records have meta.accounts set. We are using the invite API to create the records POST "/admin/projects/:id/invite". But also when I just manually create a patient record, meta.accounts is not populated. So that raises my next question: How/when should we be setting Patient.meta.accounts?
f
d
Yes, thank you. So if I'm tracking properly, your recommendation is that when I invite a patient to the system I first call the admin invite endpoint and then immediately call the set-accounts operation on the newly created patient with this:
Copy code
{
  "resourceType": "Parameters",
  "parameter": [
    {
      "name": "accounts",
      "valueReference": {
        "reference": "Patient/<new-patient-id>"
      }
    },
    {
      "name": "propagate",
      "valueBoolean": true
    }
  ]
}
This feels odd that I need to set the account on the patient to be the patient
f
Hmm, setting the accounts is essentially a way to write to
meta.compartment
which is a read only field
If your Communication already has the patient in it's
Communication.meta.compartment
, why do you need it in`Communitation.meta.accounts`?
The Patient reference to itself is auto inherited by the Patient and all other resources linked to it via its [Patient compartment definition](https://hl7.org/fhir/R4/compartmentdefinition-patient.html). So you shouldn't need to add a Patient reference as a parameter in
$set-accounts
d
The core issue is that when a Pracititioner creates a Communication resource, it does not have meta.accounts set. This then leads to the patient subscription not firing due to this code https://github.com/medplum/medplum/blob/06bf457aee2f2c1174fb8aa3dd635c06470f6bc0/packages/core/src/subscriptions/index.ts#L563. This is why manually adding meta.accounts to each Practitioner created Communication fixed the subscription issue. Here is an example of a Communication resource that was created before we implemented the meta.accounts fix:
Copy code
{
  "resourceType": "Communication",
  "status": "completed",
  "subject": {
    "reference": "Patient/ade63041-9777-4133-ad33-3b586cb5e76e",
    "display": "John Doe"
  },
  "sender": {
    "reference": "Practitioner/019a0d5e-2804-711f-9eb7-1d42f8caacb6",
    "display": "Dr. Jane Doe"
  },
  "recipient": [
    {
      "reference": "Patient/ade63041-9777-4133-ad33-3b586cb5e76e",
      "display": "John Doe"
    }
  ],
  "sent": "2025-12-10T19:25:34.445Z",
  "partOf": [
    {
      "reference": "Communication/95e17fe0-22b1-4ec1-9341-703b71f2eb3a"
    }
  ],
  "payload": [
    {
      "contentString": "hello"
    }
  ],
  "id": "5224fe58-5204-41e8-8a2b-f82180dedeb9",
  "meta": {
    "versionId": "39b2565c-2803-43a6-88aa-66f672009df7",
    "lastUpdated": "2025-12-30T16:53:03.090Z",
    "project": "019a0d56-8f3d-72fa-9436-4e2f8fce4e5c",
    "compartment": [
      {
        "reference": "Project/019a0d56-8f3d-72fa-9436-4e2f8fce4e5c"
      },
      {
        "reference": "Patient/ade63041-9777-4133-ad33-3b586cb5e76e"
      }
    ]
  },
  "received": "2025-12-30T16:53:02.982Z"
}
As you can see, the compartment is set properly but meta.accounts is missing. I think my core question is how should I setup/configure my resources such that meta.accounts is automatically populated properly for Communications sent TO a patient
f
Hmm, can you send the Json for your Subscription resource? What is the reason for adding
meta.accounts
to your Subscription?
d
I'm creating a subscription via
useSubscription
. This is the body of the create subscription request:
Copy code
{
  "resourceType": "Subscription",
  "status": "active",
  "reason": "WebSocket subscription for Patient/019a6ff3-07ac-73aa-a446-a0c430e32454",
  "channel": {
    "type": "websocket"
  },
  "criteria": "Communication?part-of%3Amissing=false&subject=Patient%2F019a6ff3-07ac-73aa-a446-a0c430e32454"
}
and it is being automatically given a
meta.accounts
of
[{reference: "Patient/019a6ff3-07ac-73aa-a446-a0c430e32454"}]
by the server. The fix that I have found is that when I create the Communication on the Provider app, I have to add
meta.accounts: [{reference: "Patient/019a6ff3-07ac-73aa-a446-a0c430e32454"}]
in order to get past the accounts comparison
f
@Doug DeBold , so the response to your Subscription create request is a Subscription resource with
meta.accounts
of
[{reference: "Patient/019a6ff3-07ac-73aa-a446-a0c430e32454"}]
? Something like
Copy code
{
  "resourceType": "Subscription",
  "status": "active",
  "reason": "WebSocket subscription for Patient/019a6ff3-07ac-73aa-a446-a0c430e32454",
  "channel": { "type": "websocket" },
  "criteria": "Communication?part-of%3Amissing=false&subject=Patient%2F019a6ff3-07ac-73aa-a446-a0c430e32454",
  "meta" : {
     "accounts" : [{reference: "Patient/019a6ff3-07ac-73aa-a446-a0c430e32454"}],
//...
}
Could you send your full AccessPolicy? Does it have that Patient as a reference on its
AccessPolicy.compartment
?
d
@finnbergquist yes this is the returned subscription. I also just tried to do a subscription for Task for our patient app and had the exact same issue. When I added
meta.accounts
to the patient's own task. Where
"owner": {  "reference": "Patient/019a6ff3-07ac-73aa-a446-a0c430e32454" }
Copy code
{
  "resourceType": "Subscription",
  "status": "active",
  "reason": "WebSocket subscription for Patient/019a6ff3-07ac-73aa-a446-a0c430e32454",
  "channel": {
    "type": "websocket"
  },
  "criteria": "Task",
  "id": "c32b7d30-3d79-402e-9849-9d41f62d7da4",
  "meta": {
    "versionId": "40dbe113-beff-4176-9c63-ff5d068fefd0",
    "lastUpdated": "2026-01-16T01:08:16.418Z",
    "author": {
      "reference": "Patient/019a6ff3-07ac-73aa-a446-a0c430e32454",
      "display": "Doug DeBold"
    },
    "project": "019a0d56-8f3d-72fa-9436-4e2f8fce4e5c",
    "account": {
      "reference": "Patient/019a6ff3-07ac-73aa-a446-a0c430e32454"
    },
    "accounts": [
      {
        "reference": "Patient/019a6ff3-07ac-73aa-a446-a0c430e32454"
      }
    ],
    "compartment": [
      {
        "reference": "Project/019a0d56-8f3d-72fa-9436-4e2f8fce4e5c"
      },
      {
        "reference": "Patient/019a6ff3-07ac-73aa-a446-a0c430e32454"
      }
    ]
  }
}
Here is our truncated access policy:
Copy code
{
  "resourceType": "AccessPolicy",
  "name": "Patient Access Policy (SHARED)",
  "id": "0198e7ee-6144-739b-a09b-51a00e3bb11b",
  "compartment": {
    "reference": "%patient"
  },
  "resource": [
    {
      "resourceType": "Patient",
      "criteria": "Patient?_compartment=%patient"
    },
    {
      "resourceType": "Task",
      "criteria": "Task?owner=%patient"
    },
    {
      "resourceType": "Communication",
      "criteria": "Communication?_compartment=%patient"
    },
    {
      "resourceType": "Slot",
      "readonly": true
    },
    {
      "resourceType": "Subscription"
    }
  ],
  "meta": {
    "project": "0198e7e7-a516-72f2-aa38-aa28985be53c",
    "compartment": [
      {
        "reference": "Project/0198e7e7-a516-72f2-aa38-aa28985be53c"
      }
    ],
    "versionId": "93dc77a6-6aea-468e-b682-388908cb223d",
    "lastUpdated": "2025-12-29T15:02:13.881Z",
    "author": {
      "reference": "Practitioner/abee1f77-4cd2-469d-94f8-90ab4d76eaca",
      "display": "Omar Alkhadori"
    }
  }
}
There is ONE potential thing. This access policy is shared across multiple projects and so we have a single project where we put all shared resources in and this comes out of that. I doubt that matters but just FYI
Also we are currently running version 4.5.2 of the API
This behavior seems to have been introduced in 4.5.2 https://github.com/medplum/medplum/pull/7643
f
@Doug DeBold , what is your reason for including this in your access policy?
Copy code
"compartment": {
    "reference": "%patient"
  }
If you are able to remove that, then you won't have this issue, because the Subscription will not have the Patient reference in it's
meta.accounts
.
These resource criteria will still work because those resource types are connected to the Patient resource via the [Patient compartment definition](https://hl7.org/fhir/R4/compartmentdefinition-patient.html)
Copy code
{
      "resourceType": "Patient",
      "criteria": "Patient?_compartment=%patient"
    },
    {
      "resourceType": "Task",
      "criteria": "Task?owner=%patient"
    },
    {
      "resourceType": "Communication",
      "criteria": "Communication?_compartment=%patient"
    },
So if you create a Communication for example, and it's subject, sender, or recipient includes your Patient, that Communication's
meta.compartment
will automatically include the patient reference, without needing to include the Patient reference in ``AccessPolicy.compartment` https://cdn.discordapp.com/attachments/1453122034870521902/1461782183197933589/Screenshot_2026-01-16_at_9.59.17_AM.png?ex=697c48ff&is=697af77f&hm=7b160470721217117efa65ec0d99ced31f6998b7d9524340fc78f70e653fbdee&
I also see that our recommended patient Patient AccessPolicy includes
Copy code
"compartment": {
    "reference": "%patient"
  }
so apologies for any confusion this may have caused
d
Thanks @finnbergquist! I did indeed start from the recommended patient access policy. Without diving through all of the code, when a subscription is setup like this, will it automatically apply the restrictions of the profile's access policy? E.g. if in my patient app I make a subscription with criteria of
criteria: 'Task'
and I have the access policy above, is there any risk that the subscription would return Tasks that are outside of the scope of the access policy?
I'm just a little paranoid about AuthZ, and especially paranoid when using more complicated systems where I can't easily add my own unit tests to verify
f
No, resources that can trigger your in-memory subscription are limited by the subscription author's access policy. (ie. there is no risk that risk that the subscription would return Tasks that are outside of the scope of the access policy) [Here](https://github.com/medplum/medplum/blob/6507d98d7577ba7b1629bc4ea0c65cdf8766faf9/packages/server/src/workers/subscription.ts#L315-L318) is where that AccessPolicy check is applied.
Good question by the way!
d
Awesome, thank you!! Yeah access policy based authorization is super powerful and configurable but scares me b/c I don't have strict unit-test verified guarantees of correctness
And just out of curiosity, what does the compartment functionality in the access policy do?
If you have a reference set in a User's
AccessPolicy.compartment
, any resource created by that User will have that reference added to it's
meta.account
and
meta.compartment
The reason this isn't always necessary is that for resources which are in the [Patient's compartment definition](https://hl7.org/fhir/R4/compartmentdefinition-patient.html), they will automatically have the Patient reference in their
meta.compartment
, even if
AccessPolicy.compartment
is not set.
d
Yep, got it. So this is effectively a more strict limitation on the access policy
f
So this kind of criteria
criteria": "Communication?_compartment=%patient
will still work
d
Thanks for the explanation! Last question... we are using
Task.owner
coupled with
{ "resourceType": "Task",  "criteria": "Task?owner=%patient" }
because we need to differentiate tasks between those that are for a Practitioner and about a patient from those that are actually assigned for a patient to complete. This is working but 1) do you see any AuthZ concerns with that 2) is there a better way?
f
You should structure your Tasks that are "about" a Patient by linking the Patient in the
Task.for
reference field (but
Task.owner
still points to the Practitioner). Semantically,
Task.for
is for the
Copy code
Beneficiary of the Task

The entity who benefits from the performance of the service specified in the task (e.g., the patient).
And your Tasks that are assigned to the Patient have the Patient linked in the
Task.owner
field. If you can distinguish between these different types with distinct Tasks data modeling, you should have no concerns about AuthZ
82 Views