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}
Note: 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);
Warning: A redirect replaces the entire original response. Headers are still applied to the redirect response, but the original response body is discarded.

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!