All `content.json` files use an `items` array of typed objects. Every object has a `type` field; all other fields depend on the type. Optional fields are marked with `?`.
Token substitution is available in most string fields: any `%TOKEN%` placeholder is replaced with the corresponding value. Where noted, a string that resolves to a JSON array or object is automatically parsed back into the appropriate structure.
---
## Access control
Any item (and any table column or row action) can carry a `client_requirement` field. Items whose requirement is not satisfied are omitted entirely from the rendered output.
Roles are ranked from lowest to highest:
| Rank | Role | Description |
|------|------|-------------|
| 3 | `manager` | Superuser; can manage accounts and administrators |
| 2 | `administrator` | Full configuration access |
| 1 | `viewer` | Read-only authenticated user |
| 0 | `nothing` | Unauthenticated (no session) |
The value is a role name with a suffix that controls the comparison:
| Suffix | Meaning |
|--------|---------|
| `+` | Client rank must be greater than or equal to the named role |
| `=` | Client rank must equal the named role exactly |
| `-` | Client rank must be less than or equal to the named role |
Possible values for `client_requirement` field:
| Value | Meaning |
|----------|-------------|
| `client_is_manager=` | Client must be manager |
| `client_is_manager-` | Client must be manager or lower |
| `client_is_administrator+` | Client must be administrator or higher |
| `client_is_administrator=` | Client must be administrator |
| `client_is_administrator-` | Client must be administrator or lower |
| `client_is_viewer+` | Client must be viewer or higher |
| `client_is_viewer=` | Client must be viewer |
| `client_is_viewer-` | Client must be viewer or lower |
| `client_is_nothing+` | Client may be anybody (logged in or not logged in) |
| `client_is_nothing=` | Client must be nothing (not logged in) |
General-purpose card with an optional header label. Setting `hidden: true` renders the card with the `hidden` attribute, which is used for `js_edit` reveal targets. The `id` attribute wires it up as a `js_edit` target.
| `interface_picker` | Table picker for network interfaces | `data?`: token resolving to JSON array of interface rows; `value?` |
`depends` is a list of field names whose values are sent as `data-depends` for JS-driven conditional validation.
---
### `field_row`
```json
{
"type": "field_row",
"cols?": 2,
"items": [ ... ]
}
```
Horizontal row of fields in `cols` equal columns (default 2). Children are typically `field` items.
---
### `subnet_row`
```json
{
"type": "subnet_row",
"label?": "Subnet",
"subnet_name?": "subnet",
"prefix_name?": "subnet_mask",
"subnet_value?": "%VLAN_SUBNET%",
"prefix_value?": "24",
"subnet_placeholder?": "e.g. 192.168.1.0"
}
```
Combined subnet address and prefix length inputs. Displays the dotted mask equivalent (e.g. `/24` becomes `255.255.255.0`) as a hint below. Auto-validates via JS.
---
### `select`
```json
{
"type": "select",
"name": "filter_field",
"options": "<optionvalue=\"a\">A</option>",
"filter_col?": "status"
}
```
Bare `<select>` outside a form-group. `options` is raw HTML. `filter_col` wires it to filter a sibling table column via JS.
---
### `record_editor`
```json
{
"type": "record_editor",
"label": "Hostnames",
"name": "hostnames",
"empty_message?": "No hostnames added.",
"fields": [
{
"label": "Hostname",
"name": "hostname",
"placeholder?": "e.g. myhost.example.com",
"required?": true,
"validate?": "hostname",
"valtype?": "string",
"attrs?": { "data-custom": "value" }
}
]
}
```
JS-driven table where the user can add, edit, and remove rows. Submits as a JSON array in the named hidden input. Each entry in `fields` gets its own column and a corresponding input in the add/edit form.
Read-only textarea with an "Override" checkbox. Checking the box enables editing. `override_name` is the checkbox's `name` attribute and defaults to `{name}_override`.
---
### `editable_list`
```json
{
"type": "editable_list",
"name": "blocklist_urls",
"item_placeholder?": "https://...",
"add_label?": "Add URL",
"hint?": "One URL per entry.",
"items?": "%BLOCKLIST_URLS_JSON%"
}
```
Simple list where the user can add and remove text items. Submits as a JSON array.
---
### `credential_fields`
```json
{
"type": "credential_fields",
"provider_select?": "provider"
}
```
Group of hidden credential sub-groups (API token vs. No-IP username/password). JS shows the correct group based on the named provider `<select>`. `provider_select` is the `name` of that select and defaults to `"provider"`.
---
## Validation
The `validate` field attaches a named validator to an input. Validators run live as the user types, show inline hint messages, and keep the form's submit button disabled until all validated fields pass.
Three mechanisms exist depending on context:
-`field`, `editable_list`: sets `data-validate` on the input. The named classifier runs on every keystroke and marks the field valid, incomplete (warning), or invalid (error).
-`overridable_textarea`: sets `data-validate-lines` on the textarea. When the override checkbox is checked, each non-empty line is validated as an IP address. If subnet context is available (from sibling subnet/mask inputs), each line is also checked against that subnet.
-`record_editor` fields: use `valtype` instead of `validate`. The field is wired to sibling subnet/mask inputs via `data-dep-subnet` and `data-dep-mask` attributes.
### `validate` values (for `field` and `editable_list`)
| Value | Accepts |
|-------|---------|
| `ipv4` | IPv4 address. Four dot-separated octets, each 0-255. |
| `mac` | MAC address. Six colon-separated two-digit hex groups. |
| `url` | HTTP or HTTPS URL. Must begin with `http://` or `https://`, have a valid hostname, and an optional port in range 1-65535. |
| `port` | Port number. Integer 1-65535, digits only. |
| `ipv4cidr` | IPv4 address or CIDR notation. Accepts a bare IP or `IP/prefix` where prefix is 0-32. |
| `endpoint` | Network endpoint. Accepts an IPv4 address, IPv6 address, or hostname. Hostnames may include a `:port` suffix. |
| `dashname` | Dash-delimited identifier. Lowercase letters, digits, and hyphens. No leading, trailing, or consecutive hyphens. |
| `domainname` | Domain name. Letters, digits, hyphens, and dots. Each label must not start or end with a hyphen. |
| `networkname` | Network identifier. Letters, digits, hyphens, and underscores. No leading, trailing, or consecutive special characters. |
| `positive_int` | Positive integer. Digits only. Respects the field's `min` and `max` bounds when present. |
| `vlan_id` | VLAN ID. Integer 1-4094. Also checks uniqueness against existing IDs read from the input's `data-existing-ids` attribute. |
### `validate` value (for `overridable_textarea`)
| Value | Behavior |
|-------|---------|
| `ip_in_subnet` | Each non-empty line must be a valid IPv4 address. When the VLAN subnet and mask are both filled in, also verifies each address falls within that subnet. |
### `valtype` values (for `record_editor` fields)
`valtype` wires subnet-aware real-time validation using sibling inputs pointed to by `data-dep-subnet` and `data-dep-mask` attributes on the field.
| Value | Validates |
|-------|---------|
| `address` | IPv4 host address. Must be a valid IP within the known subnet, and must not be the network or broadcast address. |
| `subnet` | IPv4 subnet address. All host bits must be zero for the given mask. |
| `mask` | Prefix length. Integer 1-30. |
| `format` | IPv4 address format only. No subnet membership check. |
**`"post"`** renders a form with `row_index` and `config_hash` hidden inputs. `disable_if` can disable the button when `row[field] == value`.
**`"js_edit"`** opens a reveal card by ID. Row data is passed as `data-row` JSON. The card element (a `card` item with matching `id`) is populated and shown by JS.
```json
{
"method": "js_edit",
"text": "Edit",
"target": "edit-vlan-form"
}
```
**`"inline_edit"`** converts the table row into editable inputs in place. Fields are defined inline:
Non-standard `input_type` values in inline_edit fields (anything not in `text`, `password`, `number`, `checkbox`, `select`, `textarea`) require a **table worker** registered in JS. The factory emits a `<script>` block via `registerTableWorker(id, impl)` automatically when a non-standard type is detected. Currently the only non-standard inline type is:
| `input_type` | Page | What the worker renders |
|---|---|---|
| `credentials` | DDNS Accounts | Username/password or API token based on the row's `provider` field |
---
## Navigation (navbar.json only)
These types are used only in `app/navbar.json`, not in page `content.json` files.
### `nav_item`
A link or action in the navbar.
```json
{
"type": "nav_item",
"label": "Dashboard",
"map_to": "overview",
"client_requirement?": "client_is_viewer+"
}
```
### `nav_action`
Same as `nav_item` but uses an `action` field (POST) instead of a link.