Business Rules in Dataverse let you enforce data quality and control form behavior without writing a single line of code—but scope, condition logic, and action types have sharp edges that trip up even experienced makers. This lesson goes deep on every mechanism so you can build rules that actually work in production.

Picture this: your sales team has been entering opportunities in your CRM for three months. You pull a pipeline report and discover that a third of the records have no close date, half have a status of "Active" but no assigned owner, and several high-value deals are marked as "Won" without a revenue figure. Your Dataverse data model is perfectly designed—the relationships are clean, the columns are well-typed—but the data inside is chaos.
You could write a plugin in C#. You could orchestrate a Power Automate flow that fires on every save. Or you could open the Business Rules designer, spend twenty minutes clicking through a visual canvas, and have validation logic running server-side before lunch. That's the promise of Dataverse Business Rules: they put meaningful data enforcement in the hands of people who understand the business, without requiring a developer to be in the loop.
But Business Rules are more nuanced than a simple "required field" toggle. They have a scope model that determines where they run, a condition engine that can express surprisingly complex logic, and a set of actions that go well beyond just showing error messages. Done right, they are a foundational layer of data integrity for any serious model-driven application. Done wrong, they create subtle bugs, silent failures, and user experience problems that are infuriating to diagnose. By the end of this lesson, you'll know the difference.
What you'll learn:
You should be comfortable with the fundamentals of Dataverse tables, columns, and row structure before diving in. If you need a refresher, Dataverse Fundamentals: Tables, Columns, and Rows Explained for Power Apps Makers covers the conceptual foundation. You should also have at least built a basic model-driven app and understand how forms work—Designing Model-Driven Forms: Sections, Tabs, Subgrids, and Quick View Forms is the right primer there. Familiarity with column types (text, choice, lookup, currency, date) is assumed throughout.
Before we build anything, let's be precise about what Business Rules are at an architectural level, because the documentation is often vague in ways that cause real confusion.
A Business Rule is a metadata-driven automation artifact that lives on a Dataverse table. It consists of a condition (which can be compound) and one or more actions. When the rule fires, it evaluates the condition and executes the actions on the matching branch.
Critically, Business Rules are not code. They are serialized as metadata in the solution layer and interpreted at runtime by the platform. This has two important consequences:
Business Rules are authored in the classic Power Apps maker experience at make.powerapps.com, navigating to your table and selecting the "Business Rules" tab, or directly from the form editor. The visual designer looks like a flowchart canvas where you drag conditions and actions onto lanes.
Note
Business Rules authored in the new modern form designer experience are the same underlying artifacts—there's just a different entry point. The designer canvas itself hasn't changed substantially in years, which means some of the UI feels dated, but the underlying power is real.
Every Business Rule has a scope setting. Get this wrong and your rule either doesn't run where you expect it to, or runs in places that cause unintended side effects. There are two scope options:
When you set a Business Rule to Entity scope, it runs server-side on the Dataverse platform itself. This means the rule fires any time a record is created or updated—regardless of what triggered that operation. An import via the Data Connector, a bulk update from a Power Automate cloud flow, an API call from a third-party integration, a save from a model-driven app form, or a save from a canvas app—all of these trigger Entity-scoped rules.
The trade-off is that Entity scope has a significantly restricted action set. Specifically, you cannot:
Those are UI-level actions. Entity scope can only:
This makes intuitive sense once you think about it. If a flow triggers your rule, there is no form on screen. There are no fields to show or hide. Only data-level actions apply.
When you scope a Business Rule to a specific form (or the legacy "All Forms" option), it runs client-side, inside the model-driven app form. This unlocks the full action palette—show/hide fields, lock fields, set required/optional, plus set values and show errors.
The critical limitation: form-scoped rules only fire when a user interacts with that form. Bulk imports, API updates, and flow-triggered operations bypass them entirely.
Key insight
This distinction determines whether your business rule is a data integrity mechanism or a user experience mechanism. Entity scope enforces data integrity at the platform level. Form scope creates a better user experience on forms. Most serious implementations need both layers—Business Rules for UX, plus a plugin or server-side validation for true data integrity.
Here's a concrete example of why this matters. Suppose you have a Business Rule that sets a field named estimated_revenue as required when opportunity_status equals "Proposal Sent." If that rule is Form-scoped only, a Power Automate flow can create an Opportunity record with status "Proposal Sent" and no revenue figure, and the Business Rule will never fire. The data enters Dataverse unchallenged.
If the same rule is Entity-scoped, it runs during the save transaction on the server, and the flow will receive an error response preventing the bad record from being created.
Follow this decision logic:
The most common mistake I see in production environments is developers who write Form-scoped rules with error message actions and assume they've enforced data integrity. They haven't. They've improved UX. The data can still be compromised by any non-form interaction.
Let's walk through the components of the designer before building anything.
The condition is where you define when the rule fires. You evaluate column values using comparison operators. Available operators vary by column type:
You can compare a column to:
Comparing two columns lets you build rules like "if actual_close_date is earlier than scheduled_close_date, show an error." That's a genuinely useful cross-field validation that requires zero code.
A single condition block can contain multiple rows connected by AND or OR operators. However, the condition editor has a limitation: all rows in a single block share the same logical operator. You cannot mix AND and OR within one condition block without using the branching structure.
To express (Status = "Won" OR Status = "Lost") AND Revenue > 0, you need to think in terms of the condition tree. The designer allows you to add multiple condition components and chain them with AND or OR at the block level. For complex compound logic, you sometimes need to use the branching feature—where a True branch and False branch each have their own condition—to achieve equivalent behavior.
Tip
When you need (A OR B) AND C logic, the cleanest approach is often to reverse it: create a condition that's NOT (A OR B) or break the rule into two separate rules that work together. Business Rules aren't a full programming language—they're a structured decision tree—so approaching them with that mental model saves frustration.
This is the vocabulary of what your rule can do. Let's go through each action type with context on when it's the right tool.
Show/Hide a field
Controls whether a field appears on the form. Hidden fields are not deleted—their data persists in the record. This is commonly used for conditional fields: "Show the cancellation_reason field only when status is 'Cancelled'." Use this to keep your form clean and prevent cognitive overload for users who never need to see certain fields.
Lock/Unlock a field
Makes a field read-only (locked) or editable (unlocked). This is excellent for calculated or system-managed fields you want users to see but not override. For example, locking a priority_score field once a record reaches "Active" status prevents accidental overwrites of a value that a flow or plugin manages.
Set field as Business Required / Not Business Required / Optional This is distinct from the Required setting in the column definition. "Business Required" adds a mandatory marker to the field at the form level and prevents saving if the field is empty. "Optional" removes that requirement. This is the mechanism for conditional required fields, which are one of the most requested data quality features in any CRM or case management scenario.
Note the distinction between "Business Required" (set by Business Rule) and the Required constraint you can set on the column definition itself. The column-level required setting is a permanent, unconditional constraint that runs at the data layer. The Business Rule version is conditional and form-level.
Set a field's value
Programmatically sets the value of a column when the condition is met. This can default fields, clear fields (by setting to a blank/null value), copy a value from another field, or set a fixed value. This is where you implement "auto-populate" logic: when customer_type is set to "Enterprise," automatically set support_tier to "Premium."
Show an error message Displays a validation message attached to a specific field. At Form scope, this shows the message inline on the form and prevents saving. At Entity scope, the error surfaces as an HTTP 400 from the platform API, which means integrations calling Dataverse will see an exception they need to handle. The error message text is a static string—you cannot dynamically compose a message with field values in it.
Warning
The error message action in Entity scope behaves differently than you might expect. It does not return a friendly message to the end user through a model-driven app form—it returns a raw platform error. Test this behavior explicitly with your Entity-scoped validation rules before you ship them. Users seeing generic error dialogs is a poor experience.
Let's build something worth building. We're working with an Opportunity table in a sales CRM context. The requirement is:
When an Opportunity's Status is "Won" or "Lost," the
actual_revenueandclose_datefields must be populated, and thestagefield should be locked to prevent further editing.
This is a three-action rule with a compound condition. Here's how to build it step by step.
Step 1: Navigate to the Business Rules designer
In make.powerapps.com, select your environment, go to Dataverse > Tables, open your Opportunity table, and click the "Business Rules" tab. Click "New Business Rule."
The designer opens with a blank canvas showing a START node connected to a CONDITION node connected to an ACTION node, with a final SAVE block.
Step 2: Name your rule
Click the rule name at the top (defaults to "Business Rule") and rename it to something descriptive: Validate Closed Opportunity Fields. Good naming matters here because you may have dozens of Business Rules on a table in a mature solution.
Step 3: Configure the condition
Click the CONDITION block. In the properties panel, add your first rule row:
StatusClick "Add" to add a second condition row, but this time change the logical operator (the AND/OR dropdown) to OR:
StatusYou now have: Status = "Won" OR Status = "Lost"
Step 4: Add actions on the True branch
The True branch is what executes when your condition is satisfied. Click the "+" button on the True branch (below the condition's YES path) to add an action.
Add three actions:
actual_revenueThis is where many people get stuck. You can't nest a full condition inside an action. The correct approach is to add a nested condition by using the condition-action branching in sequence. Here's the pattern:
After the outer condition (Status = Won OR Lost), add a second CONDITION block on the True branch:
actual_revenueIf that condition is True (revenue is blank), then show the error message. If False (revenue exists), proceed.
Chain another condition block: close_date Does not contain data → show error "Close Date is required for closed opportunities."
Add a "Set Business Required" or "Lock" action for the stage field. This one doesn't need a nested condition—we always want to lock Stage when the opportunity is Won or Lost, regardless of other field values.
Step 5: Set scope
Because we want this rule to fire both on the form and server-side, we need two rules. For the lock action (which is UI-only), keep this rule Form-scoped. Create a second Business Rule at Entity scope for just the error message validation actions.
Step 6: Activate the rule
Business Rules are inactive by default. Click the Activate button in the command bar. The rule is now live for any form that includes this table.
Tip
A newly activated Business Rule applies to forms automatically—you don't need to republish forms. However, if you're authoring rules from within the form designer, activating from there publishes the form at the same time.
The conditional required field pattern is the single most-requested Business Rule use case in enterprise apps. It's worth treating it with depth.
The scenario: you have a support_case table. When case_type equals "Hardware," the field asset_serial_number should be required. When case_type is anything else, it's optional.
The naive approach: one rule that sets asset_serial_number to Business Required when case_type = "Hardware."
The problem with this approach: what happens when a user sets case_type to "Hardware," sees the required field, and then changes case_type to "Software"? The field remains marked as required unless you have a corresponding rule that sets it back to optional.
You must always have a symmetric rule. For every "set required" action, have a corresponding "set optional" action on the opposing condition branch (or in a second rule). Here's the complete pattern:
Rule: Conditionally Require Serial Number
Condition: case_type Equals "Hardware"
asset_serial_number as Business Requiredasset_serial_number as OptionalNotice we're using both the True AND False branches of the same rule. This is cleaner than two separate rules because it guarantees the symmetric behavior is always in sync. If you use two separate rules that reference each other's conditions, there's a risk they get out of sync during deployments.
Warning
If you use the False branch action to set a field to "Optional" and that field also has its column-level Required property set to true, the Business Rule will appear to do nothing. The column-level Required constraint cannot be overridden by a Business Rule. Only Business Rule-level required status can be controlled by Business Rules.
Sometimes you have multiple conditions that each independently require a field. For example:
asset_serial_number is required when case_type = "Hardware"asset_serial_number is also required when priority = "Critical"In this case, you need to be careful. If you write two separate rules each toggling required/optional, they'll conflict: the second rule's False branch might set the field to Optional even when the first rule's condition is still True.
The clean solution: consolidate into a single rule with compound OR conditions:
Condition: case_type Equals "Hardware" OR priority Equals "Critical"
asset_serial_number as Business Requiredasset_serial_number as OptionalNow the field is required when either condition is met, and optional only when neither is.
The "Set field value" action is where Business Rules start to feel like low-code automation rather than just validation. You can use it to:
Let's look at a realistic pattern: a service management application where setting priority to "Critical" should automatically set response_sla_hours to 1, "High" to 4, "Medium" to 8, and "Low" to 24.
This requires four separate rules (one per priority level), because each rule can only evaluate a single condition value at a time in a clean way. Or, more elegantly, you use the branching structure within one rule:
Rule: Default SLA Hours by Priority
Condition 1: priority Equals "Critical"
response_sla_hours = 1On the False branch, add another CONDITION:
Condition 2: priority Equals "High"
response_sla_hours = 4Continue this chain. This creates a waterfall evaluation pattern—each False branch leads to the next condition. It's effectively an IF / ELSE IF / ELSE IF chain.
Key insight
This cascade pattern works well up to about 4-5 branches. Beyond that, the visual canvas becomes difficult to navigate and maintain. At that complexity threshold, consider whether a calculated column, a Power Automate flow, or a real-time plugin would be more maintainable.
When setting a field's value, instead of typing a static value, you can choose "Field" as the source type and pick another column on the same record. This enables things like:
initial_estimate to baseline_estimate when a project is approvedactual_start_date to the value of scheduled_start_date when both fields are in play during record creationThis is a frequently overlooked feature. Most people use Business Rules exclusively with static values, but field-to-field copying opens up genuinely useful automation that runs without any cloud flow overhead.
Business Rules don't exist in isolation. In a production Dataverse environment, you typically have several automation layers operating simultaneously:
Understanding the execution order and interaction between these layers prevents a whole category of bugs.
When a user saves a form in a model-driven app:
The practical implication: if an Entity-scoped Business Rule blocks a save, Power Automate flows never trigger. This is the correct behavior—you want validation to happen before downstream automation fires.
Note
Entity-scoped Business Rules and plugins can conflict if both attempt to set the same field value. The plugin's value will generally win because plugins have full access to the execution context and can write values after the Business Rule has run. Design your automation layers so they don't compete over the same fields.
Business Rules break down in several scenarios:
Cross-record validation: Business Rules can only see the current record's fields. If you need to validate that a field value is unique across all records, or that a parent record is in a valid state before allowing a child record to be created, you need a plugin.
Complex calculated values: If a field's value depends on aggregating related records, a calculated column (for simple math) or a rollup column is more appropriate. Business Rules with set-value actions fire on user interaction, not on schedule—they can't recalculate a value that changes based on related record updates.
Dynamic error messages: Business Rules only support static error strings. If your validation message needs to include field values ("Close Date must be after " + today's date formatted as a string), you need a plugin.
Validation in canvas apps: Business Rules at Entity scope do fire for canvas app saves—but only if the canvas app writes directly to Dataverse via its native connector. If your canvas app uses the Patch function or a collection and then saves to Dataverse, the Entity-scoped Business Rules will run. But if you want form-level UX validation in a canvas app, you'll need to implement that yourself using Power Apps formula logic—Power Apps Data Validation: Using If, IsBlank, and IsMatch to Prevent Bad Data in Forms covers that pattern in depth.
If you're building model-driven forms with significant Business Rule logic, the way you design the form itself matters. Business Rules reference columns by their logical name. If a column isn't present on the form, a Business Rule that references it still evaluates the condition—it just can't perform UI-level actions on a field that doesn't exist on the form.
This creates a subtle bug pattern: you add a Business Rule that shows/hides Column A based on Column B, but Column A isn't on the form you're testing. The rule appears to do nothing, and you assume it's broken. It isn't—it just has nothing to act on.
The design principle: if a Business Rule needs to show, hide, lock, or mark a field as required, that field must be on the form. If the field shouldn't normally be visible by default, add it to the form with its default visibility set to Hidden, and let the Business Rule control visibility.
The Designing Model-Driven Forms: Sections, Tabs, Subgrids, and Quick View Forms lesson covers how to set default visibility on sections and fields—coordinating that with your Business Rules is an important design step.
Tip
Create a dedicated "Hidden Fields" section on your form—set the section itself to not visible, and place fields there that are referenced by Business Rules but don't need to be visible by default. This keeps your form layout clean while still giving Business Rules something to act on.
If your table has multiple forms (for example, a Quick Create form, a main form, and a mobile-optimized form), remember that:
When you scope a rule to "All Forms," it runs on every form that contains the referenced fields. This is convenient but requires that the fields referenced actually exist on all relevant forms.
For enterprise solutions with multiple forms used by different roles, I recommend scoping rules to specific forms rather than "All Forms." This gives you precise control and avoids unexpected behavior when forms diverge in structure over time. This pairs well with role-based security considerations—different security roles see different forms, so having form-specific Business Rules means the logic matches the user's context.
A common UX issue: a user sets a choice column (let's say region), which causes a dependent field (territory_manager) to auto-populate via a Business Rule. The user then changes region. The Business Rule auto-populates territory_manager again with the new value—but what if the new value evaluates to a branch that doesn't set territory_manager? The old value lingers.
The solution: add a "clear on change" rule. Create a Business Rule that fires when region contains data (always true when region is set) and clears territory_manager first, before the subsequent auto-populate rules set the appropriate value.
The ordering matters here. Business Rules with the same trigger condition fire in a deterministic order—generally the order they were created or activated. You can control execution order by activating rules in the sequence you want them to run. Document this in your solution notes, because it's not obvious from the designer.
A powerful governance pattern: once an opportunity reaches "Closed Won," lock all the key commercial fields so the data becomes a reliable historical record. Business Rules can't lock an entire form, but they can lock individual fields.
Create a Business Rule at Form scope:
Condition: status Equals "Closed Won" OR status Equals "Closed Lost"
Actions (True branch):
estimated_revenueclose_dateownerstagecompetitorThis pattern works well for audit compliance—you can demonstrate to auditors that closed records cannot be silently edited through the standard form interface.
Warning
Locking fields via Business Rule only prevents editing through the model-driven form. An administrator with appropriate privileges can still update the record via the API, a flow, or by exporting and re-importing data. For genuinely tamper-evident records, you need a combination of Business Rules (for the form UX), field-level security (from Power Apps Security: Roles, Sharing, and Data Permissions), and possibly an audit log strategy.
Business Rules are a lightweight alternative to Business Process Flows for guiding data entry on complex forms. Rather than a staged BPF with mandatory stages, you can use Business Rules to progressively reveal sections of the form:
record_type is set: reveal Section 2 (type-specific fields)This creates a guided experience without the overhead of a Business Process Flow. It's less prescriptive than a BPF—users can still scroll down and fill things out of order if they want—but it reduces cognitive load for new users.
The limitation is that Business Rules evaluate field values, not "section completion." You'll be checking individual field values as proxies for "this section is complete."
For any serious deployment, you're managing Business Rules as part of a well-designed Dataverse data model that moves through dev/test/production environments via solutions.
Business Rules are solution components. They're included when you add the table to a solution, and exported/imported as metadata. A few considerations:
Activation state travels with the rule. When you export a solution with an active Business Rule and import it to another environment, the rule imports in its active state. For managed solutions (the standard deployment pattern), this is usually what you want. Be aware that importing an active rule immediately affects that environment's behavior.
Naming matters for conflict resolution. If two solutions include Business Rules on the same table, conflicts are resolved by the solution layering order (active layer wins). Use clear, namespaced naming for Business Rules in multi-team solutions.
Deactivating doesn't delete. If you need to remove a Business Rule from an environment, deactivating it is not enough for managed solutions—you need to remove the component from the solution and deploy the solution with the rule removed. Deactivating only prevents it from running; the metadata remains.
Let's put this into practice with a scenario you can build in any Dataverse environment with a standard Account table.
The Scenario: Accounts at your company have a customer_tier choice column with values: Bronze, Silver, Gold, Platinum. Company policy states:
credit_limit greater than 100,000 (you can approximate this with an error message rule—Business Rules can't do greater-than validation, so we'll use a creative workaround)dedicated_rep lookup field populatedcredit_limit field hidden (it doesn't apply to them)customer_tier changes, the tier_effective_date field should auto-populate with today's date... except Business Rules can't access "today's date" dynamically. Use a workaround: show the tier_effective_date field and mark it as required when tier is set.Actually working through constraint #1 will teach you something important: Business Rules can't do numeric greater-than validation against a threshold in combination with another condition. You'd need to show an error message like "Platinum accounts require a Credit Limit. Please enter a value before saving"—the specific threshold validation would require a plugin. This is a good opportunity to recognize where Business Rules end and where other tools begin.
Build:
Create a Business Rule on the Account table named BR - Tier Field Visibility
customer_tier Equals "Bronze"credit_limitcredit_limitCreate a Business Rule named BR - Require Dedicated Rep for Gold Plus
customer_tier Equals "Gold" OR customer_tier Equals "Platinum"dedicated_rep as Business Requireddedicated_rep as OptionalCreate a Business Rule named BR - Require Tier Date on Tier Set
customer_tier Contains datatier_effective_date as Business Required; Show tier_effective_datetier_effective_date as Optional; Hide tier_effective_dateActivate all three rules. Test on the Account form, then also test by updating a record via a Power Automate instant flow and observe that Rule #2 fires but Rules #1 and #3 do not.
Check in this order:
Form-scoped error messages should prevent saving. If the record saves anyway, check whether you're working in an editable grid or a subgrid—Business Rules don't run inside editable grids consistently across all versions of the platform. Also check whether the rule is scoped to the main form but you're viewing it from a quick create form.
Expected behavior if your rule is Form-scoped. Switch the rule to Entity scope for data-layer enforcement. Remember that Entity scope can't mark fields as required (it can only show errors), so restructure the action to "show error if field does not contain data" rather than "set as business required."
Business Rules at the same scope level run in the order they were last activated. Deactivate and reactivate them in the order you want them to execute. Document this order explicitly—it's not visible in the UI.
Your Entity-scoped Business Rule is throwing a validation error that the flow isn't handling gracefully. Add a "Configure run after" error handling block in your flow to catch the error and log or surface it meaningfully. The error object from Dataverse will include the Business Rule's error message text in the error body.
Confirmed limitation. Use "Contains data / Does not contain data" for lookup fields in Business Rule conditions. For specific-record comparisons, use a plugin or a real-time flow with additional logic.
Tip
When debugging Form-scoped Business Rules in a model-driven app, open the browser's developer tools and watch the console for errors or rule evaluation messages. The platform logs Business Rule evaluations in verbose mode. You can also temporarily add a visible notification action (show error message with a benign message like "Rule fired") to confirm the condition is evaluating as expected.
Business Rules in Dataverse are a genuinely powerful tool when you understand what they are and aren't. They are a structured, metadata-driven mechanism for two distinct jobs: data integrity validation (via Entity scope) and form user experience control (via Form scope). Conflating the two—or choosing the wrong scope—produces rules that silently fail in exactly the scenarios where you need them most.
The key principles to carry forward:
From here, the natural next steps in the Model-Driven Apps & Dataverse learning path are Business Process Flows—which give you staged, guided processes across the entire lifecycle of a record, stepping up from form-level logic to multi-stage workflow. You should also look at how your Business Rules interact with the security model in Power Apps Security: Roles, Sharing, and Data Permissions, because security roles affect which forms users see, which directly determines which Form-scoped rules apply to them.
For teams building heavy automation around Dataverse records, the interaction between Business Rules and Power Automate flows is worth studying carefully. Business Rules are synchronous and fast; flows are asynchronous and flexible. Together, they form a complete automation picture—but only if you're deliberate about which layer handles which concern.