> ## Documentation Index
> Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Advanced Validation Expressions Guide

> > User guide for creating powerful validation rules using expression-based validation in wizard steps.

> User guide for creating powerful validation rules using expression-based validation in wizard steps.

## Overview

Advanced Validation Expressions allow you to create complex validation rules that go beyond simple required/min/max checks. Using a JavaScript-like syntax, you can validate:

* Age requirements
* Date comparisons
* Conditional required fields
* Pattern matching
* Cross-field validation
* Custom business rules

## Getting Started

### Accessing Expression Validation

1. Open a wizard template in the Builder
2. Select a step
3. Go to the **Validation** tab
4. Click the **Advanced Expressions** sub-tab

### Your First Expression

Let's create a simple age validation:

```
age(date_of_birth) >= 18
```

This expression:

* Takes the `date_of_birth` field value
* Calculates the age in years
* Checks if it's 18 or older

## Expression Syntax

### Basic Structure

```
function(field_name) operator value
```

### Operators

| Operator | Description      | Example                       |
| -------- | ---------------- | ----------------------------- |
| `==`     | Equals           | `status == "active"`          |
| `!=`     | Not equals       | `status != "inactive"`        |
| `>`      | Greater than     | `age(dob) > 21`               |
| `>=`     | Greater or equal | `amount >= 100`               |
| `<`      | Less than        | `quantity < 1000`             |
| `<=`     | Less or equal    | `years_since(hire_date) <= 5` |
| `&&`     | Logical AND      | `a > 0 && b > 0`              |
| `\|\|`   | Logical OR       | `a > 0 \|\| b > 0`            |
| `!`      | Logical NOT      | `!is_empty(email)`            |

### Combining Expressions

Use `&&` (AND) and `||` (OR) to combine multiple conditions:

```
// Both must be true
age(dob) >= 18 && !is_empty(email)

// Either can be true
is_empty(phone) || is_valid_phone(phone)

// Complex combination
(age(dob) >= 18 && is_employed) || has_guardian_consent
```

## Available Functions

### Date Functions

| Function                  | Description       | Example                              |
| ------------------------- | ----------------- | ------------------------------------ |
| `age(date)`               | Years since date  | `age(date_of_birth) >= 18`           |
| `years_since(date)`       | Years since date  | `years_since(hire_date) >= 1`        |
| `months_since(date)`      | Months since date | `months_since(start_date) >= 6`      |
| `days_since(date)`        | Days since date   | `days_since(last_review) <= 365`     |
| `is_before(date1, date2)` | date1 \< date2    | `is_before(start_date, end_date)`    |
| `is_after(date1, date2)`  | date1 > date2     | `is_after(hire_date, training_date)` |
| `today()`                 | Current date      | `is_before(deadline, today())`       |

### String Functions

| Function                     | Description        | Example                     |
| ---------------------------- | ------------------ | --------------------------- |
| `is_empty(value)`            | Check null/empty   | `!is_empty(email)`          |
| `is_not_empty(value)`        | Check has value    | `is_not_empty(email)`       |
| `len(value)`                 | String length      | `len(name) >= 2`            |
| `matches(value, pattern)`    | Regex match (safe) | `matches(zip, "^\\d{5}$")`  |
| `starts_with(value, prefix)` | Check prefix       | `starts_with(phone, "+1")`  |
| `ends_with(value, suffix)`   | Check suffix       | `ends_with(email, ".edu")`  |
| `contains(value, substring)` | Check substring    | `contains(notes, "urgent")` |

### Validation Functions

| Function                | Description   | Example                  |
| ----------------------- | ------------- | ------------------------ |
| `is_valid_email(value)` | Email format  | `is_valid_email(email)`  |
| `is_valid_phone(value)` | Phone format  | `is_valid_phone(phone)`  |
| `is_valid_ssn(value)`   | SSN format    | `is_valid_ssn(ssn)`      |
| `is_valid_zip(value)`   | US ZIP format | `is_valid_zip(zip_code)` |

### Number Functions

| Function                    | Description | Example                   |
| --------------------------- | ----------- | ------------------------- |
| `is_between(val, min, max)` | Range check | `is_between(age, 18, 65)` |

### Comparison Functions

| Function    | Description      | Example                 |
| ----------- | ---------------- | ----------------------- |
| `eq(a, b)`  | Equals           | `eq(status, "active")`  |
| `neq(a, b)` | Not equals       | `neq(status, "closed")` |
| `gt(a, b)`  | Greater than     | `gt(age, 18)`           |
| `gte(a, b)` | Greater or equal | `gte(score, 70)`        |
| `lt(a, b)`  | Less than        | `lt(quantity, 100)`     |
| `lte(a, b)` | Less or equal    | `lte(price, 1000)`      |

### Conditional Functions

| Function                        | Description          | Example                                   |
| ------------------------------- | -------------------- | ----------------------------------------- |
| `required_if(condition, field)` | Conditional required | `required_if(is_employed, employer_name)` |
| `one_of(value, options)`        | Value in list        | `one_of(status, ["active", "pending"])`   |
| `not_one_of(value, options)`    | Value not in list    | `not_one_of(role, ["admin", "super"])`    |
| `all(a, b, ...)`                | All truthy           | `all(has_consent, is_adult)`              |
| `any(a, b, ...)`                | Any truthy           | `any(has_phone, has_email)`               |
| `not(value)`                    | Logical NOT          | `not(is_locked)`                          |

## Common Patterns

### Age Verification

```
// Must be 18 or older
age(date_of_birth) >= 18

// Must be between 18 and 65
is_between(age(date_of_birth), 18, 65)

// Under 18 requires guardian
age(date_of_birth) >= 18 || !is_empty(guardian_name)
```

### Date Range Validation

```
// End date must be after start date
is_after(end_date, start_date)

// Event must be in the future
is_after(event_date, today())

// Must be within 90 days
days_since(submission_date) <= 90
```

### Conditional Required Fields

```
// Employer required if employed
required_if(is_employed, employer_name)

// Phone OR email required
!is_empty(phone) || !is_empty(email)

// Address required if not same as billing
required_if(!same_as_billing, shipping_address)
```

### Format Validation

```
// US phone format (10 digits)
matches(phone, "^\\d{10}$")

// ZIP code (5 or 9 digits)
matches(zip, "^\\d{5}(-\\d{4})?$")

// Strong password (8+ chars, number, special)
len(password) >= 8 && matches(password, "[0-9]") && matches(password, "[!@#$%]")
```

### Cross-Field Validation

```
// Confirm password matches
password == confirm_password

// Total must equal sum of parts
total == (subtotal + tax)

// End date at least 30 days after start
days_since(start_date) >= 30 || is_after(end_date, start_date)
```

## Creating Expression Rules

### Step-by-Step

1. **Open Validation Panel**: Select a step and click "Validation" tab
2. **Switch to Advanced**: Click "Advanced Expressions" sub-tab
3. **Add Rule**: Click "Add Expression Rule"
4. **Write Expression**: Enter your validation expression
5. **Set Error Message**: Define the message shown when validation fails
6. **Set Timing**: Choose when to validate (on change, blur, or submit)
7. **Test**: Use the Test Runner to verify

### Rule Properties

| Property      | Description                                      |
| ------------- | ------------------------------------------------ |
| Expression    | The validation logic                             |
| Error Message | Shown when validation fails                      |
| Timing        | When to run: `on_change`, `on_blur`, `on_submit` |
| Priority      | Order of execution (lower runs first)            |
| Enabled       | Toggle rule on/off without deleting              |

## Testing Expressions

### Using the Test Runner

1. Click **Test Expressions** button
2. Enter sample values for each field
3. Click **Run Tests**
4. View pass/fail results for each rule

### Test Runner Features

* **Field Inputs**: Auto-generated from step fields
* **Quick Fill**: Sample data suggestions
* **Results Panel**: Shows which rules pass/fail
* **Error Preview**: See exact error messages

### Testing Tips

1. Test both valid and invalid inputs
2. Test edge cases (empty, null, boundaries)
3. Test with realistic data
4. Verify error messages are helpful

## Error Messages

### Writing Good Messages

❌ **Bad**: "Invalid"\
✅ **Good**: "Date of birth indicates you must be at least 18 years old"

❌ **Bad**: "Required"\
✅ **Good**: "Employer name is required when employment status is 'Employed'"

### Message Variables

You can reference field values in messages (coming soon):

```
"Age {{age(date_of_birth)}} does not meet the minimum requirement of 18"
```

## Best Practices

### Expression Design

1. **Keep It Simple**: Break complex rules into multiple expressions
2. **Be Specific**: Target specific fields, not entire forms
3. **Handle Empty**: Always consider null/empty cases
4. **Use Parentheses**: Make precedence explicit

### Performance

1. **Timing Matters**: Use `on_submit` for expensive validations
2. **Avoid Redundancy**: Don't repeat standard validations
3. **Order by Priority**: Put quick-fail checks first

### Maintenance

1. **Document Rules**: Use clear error messages as documentation
2. **Group Related Rules**: Keep related validations together
3. **Test After Changes**: Always re-test after modifications
4. **Version Control**: Track changes in template versions

## Healthcare & PHI Compliance

### PHI Field Naming Guidelines

When creating validation expressions that may touch PHI/PII:

1. **Use Generic Field Names**: Prefer `patient_id`, `resident_id`, `client_id` over revealing field names
2. **Avoid Revealing Names**: Never use field names that expose sensitive data types (avoid `ssn`, `medical_record_number`, `diagnosis_code`)

### Expression Audit Requirements

All expressions touching PHI fields must be:

1. **Reviewed** by a compliance officer before deployment
2. **Logged** for creation/modification with user ID and timestamp
3. **Never log actual PHI values** - only log field references and expression text

### Error Message Rules (Critical)

Functions like `age()`, `matches()`, `required_if()` must:

* **Never** include actual field values in error messages
* Use generic messages: "Age requirement not met" not "Your age of 17 does not meet..."
* Avoid revealing field contents in validation failures

### Timing Recommendations for PHI

* **Use `on_submit`**: For PHI field validations to minimize data exposure during typing
* **Avoid `on_change`**: Real-time validation can increase attack surface

### Access Control

* Only users with appropriate permissions should edit expressions
* Expression editing should be restricted to form administrators
* Implement retention policies for expression audit logs (recommended: 7 years for healthcare)

## Security Considerations

### Safe by Design

Expression validation is sandboxed:

* No `eval()` or code execution
* No access to global objects
* No network requests
* No file system access

### What's Blocked

* JavaScript code injection
* Function definitions
* Variable assignments
* External references

### What's Allowed

* Field references by name
* Approved validation functions
* Operators (comparison, logical)
* Literal values (strings, numbers, booleans)

## Troubleshooting

### Common Errors

| Error              | Cause                        | Fix                             |
| ------------------ | ---------------------------- | ------------------------------- |
| "Unknown function" | Typo or unsupported function | Check function name spelling    |
| "Unknown field"    | Field name not found         | Verify field `name` property    |
| "Syntax error"     | Malformed expression         | Check operators and parentheses |
| "Type mismatch"    | Wrong value type             | Ensure correct field types      |

### Debugging Tips

1. **Simplify**: Reduce to smallest failing case
2. **Test Runner**: Use to isolate issues
3. **Check Field Names**: Match exactly (case-sensitive)
4. **Review Syntax**: Watch for missing quotes/parentheses

## See Also

* [Wizard Builder Documentation](../../src/platform/wizards/README.md)
* [Marketplace Guide](./wizard-marketplace-guide.md)
