Custom Conditions and Actions
Beyond the built-in types, you can create your own conditions and actions that are auto-discovered and available in the fluent builder.
Custom Conditions#Copied!
1. Scaffold the Condition#Copied!
1wp acorn rules:make:condition IsAdmin
This creates app/Rules/Conditions/IsAdmin.php:
1<?php
2
3namespace AppRulesConditions;
4
5use MilliRulesConditionsBaseCondition;
6use MilliRulesContext;
7
8class IsAdmin extends BaseCondition
9{
10 public function get_type(): string
11 {
12 return 'is_admin';
13 }
14
15 protected function get_actual_value(Context $context)
16 {
17 // Return the value to compare. BaseCondition handles the operator + expected value.
18 // $context->get('route.name'), $context->get('route.parameters.slug'), etc.
19 return '';
20 }
21}
2. Implement the Logic#Copied!
Fill in get_actual_value() to return the value that should be compared against the condition's expected value:
1<?php
2
3namespace AppRulesConditions;
4
5use MilliRulesConditionsBaseCondition;
6use MilliRulesContext;
7
8class IsAdmin extends BaseCondition
9{
10 public function get_type(): string
11 {
12 return 'is_admin';
13 }
14
15 protected function get_actual_value(Context $context): string
16 {
17 $user = auth()->user();
18
19 return $user && $user->is_admin ? 'true' : 'false';
20 }
21}
get_actual_value() should return a string (or scalar). The BaseCondition parent class handles all operator logic (=, !=, LIKE, REGEXP, IN) automatically.3. Use in a Rule#Copied!
The condition is auto-discovered and immediately available as ->isAdmin() in the builder:
1Rules::create('admin-debug-headers')
2 ->when()
3 ->isAdmin('true')
4 ->then()
5 ->setHeader('X-Debug', 'enabled')
6 ->register();
4. Verify#Copied!
1# Confirm the condition is registered
2wp acorn rules:conditions --package=Acorn
3
4# Confirm the rule uses it
5wp acorn rules:show admin-debug-headers
Custom Actions#Copied!
1. Scaffold the Action#Copied!
1wp acorn rules:make:action CorsHeaders
This creates app/Rules/Actions/CorsHeaders.php:
1<?php
2
3namespace AppRulesActions;
4
5use MilliRulesActionsBaseAction;
6use MilliRulesContext;
7
8class CorsHeaders extends BaseAction
9{
10 public function get_type(): string
11 {
12 return 'cors_headers';
13 }
14
15 public function execute(Context $context): void
16 {
17 // $value = $this->get_arg(0, 'default')->string();
18 //
19 // Modify the HTTP response:
20 // app('millirules.response')->addHeader('X-Custom', $value);
21 // app('millirules.response')->setRedirect('/path', 302);
22 }
23}
2. Implement the Logic#Copied!
Use $this->get_arg() to read arguments from the builder and app('millirules.response') to modify the HTTP response:
1<?php
2
3namespace AppRulesActions;
4
5use MilliRulesActionsBaseAction;
6use MilliRulesContext;
7
8class CorsHeaders extends BaseAction
9{
10 public function get_type(): string
11 {
12 return 'cors_headers';
13 }
14
15 public function execute(Context $context): void
16 {
17 $origin = $this->get_arg(0, '*')->string();
18
19 $collector = app('millirules.response');
20 $collector->addHeader('Access-Control-Allow-Origin', $origin);
21 $collector->addHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
22 $collector->addHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
23 }
24}
3. Use in a Rule#Copied!
The action is auto-discovered and available as ->corsHeaders() in the builder:
1Rules::create('api-cors')
2 ->when()
3 ->routeName('api.*', 'LIKE')
4 ->then()
5 ->corsHeaders('https://example.com')
6 ->register();
4. Verify#Copied!
1# Confirm the action is registered
2wp acorn rules:actions --package=Acorn
3
4# Confirm the rule uses it
5wp acorn rules:show api-cors
ResponseCollector API#Copied!
Custom actions modify the HTTP response through the ResponseCollector singleton, accessed via app('millirules.response'). The middleware reads the collector after rule execution and applies changes to the outgoing response.
Available Methods#Copied!
| Method | Description |
|---|---|
addHeader(string $name, string $value) |
Queue a header to be set on the response. If the same header name is added multiple times, the last value wins. |
setRedirect(string $url, int $status = 302) |
Queue a redirect response. Replaces the original response entirely. If multiple redirects are queued, the last one wins. |
1$collector = app('millirules.response');
2
3// Add a header
4$collector->addHeader('X-Custom', 'value');
5
6// Queue a redirect (replaces the response)
7$collector->setRedirect('/new-location', 301);
Using Route Context in Custom Types#Copied!
Custom conditions and actions can access the route context by loading it from the Context object:
1protected function get_actual_value(Context $context): string
2{
3 // Load route context (lazy-loaded on first call)
4 $context->load('route');
5
6 // Access individual keys
7 $routeName = $context->get('route.name', '');
8 $productParam = $context->get('route.parameters.product', '');
9 $controller = $context->get('route.controller', '');
10
11 return is_string($routeName) ? $routeName : '';
12}
The $context->load('route') call is idempotent — it loads route data once and subsequent calls are no-ops. See the Route Context reference for all available context keys.
Next Steps#Copied!
- Configuration — customize middleware groups and stubs
- Route Context Reference — all available context keys, types, and examples
- For advanced patterns like custom operators and context providers, see the MilliRules Custom Conditions and Custom Actions documentation