diff --git a/docs/name_validation_rules.md b/docs/name_validation_rules.md
index 35abb4a5e..e7bd1803e 100644
--- a/docs/name_validation_rules.md
+++ b/docs/name_validation_rules.md
@@ -39,7 +39,7 @@ The following ASCII characters are forbidden in **Model Names** and **Port Names
### Port Name
- **Cannot be empty**
- **Cannot start with a digit**
-- **Cannot be a reserved attribute**: `ID`, `name`, `_description`, `_skipIf`, `_successIf`, `_failureIf`, `_while`, `_onSuccess`, `_onFailure`, `_onHalted`, `_post`, `_autoremap`, `__shared_blackboard`
+- **Cannot be a reserved attribute**: `ID`, `name`, `_description`, `_skipIf`, `_successIf`, `_failureIf`, `_while`, `_onlyIf`, `_onStart`, `_onSuccess`, `_onFailure`, `_onHalted`, `_post`, `_autoremap`, `__shared_blackboard`
- No forbidden characters (see table above)
### Instance Name
diff --git a/docs/pre_postconditions.md b/docs/pre_postconditions.md
index cc2243c06..2aec8e86e 100644
--- a/docs/pre_postconditions.md
+++ b/docs/pre_postconditions.md
@@ -14,6 +14,8 @@ Pre-conditions are evaluated **before** a node's `tick()` method is called. They
| `_successIf` | IDLE only | If true, return SUCCESS without calling tick() |
| `_skipIf` | IDLE only | If true, return SKIPPED without calling tick() |
| `_while` | IDLE and RUNNING | If false when IDLE, return SKIPPED. If false when RUNNING, halt node and return SKIPPED |
+| `_onlyIf` | IDLE only | If false, return SKIPPED without calling tick() (the opposite of `_skipIf`) |
+| `_onStart` | IDLE only | Not a condition: execute the script right before the first tick() |
### Evaluation Order
@@ -22,12 +24,15 @@ When a node has multiple pre-conditions, they are evaluated in this order:
2. `_successIf`
3. `_skipIf`
4. `_while`
+5. `_onlyIf`
+6. `_onStart`
-The first condition that triggers will determine the result.
+The first condition that triggers will determine the result. `_onStart` comes last,
+so its script is executed only if the node is actually going to tick.
### Important: One-time Evaluation
-**`_failureIf`, `_successIf`, and `_skipIf` are evaluated only once** when the node transitions from IDLE (or SKIPPED) to another state. They are **NOT re-evaluated** while the node is RUNNING.
+**`_failureIf`, `_successIf`, `_skipIf`, `_onlyIf` and `_onStart` are evaluated only once** when the node transitions from IDLE (or SKIPPED) to another state. They are **NOT re-evaluated** while the node is RUNNING.
This means if you have:
```xml
@@ -48,10 +53,12 @@ If `battery_ok` becomes false while `MyAction` is running, the action is interru
### When to Use Each Pre-condition
-- **`_skipIf`**: Skip a node without failing the parent (useful in Sequences)
+- **`_skipIf`** / **`_onlyIf`**: Skip a node without failing the parent (useful in Sequences)
- **`_failureIf`**: Fail early based on a condition (useful in Fallbacks)
- **`_successIf`**: Succeed early based on a condition
- **`_while`**: Guard that must remain true for the entire execution
+- **`_onStart`**: Initialize or reset blackboard entries when the node starts,
+ e.g. ``
### The `` Decorator
diff --git a/include/behaviortree_cpp/tree_node.h b/include/behaviortree_cpp/tree_node.h
index b4ae9506f..bc6fdcda9 100644
--- a/include/behaviortree_cpp/tree_node.h
+++ b/include/behaviortree_cpp/tree_node.h
@@ -48,9 +48,9 @@ using NonPortAttributes = std::unordered_map;
* @brief Pre-conditions that can be attached to any node via XML attributes.
*
* Pre-conditions are evaluated in the order defined by this enum (FAILURE_IF first,
- * then SUCCESS_IF, then SKIP_IF, then WHILE_TRUE).
+ * then SUCCESS_IF, SKIP_IF, WHILE_TRUE, ONLY_IF and finally ON_START).
*
- * **Important**: FAILURE_IF, SUCCESS_IF, and SKIP_IF are evaluated **only once**
+ * **Important**: all of them except WHILE_TRUE are evaluated **only once**
* when the node transitions from IDLE (or SKIPPED) to another state.
* They are NOT re-evaluated while the node is RUNNING.
*
@@ -62,6 +62,11 @@ using NonPortAttributes = std::unordered_map;
* If false when IDLE, return SKIPPED. If false when RUNNING, halt the node
* and return SKIPPED. This is the only pre-condition that can interrupt
* a running node.
+ * - `_onlyIf="