---
tags: [data policy, field enforcement, mandatory, read-only, server-side validation, import sets, soap, sys_data_policy2, sys_data_policy_rule, data integrity]
---
# Data Policy

Guide for creating ServiceNow Data Policies (`sys_data_policy2`) using the Fluent API. Data policies enforce field mandatory and read-only rules server-side across all interfaces — forms, imports, web services, and APIs — and cannot be bypassed, unlike client-side UI Policies.

## When to Use

**Use Data Policy when:**

- User explicitly requests "data policy" or "data policies"
- Need ONLY mandatory or read-only enforcement across ALL interfaces (forms, imports, APIs, web services)
- Need server-side enforcement that cannot be bypassed
- Protecting critical fields from modification after certain conditions (e.g., closed records)
- Ensuring data integrity from external sources (imports, API calls, integrations)
- A field must be populated / required / non-empty when a condition is true — even if the word "validate" appears in the requirement

### Natural language signals — Data Policy vs UI Policy vs Business Rule

The following phrases indicate mandatory/read-only enforcement. **Default to Data Policy for server-side security** unless the prompt explicitly mentions client-side context:

| Prompt says… | Means… | Tool |
|---|---|---|
| "must hold a value", "must carry a value", "must have a value", "is required when", "required if" | Field mandatory under a condition | **DataPolicy** `mandatory: true` |
| "locked", "read-only when", "cannot be edited when", "fields must be locked", "prevent modification" | Field read-only under a condition | **DataPolicy** `readOnly: true` |
| "cannot be modified once", "immutable after", "freeze fields after", "all fields locked" | Read-only after a state/condition | **DataPolicy** `readOnly: true` (+ Business Rule for transition guard if needed) |
| "locked against edits", "prevent changes if [field] was [value]" | Needs comparison to `previous` value | **BusinessRule** `before` — Data Policy cannot access `previous` |

**Prefer `table` skill for unconditional enforcement:** For fields that are always mandatory or always read-only, set `mandatory: true` or `read_only: true` directly on the column definition (see the `table-guide` topic). Use Data Policy when enforcement is conditional, OR when you need guaranteed cross-API/import enforcement as an additional security layer.

## Instructions

1. **STOP if prompt mentions visibility or messages:** Check if the prompt mentions ONLY visibility, messages, or value-setting — use UI Policy instead. If both mandatory/read-only AND visibility/value needs exist, create a Data Policy for the mandatory/read-only part and a UI Policy for the rest.
2. **Verify Data Policy is the right tool:** Confirm the requirement is ONLY for mandatory or read-only enforcement. If the user needs visibility control, field values, scripts, or complex logic, use a different skill (see "When to Use" above).
3. **Use the `DataPolicy` API** from `@servicenow/sdk/core`. Every Data Policy must have `$id`, `table`, and `shortDescription`. Every individual rule inside `rules` must also have a `$id`.
4. **`$id` naming convention:** Use descriptive snake_case names in `Now.ID[]` — e.g., `Now.ID['scope_policy_purpose']`. Good: `Now.ID['hr_employee_mandatory_fields']`. Bad: `Now.ID['policy1']`.
5. **Default properties:** See "Default Property Values" section below. Only specify properties when overriding defaults.
6. **Never set both `mandatory: true` and `readOnly: true` on the same field:** These are mutually exclusive — a read-only field cannot be mandatory. This causes a build error.
7. **Conditions use encoded query syntax** — e.g., `"priority=1^state!=6"`. Empty conditions apply to all records. Use `^` for AND, `^OR` for OR.
8. **Always use stored values in conditions:** Choice fields use stored values, not display labels. Right-click field → Show Choice List → use the "Value" column.
9. **Omit `'ignore'` rule properties for cleaner code:** Since `'ignore'` is the default, only specify `mandatory` or `readOnly` when you need to change the field's state.
10. **Use dot-walk notation for cross-table rules:** To enforce rules on fields of a referenced table, use `'reference_field.target_field'` as the rule key (e.g., `'caller_id.email'`). Multiple levels are supported (e.g., `'employee.manager.department'`).
11. **Do not make fields mandatory for out-of-scope tables:** The mandatory flag is silently disabled for tables outside the current scope. Read-only rules are not affected.
12. **Competing policies — most restrictive wins:** When multiple policies target the same field on the same record, ServiceNow applies the most restrictive result. There is no evaluation order. To release a constraint, tighten the condition on the restrictive policy itself rather than relying on a second policy with `mandatory: false`.

## Key Concepts

### Data Policy vs UI Policy

| Aspect | Data Policy | UI Policy |
|--------|-------------|-----------|
| Execution | Server-side | Client-side (browser) |
| Can be bypassed via API? | No | Yes |
| Applies to imports | Yes (default) | No |
| Applies to REST/SOAP | Yes (default) | No |
| Visibility control | No | Yes |
| Field value actions | No | Yes |
| Scripting support | No | Yes |
| Field messages | No | Yes |

Use Data Policies for **data integrity and security**. Use UI Policies for **UX enhancements**.

**Use UI Policy instead when prompt includes:**
- "on the form only", "in the UI only", "to the user", "for the user"
- Visibility requirements alongside mandatory/read-only ("show section and make field required")
- Messages or warnings ("make mandatory and display a message")
- Client-side only context ("prevent users from editing on the form")

**Use both Data Policy + UI Policy when:**
- Prompt requires server-side enforcement (API/imports) AND client-side UX (messages/visibility)
- Example: "Make vendor mandatory for high-value contracts and show a warning message" → Data Policy (mandatory) + UI Policy (message)

**For detailed UI Policy usage,** see the `ui-policy-guide` topic.

**Cross-policy conflict:** If a UI Policy makes a field optional but a Data Policy makes it mandatory, the field is mandatory on save. Server-side always takes precedence.

### Data Policy vs Business Rule

| Scenario | Data Policy | Business Rule |
|---|---|---|
| Field must be mandatory/read-only under a condition | ✅ | — |
| Field must be mandatory across all interfaces (imports, APIs) | ✅ | — |
| Need a custom error message | — | ✅ |
| Condition requires scripting or cross-field logic | — | ✅ |
| Auto-populate or calculate a field value | — | ✅ |
| Cascade updates to related records | — | ✅ |
| Enforce on direct GlideRecord with `setWorkflow(false)` | — | ✅ |
| Enforcement on out-of-scope table fields (mandatory) | — | ✅ |

**Use Business Rule instead when:**
- Need to compare field's `previous` value (e.g., "prevent changes if field was X")
- Need custom error messages or validation logic
- Need to auto-populate or calculate field values (e.g., "auto-populate field from another field")
- Condition requires scripting or complex cross-field logic
- Need transition guards (e.g., additional validation when state changes)

**Decision rule:** If the requirement can be expressed as "when [condition] is true, field X must have a value / must be read-only" **without mentioning forms, UI, or messages** — that is always a Data Policy, not a Business Rule or UI Policy. Only reach for a Business Rule when the requirement needs `previous`, scripting, custom messaging, or value manipulation that Data Policy cannot express.

**For detailed Business Rule usage,** see the `business-rule-guide` topic.

### Default Property Values

These properties default to `true` and **should not be written in code unless overriding**:

| Property | Default | Override to `false` when... |
|----------|---------|------------------------------|
| `active` | `true` | Deliberately disabling the policy |
| `applyToImportSets` | `true` | Policy should not apply to imports |
| `applyToSOAP` | `true` | Policy should not apply to REST/SOAP |
| `useAsUiPolicyOnClient` | `true` | Only server-side enforcement is wanted |
| `reverseIfFalse` | `true` | Rules should remain even when conditions are false |

These default to `false` and **should not be written in code unless overriding**:

| Property | Default | Override to `true` when... |
|----------|---------|----------------------------|
| `inherit` | `false` | Policy should apply to child tables in hierarchy |

### When Policies Apply

- **No conditions (omitted)**: Policy applies to ALL records on the table
- **With conditions**: Policy applies only when conditions evaluate to true
- **`reverseIfFalse: true` (default)**: Rules invert when conditions are false (mandatory → optional, read-only → editable)
- **`reverseIfFalse: false`**: No action when conditions are false — rules only apply when conditions match
- **Competing policies**: When multiple policies match the same record and target the same field, `mandatory: true` from any matching policy overrides `mandatory: false` from another. To release a mandatory constraint for a specific case, tighten the condition on the mandatory policy itself.

### Table Inheritance

- **`inherit: false`** (default): Policy applies only to the specified table
- **`inherit: true`**: Policy applies to all tables that extend the specified table (e.g., `task` → `incident`, `problem`, `change`)

Use inheritance when child tables should follow the same rules as their parent without defining the policy separately on each.

### Condition Syntax

**AND operator (`^`)** -- all conditions must be true:

```
conditions: "priority=1^state=2"
```

**OR operator (`^OR`)** -- at least one must be true:

```
conditions: "priority=1^ORpriority=2"
```

**Common operators:** `=`, `!=`, `>`, `>=`, `<`, `<=`, `IN`, `LIKE`, `NOT LIKE`, `STARTSWITH`, `ANYTHING`, `EMPTYSTRING`, `SAMEAS`, `NSAMEAS`, `BETWEEN`

**Choice field patterns:**

| Pattern | Condition Example |
|---------|-------------------|
| Specific value | `category=hardware` |
| Multiple values (OR) | `categoryINhardware,software` |
| Not a value | `category!=hardware` |
| Any value selected | `category!=NULL` |
| No value selected | `category=NULL` |

### Rules Configuration

Basic rule syntax for field enforcement:

```typescript
rules: {
  field_name: {
    $id: Now.ID['policy_name_field_name_rule'],
    mandatory?: boolean | 'ignore',  // Default: 'ignore'
    readOnly?: boolean | 'ignore'    // Default: 'ignore'
  }
}
```

- **$id** (required): Unique identifier for the rule record
- **mandatory** (optional): `true` = required, `false` = optional, `'ignore'` = no change
- **readOnly** (optional): `true` = locked, `false` = editable, `'ignore'` = no change

**Important:**
- Each field can have only one rule per policy
- Cannot set both `mandatory: true` and `readOnly: true` on the same field
- Omit properties set to `'ignore'` for cleaner code

### Cross-Table Enforcement

Use dot-walk notation in rule keys to enforce rules on fields in referenced tables:

```typescript
rules: {
  'caller_id.email': { $id: Now.ID['policy_caller_email_rule'], mandatory: true },
  'assigned_to.department': { $id: Now.ID['policy_assignee_dept_rule'], readOnly: true }
}
```

**Technical note:** While a `table` property exists in `DataPolicyRuleConfig` for specifying which table a rule applies to, **dot-walk notation is the recommended approach** for cross-table enforcement. The `table` property is primarily used by the plugin when transforming from ServiceNow XML and for internal scope validation. For new code, always use dot-walk notation in rule keys (e.g., `'employee.department'`) — it is clearer and more maintainable. Never use the `table` property inside rules for cross-table enforcement.

## API Reference

See the `datapolicy-api` topic for the full property reference.

## Examples

### Conditional Mandatory -- Require Assignment for High Priority Records

Make fields mandatory for high-priority records. `reverseIfFalse` defaults to `true` — fields become optional automatically when conditions are not met.

```typescript fluent
import { DataPolicy } from '@servicenow/sdk/core'

export const highPriorityPolicy = DataPolicy({
    $id: Now.ID['high_priority_incident_policy'],
    table: 'incident',
    shortDescription: 'Require assignment for high priority incidents',
    conditions: 'priority=1^ORpriority=2',
    rules: {
        assigned_to: {
            $id: Now.ID['high_priority_incident_policy_assigned_to_rule'],
            mandatory: true,
        },
        assignment_group: {
            $id: Now.ID['high_priority_incident_policy_assignment_group_rule'],
            mandatory: true,
        },
    },
})
```

### Conditional Read-Only -- Lock Fields After Record Closure

Lock resolution fields once an incident is resolved or closed.

```typescript fluent
import { DataPolicy } from '@servicenow/sdk/core'

export const closedIncidentProtection = DataPolicy({
    $id: Now.ID['closed_incident_protection'],
    table: 'incident',
    shortDescription: 'Protect closed incident fields from modification',
    conditions: 'state=6^ORstate=7',
    rules: {
        close_code: { $id: Now.ID['closed_incident_protection_close_code_rule'], readOnly: true },
        close_notes: { $id: Now.ID['closed_incident_protection_close_notes_rule'], readOnly: true },
        resolved_at: { $id: Now.ID['closed_incident_protection_resolved_at_rule'], readOnly: true },
        resolved_by: { $id: Now.ID['closed_incident_protection_resolved_by_rule'], readOnly: true },
    },
})
```

### Unconditional Mandatory -- Validate All Records Including Imports and APIs

Ensure records arriving from external systems have required fields. Omitting `conditions` applies the policy to all records. `applyToImportSets` and `applyToSOAP` default to `true` — no extra config needed.

```typescript fluent
import { DataPolicy } from '@servicenow/sdk/core'

export const userImportValidation = DataPolicy({
    $id: Now.ID['user_import_validation'],
    table: 'sys_user',
    shortDescription: 'Validate user imports and API calls',
    rules: {
        user_name: { $id: Now.ID['user_import_validation_user_name_rule'], mandatory: true },
        email: { $id: Now.ID['user_import_validation_email_rule'], mandatory: true },
        first_name: { $id: Now.ID['user_import_validation_first_name_rule'], mandatory: true },
        last_name: { $id: Now.ID['user_import_validation_last_name_rule'], mandatory: true },
    },
})
```

### Mixed Rules -- Different Enforcement per Field in One Policy

Apply different rule types to different fields in one policy.

```typescript fluent
import { DataPolicy } from '@servicenow/sdk/core'

export const comprehensivePolicy = DataPolicy({
    $id: Now.ID['comprehensive_policy'],
    table: 'incident',
    shortDescription: 'Comprehensive field enforcement for critical incidents',
    conditions: 'priority=1^urgency=1',
    rules: {
        short_description: { $id: Now.ID['comprehensive_policy_short_description_rule'], mandatory: true },
        state: { $id: Now.ID['comprehensive_policy_state_rule'], readOnly: true },
        priority: { $id: Now.ID['comprehensive_policy_priority_rule'], mandatory: true, readOnly: false },
        category: { $id: Now.ID['comprehensive_policy_category_rule'], readOnly: true },
        subcategory: { $id: Now.ID['comprehensive_policy_subcategory_rule'], mandatory: true },
    },
})
```

### Inherited Policy -- Apply Once, Enforce Across a Table Hierarchy

Apply a policy to a parent table so all child tables automatically inherit it.

```typescript fluent
import { DataPolicy } from '@servicenow/sdk/core'

export const taskAssignmentPolicy = DataPolicy({
    $id: Now.ID['task_assignment_policy'],
    table: 'task',
    shortDescription: 'Require assignment for in-progress tasks',
    description: 'Applies to all tables extending task (incident, problem, change, etc.)',
    inherit: true,
    conditions: 'state=2',
    rules: {
        assigned_to: { $id: Now.ID['task_assignment_policy_assigned_to_rule'], mandatory: true },
        assignment_group: { $id: Now.ID['task_assignment_policy_assignment_group_rule'], mandatory: true },
    },
})
```

### Cross-Table -- Enforce Rules on Referenced Fields via Dot-Walk

Enforce rules on fields in referenced tables using dot-walk notation as rule keys.

```typescript fluent
import { DataPolicy } from '@servicenow/sdk/core'

export const incidentReferenceValidation = DataPolicy({
    $id: Now.ID['incident_reference_validation'],
    table: 'incident',
    shortDescription: 'Enforce caller contact information for high priority incidents',
    conditions: 'priority=1^urgency=1',
    rules: {
        'caller_id.email': { $id: Now.ID['incident_reference_validation_caller_id_email_rule'], mandatory: true },
        'caller_id.phone': { $id: Now.ID['incident_reference_validation_caller_id_phone_rule'], mandatory: true },
        'assigned_to.department': { $id: Now.ID['incident_reference_validation_assigned_to_department_rule'], readOnly: true },
        'assignment_group.manager': { $id: Now.ID['incident_reference_validation_assignment_group_manager_rule'], mandatory: true },
    },
})
```

### Multi-Condition -- Strict Validation Using IN and AND Operators

Combine multiple condition operators to enforce strict requirements for a specific record type and state. The `IN` operator matches multiple states, `^` joins conditions with AND.

```typescript fluent
import { DataPolicy } from '@servicenow/sdk/core'

export const emergencyChangeValidation = DataPolicy({
    $id: Now.ID['emergency_change_validation'],
    table: 'change_request',
    shortDescription: 'Strict validation for emergency changes',
    description: 'Emergency changes require CAB approval, detailed justification, and rollback plan',
    conditions: 'type=emergency^stateIN1,2,3',
    rules: {
        cab_delegate: { $id: Now.ID['emergency_change_validation_cab_delegate_rule'], mandatory: true },
        justification: { $id: Now.ID['emergency_change_validation_justification_rule'], mandatory: true },
        backout_plan: { $id: Now.ID['emergency_change_validation_backout_plan_rule'], mandatory: true },
        risk_impact_analysis: { $id: Now.ID['emergency_change_validation_risk_impact_analysis_rule'], mandatory: true },
        emergency_contact: { $id: Now.ID['emergency_change_validation_emergency_contact_rule'], mandatory: true },
    },
})
```

### Layered Validation -- Data Policy for Security, UI Policy for UX

Use Data Policy for server-side security and UI Policy for client-side UX — together for complete coverage.

```typescript fluent
import { DataPolicy, UiPolicy } from '@servicenow/sdk/core'

// Data Policy: server-side enforcement (cannot be bypassed)
export const contractDataPolicy = DataPolicy({
    $id: Now.ID['contract_approval_data_policy'],
    table: 'ast_contract',
    shortDescription: 'Enforce contract approval requirements',
    conditions: 'value>100000',
    rules: {
        vendor: { $id: Now.ID['contract_approval_data_policy_vendor_rule'], mandatory: true },
        approver: { $id: Now.ID['contract_approval_data_policy_approver_rule'], mandatory: true },
        legal_review: { $id: Now.ID['contract_approval_data_policy_legal_review_rule'], mandatory: true },
    },
})

// UI Policy: client-side visibility and messages
export const contractUiPolicy = UiPolicy({
    $id: Now.ID['contract_approval_ui_policy'],
    table: 'ast_contract',
    shortDescription: 'Show approval section for high-value contracts',
    onLoad: true,
    conditions: 'value>100000',
    actions: [
        { field: 'approval_section', visible: true },
        { field: 'approval_notes', visible: true, mandatory: true },
        { field: 'vendor', fieldMessage: 'Vendor approval required for contracts over $100,000', fieldMessageType: 'warning' },
    ],
})
```

## Avoidance

- **Never include default value properties in code** — do NOT write `active: true`, `applyToImportSets: true`, `applyToSOAP: true`, `useAsUiPolicyOnClient: true`, `reverseIfFalse: true`, `inherit: false`, or `conditions: ''`
- **Never set both `mandatory: true` and `readOnly: true` on the same field** — mutually exclusive; causes a build error
- **Never use display labels in conditions** — always use stored values for choice fields (right-click field → Show Choice List → "Value" column)
- **Never make fields mandatory for out-of-scope tables** — the mandatory flag is silently disabled; use a Business Rule as an alternative (see the `business-rule-guide` topic)
- **Never use a `table` property inside rules for cross-table enforcement** — use dot-walk notation as the rule key (`'employee.department'`) instead
- **Never create conflicting Data Policies** — overlapping conditions with opposing rules cause unpredictable results; most restrictive wins
- **Never use Data Policies for visibility control** — use UI Policies instead (see the `ui-policy-guide` topic)
- **Never rely on UI Policies alone for critical validation** — they can be bypassed via API, imports, or browser manipulation (see the `ui-policy-guide` topic)
- **Never create policies with empty conditions unintentionally** — omitting `conditions` applies the policy to ALL records on the table; be deliberate
