Custom Fields & Fieldsets
Custom Fields let you record data ERPat's built-in asset form doesn't have a box for — serial-adjacent details like IMEI, MAC address, OS version, or BIR property sticker number. Fields are defined once as a global catalog, bundled into Fieldsets, and a Category binds one fieldset — every asset in that category then shows those fields as an "Additional fields" section on its add/edit form.
Safekeep › Custom Fields (URL ams/custom_fields).
It is a standalone page headed Custom Fields with two tabs: "Browse" (the field catalog)
and "Fieldsets" (which loads the Fieldsets screen, ams/fieldsets, into the second tab).
The Fieldsets tab is the only door — there is no separate Fieldsets item in the Safekeep sidebar.
Who can use this
Both screens are gated on the Assets module being enabled plus the single base permission
asset ("Assets: Entries"). There are no separate create / update / delete permits here:
anyone who can open Custom Fields or Fieldsets can also add, edit, and delete records. Plan role
assignments accordingly — see Permissions & Roles.
How a custom field reaches an asset
Fields never attach to an asset directly. The chain is:
On every save the system compiles the fieldset into a cached schema on the category, so the asset form (and the mobile API) always reads an up-to-date field list. An Asset Model can also carry a fieldset reference, but it is the category's bound fieldset that drives the "Additional fields" section on the asset form.
The values a user types are stored inside the asset record's meta JSON, keyed by each field's Field Key — see Adding & Editing Assets.
Part 1 — Custom Fields (Browse tab)
The Browse tab is the global catalog of reusable field definitions. It is a searchable DataTable with a refresh button and XLS export, sorted ascending by Name. The header button — labeled simply "Custom Field" (plus-circle icon; note it's the noun, not "Add Custom Field") — opens the add modal.
List columns
| Column | Meaning | Notes |
|---|---|---|
| Name | The field's display label. | Shown as the field label on the asset form. |
| Field Key | The stable snake_case storage key. | Rendered in code style; this is the map key inside the asset's meta JSON. |
| Field Type | The input element type. | One of the 8 element types below (e.g. "Text", "Dropdown (select)"). |
| Format / Validation | The validation rule string. | Default ANY (no extra validation). |
| Required | "Yes" / "No". | The default required flag — the working required-ness is set per row inside a Fieldset (see below). |
| Primary (identifying) field | "Yes" / "No". | Marks the field as an identifying attribute of the asset. |
| Created by | Author. | Profile link. |
| Options (☰) | Row actions. | Pencil = edit (opens the same modal pre-filled); × = delete with confirmation. |
The Custom Field form
Add and edit share one modal (posts to ams/custom_fields/save) with a single footer
Save button. On success you'll see the toast "The record has been saved."
| Field | Meaning | Required | Notes |
|---|---|---|---|
| Name | The label users see on the asset form. | Required | Text, autofocus. Enforced client-side ("This field is required.") and server-side. |
| Field Key | The permanent storage key inside the asset's meta JSON. | No | Placeholder auto_generated_from_name. Help text: "Stable snake_case key stored in
the asset's meta JSON. Leave blank to derive it from the name." Whatever you type is slugified
to lowercase snake_case; left blank, it is derived from Name (e.g. "MAC Address" →
mac_address). |
| Field Type | Which input element the asset form renders. | No (defaults to "Text") | Select2 dropdown of the 8 element types below. |
| Format / Validation | An intended validation rule for the entered value. | No (defaults to ANY) |
Free text; the placeholder shows the accepted grammar:
ANY | NUMERIC | DATE | EMAIL | URL | regex:/.../. The rule is stored and carried
into the compiled schema, but it is not enforced when an asset is saved today (see
"Validation on the asset form" below). |
| Field Values / Options | The choice list for choice-type fields. | No | Textarea; placeholder "One option per line (for select / checkbox / radio)."; help text
"Used only by select, checkbox and radio fields. One option per line." (Pipe
| separators are also accepted.) |
| Help Text | A hint stored with the field. | No | Plain text. It is carried into the compiled schema, but the asset form does not currently display it under the input. |
| Options (checkbox group) | Six behavior flags. | No | See the flags table below. Only Display to user is ticked by default on new fields. |
The 8 field types
| Type label | Renders on the asset form as | Uses "Field Values / Options"? |
|---|---|---|
| Text | Single-line text input. | No |
| Textarea | Single-line text input (currently the same as Text — no multi-line box yet). | No |
| Number | Numeric input. | No |
| Date | Text input with a date picker. | No |
| Dropdown (select) | Single-choice dropdown (with a "-" blank option). | Yes — one option per line |
| Checkbox | Single-choice dropdown (currently the same as Dropdown). | Yes — one option per line |
| Radio | Single-choice dropdown (currently the same as Dropdown). | Yes — one option per line |
| Yes / No (boolean) | A single checkbox. | No |
The Options flags — which ones actually do something
| Flag | Default | Effect today |
|---|---|---|
| Required | Unchecked | Informational only. It shows as the "Required" Yes/No column on the Browse list, but it is not what the asset form enforces — and it doesn't even pre-fill the Fieldset builder (new rows always start unchecked). The working required flag is the per-row "Required" checkbox inside the Fieldset builder. |
| Primary (identifying) field | Unchecked | Carried into the compiled field schema as the field's primary marker and shown as a Yes/No list column. |
| Unique | Unchecked | Stored, not used — saved to the field record but not carried into the compiled schema; no runtime effect on the asset form yet. |
| Encrypted | Unchecked | Stored, not used — same: no encryption is applied to the stored value today. |
| Show in email | Unchecked | Stored, not used — no email currently reads this flag. |
| Display to user | Checked (new fields) | Stored, not used — not carried into the compiled schema; it does not hide the field from anyone yet. |
Validation on the asset form
When an asset is saved, each custom field is validated against its compiled definition. The exact error
messages, with %s replaced by the field's name:
"%s must be a number." — a Number-type field received a non-numeric value.
Those two checks — required and numeric — are the only runtime validations. The
Format / Validation rule (NUMERIC | DATE | EMAIL | URL | regex:/.../) is stored with
the field but not applied when an asset is saved today.
Behind the scenes, every save or delete of a custom field immediately re-syncs the compiled schema of every category using it, so asset forms never show a stale field definition.
Part 2 — Fieldsets
A fieldset is an ordered, required-aware bundle of custom fields — think of it as the template of
extra fields for one kind of asset (e.g. a "Laptops" fieldset with IMEI-style identifiers, OS version,
and RAM). You reach it via the Fieldsets tab on the Custom Fields page (which loads
ams/fieldsets into the tab).
List columns
| Column | Meaning | Notes |
|---|---|---|
| Name | The fieldset's name. | Sorted ascending by default. |
| Notes | Free-text notes. | Line breaks preserved. |
| Custom Fields | How many fields are bound. | A live count of bound, non-deleted fields. |
| Created by | Author. | Profile link. |
| Options (☰) | Row actions. | Pencil = edit; × = delete with confirmation. |
The header button — labeled "Fieldset" (plus-circle icon) — opens a large modal.
The Fieldset form (with the field builder)
Who: anyone with the
asset permission.Preconditions: the custom fields you want to bundle already exist on the Browse tab.
-
Name the fieldset
Name is the only required field (client- and server-side). Notes is an optional textarea.
-
Build the field list
The Custom Fields builder table has columns "Custom Field" | "Required" | (remove). Click "Add" (plus-circle) to append a row: a dropdown of every custom field shown as Name (field_key) with a "-" blank option, a per-row Required checkbox, and a red × to remove the row. Blank rows are skipped on save.
-
Order the rows
Row order is the display order on the asset form. The help text under the table says it all: "Bind custom fields to this fieldset; row order sets the display order. Categories using this fieldset are recompiled automatically."
-
Save
Click Save. The system replaces the fieldset's bindings with exactly the rows you submitted and immediately recompiles the cached schema of every category bound to this fieldset — the asset form picks up the change on its next open.
Attaching a fieldset to a category
Open Categories (Safekeep → Categories), edit a category, and pick the fieldset in its "Fieldset" dropdown. From then on, every asset in that category shows the fieldset's fields under the "Additional fields" heading on the add/edit asset modal, in row order, with the per-fieldset Required flags enforced.
What the compiled schema keeps per field: key, label, type, required, primary, options, help, and format.
Element types are simplified for rendering — text/textarea stay text-like, number and date stay themselves,
and select/checkbox/radio all render from the options list; the format rule is included only when it isn't
ANY.
Deleting fields and fieldsets
There are no in-use protections on either screen — every delete is an immediate soft delete after the confirmation prompt, and it always succeeds with the toast "The record has been deleted." (The message "The record is in use, you can't delete the record!" exists in the code but is effectively never shown here.)
The field is removed from the catalog and immediately dropped from the compiled schema of every category whose fieldset used it — the asset form stops showing it. Values already saved in asset meta JSON remain stored (but unsurfaced) until each asset is next saved, when its meta is rebuilt without them.
The fieldset AND all its field bindings are soft-deleted, and every category bound to it is recompiled to an empty schema — assets in those categories silently lose their entire "Additional fields" section. Stored meta values disappear from the Edit modal and are wiped from the JSON the next time each asset is saved.