# `Cardbox.Type`
[🔗](https://github.com/hattoriai/cardbox/blob/v0.1.0/lib/cardbox/type.ex#L1)

The types of fields: what each takes, how it keeps a value, and how it
filters.

| Type | A value | Options |
| --- | --- | --- |
| `text` | a text | `long`: a text of several lines |
| `number` | an integer or a number | `format`: `number`, `integer`, `percent` or `currency`; `decimals`; `currency` (`"EUR"`) |
| `checkbox` | `true` or `false` | |
| `date` | a day, or a moment with `time` | `time`: a day and a time, kept in UTC |
| `select` | one of the choices | `choices` (`Cardbox.Choice`) |
| `multi_select` | some of the choices | `choices` |
| `status` | one of the choices, each of a group (to do, doing, done) | `choices` |
| `person` | the id of a person or an agent of your app | `multiple`: several |
| `url`, `email`, `phone` | a text of that form | |
| `relation` | records of a collection | `collection_id` (required); `limit`: at most so many; `two_way`: the other collection shows the links too, in a field named `reverse_name` (its own name unless given) |
| `created_at`, `updated_at` | when the record was made, changed | read-only |
| `created_by`, `updated_by` | who made it, changed it last | read-only |

## How values are kept

A record's values are one JSON map, by field key (`Cardbox.Record`).
An empty value is no key at all: a text of no characters, an empty
list and an unticked checkbox are kept as nothing, so "is empty" is one
question whatever the type. A day is `"2026-10-25"`, a moment
`"2026-10-25T14:30:00Z"`, both of which sort as text; a choice is its id;
a relation a list of record ids.

The four read-only types are the record's own columns, and are not in
its values.

## Values given

`cast/3` takes what a form posts, what an agent sends and what Elixir
code has: `"12.5"` and `12.5` for a number, `"2026-10-25"` and a
`Date` for a date, a choice's id or its name for a select. Each value
is checked against its field, and an error says what the field takes.

# `kind`

```elixir
@type kind() :: :text | :number | :boolean | :date | :datetime | :choice | :list
```

How a type's values compare, in Elixir and in SQL.

# `cast`

```elixir
@spec cast(map(), term(), keyword()) :: {:ok, term()} | {:error, String.t()}
```

Checks a value given for a field, and returns it as it is kept: `nil` for
an empty value.

`opts`: `:time_zone`, where a day and a time given without an offset are
(UTC unless given).

# `check_options`

```elixir
@spec check_options(String.t(), map(), map()) :: {:ok, map()} | {:error, String.t()}
```

Checks a type's options, with `previous`, the field's options before (so a
choice renamed keeps its id). Returns them as they are kept.

# `kind`

```elixir
@spec kind(map()) :: kind()
```

How a field's values compare: `:text`, `:number`, `:boolean`, `:date`
(a day), `:datetime`, `:choice` (one id) or `:list` (several).

# `load`

```elixir
@spec load(map(), term()) :: term()
```

A value as it is kept, read for Elixir: a `Date` or a `DateTime` for a
date, `false` for an unticked checkbox, `[]` for an empty list.

# `names`

```elixir
@spec names() :: [String.t()]
```

The types, by name.

# `operators`

```elixir
@spec operators(map()) :: [atom()]
```

The filter operators of a field (`Cardbox.Filter`).

# `sortable?`

```elixir
@spec sortable?(map()) :: boolean()
```

Whether a field sorts: every field but those of several values.

# `system?`

```elixir
@spec system?(String.t()) :: boolean()
```

Whether a type is one of the record's own columns, which nobody writes.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
