Server Settings ​
Phlix keeps server-wide configuration in two layers: the read-only config/*.php files baked in at boot, and a DB-backed settings store that lets an admin override individual values at runtime. The store is exposed through an admin JSON API so settings can be read and changed without editing files or restarting the server.
Settings SPA ​
The admin console exposes a graphical Settings page at /admin/settings. It consumes the GET/PUT /api/v1/admin/settings contract described below — no new endpoints were added; the page is the UI layer on top of the 0.5 API.
Access ​
Navigate to /admin/settings in the admin console sidebar (entry: Settings, positioned after Users). Requires admin authentication.
8 Group Tabs ​
The page renders all settings keys split across 8 tabbed sections:
| Tab | Keys |
|---|---|
| Transcoding | hwaccel.enabled, hwaccel.prefer_hardware, hwaccel.probe_timeout |
| Metadata | tmdb.api_key |
| Markers | marker_detection.similarity_threshold, marker_detection.intro_max_duration |
| Subtitles | subtitles.enabled, subtitles.default_language, subtitles.burn_in_by_default |
| Discovery | discovery.discovery_port |
| Trickplay | trickplay.enabled, trickplay.interval_seconds |
| Newsletter | newsletter.enabled, newsletter.send_hour |
| Port Forward | port-forward.port_forwarding.upnp_enabled |
Field Types ​
| Type | Control |
|---|---|
bool | Toggle switch |
int / float | Number input with min/max from schema constraints |
string | Text input; tmdb.api_key renders as a password field with Show/Hide toggle |
Overrides ​
Each key shows a "custom" badge (blue accent pill) when its effective value comes from the server_settings DB table rather than the config-file default. The overridden array returned by GET /api/v1/admin/settings drives this indicator.
Saving ​
The sticky Save settings footer button fires PUT /api/v1/admin/settings with { settings: { key: value, ... } }. On 200 the page re-renders with the refreshed overridden list and shows a success toast. On 400 per-field validation errors appear inline next to the relevant inputs. On 500 an error toast is shown.
Overrides persist across restarts — the database is the durable store.
The Effective-Value Model ​
Every editable setting has up to three layers:
| Layer | Source | Notes |
|---|---|---|
| Default | the value in config/<file>.php | Baked in at boot, read-only. |
| Override | a row in the server_settings table | Written by PUT /api/v1/admin/settings. |
| Effective | the override when present, else the default | What the API returns and what the server uses. |
An override survives a restart because the database — not the process — is the durable store. Removing an override (a future capability) would let the config-file default win again.
Dotted Keys ​
Settings are addressed with dotted keys. The first segment names the config/<file>.php file; the remaining segments walk into the array that file returns. For example:
hwaccel.enabled→ theenabledkey ofconfig/hwaccel.php.port-forward.port_forwarding.upnp_enabled→ walks two levels intoconfig/port-forward.php.
The leading file segment is restricted to ^[A-Za-z0-9_-]+$, so a crafted key can never escape the config directory.
Authentication ​
Both endpoints sit in the /api/v1/admin route group and are gated by the admin middleware. Send a valid admin JWT as a Bearer token:
Authorization: Bearer <admin-access-token>- An unauthenticated request returns
401. - A non-admin request returns
403.
Both error responses are JSON.
Read Settings ​
GET /api/v1/admin/settingsReturns the effective value of every allow-listed key, the subset that is currently overridden, and the type map.
{
"success": true,
"data": {
"settings": {
"hwaccel.enabled": true,
"hwaccel.probe_timeout": 5,
"tmdb.api_key": "",
"marker_detection.similarity_threshold": 0.85
},
"overridden": [
"hwaccel.enabled"
],
"types": {
"hwaccel.enabled": "bool",
"hwaccel.probe_timeout": "int",
"tmdb.api_key": "string",
"marker_detection.similarity_threshold": "float"
}
}
}settings— effective value per key (override or, failing that, config default).overridden— the keys whose effective value comes from a stored override.types— the declared type of each key (string|int|bool|float|json).
Update Settings ​
PUT /api/v1/admin/settings
Content-Type: application/json{
"settings": {
"hwaccel.enabled": true,
"marker_detection.similarity_threshold": 0.9
}
}On success the overrides are persisted and the refreshed effective values are returned:
{
"success": true,
"message": "Settings updated.",
"data": {
"settings": { "hwaccel.enabled": true, "marker_detection.similarity_threshold": 0.9 },
"overridden": [ "hwaccel.enabled", "marker_detection.similarity_threshold" ]
}
}Validation Rules ​
Every submitted value is checked against a typed allow-list before anything is written:
- The body must contain a non-empty
settingsobject, or the request returns400(Invalid payload). - An unknown key (not in the allow-list) returns
400(Validation failed) with the offending key inerrors. - A wrong-type value returns
400(Validation failed) withExpected type <type>.. - Validation is all-or-nothing: if any key fails, nothing is persisted.
Numeric strings are accepted for int/float, and the canonical bool-ish set (true/false/1/0, as bool, int, or string) is accepted for bool — values are coerced to their canonical PHP type before storage.
Example validation-failure response:
{
"success": false,
"error": "Validation failed",
"errors": {
"hwaccel.probe_timeout": "Expected type int.",
"made.up.key": "Unknown setting key."
}
}Editable Keys ​
The current allow-list maps each dotted key to its type and the config/<file>.php default it overrides. As of step 0.7 this allow-list is derived from the shared server-settings.schema.json (bundled in detain/phlix-shared); see Shared schemas.
| Key | Type | Backing config |
|---|---|---|
hwaccel.enabled | bool | config/hwaccel.php |
hwaccel.prefer_hardware | bool | config/hwaccel.php |
hwaccel.probe_timeout | int | config/hwaccel.php |
tmdb.api_key | string | config/tmdb.php |
marker_detection.similarity_threshold | float | config/marker_detection.php |
marker_detection.intro_max_duration | int | config/marker_detection.php |
subtitles.enabled | bool | config/subtitles.php |
subtitles.default_language | string | config/subtitles.php |
subtitles.burn_in_by_default | bool | config/subtitles.php |
discovery.discovery_port | int | config/discovery.php |
trickplay.enabled | bool | config/trickplay.php |
trickplay.interval_seconds | int | config/trickplay.php |
newsletter.enabled | bool | config/newsletter.php |
newsletter.send_hour | int | config/newsletter.php |
port-forward.port_forwarding.upnp_enabled | bool | config/port-forward.php |
This is a curated, representative slice of the Phase-1.3 settings groups (transcoding/hardware acceleration, metadata providers, marker detection, subtitles, discovery, trickplay, newsletter, port-forward/UPnP). The single source of truth is the shared server-settings.schema.json; the controller's AdminSettingsController::allowedKeys() derives this map from that schema at runtime (replacing the former inline ALLOWED_KEYS constant).
Storage ​
Overrides live in the server_settings table. Each row records the dotted setting_key (unique), the setting_value as text, and a value_type (string | int | bool | float | json) describing how to decode it back into a PHP value. Writes use an upsert (INSERT ... ON DUPLICATE KEY UPDATE) so re-saving a key replaces its previous override in place.
Roadmap ​
- Settings UI (Phase 1.3) — graphical screens in the admin console for editing these groups will be added; this page will gain a UI walkthrough section then.
- Shared schema (step 0.7) ✅ shipped — the shared
server-settings.schema.json(indetain/phlix-shared) is now the single source of truth for key names and types;AdminSettingsControllerderives its allow-list from it. See Shared schemas.