Apps on Hubitat Elevation are the means by which users configure automations. Hubitat comes with several built-in apps (e.g., Room Lighting, Button Controller, Notifications, and more), but it is also possible to write user apps, also known as custom apps. Writing user apps is the focus of this document.
To create or modify a user app, navigate to Apps Code in the Hubitat web interface (expand the Developer Tools section if you do not see this in the sidebar). Select New App to create a new app, or select an existing app in the list to modify that app. When finished, select Save to commit your changes. Note that only valid code will save; any errors will appear in a a yellow bar at the top of the editor.
For an overview of the Hubitat development environment in general, see Developer Overview if you have not already read it.
The code below is a fully complete Hubitat app, though it doesn't do much besides display "Hello, world!". Still, it demonstrates all components necessary for a basic app. Try adding it to Apps Code and installing it under Apps > Add User App to see how it works!
definition(
name: "My First App",
namespace: "MyNamespace",
author: "My Name",
description: "A simple example app",
category: "Convenience",
iconUrl: "",
iconX2Url: ""
)
preferences {
page(name: "mainPage", title: "My Page", install: true, uninstall: true) {
section {
paragraph "Hello, world!"
}
}
}
def installed() {
log.trace "installed()"
}
def updated() {
log.trace "updated()"
}
def uninstalled() {}
An explanation of these components is provided below.
Note that this app demonstrates use of the built-in logger, log
, available to all apps and drivers. This outputs the provided text to Logs, and you will see this output when these methods are called (installed()
when the app is first installed on either first open or by the user selecting the Done button, depending on your app configuration; and updated()
whenever the user selects the Done button after that). Generally, you will need to do something in one or more of these methods, but for this example, we simply log when they are called.
The definition()
in an app contains data about the app, including the name and description (shown on the Add User App page).
name
: The name of the app as will be displayed in the Add User App dialog, the App Status page, and other locationsnamespace
: A unique identifier for the developer (app name plus namespace combinations must be unique), typically a username typically used by that developer (often their GitHub username)author
: A string identifying the developer, sometimes a real name or company name (unlike the name and namespace, this is used only for display purposes)description
: A short description of the app's purpose (shown under the app name on the Add User App page)category
: currently not usediconUrl
: currently not used; should be set to empty stringiconX2Url
: currently not used; should be set to empty stringiconX3Url
: Not currently used, should be set to an empty stringinstallOnOpen
: true
or false
, defaults to false
; will install app as soon as app is opened for first time without user needing to select DonedocumentationLink
: A link to the documentation for this app (often a Community post for user apps); the "?" icon in the upper right of the app pages will link to this URLvideoLink
: A link to a video recording (e.g., a how-to or other video documentation) for this app. Will be linked to from video icon in upper right of app pages.importUrl
- The URL where the "raw" Groovy code for this app can be found (not currently used)oauth
: true
or false
, defines whether this app makes use of OAuth (not currently used; OAuth must be manually enabled by user regardless)parent
: If this is a child app, specifies the parent app in the format "namespace:app name"
singleInstance
: true
or false
; if set to true
, only a single instance of this app can be installed. The default is false
.The preferences
block defines the user interface for the app. The preferences
block should consist of one or more page
blocks. Each page
consists of one or more section
blocks. The bulk of the user interface — the ability to select devices, type in or select other input, display paragraphs, etc. — is done inside a section
.
For more control over page content, you may wish to use dynamicPage
instead of page
. This allows the use of Groovy expressions inside the page, e.g., an if
statement that may display different inputs depending on the value of other inputs.
Alternatively, for single-page apps, the page
block can be omitted entirely and replaced directly with one or more section
blocks. (The example above is a single-page app, though it included page
for the sake of a complete demonstration.) If page
is omitted, two default features are provided for you:
an input for the user to provide a "label," or name, for your app (this will default to the app name specified in your definition
but allows the user to change it) — e.g., how the app appears in the Apps list
an input for the user to select "only in certain mode(s)" as a means to restrict execution of your app when outside the selected mode(s); selecting no modes will run in all modes
Apps that explicitly define one or more pages will not automatically have these features added, but developers can implement similar features manually if it makes sense for the app.
All page
parameters are optional:
name
: displays at top of pagenextPage
: for multi-page apps, should be set to the name
of the page to navigate to when user selects the Next buttonuninstall
: if true
, will show Remove (App Name) button at bottom of this page (should be set to false
only for multi-page apps and true
on at least one page)install
: if true
, will show button at bottom of page to install app if not already installed (e.g., user just added new app)Sections can be defined as:
section {}
section("Title") {}
section(Boolean hideable, "Title") {}
hideable
: determines if section is "collapsible" by selecting headingsection(Boolean hideable, Boolean hidden, "Title") {}
hideable
: determines if section is "collapsible" by selecting heading (true
means can be collapsed)hidden
: if hideable, determines if is collapsed or expanded by default (true
means collapsed)The input()
method method allows you to display various types of inputs (device selectors, string or numeric inputs, etc.). The general format is:
input(String name: elementName, String type: elementType, String title: elementTitle, /* ... additional options */ )
Groovy allows omitting the parentheses, and the name
and type
labels can be omitted, allowing for simpler calls like this:
input "myName", "myType", title: "My Input"
The value users provide for all input
fields is saved in to a Map
built in to each instance of the app called settings
with the key being the name
of the input
. These values can be accessed in the same way as values for any map, e.g., settings.myName
or settings["myName"]
. Inputs can also be accessed in the app by using simply the name
(as if it were a field/variable), e.g., myName
.
Options for type
on input
are:
capability.capabilityName
: provides a way for the user to select a device of the specified capability (obtains reference to DeviceWrapper object)device.DriverName
: provides a way for the user to select a device using the specified driver name (matched by driver name with spaces removed). Selecting by capability instead is generally recommended.text
: Stringtextarea
: String (from a multi-line re-sizable text box; parameter rows
is optional, for number of rows, with 2 the default)number
: Integerdecimal
: Doubleenum
: List; presents a drop-down list of options; requires a parameter options
of type List
with optionsbool
: Boolean; this is an on/off slider.time
: dateTime String (formatted "yyyy-MM-dd'T'HH:mm:ss.sssXX"); this pulls up a time picker.date
: date String; this pulls up a date picker.color
: color Map; this pulls up a color picker.button
: renders a UI button with the title in the button; generates a callback to a method defined in the app called appButtonHandler(String buttonName)
, which is passed the String name of the input when the UI button is pressed, which also causes a page refresh. (As a callback, the appButtonHandler
method is not called within the page where the button is rendered, so it cannot render anything itself, but can only set state to be dealt with by the page method upon its refresh.)input
optionsThe options
of an enum
can be a List
of options or a List
of Maps
(with string keys and values), where the key of the map element is saved as the value of the selection while the String value is displayed in the pull-down menu.
For device (capability or driver) and enum
inputs, an optional parameter of multiple: true
is allowed, and the setting value will be stored as a List
rather than a single element
A defaultValue: value
can be specified for an input, which will display in the UI on load but will not be saved to the corresponding setting until the page is refreshed or saved (like other inputs).
There is rudimentary formatting available for inputs, including width: number
, where the width can be 1 to 12, with 12 being the full width for a desktop screen, and 4 for a mobile device screen.
The submitOnChange: true
parameter the page to be refreshed upon completion of (selection away from) the input by the user. This is often used for inputs in dynamic pages because it allows for the progression of the UI as the user interacts with the app.
Adding required: true
forces an input to be completed before leaving the page.
The paragraph(String paragraphText)
method will display text output (rather than a field for user input) in the app. This may include HTML to create tables, or other presentation elements. It is also possible to use limited JavaScript in the paragraph text, though this is not necessary for most apps. This is used in the example app above to display text:
paragraph "Hello, world!"`
It possible to link to other app pages using href()
, and it is possible to pass parameters to those other pages. Parameters for href()
include:
name
: String
, name of this href
(should be unique)page
: String
, the name
of the page
to link toparams
: Map
, optional; a Map
of values that can be passed to the other pagetitle
: String
, title/heading for this href
description
: String
; additional text to display on this href
state
: String
, set to "complete"
to show in blue text, often indicating completion; or null
to show in gray (default)Example:
href name: "myHref", page: "MyOtherPage", title: "Go to other page!"
Apps are not always "running." An app will wake in response to certain events, including:
subscribe()
(this will run the developer-specified callback method) – this is the most common reason a typical app created to perform an automation would wakerunIn()
or similar methods (this will run the developer-specified callback method)preferences
)installed()
, updated()
, or uninstalled()
)mappings
, described elsewhere)To store data between executions, apps have a built-in state
object available. This object behaves like a Map
and allows storing most common data types (strings, numbers, maps, etc.). A similar object named atomicState
is also available. They work similarly, except that state
writes data just before the app goes to sleep again, whereas atomicState
commits the changes as soon as they are made. Most developers begin with state
, which is more efficient, and change to atomicState
if there are concerns for simultaneous execution and concerns over state data related to that. (These objects do refer to the same data and can be mixed in the same app if needed, though most developers choose one or the other.)
Example: state.foo = "bar"
As hinted at in the demo, apps (and drivers) have a built-in log
object that can be used to write entries to Logs. Available methods are:
log.info
log.debug
log.trace
log.warn
log.error
These will tag the log entry in Logs with the specified "level," e.g., a white "info" box next to the log entry, a blue "debug" box or a red "error" box. Info and debug logs are the most commonly used type across apps and drivers. Many developers find logging helpful as a tool for debugging or other troubleshooting when developing an app (and may use far more during initial development than would be found in a more stable app).
Example: log.debug "The value of state.foo is ${state.foo}"
To get started building a simple app — one that actually response to device events and sends commands to devices — continue with:
It may also be helpful to look at example apps, many of which can be found in the HubitatPublic GitHub repository: https://github.com/hubitat/HubitatPublic/tree/master/example-apps.
The remaining developer documentation provides more information on what methods are available for app (and driver) code and what methods and properties are available on Hubitat-provided objects or utilities. For apps, the following may be particularly helpful: