> For the complete documentation index, see [llms.txt](https://mercure-technologies.gitbook.io/xprem/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mercure-technologies.gitbook.io/xprem/observe/custom-attributes.md).

# Custom attributes

Custom attributes let you attach your own metadata to the devices in the registry: a subscription plan, a cohort, an internal account id, a feature-flag bucket, anything that helps you slice your fleet. Attributes appear on each device in the Identity dashboard, and every device list can be filtered by them.

Storing custom attributes requires an active enterprise license. Everything else in the device registry (inventory, hardware and OS, update health, location) works in every edition, and a device whose app sends attributes without a license is still registered and located; only the attributes are dropped.

### Declare the schema first

Custom attributes are allowlist-based: a value is only stored if its key was declared beforehand, in the Identity section of the dashboard.

Each key declares a type (`string`, `number` or `boolean`) and, for strings, a maximum length (256 by default, up to 1024). Key names use letters, digits, `_`, `.` and `-`, up to 64 characters, and an app can declare up to 100 keys.

Anything an app sends outside the schema is dropped, never stored.

### Send values from the app

Attributes travel as three conventional log events on the expo-observe log stream (Expo SDK 56 or later):

```ts
import { Observe } from 'expo-observe';

// Set or overwrite attributes on this device.
Observe.logEvent('$set', { attributes: { plan: 'pro', seats: 12 } });

// Write an attribute only if this device never had it.
Observe.logEvent('$set_once', { attributes: { acquisition_channel: 'app-store' } });

// Remove attributes.
Observe.logEvent('$unset', { attributes: { keys: ['plan'] } });
```

The semantics, key by key:

* `$set` merges into the device's attributes and overwrites existing values.
* `$set_once` writes only keys that have no value yet on this device; existing values are kept. Use it for facts that must never change once observed, like an acquisition channel.
* `$unset` removes the listed keys; its payload is a `keys` array rather than key/value pairs.

Values must match the declared type: a string sent for a `number` key, an over-long string, or an undeclared key is dropped while the rest of the operation still applies. Operations ride the normal telemetry batches, so they are applied when the app backgrounds, in event order, and a batch the client re-sends after a network failure is safe to re-apply.

### Filter by attributes

The device lists in the Identity dashboard filter on `key:value` pairs, with value autocomplete backed by what your fleet actually reports. Several values for the same key match any of them; filters on different keys must all match.

Attributes are stored in your PostgreSQL database as-is. They are meant for operational segmentation, so avoid putting anything there you would not want persisted alongside the device, like tokens or personal data you have no use for.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://mercure-technologies.gitbook.io/xprem/observe/custom-attributes.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
