# Record Types

A **Record Type** is a sub-category inside an [Object](/docs/concepts/objects). It lets you classify records of the same Object into distinct buckets, each with its own subset of fields, views, and behaviors.

Examples:

- The `People` Object might have record types `Lead`, `Vendor`, `Prospect`.
- The `Deals` Object might have record types `New Business`, `Renewal`, `Expansion`.


Every Object has **at least one** record type. One of them is the **default** and is used when a record is created without specifying a record type.

> Record Types belong to Objects, not [Tables](/docs/concepts/tables). Tables do not have categorical subdivisions.


## Identity

| Field | Type | Notes |
|  --- | --- | --- |
| `id` | number | Use as `recordTypeId`. |
| `name` | string | Internal identifier within the Object. |
| `display_name` | string | User-facing label. |
| `is_default` | boolean | Exactly one record type per Object has this set to `true`. |
| `single_noun` | string? | Singular noun used in UI copy (e.g."Lead" for the `Leads` type). |
| `emoji` | string? | **Icon-key string** from the same allowlist [Objects](/docs/concepts/objects) uses (e.g. `"repeat"`, `"star"`, `"flag"`). Not a Unicode emoji. |
| `icon_color` | string? | Color name or hex. |
| `field_ids` | number[]? | Subset of the Object's fields to show for records of this type. |
| `views` | array? | Saved views scoped to this record type. |


## How they interact with fields and views

- Field membership is record-type-scoped via `field_ids`. A field on the Object can appear in some record types and not others.
- Views can be scoped to a record type. Switching record type in the UI swaps the visible columns and the active view.
- Creating or updating a record can pin it to a specific record type. If you don't, it goes to the default.


## Operations

| Verb | Path |
|  --- | --- |
| `GET` | `/public/v1/objects/{name}/record-types` |
| `POST` | `/public/v1/objects/{name}/record-types` |
| `PATCH` | `/public/v1/objects/{name}/record-types/{recordTypeId}` |
| `DELETE` | `/public/v1/objects/{name}/record-types/{recordTypeId}` |
| `POST` | `/public/v1/objects/record-types/{recordTypeId}/views` |
| `PATCH` | `/public/v1/objects/record-types/{recordTypeId}/views/{viewId}` |
| `DELETE` | `/public/v1/objects/record-types/{recordTypeId}/views/{viewId}` |


The **default** record type is protected: you cannot change its `name` or `display_name`, and you cannot delete it (both return `400`). You can still update its other attributes (assigned `columnIds`, emoji, color) while it is default.

### Who can change record types

`POST`, `PATCH`, and `DELETE` require a **USER API key** with **ADMIN** or **OWNER** role; any other role gets a `403`. `GET` is available to any user who can see the Object. Record types shape the account's CRM model, so they are deliberately not delegated through resource sharing the way [Table](/docs/concepts/tables) schemas are.

Because record types are an Object-only feature, these endpoints reject Tables: passing a Table name returns `404` with a pointer to the `/tables` endpoints.

## CLI

```bash
frontline object record-type list contacts
frontline object record-type create contacts --data '{"name":"vendor","displayName":"Vendor","fieldIds":[73],"emoji":"tag"}'
frontline object record-type update contacts <id> --data '{"displayName":"Vendor","fieldIds":[73,74]}'
frontline object record-type delete contacts <id>
```

On create/update, `columnIds`, `fieldIds`, and `field_ids` are equivalent (assign fields to the record type). Aliases `display_name`, `singleNoun`, and `singular_noun` are also accepted.

## Record-level security (RLS)

**Record-level security** (RLS; some legacy references say "FLS") controls who can see and edit individual records within a record type. It is configured **per record type**, not per object.

When enabled, access is determined by **virtual owner columns**: User-relation fields on the record type (e.g. a `Users` column). Users listed in those fields become the virtual owners of the record and gain edit access according to workspace defaults.

> This is separate from **record sharing** (per-record grants to specific users/teams) and from **resource sharing** (who can access the object schema itself). See the `sharing` CLI skill for those.


### Enabling RLS

When RLS is enabled for the first time on a record type:

- If workspace default is `FULL_ACCESS`, it is set to `NO_ACCESS` automatically.
- Existing rows without `_grants` are backfilled to private so list/get/update enforcement applies immediately.


### API enforcement (list / get / update)

| RLS state | Non-owner USER |
|  --- | --- |
| **OFF** | Normal record-type permissions; all rows visible |
| **ON + workspace NO_ACCESS** | Only own rows (manual/virtual owner) or explicit grants |
| **ON + workspace FULL_ACCESS** | Open rows readable/editable by the whole account |


Response fields: `_recordAccess` (record-type CRUD) vs `_recordShareAccess` (per-record RLS level).

Non-owners cannot get list/read access except via virtual-owner columns, per-record `_grants`, or an open workspace default. There is no separate ACL grant API on the record type.

### Operations

| Verb | Path |
|  --- | --- |
| `GET` | `/public/v1/objects/{name}/record-types/{recordTypeId}/security` |
| `PATCH` | `/public/v1/objects/{name}/record-types/{recordTypeId}/security` |


Both endpoints require a **USER API key** with **ADMIN** or **OWNER** role.

### Response shape

```json
{
    "ok": true,
    "data": {
        "enabled": false,
        "workspace": "FULL_ACCESS",
        "virtual_owner_column_ids": [42],
        "virtual_owner_columns": [{ "id": 42, "name": "Users", "key": "users" }]
    }
}
```

### Request body (PATCH)

All fields are optional (partial update). Unknown keys return `400`.

```json
{
    "enabled": true,
    "virtual_owner_column_ids": [42, 55],
    "workspace": "READ_ONLY"
}
```

Aliases `virtualOwnerColumnIds`, `default_workspace_record_permission`, and `defaultWorkspaceRecordPermission` are also accepted for their respective fields.

`workspace` values: `FULL_ACCESS` | `READ_ONLY` | `NO_ACCESS` — default access for **new** records when RLS is enabled.

`virtual_owner_column_ids` must reference **User-relation fields** that belong to the record type. Invalid column IDs return `400`.

### Lazy defaults

If no config has been saved yet, `GET` returns in-memory defaults (`enabled: false`, auto-detected `Users` column if present) **without writing to the database**. RLS enforcement only activates after an admin saves the config via `PATCH`.

### CLI

```bash
frontline object record-type security get people 3
frontline object record-type security update people 3 --data '{"enabled":true,"virtual_owner_column_ids":[42],"workspace":"READ_ONLY"}'
```

Get field IDs from `frontline object get <object>` or `frontline object field list <object>`.