Skip to content

Policy Hierarchy ​

The Policy Engine resolves every policy value through a 5-level hierarchy. This page explains how the hierarchy works, when each level is appropriate, and how conflicts are resolved.

The 5 levels ​

Global  -->  Entitlement Class  -->  Organization  -->  Product  -->  Individual Entitlement
LevelWhere it is configuredTypical use case
GlobalPlugin settings in WP AdminCompany-wide defaults for all entitlements
Entitlement ClassPer-class settings (PLG, ENV, SVC, ORD, AFL, EDU)Rules that differ by type of product/service
OrganizationOrganization metadata in WP AdminCustomer-specific exceptions
ProductProduct YAML policies: blockRules tied to a specific WooCommerce product
Individual EntitlementEntitlement metadata in WP AdminOne-off exceptions for a single entitlement record

How override works: most specific wins ​

The engine resolves each policy field independently by walking from the most specific level up to the least specific:

  1. Check the individual entitlement -- if a value is set, use it.
  2. If not set, check the product -- if a value is set, use it.
  3. If not set, check the organization -- if a value is set, use it.
  4. If not set, check the entitlement class -- if a value is set, use it.
  5. If not set, check the global setting -- if a value is set, use it.
  6. If nothing is set anywhere, use the hardcoded default from the policy schema.

Key rule: no merging ​

Values are never merged across levels. The winning level provides the entire value for that field. If the global setting defines expiry_warning_days: [30, 7, 1] and an organization overrides it to [60, 30], the organization gets [60, 30] -- not [60, 30, 7, 1]. The override replaces; it does not combine.

Examples ​

Example 1: Grace period override ​

Scenario: The global Payment Recovery Policy sets suspended_to_cancelled_days: 30. Organization "Acme Corp" needs 60 days because they have slow internal procurement.

Global:              suspended_to_cancelled_days = 30
Class PLG:           suspended_to_cancelled_days = 14
Organization Acme:   suspended_to_cancelled_days = 60
Product:             (not set -- inherits from organization)
Entitlement PLG-001: (not set -- inherits from organization)

Result for PLG-001 (owned by Acme): 60 days. The organization override (60) is more specific than the class override (14).

Result for PLG-002 (owned by Beta Corp, no org override): 14 days. Beta Corp has no override, so the class value (14) applies.

Result for SVC-003 (owned by Beta Corp): 30 days. No org override, no class override for SVC on this field, so the global value (30) applies.

Example 2: Credit expiration for an enterprise deal ​

Scenario: Credits expire in 12 months globally. An enterprise organization negotiated 24-month expiration.

Global:              expiration_months = 12
Class SVC:           expiration_months = 12  (explicit, same as global)
Organization X:      expiration_months = 24
Product SVC-HOST:    (not set)
Entitlement SVC-001: (not set)

Result for SVC-001 (owned by Org X): 24 months. The organization override wins.

Example 3: Tier change cooldown for a specific product ​

Scenario: Globally there is no cooldown. The SVC class sets 30 days. A specific hosting product needs 60 days. An enterprise contract negotiated 90 days on one particular entitlement.

Global:              cooldown_days = 0
Class SVC:           cooldown_days = 30
Organization Z:      (not set)
Product SVC-HOST:    cooldown_days = 60
Entitlement SVC-005: cooldown_days = 90

Result for SVC-005: 90 days. The entitlement-level value is the most specific.

Result for SVC-006 (same product, different entitlement, no entitlement override): 60 days. Falls through to the product level.

Result for SVC-007 (different SVC product, no product or entitlement override): 30 days. Falls through to the class level.

When to use each level ​

Global ​

Use for company-wide defaults that apply to all entitlement types. This is where you set your baseline -- the rules that apply unless something more specific overrides them.

Good for: Renewal grace period, notification channels, refund window, data retention period.

Entitlement Class ​

Use when a category of products needs different rules. Plugins are self-serve and low-touch. Services are high-touch with manual provisioning. Environments need deprovisioning on cancel. These differences are structural, not customer-specific.

Good for: SLA tiers, provisioning mode (auto vs manual), trial availability, refund generosity.

Organization ​

Use for customer-specific exceptions. An enterprise customer with a negotiated contract might get longer grace periods, extended credit expiration, or a higher SLA tier. A customer with a history of late payments might get stricter payment recovery.

Good for: Negotiated terms, customer-specific SLA, payment recovery adjustments, credit expiration extensions.

Product ​

Use when a specific WooCommerce product has different rules from others in the same class. A premium hosting plan might have a higher uptime target than the basic plan, even though both are ENV class.

Good for: Product-specific SLA targets, cooldown periods, trial duration, refund terms.

Individual Entitlement ​

Use sparingly, for one-off exceptions. A specific entitlement that was part of a special deal, a migration edge case, or a customer escalation resolution.

Good for: Contractual exceptions, migration accommodations, escalation resolutions. Should be rare.

Visualizing the cascade ​

In this diagram, three entitlements resolve the same field (suspended_to_cancelled_days) to three different values based on where overrides are set.

Common mistakes ​

Setting everything at the entitlement level. If you find yourself overriding the same value on dozens of individual entitlements, you probably need a class-level or organization-level override instead.

Forgetting that overrides replace, not merge. If you set notification.events at the organization level, you must include ALL events you want -- not just the additions. The global list is fully replaced.

Overriding at the wrong level. Customer-specific terms belong at the organization level, not the product level. Product-level overrides affect ALL organizations that buy that product.

Next steps ​