> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kayanos.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Serial IDs

> Configure traceable, collision-safe human references for KayanOS records, with safe numbering rules, testing, and change history.

A serial ID is the human-readable reference that staff, citizens, and documents use to identify one record: for example, `CSR-2026-00042`. It is different from the internal record ID and from a reference supplied by another system. A good serial rule makes a request easy to find, quote on a letter, and trace during review without asking staff to invent numbers.

This guide explains the `serial_id` entity field. It covers the number itself, its sequence namespace, the expression that renders it, the limited ways a value can be assigned or changed, and the checks that collision-protect active issued values while retaining change history. Use stable field keys in every expression; a translated field title is not an expression key.

The captured builder view below shows a real Serial ID configuration for the **Citizen Service Request** entity, including automatic generation, the manual-entry policy, namespace and format expressions, the starting value, and timezone.

![KayanOS Serial ID configuration for the Citizen Service Request entity.](https://kayanos.app/docs-images/en/build/serial-ids.png)

## When to use it

Use a serial ID when a record needs one durable reference that people can safely repeat across a workflow. Typical examples are a citizen-service request reference, permit number, inspection reference, maintenance ticket, decision number, or outgoing-letter number. Put the serial near the top of the record layout and use it in search, documents, notifications, and staff conversations.

Choose another field when the value has a different job:

| Need                                             | Better choice    | Why                                                                              |
| ------------------------------------------------ | ---------------- | -------------------------------------------------------------------------------- |
| The database’s internal record key               | System record ID | It is not designed as a public, formatted reference.                             |
| A number supplied by a citizen or another system | Text             | Preserve the external value separately from the KayanOS-issued reference.        |
| A service classification such as permit type     | Select or Status | A sequence should not carry workflow meaning that belongs in a controlled field. |
| A value derived from amounts or dates            | Calculated field | Calculations explain a result; a serial is an issued identity.                   |

Start with one serial field for one business identity. Do not reuse a single field for both an intake reference and a final certificate number unless those numbers are genuinely the same throughout the lifecycle. If a later decision needs its own legal or document reference, model it separately and relate the records.

The service keeps each issued value as an issue record. A retry of the same allocation uses an idempotency key. The counter is keyed by organization and namespace, and the active-issue constraint collision-protects a rendered value against another **active** issue in that same organization and namespace. A stored sequence value has a separate constraint in the same scope. This protects a live allocation from a race or repeated request; it is not a reason to make the visible format ambiguous. When an explicit change supersedes an issue, its old rendered value remains in history but no longer occupies the active rendered-value constraint, so storage can permit that historical text to be used again. Consult the issue history for traceability, and adopt a human no-reuse policy whenever a service must forbid that outcome.

## Configuration

In the entity builder, add a field whose type is **Serial ID**, save the entity and field, then configure its serial settings. A serial configuration has six decisions: generation mode, manual-entry policy, namespace expression, format expression, start number, and timezone. Test the configuration before making the field part of a public-facing form or document.

### Generation mode

| Mode                   | What happens                                                                                                                                                                  | Best use                                                                      |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| `automatic`            | A blank serial value is allocated during normal record creation.                                                                                                              | Standard intake where every new request must receive a reference immediately. |
| `request`              | Creation can leave the serial blank; an authorized update operation can request generation later.                                                                             | A reference must be issued only after a review or acceptance step.            |
| `automatic_or_request` | Both capabilities are enabled. A blank value in the normal create flow is still allocated automatically; request generation is available only while the record has no serial. | A process with more than one supported intake path, after testing each path.  |

The hybrid mode is not a promise that a user will be asked to choose a method each time. In the usual create flow, a blank value is allocated because the configuration includes automatic generation. The on-request operation does not issue a second value for an already assigned field.

### Manual entry and later changes

The manual-entry policy is intentionally narrow:

| Policy       | Meaning                                                                                                                                                                           |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `blank_only` | A staff member may provide a non-empty value only while the record has no assigned serial, subject to the create or update permission and duplicate checks against active issues. |
| `never`      | A custom value cannot be registered through the ordinary assignment path.                                                                                                         |

Neither policy makes an assigned serial freely editable. A normal update that tries to replace or clear an existing serial is rejected. A deliberate replacement uses the explicit serial-change operation, requires an authorized update operation, and is unavailable when the field is fixed. That operation creates a new issue record, marks the prior issue as superseded, and can retain the reason and the acting member. The superseded value remains traceable in history, but it is no longer a permanent rendered-value lock in storage. Treat the operation as an exceptional correction process with an accountable reason and a documented reuse policy, not as a convenience edit.

### Namespace and format expressions

The namespace determines which records share a counter and an active-rendered-value collision scope. Its default is the organization, entity key, and field key together:

```jexl theme={null}
$organizationId + ':' + $entityKey + ':' + $fieldKey
```

For an organization `org-1`, entity `citizen_service_requests`, and field `request_reference`, that resolves to `org-1:citizen_service_requests:request_reference`. The organization and namespace identify one counter. The database prevents a rendered value held by one current, non-superseded issue from being assigned to another current issue in that scope; it separately prevents reuse of a stored sequence value there. This is not a permanent global registry of rendered text: after an explicit change supersedes an issue, the historical rendered text can be reused by storage. Keep the default when each entity field needs its own numbering stream. Use a shared namespace only when several fields intentionally draw from one controlled series, review every format for collisions between active issues, and consult issue history before treating a visible reference as historically unique.

The format expression turns the next sequence into the visible reference. The default format is `$sequence`. A readable public-service format can be:

```jexl theme={null}
'CSR-' + serialDate('yyyy', $now, $timezone) + '-' + serialPad($sequence, 5)
```

With sequence `42` in 2026, the expected result is `CSR-2026-00042`. `serialPad(value, width, char?)` pads the left side with `0` unless a different single padding character is supplied. `serialDate(format, value?, timezone?)` formats a date using `yyyy`, `yy`, `MM`, and `dd`; its second argument is the value to format and its third argument is the timezone. Use the exact argument order shown above.

Both expressions are evaluated in a record-aware context:

| Variable or helper                           | Available in         | Use                                                                    |
| -------------------------------------------- | -------------------- | ---------------------------------------------------------------------- |
| `$` and `$record`                            | Namespace and format | The current record data.                                               |
| `$organizationId`, `$entityKey`, `$fieldKey` | Namespace and format | Stable identity of this configuration.                                 |
| `$timezone`, `$now`                          | Namespace and format | The selected organization or UTC timezone and current allocation time. |
| `$namespace`, `$sequence`                    | Format only          | The resolved namespace and next sequence as text.                      |
| `serialPad`, `serialDate`                    | Namespace and format | Padding and date rendering helpers.                                    |

Do not put a citizen name, national identifier, contact detail, password, or access token in a serial expression. The reference may appear in exports, documents, and conversations. Also avoid a format that omits `$sequence` unless the remaining values distinguish every concurrently active allocation; otherwise a rendered collision with an active issue is retried and can ultimately fail rather than silently replacing that issue.

### Start number, timezone, preview, and prefill

**Start at** is the first positive sequence value for a new namespace. Use it when an approved paper register already starts at a known number, but do not lower it after issuing live references. Invalid or non-positive input is normalized to a safe positive start, so enter and review the intended value explicitly.

**Timezone** chooses either the organization’s default timezone or UTC for `$timezone`. It matters when the format includes a date at a local-day boundary. Choose organization time for an office’s operating day; choose UTC only when one global date convention is required. Change it carefully: the same instant can produce a different date prefix in a different timezone.

Use the serial preview before issuing a record. It resolves the expressions and shows the next candidate, but it is non-binding: it does not advance the counter or reserve the value. A later real allocation can therefore differ if another record is issued first.

For existing records with no value, use the bulk-prefill control only after saving both the entity and the field. Filter the candidate set, inspect the preview count and samples, then start the background job. The operation is intended for blank serial fields; it leaves records that have been assigned a value alone. Keep the filter narrow for the first run and verify the job outcome before widening it.

## Worked example

A public-service directorate in Syria receives citizen requests for a local service. Intake officers need a reference that is readable over the phone, appears on an acknowledgement document, and remains stable while the request is assigned, inspected, and decided. The entity is `citizen_service_requests`; the serial field key is `request_reference`.

Configure the field as follows:

| Setting      | Value                                    | Reason                                                          |
| ------------ | ---------------------------------------- | --------------------------------------------------------------- |
| Mode         | `automatic`                              | Every completed intake should get a reference immediately.      |
| Manual entry | `never`                                  | Intake officers should not create look-alike references.        |
| Namespace    | Default expression                       | This request field has its own organization-scoped sequence.    |
| Format       | `CSR-` prefix, year, five-digit sequence | Staff can recognize the purpose and quote a fixed-width number. |
| Start at     | `1`                                      | New service register.                                           |
| Timezone     | Organization                             | The date prefix follows the directorate’s local working day.    |

Save the configuration, then create a request with the serial left blank. The create flow allocates the next number and stores it with the request. If the next sequence is 42 at a 2026 local time, the expression below produces the value staff will see:

```jexl theme={null}
'CSR-' + serialDate('yyyy', $now, $timezone) + '-' + serialPad($sequence, 5)
```

Expected record value: `CSR-2026-00042`. The acknowledgement template should use the stored `request_reference`, not re-evaluate the serial expression. An inspection record can relate to the request and display this reference; it should not copy the serial into a second editable text field.

If the directorate needs a later-issued certificate reference, create a separate certificate entity or serial field. For a review-first process, use `request` mode instead: leave the serial blank at intake, allow the authorized reviewer’s update operation to generate it after acceptance, and test that a second request returns the already assigned value rather than a new one.

For a controlled legacy migration, set manual entry to `blank_only`, filter only the records whose `request_reference` is blank, and give each imported legacy value its own initial assignment. Do not attempt to change assigned values through normal updates. If a correction is legally approved, use the explicit change flow, include a reason such as “paper register transcription correction,” and retain the old and new issue history for audit review.

## Testing

Test the configuration with a non-production workspace and a small, named set of records. Record each expected value before running the test so a reviewer can distinguish a correct result from an accidental one.

| Test                    | Setup                                                                                                   | Expected result                                                                                                                                              |
| ----------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| First allocation        | Automatic mode, empty record, start at `1`                                                              | The stored serial is the formatted sequence `1`.                                                                                                             |
| Consecutive allocation  | Create two blank records                                                                                | Each active issue receives the next value from the same namespace without an active rendered-value collision.                                                |
| Retry safety            | Repeat the same create or generation request when supported                                             | The retry returns the same issued value; it does not consume another number.                                                                                 |
| Manual policy           | Try a custom value before assignment, then after assignment                                             | `blank_only` permits only the initial assignment and checks active issues; `never` rejects custom entry; normal replacement is rejected.                     |
| Collision               | Use a shared namespace and attempt the same manual rendered value twice while the first issue is active | The second request fails with a duplicate conflict rather than overwriting the active issue.                                                                 |
| Time boundary           | Preview just before and after the organization’s local midnight                                         | The date prefix follows the configured timezone.                                                                                                             |
| Prefill                 | Filter a few blank legacy records and inspect samples                                                   | Only the intended blank records are candidates; the job result is reviewed before a larger run.                                                              |
| Explicit change         | Use an authorized non-fixed field and a reason                                                          | A replacement is recorded with the prior issue superseded; fixed fields remain unchangeable.                                                                 |
| Historical traceability | Inspect issue history after an explicit replacement                                                     | The old rendered text remains traceable. Decide through service policy whether it must never be reused, because storage can permit reuse after supersession. |

Test the format expression independently with known data. For example, use a five-digit width and a known sequence to make the expected padding unambiguous:

```jexl theme={null}
'CSR-' + serialPad($sequence, 5)
```

With `$sequence` equal to `7`, the expected result is `CSR-00007`. Then test a namespace expression that deliberately uses a record value only if a separate sequence per category is policy, not an accident:

```jexl theme={null}
$organizationId + ':citizen-service:' + $.service_category
```

This creates a separate organization-scoped counter for each stored `service_category` value. Test an empty or changed category before relying on it, and prefer the default namespace when there is no approved reason to split the sequence.

## Troubleshooting

| Symptom                                  | Check first                                                                                                         | Safe response                                                                                                   |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| A new record has no serial               | Is the field in `request` mode, or was a value deliberately left blank?                                             | Use the authorized request-generation operation when the process reaches the issuance step.                     |
| Preview differs from the final value     | Did another record allocate from the same namespace after the preview?                                              | Treat previews as estimates, not reservations.                                                                  |
| The format is blank or errors            | Does the expression return non-empty text, use the correct `serialDate` argument order, and reference valid keys?   | Reduce it to `$sequence`, verify it, then add one component at a time.                                          |
| A manual value is rejected               | Is policy `never`, is the value blank, does it duplicate an active issue, or does the record already have a serial? | Use the approved initial assignment or explicit correction path; do not bypass the history.                     |
| A normal update cannot change the serial | Has the field already been assigned?                                                                                | This is expected. Use an authorized explicit change only when the field is not fixed and a reason is available. |
| Prefill cannot start                     | Have the entity and field been saved, and does the member have builder-level field-management access?               | Save first, review filters, and run a small preview.                                                            |
| Numbers look correct but collide         | Do two configurations share a namespace and render the same prefix/sequence pattern for active issues?              | Separate namespaces or change the approved format before issuing more values.                                   |

## Permissions and data-quality limits

Building or bulk-prefilling a serial field requires field-management access. Previewing a serial requires permission to create or update the relevant entity. Request generation and explicit replacement require an authorized update operation on the specific record; scoped access can prevent an otherwise authorized member from editing that record. A fixed serial field cannot use the explicit replacement path.

Permissions protect the operation, not the quality of the policy. Before publishing, have the service owner approve the prefix, year behavior, start number, namespace boundary, and correction reason. Keep the serial field read-only in layouts where staff should not supply it. Preserve external references in a separate field, and never treat an attractive serial format as evidence that the underlying request was approved.

The issue history records how a value was created—generated, manual, bulk-prefilled, or changed—and links a replacement to the superseded issue. Review the full history, not only the current field value, during corrections and migration reconciliation. A superseded issue keeps its historical rendered text, but that text no longer blocks a new active issue at the storage layer. If historical text must never be reused, make that an explicit human operating policy and enforce it in review. History supports accountability, but it does not replace record-level permissions, document retention controls, or an approved operating policy.

## Related guides

* Choose the field and layout around the reference in [Entity fields and layouts](/build/entity-fields-and-layouts).
* Model the request, inspection, and decision as connected records in [Entities](/build/entities).
* Learn expression syntax, operators, and helper behavior in [Expression language syntax](/reference/expressions/language-syntax).
* Use derived amounts, flags, and summaries in [Calculated fields](/build/calculated-fields).
