Add options
Let users configure your app. Define options and their types, read them in your scripts, and give them translated labels.
Options are the settings of your app that users change on its Configuration tab. You define them in config.yaml, the Supervisor validates what the user enters, and your app reads the result from /data/options.json.
Two keys work together:
optionsholds the default values. These are what a new user starts with.schemaholds the type of every option. The Supervisor validates the user's input against it, and the UI builds the form from it.
options:
message: "Hello world"
port: 8080
logins:
- username: "beer"
password: "123456"
log_level: "info"
schema:
message: str
port: port
logins:
- username: str
password: password
log_level: "list(debug|info|warning|error)"
api_key: "str?"Every key in schema is required, unless its type ends with ?. Here:
message,port,logins, andlog_levelare required, and have a default.api_keyis optional. It has no default, so it is not inoptions.
If you give a required option no default, or a default of null, the user has to fill it in before the app can start. Leave optional options out of options, so they stay unset until the user fills them in.
Options can be nested: a list of values, a list of mappings, or a mapping. A list cannot contain another list directly.
Set schema: false to turn off validation completely. The app then gets the options as they are, and the UI shows no form.
| Type | Accepts | Form field |
|---|---|---|
str |
Any text | Text |
str(min,max) |
Text with a minimum and maximum length. Leave one out for no limit, like str(3,) |
Text |
password |
Text, shown hidden. Also takes (min,max) |
Password |
bool |
true or false |
Toggle |
int |
A whole number. Also takes (min,max), like int(1,10) |
Number |
float |
A number. Also takes (min,max), like float(0.5,) |
Number |
email |
An email address | |
url |
A URL | URL |
port |
A network port, 1 to 65535 | Number |
match(regex) |
Text that matches the regular expression | Text |
list(a|b|c) |
One of the listed values | Dropdown |
device |
The path of a device on the host | Dropdown of devices |
device(subsystem=tty) |
A device of that subsystem, like serial ports | Dropdown of devices |
The Supervisor checks the hash of every password value against the Have I Been Pwned database and warns the user if it shows up there.
Users can reference a secret from the secrets.yaml of Home Assistant instead of typing a value, with !secret name. The Supervisor looks it up and validates the real value. Your app receives the resolved value in options.json, so you do not need to do anything for this to work.
The Supervisor writes the validated options to /data/options.json every time your app starts:
{
"message": "Hello world",
"port": 8080,
"logins": [{ "username": "beer", "password": "123456" }],
"log_level": "info"
}In a shell script, read them with Bashio. Bashio does not read the file: it asks the Supervisor API for the same options, so start the script with with-contenv to give it the token it needs.
#!/command/with-contenv bashio
message=$(bashio::config 'message')
first_user=$(bashio::config 'logins[0].username')
if bashio::config.has_value 'api_key'; then
api_key=$(bashio::config 'api_key')
fiThe key is a jq path, so nested options work too. In any other language, read /data/options.json as JSON.
Options change only when the user saves them, and the Supervisor writes the file before your app starts. So read them at startup; the user has to restart your app for new values to apply.
Without translations, the form shows the raw option keys, like log_level. Add a translation file to give each option a readable name and a description:
# translations/en.yaml
configuration:
message:
name: "Message"
description: "The message to show in the log at startup."
log_level:
name: "Log level"
description: "How much detail to write to the log."
logins:
name: "Logins"
description: "The users that can log in."
fields:
username:
name: "Username"
password:
name: "Password"The keys under configuration match the keys of your schema; fields does the same for nested options. Add a file per language, like nl.yaml or de.yaml. The format is in App files.
When you drop an option from your schema, users who already installed your app still have it saved. The Supervisor then logs a warning like Option 'old_key' does not exist in the schema for My app (local_my_app) every time it validates the options.
To clean that up, remove the option from the saved options at startup. With Bashio, bashio::app.option without a value deletes the key:
#!/command/with-contenv bashio
if bashio::jq.exists "$(bashio::app.options)" ".old_key"; then
bashio::log.info "Removing old option old_key"
bashio::app.option 'old_key'
fiThis calls the Supervisor API on /addons/self/options, which every app may use without hassio_api.
Some software is easier to configure with its own configuration file than with a form. Think of a complex list the form cannot express, a binary file like a certificate, or a file the software reloads when it changes.
For these cases, map app_config:
map:
- type: app_config
read_only: falseEach app then gets its own public configuration folder. Inside your container, it is at /config. Users find it in the app_configs folder (through Samba, SSH, or a file editor app), in a subfolder named after the app ID, like app_configs/local_my_app.
Tell users in your documentation which file to put there, or add an option with the file name. Write files to this folder too, if users need them: logs, a generated configuration, or a database to inspect. The folder is part of the backups of your app, and is removed when the app is uninstalled, if the user chooses to remove its data.