Custom Fields — Reference
The Custom Fields surface is the schema-extension plane for an SGEN site. It owns the user-defined fields that attach to a content type (pages, posts, products, custom objects), the field-type catalog (text, number, date, dropdown, file picker, repeater, and others), the per-field validation rules, and the binding map that decides which fields appear on which content-type forms. Anything that lets an operator capture content beyond the platform's built-in fields traces back to a record managed here.
This page is a reference for platform engineers and integrators who need to understand the surface before extending it, scripting against it, or migrating a custom-fields configuration from a legacy CMS. Customer-facing walkthroughs for adding a custom field and using it on a content form live in the customer docs set; this page describes the shape of the surface, not the steps to drive it.
Overview
Custom Fields live under the Custom Fields module in SG-Admin. The module renders three primary views — the field list grouped by content-type binding, the field create / edit form, and the field-group panel that bundles related fields for a single content type — and exposes a small set of write operations for create, update, reorder, soft delete, restore, and permanent delete.
The module is paired by convention with the content-type forms it extends. A custom field is defined here, bound to a content type, and then renders inside that content type's edit form. The pairing is by binding declaration — the content-type form does not know about the field at compile time; it queries the binding map at render time.
A second surface — field-group composition — lives on the field-group view. Groups bundle related fields (for example, event details might bundle a date picker, a location dropdown, and a description text field) so that they appear together on the form, with shared visibility rules and shared reordering controls.
Where it lives in SG-Admin:
- Sidebar: SG-Admin → Custom Fields
- URL prefix:
/sg-admin/custom_fields/ - View templates:
application/views/Admin/CustomFields/
┌──────────────────────────────────────────────────────────────────────┐│ SG-Admin → Custom Fields Binding: [Posts ▾] [+ New field] │├──────────────────────────────────────────────────────────────────────┤│ Group: Event details ││ Field Type Required Used in Status ││ ──────────────── ────────── ──────── ─────── ──────── ││ Event date date ✔ 48 active ││ Event location dropdown — 48 active ││ Event capacity number — 48 active ││ Event description rich-text ✔ 48 active ││ ││ Group: SEO overrides ││ Canonical URL text — 12 active ││ Schema overrides repeater — 4 active ││ ││ Ungrouped ││ Featured flag boolean — 112 active ││ External link text — 18 active ││ ││ [⋯ Edit] [⋯ Reorder] [⋯ Move to group] [⋯ Delete] │└──────────────────────────────────────────────────────────────────────┘Actions
The Custom Fields surface exposes the following operations. Each is described by what it does to the site state, not by its internal method name.
List fields
Returns the custom fields defined on the site, optionally scoped to a binding (Posts, Pages, Products, a registered custom object), with name, type, required flag, used-in count, and status columns. Supports column sort and a free-text filter by name or group. Default view groups fields by their field-group binding; an ungrouped section collects fields that belong to no group.
Create field
Opens an empty field form. Required fields at minimum: name, internal key, type, binding. Optional fields cover label override, help text, default value, validation rules, group assignment, conditional visibility rules, and sort order. On submit, the surface validates uniqueness of the internal key within the binding (no two fields on the same content type share an internal key), persists the record, and returns the new field identifier.
Edit field
Loads an existing field into the same form shape used for create, pre-populated. Submit replaces the stored configuration. Changing the type of a field is allowed only when no records currently hold a value for the field; once values exist, the type is frozen and the surface returns a structured rejection on type-change attempts.
Type-change blocks at value-presence time — the surface checks the records table at the start of the edit submission. Operators who need to convert a field type with existing data must first run a bulk migration of existing values, clear the field, and then change the type.
Field-group create / edit
A separate write path for the group container. Owns the group name, the group's binding (which content type the group attaches to), conditional visibility rules at the group level, and the sort order of the group on the form. Fields move between groups via a standalone reassignment action.
Move field to group
A standalone write that moves a field from one group to another, or from ungrouped into a group, or out of a group into ungrouped. Validates that the destination group's binding matches the field's binding — a field bound to Posts cannot move into a group bound to Products.
Reorder fields
Sets the display order within a group (or within the ungrouped section). Drag-handle interface; on submit, the surface writes a sort-order integer per field within the parent. Reordering writes are debounced at the client.
Reorder groups
Sets the display order of groups within a content-type form. Drag-handle interface; sort-order integer per group within the binding.
Conditional visibility
A standalone write that sets a visibility rule on a field or a group. Rules are predicates against other field values on the same record — for example, show the Event capacity field only when the Event type field equals "in-person". Multiple predicates join by AND; OR groups nest one level deep.
Soft delete
Marks the field or group as trashed. Trashed fields disappear from the content-type form. The values previously captured on records remain in storage; they re-emerge if the field is restored.
Restore
Returns a trashed field or group to active status. The form picks it up on the next render; captured values reappear on the records that held them.
Permanent delete
Hard-removes the field or group record. Available only after a soft delete. Permanent delete on a field also removes every captured value for that field across all records. The operation is irreversible and the surface emits a strong confirmation prompt that displays the affected-record count.
Export schema
Produces a JSON snapshot of the custom-field configuration — every field, every group, every binding, every validation rule. Used for porting a configuration to another site or for diffing against a known-good baseline.
Import schema
Reads a JSON snapshot and applies it. Internal-key collisions on the same binding are resolved by an import strategy flag: replace (overwrite existing), skip (keep existing), or rename (suffix the incoming key with a counter). The import does not touch existing record values; if the incoming schema is incompatible with stored values, the surface surfaces a warning and the conflicting field reverts.
Data model
The Custom Fields surface stores three record types — fields, groups, and the captured values that records hold against the fields.
Field record
| Field | Type | Notes |
|---|---|---|
id | integer | Primary key. |
binding | enum | posts, pages, products, or a registered custom-object slug. |
internal_key | string | Stable identifier used in the values store. Unique within the binding. |
name | string | Display name on the form. |
label_override | string | Optional. Renders in place of name on the form. |
type | enum | text, rich-text, number, date, datetime, boolean, dropdown, multi-select, file, image, link, relation, repeater, json. |
required | boolean | Form validation only — does not affect record storage. |
default_value | JSON | Optional. Pre-fills the field on a new record. |
validation | JSON | Type-specific rules — length range for text, min / max for number, allowed list for dropdown. |
help_text | string | Optional. Renders below the field on the form. |
group_id | integer | Optional. Foreign key to a field group. Null = ungrouped. |
sort_order | integer | Display order within the group (or within ungrouped). |
visibility_rule | JSON | Optional. Predicate against other field values on the same record. |
status | enum | active or trash. |
created_at | timestamp | Immutable. |
updated_at | timestamp | Touched on any edit. |
Field-group record
| Field | Type | Notes |
|---|---|---|
id | integer | Primary key. |
binding | enum | Same enum as the field record. |
name | string | Display name on the form. |
sort_order | integer | Display order within the binding. |
visibility_rule | JSON | Optional. Group-level predicate. |
status | enum | active or trash. |
created_at | timestamp | Immutable. |
updated_at | timestamp | Touched on any edit. |
Value record
Captured field values for a record. Stored in a separate values table keyed by record and field.
| Field | Type | Notes |
|---|---|---|
id | integer | Primary key. |
field_id | integer | Foreign key to a field record. |
record_id | integer | Foreign key to the bound content record. |
record_type | string | Matches the field's binding enum. |
value | JSON | Type-specific shape. Text = string, number = number, date = ISO string, dropdown = the selected option's value, repeater = an array of sub-records. |
created_at | timestamp | Immutable. |
updated_at | timestamp | Touched on any value edit. |
text/rich-text— string with optional length validation.number— numeric with optional min / max / step.date/datetime— ISO-formatted string; UI renders a picker.boolean— true / false.dropdown/multi-select— selection from a configured option list.file/image— Media library reference.link— URL with optional internal-link resolution.relation— foreign key to another record by content-type slug + ID.repeater— array of sub-records, each with its own sub-field schema.json— free-form structured value with optional schema-validation rule.
Repeater nesting: repeater fields nest one level deep by default — a repeater field may contain sub-fields of any non-repeater type. Two-level nesting (repeater inside a repeater) is configurable per site under Settings → Taxonomies → Repeater depth.
Soft-delete and value retention: soft-deleting a field hides it from forms but retains the captured values. Permanent delete removes the field and every captured value across all records.
FIELD DEFINITION CAPTURED VALUES CONTENT RECORD┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────┐│ id │ │ id │ │ id ││ binding (posts) │◄──┐ │ field_id │───────►│... (post 142) ││ internal_key │ │ │ record_id │◄───┐ │ title, body, … ││ name, label, type │ │ │ record_type (posts) │ │ └─────────────────┘│ required │ │ │ value (JSON) │ ││ validation │ │ │ timestamps │ ││ default_value │ │ └─────────────────────┘ ││ help_text │ │ ││ group_id ───────────┼───┼──► FIELD GROUP ││ sort_order │ │ { name, binding,... } ││ visibility_rule │ │ ││ status │ └──── many fields per binding ││ timestamps │ │└─────────────────────┘ ┌─── one value per (field × record)│ pair▼VALUES TABLE(key: field_id + record_id)Permissions
Access to the Custom Fields surface is gated at two layers.
Layer 1 — admin gate. Every action under SG-Admin passes through the platform's standard admin access check at request entry. An unauthenticated request never reaches the Custom Fields surface.
Layer 2 — per-action capability. Within SG-Admin, each Custom Fields action checks a capability associated with the calling operator's role. The default role configuration ships with three roles — Administrator, Editor, Viewer — and the capability map is:
| Capability | Administrator | Editor | Viewer |
|---|---|---|---|
| List fields | ✔ | ✔ | ✔ |
| Create field | ✔ | — | — |
| Edit field | ✔ | — | — |
| Change field type (no values) | ✔ | — | — |
| Create / edit field group | ✔ | — | — |
| Move field to group | ✔ | — | — |
| Reorder fields | ✔ | ✔ | — |
| Reorder groups | ✔ | ✔ | — |
| Conditional visibility | ✔ | — | — |
| Soft delete | ✔ | — | — |
| Restore | ✔ | — | — |
| Permanent delete | ✔ | — | — |
| Export schema | ✔ | ✔ | — |
| Import schema | ✔ | — | — |
Capture-time permissions. The Custom Fields surface defines fields; the content-type forms capture values into them. Capture permissions follow the content type — an Editor with edit-post capability can write values into custom fields on posts even if the Editor cannot edit the field definitions themselves.
Self-protection rules. A field cannot change type while captured values exist on records. A field cannot be permanently deleted without an explicit confirmation that displays the affected-record count. A group cannot be permanently deleted while it contains fields — soft-delete or move the contained fields first.
Audit. Every write — field create, field edit, type change, group create, group edit, group reorder, field reorder, field-to-group move, visibility-rule change, soft delete, restore, permanent delete, schema import — emits an Activity Log entry. The log records the acting operator, the target field or group, and the change shape (field-level diff for edits, full snapshot for imports and resets). Activity Log retention follows the site's general settings.
REQUEST → /sg-admin/custom_fields/...│▼┌────────────────────────┐│ Admin gate │ unauth → reject└──────────┬─────────────┘│ authed▼┌────────────────────────┐│ Role capability check │ role lacks cap → reject└──────────┬─────────────┘│ allowed▼┌────────────────────────┐│ Type-freeze check │ type change with existing values│ (edit only) │ → reject└──────────┬─────────────┘│ allowed▼┌────────────────────────┐│ Self-protection rules │ permanent delete without confirm│ │ / group delete with fields → reject└──────────┬─────────────┘│ passes▼write executes│▼Activity Log entry(field-level diff or import snapshot)— captures happen separately on content-type forms,gated by the content type's own permissionsRelated references
- Posts — Reference. Custom fields bound to Posts appear on the post edit form. Capture permissions follow the post permissions, not the field-definition permissions.
- Pages — Reference. Same shape for fields bound to Pages.
- Products — Reference. Same shape for fields bound to Products.
- Custom Objects — Reference. Registered custom objects can opt into the Custom Fields surface by declaring a binding scope at registration time.
- Media — Reference. File and image field types reference Media records. Media deletion cascades to a placeholder on the captured value.
- Categories — Reference. Dropdown and multi-select field types can source their options from a category list, with live sync as the taxonomy evolves.
- Settings — Reference. Repeater-depth limit, default validation rules, and Activity Log retention all live in Settings.
- Activity Log — Reference. Schema writes emit Activity Log entries; the log is the audit trail for the surface.
/docs/custom-fields.