Creating Rules

Rules in MilliRules are PHP classes with a register() method that uses the MilliRules fluent builder API to define conditions and actions.

Rule Class Anatomy#Copied!

A rule class lives in the AppRules namespace and has a single register() method:

 1<?php
 2
 3namespace AppRules;
 4
 5use MilliRulesRules;
 6
 7class DocsRedirects
 8{
 9    public function register(): void
10    {
11        Rules::create('docs-redirect-old-paths')
12            ->when()
13                ->routeName('docs.legacy')
14            ->then()
15                ->redirect('/docs/{route.parameters.product}', 301)
16            ->register();
17    }
18}

Key points:

  • Namespace: AppRules — auto-discovered by the service provider
  • No base class: rule classes are plain PHP classes (no interface or abstract class required)
  • register() method: called automatically during service provider boot
  • Rules::create($id): starts the fluent builder with a unique rule ID
  • ->register(): finalizes and registers the rule with the engine
Important: Each rule ID must be unique across all packages. Use descriptive IDs like docs-security-headers or api-rate-limit-redirect.

Scaffolding#Copied!

Use the rules:make:rule command to generate a new rule class:

 1wp acorn rules:make:rule DocsRedirects

Output:

Rule created successfully.
 ⇂ Rule ID: docs-redirects
 ⇂ Package: Acorn
 ⇂ Auto-discovered on next request

The rule ID is automatically derived from the class name using kebab-case conversion (DocsRedirectsdocs-redirects).

Options#Copied!

Option Description
--package=Acorn Target package name (default: Acorn)
--force Overwrite the file if it already exists
 1# Overwrite an existing rule
 2wp acorn rules:make:rule DocsRedirects --force
Tip: rules:make is an alias for rules:make:rule. Both commands are identical.

Multiple Rules in One Class#Copied!

A single class can register multiple rules:

 1<?php
 2
 3namespace AppRules;
 4
 5use MilliRulesRules;
 6
 7class DocsRules
 8{
 9    public function register(): void
10    {
11        Rules::create('docs-security-headers')
12            ->when()
13                ->routeName('docs.*', 'LIKE')
14            ->then()
15                ->setHeader('X-Content-Type-Options', 'nosniff')
16                ->setHeader('X-Frame-Options', 'DENY')
17            ->register();
18
19        Rules::create('docs-cache-headers')
20            ->when()
21                ->routeName('docs.show')
22            ->then()
23                ->setHeader('Cache-Control', 'public, max-age=3600')
24            ->register();
25    }
26}

Each Rules::create() / ->register() pair defines an independent rule with its own ID, conditions, and actions.

Rule Ordering#Copied!

Rules execute in order determined by their order value (default: 10). Lower values execute first:

 1Rules::create('early-rule')
 2    ->order(5)
 3    ->when()
 4        ->routeName('docs.*', 'LIKE')
 5    ->then()
 6        ->setHeader('X-Processed-By', 'MilliRules')
 7    ->register();
 8
 9Rules::create('late-rule')
10    ->order(20)
11    ->when()
12        ->routeName('docs.*', 'LIKE')
13    ->then()
14        ->setHeader('X-Cache', 'HIT')
15    ->register();

Disabling Rules#Copied!

Temporarily disable a rule without removing it:

 1Rules::create('maintenance-redirect')
 2    ->enabled(false)
 3    ->when()
 4        ->routeName('docs.*', 'LIKE')
 5    ->then()
 6        ->redirect('/maintenance')
 7    ->register();

Disabled rules appear in rules:list with Enabled = No but are skipped during execution.

Next Steps#Copied!