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.
`<pre>` block for log output. Lines do not wrap; content scrolls horizontally when lines exceed the container width. Setting `scroll_to_bottom: true` adds a `data-scroll-bottom` attribute that JS uses to auto-scroll to the bottom on load and on update.
| `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"`.
The `validate` field attaches one or more `VALIDATION_*` flags 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. Flags are combined with `|`:
| `VALIDATION_RANGE_INT` | Integer. Respects the field's `min` and `max` bounds when present. Default for `number` input_type. |
| `VALIDATION_IPV4_CIDR` | Strict CIDR. A prefix is always required (`192.168.1.0/24`). Host bits must be zero. |
| `VALIDATION_IPV4_CIDRFLEX` | Flexible CIDR. Accepts a bare IPv4 address (yellow/incomplete when last octet is 0) or `IP/prefix`. |
| `VALIDATION_UNRESTRICTED` | IPv4 address must not fall within any restricted VLAN subnet. Factory automatically injects the current restricted subnets into `data-existing-ids` on the input. |
| `VALIDATION_IP_OR_DOMAIN_NAME` | IPv4 address or hostname/domain name. |
**`"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.