Why am I revisiting this topic after four years? Well, a lot has changed in OH world since then, and I think now is a good time to consolidate the new features, abilities, and additional documentation that exists now. Most of the new stuff is thanks to the excellent efforts of @Nadahar.
This is going to be long. If you are in a hurry and already know about rule templates, you can skip to the How section below.
Who Are Rule Templates For?
A rule template can be used by any user of OH. There is a marketplace where you can install rule templates from, and that may be one’s only interaction with rule templates.
A rule template can be written by anyone wanting to share a rule of their own in the most user-friendly way possible. Instead of copy/paste/edit/test/edit again and so forth, a rule template is installed either from the marketplace or manually installed (see below). This is by far the best way to share a rule with the community.
But, one can also write a rule template locally and privately. So a rule template can be useful for users who have a situation where they need multiple near copies of the same rule with different configuration. One can write a rule template and instantiate multiple rules based on that template, each with a different set of parameters.
What Is a Rule Template?
A rule template is kind of like a MainUI Widget you’d build under Developer Tools → UI Widgets. At its most basic, a rule template is a rule with properties. When a user instantiates a new rule based on a template, they provide values by filling in the properties. As a result they get a new instance of that rule customized and configured based on the properties.
When Should I Consider Using a Rule Template?
The most obvious case is when someone posted a rule template to the marketplace that does exactly what you want, or at least comes really close. Just install the template, instantiate your rule and you are ready to go.
If there is no already existing template, you might consider writing one if you have a situation where you have two or more rules/use cases that are almost the same but they have certain differences.
Consider this example from my personal OH system. Often times one will have a situation where they want something to happen when an Item(s) meets some criteria for a certain amount of time. Examples:
- turn on the humidifier when the humidity gets too low for 15 minutes
- send me an alert when no motion is detected for 8 hours
- send me an alert when a sensor stops reporting new readings for 30 minutes
- send me an alert when a service status remains OFF for 10 minutes
- send me an alert when a battery gets below a certain level and remains there for 1 hour
- send me an alert and repeat the alert when a door or window is left open for an individually configured amount of time
That’s a lot of different use cases, but they all do pretty much the same thing: wait a certain amount of time for an Item to remain in a state that meets a condition (e.g. humidity < 35 %) and do something. Stop doing that thing when it no longer meets the criteria.
I could write one rule to handle all of this. I could write size completely separate rules to handle all of these use cases. Or I can write a rule template with properties and instantiate a separate rule to handle all of these diverse yet related use cases with multiple configured instantiations of the same rule.
Where Can I Find Rule Templates?
In MainUI go to Add-on Store → Automation and look down to the Rule Templates section. You can click through and read the documentation for each and install the templates from there.
Or you can browse the marketplace on the forum at Rule Templates - openHAB Community.
If you write your own or find a rule templates somewhere else, you can place the yaml file under $OH_CONF/automation/templates.
Why a Rule Template Instead of a Library or Just Posting Code to the Forum
Rule templates take a little bit extra effort to create up front for the authors, but they pay huge benefits for sharing and usability. Some of the benefits include:
- Parameters can be rendered with complex UI elements like Item pickers, maps for location picking, cron expression builder, date-time pickers, etc.
- Sharing with the community is much easier. User can just install and configure your rule and never even need to look at it, let alone edit it and risk breaking something. Even if you don’t use the marketplace, you can share rule templates that can be dropped into the
$OH_CONF/automation/tempaltes. - Updates are much easier. When you change a template, either because the original author posted a new version to the marketplace or somewhere else, or you decided to change things. All you need to do is make the change, and then you can regenerate the rule from the template. Your changes to the template will be applied, and your selected properties will be applied.
- You can also regenerate the rule to change the properties if you decide the current behavior isn’t the best.
How?
Here is the meat of the tutorial.
Limitations
Anything that can be done in a rule can be done with a rule template. However, for now there are some limitations:
- only UI rules are supported (there is a PR to support file based rules use of templates awaiting review)
- one cannot change the type of a rule trigger using a property. You can change the data used by the trigger though. So for example, you cannot change the rule’s trigger type from Changed to Updated but you can change the Item the trigger monitors for changes or updates.
- all parameters come into the rule via a simple find-and-replace operation so additional processing may be required in the rule (for example if a parameter selects multiple Items, the comma separated list of Item names will need to be split into an array of Strings for use in the rule)
Structure of a rule template
Let’s use a recently updated rule template as an example. This rule template uses persistence to play back the states of Items stored to simulate presence when you are away. I chose this because it’s relatively short yet uses all the parts of a rule template.
uid: local_rules_tools:presence_sim
label: Local Persistence Presence Simulation
description: Triggers periodically when a given Item is ON and plays back the states of Items stored in persistence.
configDescriptions:
- name: enableSim
type: TEXT
context: item
filterCriteria:
- name: type
value: Switch
label: Enable Simulation
required: true
description: When not ON, the simulation rule will run (e.g. when Presence is OFF presence simulation will run).
- name: simItems
type: TEXT
context: item
filterCriteria:
- name: type
value: Group
label: Simulation Items Group
required: true
description: The Group whose members are to have their behaviors simulated.
- name: days
type: INTEGER
label: Num Days
required: false
description: The number of days to look back for playback (recommend multiples of 7).
defaultValue: 7
min: 0
- context: cronexpression
description: Frequency to pull the states from persistence and command the items.
label: Frequency
name: cron
required: false
type: TEXT
defaultValue: "0 0/5 * * * ? *"
triggers:
- id: "1"
configuration:
cronExpression: "{{cron}}"
type: timer.GenericCronTrigger
conditions:
- inputs: {}
id: "2"
configuration:
itemName: "{{enableSim}}"
state: "ON"
operator: "!="
type: core.ItemStateCondition
actions:
- inputs: {}
id: "3"
configuration:
type: application/javascript
script: >-
// Version 1.1
var {helpers} = require('openhab_rules_tools');
console.loggerName = 'org.openhab.automation.rules_tools.Presence Sim';
// osgi.getService('org.apache.karaf.log.core.LogService').setLevel(console.loggerName, 'DEBUG');
helpers.validateLibraries('4.1.0', '2.0.1');
var when = time.toZDT().minusDays({{days}});
items.{{simItems}}.members.forEach((i) => {
const hState = (helpers.compareVersions(utils.OPENHAB_JS_VERSION, '5.0.0') < 0) ? i.history.historicState(when) : i.persistence.persistedState(when);
if(hState === undefined || hState === null) {
console.warn(i.name + ' does not have a historic state for ' + '{{days}}' + ' days ago');
}
else {
console.debug('Commanding ' + i.name + ' to ' + hState.state);
i.sendCommandIfDifferent(hState.state.toString());
}
});
type: script.ScriptAction
As you can see, the overall structure is YAML. It’s separated into five major sections: identity info, configDescriptions, triggers, conditions, and actions.
Identity
Here we have three fields which are used to uniquely identify the rule template:
uid: must be unique and follows the same naming restrictions as Thingslabel: This is the “title” of the rule template as it will appear on the new rule page- `descriptions: This is the description.
These are shown to the end users as follows.

The segment of the code is
uid: local_rules_tools:presence_sim
label: Local Persistence Presence Simulation
description: Triggers periodically when a given Item is ON and plays back the states of Items stored in persistence.
configDescriptions
This is where you define your template parameters. Here we define four parameters, two Item pickers, a number, and a cron expression. You can see how we can restrict and constrain the parameters, provide defaults, specify certain parameters, etc. For more details and examples of just about everything you can do with parameters for bothUI Widgets and Templates see Parameter Playground. Note you can install this UI Widget for your reference.
This particular config appears to the end users as this.
These should look familiar to you as they are the same UI widgets used elsewhere to pick Items and build cron expressions.
The segment of the code is
configDescriptions:
- name: enableSim
type: TEXT
context: item
filterCriteria:
- name: type
value: Switch
label: Enable Simulation
required: true
description: When not ON, the simulation rule will run (e.g. when Presence is OFF presence simulation will run).
- name: simItems
type: TEXT
context: item
filterCriteria:
- name: type
value: Group
label: Simulation Items Group
required: true
description: The Group whose members are to have their behaviors simulated.
- name: days
type: INTEGER
label: Num Days
required: false
description: The number of days to look back for playback (recommend multiples of 7).
defaultValue: 7
min: 0
- context: cronexpression
description: Frequency to pull the states from persistence and command the items.
label: Frequency
name: cron
required: false
type: TEXT
defaultValue: "0 0/5 * * * ? *"
triggers
Everything from this point onward in the rule uses the same fields, format, and syntax as shown in the “code” tab of a UI rule. To learn details you are best off experimenting by adding triggers/conditions/actions of various types and clicking on the code tab to see what that looks like.
One thing to be aware of is that the "id" filed must be unique for each trigger. A simple one up number is sufficient. In the UI it will make the "id" unique across the whole rule.
This particular rule template only has one trigger. And you can see the first place where we have a parameter being applied to the rule.
triggers:
- id: "1"
configuration:
cronExpression: "{{cron}}"
type: timer.GenericCronTrigger
The rule templating engine will replace all instances of a parameter’s name surrounded by {{ }} with the value selected by the user for that parameter. In this case, the cronExpression will be replaced with the cron expression the user created on the form shown above.
Notice the " ". The find and replace will not add quotes and just replace the {{cron}} with the value of the cron parameter. So you often will need to add the quotes around where the replacement will occur.
conditions
The condition is a simple Item state condition. Here too we see one of the parameters being applied, the Item selected which turns the simulator on or off. The rule’s action will only run if the Item is ON.
conditions:
- inputs: {}
id: "2"
configuration:
itemName: "{{enableSim}}"
state: "ON"
operator: "!="
type: core.ItemStateCondition
All simple conditions as well as inline script conditions can be used here. If Blockly is your thing, you will be best off building the code in the UI and copying it into a template from the code tab.
actions
Here we have a single inline script action. In this case we are using JS.
actions:
- inputs: {}
id: "3"
configuration:
type: application/javascript
script: >-
// Version 1.1
var {helpers} = require('openhab_rules_tools');
console.loggerName = 'org.openhab.automation.rules_tools.Presence Sim';
// osgi.getService('org.apache.karaf.log.core.LogService').setLevel(console.loggerName, 'DEBUG');
helpers.validateLibraries('4.1.0', '2.0.1');
var when = time.toZDT().minusDays({{days}});
items.{{simItems}}.members.forEach((i) => {
const hState = (helpers.compareVersions(utils.OPENHAB_JS_VERSION, '5.0.0') < 0) ? i.history.historicState(when) : i.persistence.persistedState(when);
if(hState === undefined || hState === null) {
console.warn(i.name + ' does not have a historic state for ' + '{{days}}' + ' days ago');
}
else {
console.debug('Commanding ' + i.name + ' to ' + hState.state);
i.sendCommandIfDifferent(hState.state.toString());
}
});
type: script.ScriptAction
The code is less important here than the structure, so I won’t explain it (note this is relatively old code and is redundant in a number of ways, I’d do some things differently now). The big thing to notice is how the parameters are applied to the inline in the code.
Also note this is a rule template that I posted to the marketplace so it does a lot of extra error checking you may not care to do for your own code.
As with the condition, all simple conditions and inline scripts are supported for actions.
How Do I Develop a Rule Template?
Here is how I do it.
- Write the rule in the UI and test it every which way I can. Anything I intend to turn into a parameter I’ll define as a variable at the top of any scripts that I write. For example, the top of the script action of the rule template that attempts to figure out when OH was last shut down has this:
// Properties
var srcDirectory = new File('{{path}}');
var timestampItem = '{{item}}';
This rule template has two parameters and here I convert them into local variables to the script. This limits the number of places where a parameter may appear, making it easier to convert to a rule template.
- Create the start of a rule template file in
$OH_CONF/automation/templates. A bare skeleton of a rule template is as follows:
uid: skeleton
label: Skeleton Rule Template
description: Note, this will not parse without at least one action
configDescriptions: []
triggers: []
conditions: []
actions: []
However, that won’t parse because you must have at least one action. But it’s a good place to start.
-
Add the triggers, conditions, and actions defined in step 1. You can click on the code tab and copy all three of those sections and just paste them over the last three lines of the skeleton. Watch your logs for errors and if none, go and create a new rule from the template and make sure it looks and works like you want.
-
Now start adding your parameters. I like to do it one-by-one and testing in-between, but as you get used to it you might do it all in one go.
-
Done!
That’s it. Once you’ve added and tested out the parameters and can successfully generate a rule from the template that works like you want, you are done.
But you can do more.
How Do I Share a Rule Template?
You don’t necessarily have to. But if you choose to, the best place to do that is on the Marketplace. Create a new post and fill out the template. If you put your code somewhere like GitHub you can add a link to the raw YAML at the bottom of your post. Otherwise, you can just paste your template inline at the bottom of the post.
Once posted there and the published tag added, anyone with the Community Marketplace enabled will see it and be able to install the template.
However, there are some considerations for sharing on the marketplace or elsewhere.
- Other people who know less than you will be using this code. Pay attention to error checking and meaningful error messages where possible. I go overboard with almost half of my templates consisting of error checking but you don’t have to go that far.
- Add a version number somewhere so you know what version people are running when they ask for help.
- Do not log unnecessarily at the INFO level. Use logging levels and only log out errors and maybe one or two other lines a day normally. All other logging should be DEBUG or below and there should be lots of it.
- You can help deal with version incompatibilities by checking the openhab-js version (I have a tester function in OHRT you see being used above. You can log a meaningful error saying “I don’t work with that version of openhab-js yet/anymore”.
How Do I Refresh a Rule Based on a Template?
Thankfully this has become much easier. At the top of any rule created by a template is a “Regenerate” button. If you’ve selected a single rule, you will be presented the parameters form again, giving you the opportunity to change the parameters to adjust the rule’s behavior. Clicking “Regenerate” again will recreate the rule with any changes made to the rule template and any changes made to the parameters.
If you select more than one rule from the rule’s page and click regenerate, only the code of the selected rules will be updated with any changes made to the rule template, keeping the same parameter values.
Rules created from a template will be tagged. You can search for the tag to find all the rules created with the same template.
This is a convenient way to refresh all your rules based on one template.
To get changes made to a template posted to the marketplace navigate to MainUI → Add-on Store → Automation → the tempalte and remove then readd the template. Then regenerate the rules based on that template as described above.
Tips and Tricks
- Add a version number to your script action or script condition to make it easier to support your users.
- Test the version of openhab-js and generate a meaningful error if it’s out of the supported range. You can also have different branches in the code for different versions to increase compatibility of the rule template.
- Add lots of error checking with meaningful and actionable errors and warning messages.
- Do not spam the user’s logs. When there are no errors the rule should be mostly silent.
- You can change the behavior of the rule based on the trigger type. Use this to your advantage. For example, for many of my rule templates, running the rule manually will cause it to check that everything is configured correctly.
- If it’s a rule that’s likely to be instantiated more than once, make sure to include the
ruleUIDand possibly even the Item name that triggered the rule in the logger name. - Use the new wrapper parameter in shared rule templates to turn the wrapper off for conditions, so your templates remain compatible with and without the wrapper for JS Scripting (OH 5.1 onwards).
- If writing scruipt code in the YAML by hand, you need to add two newlines to separate two lines. With only one newline the lines will run together on the same line. However, if the code is between { } ( e.g. inside an
ifstatement) a single newline is sufficient.
For example:
one
two
three
four {
five
six
}
will produce
one
two
threefour {
five
six
}
- Test your YAML file locally before posting it to the marketpalce. Testing the YAML through the marketplace is a miserable experience.
- Expect users to change your code. So try to make it as understandable to a human as possible.
Hopefully this is enough to get you started. As you can see, it’s really no more complicated than writing a rule in the first place. The only addition is the parameters.
Ask if you have any questions or problems.

