Contact customerMetadata on the public API
Summary
Contacts support custom metadata (key/value pairs) similar to products:
updateContactMetadata— add or update an entry (max key length 40, value 500, up to 50 keys per contact).deleteContactMetadata— remove an entry by key. If the key does not exist, the mutation returns the contact unchanged and does not return an error.Contact.metadata— read entries as[MetadataEntry](key,value).
To find contacts by metadata, pass filters.metadata on the contacts query with a required key and an optional value. They match stored customerMetadata after the same sanitisation rules as updateContactMetadata.
keyonly — returns contacts that have any value stored for that key.key+value— returns contacts where that key/value pair matches the same entry.
Breaking (schema tidy): metadataKey and metadataValue are no longer top-level arguments on contacts; use filters.metadata { key value } instead.
contacts(search: …) does not match custom metadata. Free-text search continues to match name, email, phones, medical specialisation, numbers, etc., only — consistent with how products(search: …) does not search product metadata.
Contact mutation error codes: For createContact, updateContact, deleteContact, and the phone-number mutations (addContactPhoneNumber, updateContactPhoneNumber, removeContactPhoneNumber), many client-side failures now return HTTP 4xx responses with error populated in the GraphQL payload (for example 400 for validation problems, including Yup validation from the API, and 404 when the contact or a related resource is missing or not found), instead of those cases surfacing only as GraphQL 500 errors. The updateContactMetadata, deleteContactMetadata, updateProductMetadata, and deleteProductMetadata mutations also return HTTP 400 for validation errors and include the invalid field name in messages such as contactId must be an ObjectId or productId must be an ObjectId.
Metadata deletes are idempotent: deleteContactMetadata and deleteProductMetadata only remove an entry when the key is present. If the key is absent, the mutation completes successfully, returns the contact or product unchanged, and leaves error empty. Treat this as "the key is not present after the call" rather than as proof that an entry existed before the call.
Integration guidance
- Writing metadata: Call
updateContactMetadatawithcontactId,key, andvalue. - Deleting metadata: Call
deleteContactMetadatawithcontactIdandkey. Missing keys are a successful no-op. - Reading metadata: Query
contact(id:)orcontactsand requestmetadata { key value }. - Listing by metadata: Use
contacts(filters: { metadata: { key: … } })to match any value for a key, or addvalue: …for an exact key/value pair. Do not rely onsearchfor metadata-only discovery.
What changed vs an earlier draft note
An earlier version of this note described search including metadata. The shipped behaviour uses dedicated list filters instead so metadata discovery does not collide with name/email/phone matches.
Migration
If you assumed contacts(search:) would find a contact by an external id stored only in metadata, switch to filters.metadata with key (and value when you need an exact pair), or store a duplicate token in a field you already search, if you must keep a single search box.