> For the complete documentation index, see [llms.txt](https://codemash.gitbook.io/codemash/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://codemash.gitbook.io/codemash/cloud/database/schema/references.md).

# References

How a record points at users, roles, taxonomy terms, other records and files — mapped on the schema, stored as ids, checked on write, expanded on read.

A **reference** is a field that points at something that lives elsewhere: a user of the project, a role, a term of a taxonomy, a record of another collection, an uploaded file. Norbix maps the reference **on the schema**, not on the record: the schema says what the field points at and which property to show; the record stores only the **id**.

## How Norbix models it

```
schema field  →  source + target + displayField        (what it points at, what to show)
record        →  "customer": "6650…401"                 (the id — or an array of ids when multiple)
write         →  the id must exist in the target, in the request's project and environment
read          →  expandReferences: true  ⇒  "customer": { "id": "6650…401", "display": "Ada" }
```

| Kind                                                                              | Data schema                                                                         | Target                                                       | `displayField`                                                                                 | Record stores                |
| --------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------- | ---------------------------- |
| [User](/codemash/cloud/database/schema/field-types/user-reference.md)             | `{ "source": "user", "displayField": "email" }`                                     | the project's users                                          | **required** — `displayName`, `fullName`, `firstName`, `lastName`, `email`, `phone`, `company` | the user id                  |
| [Role](/codemash/cloud/database/schema/field-types/role-reference.md)             | `{ "source": "role" }`                                                              | the project's roles                                          | `name` (default, the only option)                                                              | the role id — never the name |
| [Term](/codemash/cloud/database/schema/field-types/term-reference.md)             | `{ "source": "taxonomy", "taxonomyId": "departments" }`                             | one taxonomy (slug, title or `txn_` id — stored as the slug) | `title` (default) or `slug`                                                                    | the term id                  |
| [Collection](/codemash/cloud/database/schema/field-types/collection-reference.md) | `{ "source": "collection", "collectionId": "people", "displayField": "firstName" }` | one collection (name or `sch_` id — stored as the name)      | **required** — a field name of the target schema                                               | the record id                |
| [File](/codemash/cloud/database/schema/field-types/file.md)                       | `{ "type": "array", "items": { "type": "string", "format": "uuid" } }`              | the files of the allowed storages                            | the file's name                                                                                | an array of file ids         |

Add `"multiple": true` to any of the first four and the record stores an **array of ids**. A file field is always a list.

Because the display rule is on the schema, every reader agrees on it: the record form picker, the records list, the API's expanded read and an AI agent all show `firstName` for `customer` — and changing the rule is one schema edit, not a data migration.

## In the builder

Drag **Select** into a container and choose the source in the field dialog: **Enumeration** (a fixed list, not a reference), **Users**, **Roles**, **Collection** or **Taxonomy**. For a collection, picking the target loads its schema and offers its text-like fields as **Display field**; for users you pick one of the seven properties; for a taxonomy, **title** or **slug**.

<figure><img src="https://712692422-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FHy372bqsaex2cqS3ikhL%2Fuploads%2Fgit-blob-2dd6ead8b1942ae7a2914f7c8563b67b785486ef%2Freferences-builder-1.png?alt=media" alt="Selection - Collection dialog of the schema builder: Required and Multiple checkboxes, Label Person, Unique field name person, the source tabs Enumeration, Users, Roles, Collection (selected) and Taxonomy, Collection sc_refs-cloud_people, Display field firstName (text) with the note The record stores the record id; the form and the list show this field of the referenced record instead; Cancel and Save"><figcaption><p>A collection reference in the builder: target collection and display field.</p></figcaption></figure>

The record form shows a picker per reference; a multiple reference shows the chosen entries as chips with a remove button. A target that no longer exists is shown as `missing (<id>)`.

<figure><img src="https://712692422-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FHy372bqsaex2cqS3ikhL%2Fuploads%2Fgit-blob-46d8cbe8a9366a60ca53ed13fb664e8ce88af82c%2Freferences-form-1.png?alt=media" alt="Edit Record form of sc_refs-cloud_tasks: Person shows Ada from a collection picker, People shows 2 items selected with the chips Ada and Grace, Assignee shows Ada from a user picker, Assignees shows the chips Ada and Grace, Topic shows Urgent, Topics shows Urgent and Later, Role shows Admin Portal Manager"><figcaption><p>Every reference kind on one record form, single and multiple.</p></figcaption></figure>

## On write: the id must exist

Every insert, update and replace checks each reference id against the declared target **in the request's project and environment**. One error per missing id, with `MissingId` in the metadata so a client can point at the exact picker entry:

| Kind                   | Code                     | The reason it gives                                                               |
| ---------------------- | ------------------------ | --------------------------------------------------------------------------------- |
| user                   | `CM-ERRORS-DATABASE-050` | `'usr_…' is not a user of this project`                                           |
| role                   | `CM-ERRORS-DATABASE-051` | `'Editor' is not a role of this project`                                          |
| term                   | `CM-ERRORS-DATABASE-052` | `'…' is not a term of taxonomy 'departments' in this environment`                 |
| record                 | `CM-ERRORS-DATABASE-053` | `'…' is not a record of collection 'people' in this environment`                  |
| file                   | `CM-ERRORS-DATABASE-054` | `'…' is not a file in the storage(s) this field allows`                           |
| any, target unreadable | `CM-ERRORS-DATABASE-055` | `its target 'people' cannot be read in this environment: …` (one error per field) |

A soft-deleted record, a deleted term and a term of another taxonomy all count as missing. The shape is checked first: an array on a single reference or a string on a multiple one is `CM-ERRORS-DATABASE-039` (`must be a single reference (string), not an array — the field is not multiple`), a repeated id `CM-ERRORS-DATABASE-046`.

References inside a [nested form or an array](/codemash/cloud/database/schema/nesting.md) are checked the same way at any depth (`lines[1].product`).

## On read: expandReferences

By default a read returns the stored ids. Set **`expandReferences: true`** on `find`, `findOne` or `findOwn` and every reference value comes back as `{ id, display }` — `display` being the target's `displayField` per the schema, `null` when the target is gone:

```ts
const { items } = await norbix.api.database.find({
  collectionName: 'orders',
  filter: JSON.stringify({ status: 'paid' }),
  expandReferences: true,
});
```

```json
{
  "_id": "6650a1b2c3d4e5f6a1b2c501",
  "number": "ORD-0001",
  "customer": { "id": "6650a1b2c3d4e5f6a1b2c401", "display": "Ada" },
  "assignee": { "id": "usr_3Kd9mQ2vLp8nR1tY6wXz4A", "display": "ada@example.com" },
  "department": { "id": "6650a1b2c3d4e5f6a1b2c301", "display": { "en": "Sales" } },
  "visibleTo": [ { "id": "rol_7Hq2mVx9Lk3pN5sT8wZa1B", "display": "Editor" } ],
  "lines": [ { "product": { "id": "6650a1b2c3d4e5f6a1b2c499", "display": null }, "qty": 1 } ]
}
```

```sh
norbix api database find --collectionName orders --expandReferences true
```

* The ids of one read are looked up **once per target** (all customer ids in one query, all user ids in one), whatever the page size.
* A term's `display` with `displayField: "title"` is the title as stored — a language → text object when the taxonomy is translatable; with `"slug"` it is a string.
* The records list in Cloud reads with `expandReferences: true`, so its reference columns show display values.

### Permission: every linked source must be readable

A reference is schema structure, so the server knows which sources a collection links to before it reads a record. A read with `expandReferences` runs only when the caller may read **the collection and every linked source** — the target collections, the project's users or roles, the taxonomy, the files. Otherwise the read is refused **before any record is read**, one error per refused source:

```
CM-ERRORS-DATABASE-056 — Missing read permission on collection 'people', linked by field(s) customer
— a read with expandReferences needs read permission on every linked source.
```

The metadata names `SourceKind`, `Source`, `Fields` and the `MissingPermissions` to grant: `database:read on database:collection:<name>` for a collection, `database:read on database:term:<taxonomy id>` for a taxonomy, `membership:read on membership:user:all` for users, `membership:read on membership:role:all` for roles, `files:read on files:file:all` for files. A caller without the extra permissions reads with the default (`expandReferences: false`) and gets ids. Cloud shows the refused sources with a **Show ids only** fallback.

## Good to know

* **Target changes are guarded.** The target collection cannot drop or rename the field a reference shows (`CM-ERRORS-SCHEMA-040`), and a schema cannot be deleted while reference fields point at it (`CM-ERRORS-SCHEMA-041`). A taxonomy or collection a new reference points at must exist when the schema is saved (`CM-ERRORS-SCHEMA-027` / `028`).
* **Environments.** Terms and records are per environment: an id valid in TEST is not valid in PROD until the content is [promoted](/codemash/cloud/project/settings/environments.md#promote-content-up-the-ladder). Users and roles are per project.
* **Filter on the id**, not on the display: `{ "customer": "6650a1b2c3d4e5f6a1b2c401" }`. A multiple reference matches array members the same way.
* **The record's owner is not a reference field.** *Records have an owner* (schema setting) sets `_meta.responsibleUserId` on the server; change it with `changeResponsibility`.
* **A reference to its own collection** (a parent, a "see also") is allowed.

## Do it from code

```ts
// the schema: an order points at a person and shows firstName
const dataSchema = {
  $schema: 'https://norbix.ai/schemas/meta/v1.json', title: 'orders', type: 'object', additionalProperties: true,
  properties: {
    customer: { source: 'collection', collectionId: 'people', displayField: 'firstName' },
    assignee: { source: 'user', displayField: 'email', multiple: true },
  },
  required: ['customer'],
};
// the record: ids only
await norbix.api.database.insertOne({
  collectionName: 'orders',
  document: JSON.stringify({ customer: '6650a1b2c3d4e5f6a1b2c401', assignee: ['usr_3Kd9mQ2vLp8nR1tY6wXz4A'] }),
});
// the read: { id, display }
const { item } = await norbix.api.database.findOne({ collectionName: 'orders', id, expandReferences: true });
console.log(item.customer.display); // "Ada"
```

The client setup is on the [Database page](/codemash/cloud/database.md#use-it-from-code). API: [Find](/codemash/api-reference/database/collections/find.md) · [Find One](/codemash/api-reference/database/collections/find-one.md) · [Insert One](/codemash/api-reference/database/collections/insert-one.md)

## Related

[Field type reference](/codemash/cloud/database/schema/field-types.md) · [Nested forms and arrays](/codemash/cloud/database/schema/nesting.md) · [Schema authoring guide](/codemash/cloud/database/schema.md) · [Taxonomies](/codemash/cloud/database/taxonomies.md) · [Users](/codemash/cloud/membership/users.md) · [Roles](/codemash/cloud/membership/roles.md) · [Files](/codemash/cloud/files.md) · [Permissions](/codemash/cloud/concepts/permissions.md)
