Skip to Content

Introduction to App Builder - Appendix B: The business layer in App Builder (Advanced)

This is the second appendix of the Introduction to App Builder tutorial series. These appendices complement the lessons in the series, and provide more in-depth information about concepts introduced.

In this lesson, we'll further explore the business layer of App Builder, the area where we create business objects, or rules.

Unions

Joins, introduced in Lesson 7, are used when we work with rules that pull data from more than one table in the data layer. They combine data horizontally, adding columns from a second table alongside the columns of the first.

Unions work differently: instead of adding columns, they stack rows. A union appends the results of one query on top of another, producing a combined list of records drawn from more than one table. For a union to work, both queries must include at least one column with the same name, because App Builder uses that shared column as the basis for stacking the results. This is why we can directly combine the Country columns from our Customer and Supplier tables: both columns share the same name.

Note

If you want to combine columns with different names, you can use a subquery with an alias to rename one of them so both names match. Additionally, if the data formats differ between the two columns, you can cast them to a common data type to ensure the union succeeds.

To demonstrate how a union works, let's manually create a union that shows all the countries that exist in the Northwinds data source. There are two tables, Supplier and Customer, that contain a column named Country, so we'll combine the entries from both.

  1. In App Workbench > Rules, click + Rule.

  2. When the rule creation screen appears:

    1. In the Name field, enter Supplier (Countries for Supplier and Customer).

    2. In the Purpose field, select Business Object.

    3. Make sure Supplier is selected in the Target field.

    4. Click Create.

  3. App Builder will create the rule. By default, it adds the target table and selects its primary key. However, in this rule we're only interested in the countries present in the tables, so uncheck SupplierID, select the Country column, and mark it as the primary key.

  4. We need to add the other table that contains country information, so click + Tables.

  5. Find the Customer table and click Add. App Builder will add the Customer table and you will be able to see it in the Tables tab next to Supplier.

  6. Go to the Joins tab.

  7. App Builder has created a join, but adjust it to make it the kind you want. Make sure Supplier is selected in the Left field and that the Type field says Union.

  8. Click the SQL tab to see the syntax of your union:

    SQL union syntax

  9. To see your results, click the Results button in the Rule panel.

  10. Notice that the results contain duplicate countries. This is expected: multiple customers and suppliers are based in the same regions, so each country appears once for every record in each table. To produce a clean, unique list of countries, we will use the Select Distinct button.

  11. Navigate back to the Rule Builder.

  12. In the Rule panel, click More > Edge Case. The Edge Case Settings dialog opens. This dialog provides advanced control over your rule. See Advanced options in App workbench Rules tab for a full description of all available options.

  13. Check the Select Distinct checkbox to remove redundant rows from the results, ensuring every record in the list is unique. Click Save.

  14. Click the Results button again. The rule now returns a distinct list of all countries represented across the Customer and Supplier tables.

Practice time: Use your union rule as a list control

Now, let's put your new union rule to work. Navigate to the Customer and Supplier popup pages and change the Country field's control type to List. In the List Options tab, select the union business object you just created and set both the Key and Title to the Country column.

CRUD rules

We introduced CRUD rules in Lesson 7, but now let's take a closer look at them.

The acronym CRUD stands for "Create, Read, Update, Delete". This means that CRUD rules are what App Builder developers use to insert, retrieve, alter, or erase records from the tables present in the data source.

Target layer: business vs. data

When you create a CRUD rule, you must select a target layer: either the business or data layer. This choice is critical and has significant implications.

Targeting the business layer is the recommended approach for most operations. When your CRUD rule targets the business layer (e.g., the Order (Source) business object), it acts like a user interacting with the application. It triggers all the intrinsic events (Save, Update, etc.) associated with that business object. This means all your configured validation rules, custom actions, and audit lite tracking will be executed automatically. This approach ensures data integrity and consistency with your application's logic. It also allows you to use success, failure, and rollback handlers for the event.

Targeting the data layer bypasses all business logic. When you target the data layer (e.g., the Order table), your rule interacts directly with the database. It will not trigger any intrinsic events, validations, or audit tracking configured on the business object. This is a "back door" approach, useful for high-performance bulk operations where you intentionally want to bypass business logic.

You can see this difference visually in the visual workflow; a rule targeting the business layer will show subprocesses for its event logic, while one targeting the data layer will not.

Note

App Builder also supports XP CRUD rules, which are specialized rules for writing data between two different data sources. These rules always run through the business layer to ensure all logic is applied. See CRUD (rule) for more.

CRUD update

First, let's see how to create a CRUD update rule. This means modifying existing records in a table. To demonstrate, let's create a rule that increases all quantities of the products ordered.

  1. In App Workbench > Rules, click + Rule.

  2. In the Name field, enter OrderDetail (Quantity + 1).

  3. In the Purpose field, select CRUD.

  4. In the Action field, select Update.

  5. Under Target Layer, select Data Layer. (For this simple bulk update, we will bypass the business logic).

  6. In the Target field, select OrderDetail.

  7. Click Create. App Builder will take a few seconds to create the rule.

  8. In the Tables tab, the OrderDetail table will appear, with its primary key, OrderDetailID, selected. Select the Quantity column as well by clicking its checkbox.

  9. We'll add a new column with an alias to help us visualize that the rule is working. Go to the Columns tab and click + Column. When the Column - Add Column dialog opens, enter the following information:

    1. In the Column or Expression field, enter OD.Quantity + 1.

    2. In the Alias field, enter a legible name. We'll use QuantityPlus.

    3. In the Target field, select Quantity. This tells App Builder to write the result of this expression into the Quantity column.

    4. Click Save.

  10. In the Columns tab, you will now see both Quantity and QuantityPlus. The original OD.Quantity column will also be targeted to Quantity by default. We must untarget it to avoid ambiguity:

    1. Find the OD.Quantity row in the Columns list.

    2. Click its Target dropdown. An X button appears next to it. Click it to untarget the column.

      This ensures that only our QuantityPlus expression is used to update the Quantity column. If both were targeted, the one with the higher index would "win", leading to unpredictable behavior.

  11. The rule is ready. In the Rule panel, click the Results panel to see it working. Notice the extra column you've added increases all quantities by one:

    OrderDetail results

Copy Rule

Think about how you would create a CRUD update rule similar to the one we've created above, but with the opposite effect, that is, a rule that modifies all entries in the Quantity column of the OrderDetail table by subtracting one. However, rather than building this rule from scratch, let's use App Builder's Copy Rule feature, which duplicates an existing rule so you can use it as a starting point, saving time when creating rules that are similar to ones you've already built.

  1. In App Workbench > Rules, locate the OrderDetail (Quantity + 1) rule and click More > Copy Rule.

  2. Click Copy.

  3. App Builder creates the new rule, naming it OrderDetail (Quantity + 1) (New). You are taken directly to its edit screen. In the Rule panel, update the Name field to OrderDetail (Quantity - 1), then navigate to the Columns tab.

  4. Find the expression OD.Quantity + 1 and edit it. Change the expression to OD.Quantity - 1 and update the Alias from QuantityPlus to QuantityMinus. Click Save.

  5. Verify the rule is working correctly by clicking Results in the Rule panel.

Practice time: Add quantity controls

Now that you have both quantity rules, wire them up to the Order Details panel on the Orders page.

  1. Open the OrderDetail (Source) business object and create a custom event for the OrderDetail (Quantity + 1) rule. In the event creation dialog, enter Quantity Plus in the Name field and set Refresh Scope to Row. This ensures that when the event fires, only the affected row is refreshed rather than the entire panel.

  2. Repeat step 1 to create a second event named Quantity Minus, attaching the OrderDetail (Quantity - 1) rule and also setting Refresh Scope to Row.

  3. Navigate to the Orders page edit screen. In the Order Details panel, add two Button controls: one that executes the Quantity Plus event and one that executes the Quantity Minus event.

  4. Visit the Orders page preview and test the new buttons.

CRUD delete

CRUD delete rules are used to erase records from the tables. However, because it's easy to use UI elements to delete individual records, CRUD delete rules are typically only used to delete many records at a time. To demonstrate, let's create a CRUD delete rule that deletes all the order details associated with an individual order, in order to eliminate the need to delete all order details separately when an order is cancelled.

  1. In App Workbench > Rules, click + Rule.

  2. In the Name field, enter OrderDetail (Delete OrderDetail for Order).

  3. In the Purpose field, select CRUD.

  4. In the Action field, select Delete.

  5. Under Target Layer, select Data Layer.

  6. In the Target field, select OrderDetail.

  7. Click Create.

  8. In the Tables tab, App Builder has added the OrderDetail table and selected its primary key, OrderDetailID. Keep this selected, as targeting the primary key is the most efficient and safest way to perform a delete operation. Select the OrderID column as well; we will use this column in the next section to bind the rule to a specific order.

Events

CRUD rules can only be used when they're attached to events, which are processes built into tables or business objects that get executed in response to certain actions, such as saving a record. All tables and business objects intrinsically have four events: save, update, delete, and insert, which execute as you alter records in App Builder.

Let's activate the CRUD rules we created. To do so, we'll create our own custom events. Unlike intrinsic events, custom events must be attached to a control in order to be executed. The CRUD rules we are creating require custom events to execute when we want them to.

First, let's create a custom event to associate with our CRUD delete rule, so that when an order gets deleted, the related order details get deleted as well.

  1. In App Workbench > Rules, locate and open the Order (Source) rule.

  2. In the Rule panel, click Events. The All Events (Order (Source)) page opens. It lists the four intrinsic events App Builder includes for every table and business object: Insert, Save, Update, and Delete. For a complete description of this page and its options, see Configure an event.

  3. Because we are creating a custom event, click + Rule Event. The Event page opens, where you can add the new event's details (see Event detail options for a complete description of all fields):

    1. Under Event Information, in the Name field, enter Delete Order Details.

    2. Under Messages, in the Confirmation field, enter a message that will be shown to users before the event executes, asking them to confirm if they wish to proceed. The message should clearly state what will happen if the user continues, helping prevent accidental actions. See Configure an event to learn about the other message types.

      Confirmation message

    3. In the Execution Properties section, check the Transaction checkbox. This ensures the event is an all-or-nothing execution: because our delete rule affects multiple line items, a transaction guarantees that if an error occurs halfway through, App Builder rolls back all changes. This prevents a scenario where some records are deleted while others remain, leaving data in an inconsistent state. See Execution properties for a full description of all options in this section.

    4. Leave the other fields with their default selections and click Save. App Builder will create the event.

      Note

      For concurrent-access scenarios where data integrity could be compromised, App Builder also provides Event Locking, which forces serial execution and can be scoped to individual records. Leave this unchecked for the current event, but keep it in mind for high-traffic environments or complex workflows where record-level synchronization is a requirement.

  4. The event has been created, and App Builder shows you its configuration screen:

    Delete Order Details configuration screen

    You are currently in the Grid View (shown in the screenshot above), which provides a high-level list of all actions and validations registered on this event. An alternative view of the event can be seen in the Visual Workflow Editor, which shows the execution order and the relationship between inherited events. To see it, click the icon in the Event panel. To return to the Grid View at any time, click the icon that replaces it.

    The Visual Workflow Editor transforms your events into interactive flow diagrams, making it easier to visualize the path your data takes as it moves through the system. Every event is mapped as a process with distinct start and end points, and subprocesses marked with a plus icon can be double-clicked to drill down into deeper layers of logic. You can use the dedicated buttons to add new actions or validations directly into the sequence, and select any node to edit its details, delete it, or re-sequence it within the logic chain. See Visual Workflow Editor for a full description of its features.

  5. The event doesn't have any actions attached to it yet. If you are in the Grid View, notice that the Actions panel is empty, and click its Register button. If you are in the Visual Workflow Editor, click the + Action button along the top of the Event Diagram panel. Doing so opens the Action dialog:

    1. In the Type field, select Rule.

    2. In the Rule field, select OrderDetail (Delete OrderDetail for Order), the CRUD delete rule we've created previously.

    3. Leave the other fields with their default selections. These include Position settings that control execution order and timing. See Event action types for a complete description. Click Save. The dialog for the action will show further details:

      Delete OrderDetails action

    4. In the Bindings panel, click + Binding. Select OrderID as the Source Column and OrderID as the Rule Column.

      Note

      This binding step is critical, as it filters the results of your rule by the panel record you've executed the event against. If left "unbound", the delete rule would affect all records in the OrderDetail table. By binding the Source Column (OrderID from the Order (Source) panel record) to the Rule Column (OrderID in our delete rule), we add an implicit filter (for instance, WHERE OrderID = 10248).

      This ensures that only the OrderDetail records matching that specific OrderID are deleted. If this binding were omitted, triggering the event would delete all records from the OrderDetail table.

    5. Click Save.

  6. The custom event is now ready to be implemented.

Further reading

In addition to what's shown in this example, App Builder event configuration can also include the following:

  • Where events get configured: Events can be configured on the business layer (business objects) or the data layer (tables). Registering events on the business layer allows for distinct logic per interface, whereas data layer events are global. Read more in Where events get configured.

  • Event inheritance: Business objects can inherit event logic from the underlying table or other sources. Read more in Event inheritance.

Practice time: Add a delete control

Now that your delete rule and event are ready, let's put them to work in the UI. Add a Delete Order Details button to the Orders panel and link it to the Delete Order Details event you just configured. This allows your users to clear out an entire order's worth of details with a single click.

Subquery rules

A subquery rule acts as a logic intermediary, calculating or adjusting data before passing it to another object. Unlike standard rules that interact directly with a record, subqueries do not write back to tables. Instead, they perform a specific operation, such as an aggregation, to generate a value for use elsewhere in your application.

We will demonstrate by making a subquery rule. It will be used to find the maximum OrderNumber of the Order table, and then add one to it. We'll later create a CRUD rule with the purpose of copying an order, and it will depend on this subquery to work.

  1. In App Workbench > Rules, click + Rule.

  2. In the Name field, enter Order (OrderNumber + 1).

  3. In the Purpose field, select Subquery.

  4. In the Target field, select Order.

  5. Click Create. App Builder will create the new rule.

  6. App Builder will automatically select the Order table's primary key, but we won't need it for this rule. Deselect it in the Tables tab.

    Why deselect OrderID?

    When using an aggregate function like we'll do here, adding any other column to the query (like OrderID) will cause the database to apply a GROUP BY clause for that column.

    If we included OrderID, the expression would find the maximum OrderNumber per OrderID. This would just return the OrderNumber for each order, which is not what we want.

    To find the single maximum OrderNumber across the entire table, we must not include any other columns in our query. This is why we use a subquery: to perform this aggregate calculation first so it can be used in another rule.

  7. In the Columns tab, click + Column. The Column - Add Column dialog will open.

  8. In the Column or Expression field, enter the expression Max(O.OrderNumber) + 1. The syntax Max() invokes the Max() function, which finds the largest value in the O.OrderNumber column. Then, one is added to it.

  9. In the Alias and Target field, enter an appropriate alias for this column, such as MaxOrderNumber.

  10. In the Logical Data Type field, select Number, as OrderNumber isn't a UUID value, but a natural number.

  11. Click Save.

  12. Check that the rule is working correctly by clicking the Results button. There should be only one row with one order number, which should be one number higher than the biggest order number currently present in the Order table.

Now, let's create a CRUD rule capable of copying an order. This CRUD rule will rely on the subquery rule we've just created to work.

  1. In App Workbench > Rules, click + Rule.

  2. In the Name field, enter Order (Copy Order).

  3. In the Purpose field, select CRUD.

  4. In the Action field, select Insert.

  5. In the Target Layer field, select Logic Layer.

  6. In the Target field, select Order.

  7. Click Create. App Builder will create the rule.

Once the rule is created, configure its columns and targets:

  1. In the Tables tab, App Builder has automatically added the Order table, but we'll need more. Click + Tables.

  2. Select the subquery rule we've created for this purpose, Order (OrderNumber + 1). Click Add.

  3. In the Tables tab, check the checkbox next to the search bar at the top of the column list. This selects all existing columns from the Order table at once.

  4. Click the Order table name. The Table dialog opens. Under Select all Columns, enable the checkbox. This ensures that any new columns added to the Order table in the future are automatically included in this rule. Close the dialog.

  5. From the Order (OrderNumber + 1) subquery table, select its only column, MaxOrderNumber.

  6. In the Columns tab, refine the rule's data mapping as follows:

    • Remove the target for OrderID. Since this is an auto-generated primary key, the business layer will automatically assign a unique value when the rule executes.
    • OrderNumber: Confirm that the OON.MaxOrderNumber row targets OrderNumber, mapping the new incremented value to the correct column.
    • OrderDate: Click the Column or Expression field for this column and replace its current value with Now() to set the copied order's date to today.
    • Not all columns are needed in this rule. Remove the following:
      • RequiredDate, ShippedDate, and Freight: These details should be entered manually for the new order rather than duplicated from the original.
      • AddedOn, AddedBy, ChangedOn, ChangedBy, and IsActive: App Builder auto-generates these values upon creation.

    Further reading

    Learn more about this concept in Target in business object rules.

  7. Click Results in the Rule panel to verify that the rule is working correctly.

Now let's create a second CRUD rule to copy the order's line items. This rule will be registered as a success handler on the Copy Order event, ensuring the detail lines are only created once the new order record exists.

  1. In App Workbench > Rules, click + Rule.

  2. In the Name field, enter OrderDetail (Insert) Copy Order Detail.

  3. In the Purpose field, select CRUD.

  4. In the Action field, select Insert.

  5. In the Target Layer field, select Logic Layer.

  6. In the Target field, select OrderDetail.

  7. Click Create.

  8. In the Tables tab, check the checkbox next to the search bar to select all existing columns at once. Then click the OrderDetail table name, enable Select all Columns in the Table dialog, and close the dialog.

  9. In the Columns tab, make the following changes:

    • Delete the audit lite and user-selectable columns (AddedOn, AddedBy, ChangedOn, ChangedBy, IsActive). These will be automatically populated upon creation.
    • Update the expression for OrderDetailID to NewUUID(). This ensures each copied line item receives its own unique identifier.
    • Remove the target for OrderID and set its alias to BindOrderID. This value represents the original order's ID, which we will use for binding during the event.
    • Add a new expression using Generated() targeting OrderID. The Generated() function retrieves a value created during the current event execution and takes two arguments: the value and the data type. Enter Generated('OrderID', 'UUID') as the expression.
  10. Click Results to verify the rule.

We've now created both copy rules. Let's attach the Copy Order rule to a custom event and wire the Copy Order Lines rule to fire automatically on its success.

  1. In App Workbench > Rules, locate and open the Order (Source) rule.

  2. In the Rule panel, click Events. The All Events (Order (Source)) page opens.

  3. Click + Rule Event. The Event page opens:

    1. Under Event Information, in the Name field, enter Copy Order.

    2. In the Refresh Scope field, select Panel Refresh. This ensures the panel updates immediately after the copy completes, so the new order appears without a manual refresh.

    3. Under Messages, configure a confirmation, success, and failure message using dynamic substitution. By using double curly brackets, you can pull real-time data from the underlying rule and inject it into the text. Since this event is registered on the Order (Source) rule, you have access to all its columns. For example:

      • Confirmation: Are you sure you want to duplicate order {{OrderNumber}}?
      • Success: Order {{OrderNumber}} has been successfully copied!
      • Failure: Error: Order {{OrderNumber}} failed to copy!
    4. In the Execution Properties section, check the Transaction checkbox to ensure data integrity by rolling back the entire process if any part of the duplication fails.

    5. Leave the other fields with their default selections and click Save.

      Note

      See Event detail options for a complete description of all fields.

  4. The event has been created. In the Actions panel, click Register. The Action dialog opens:

    1. In the Type field, select Rule.
    2. In the Rule field, select Order (Copy Order).
    3. In the Bindings panel, click + Binding. Select OrderID as the Source Column and OrderID as the Rule Column. Using OrderID for binding is more reliable than OrderNumber, which may not be unique.
    4. Save.
  5. In the Actions panel, there is a Handlers field that shows three buttons: Success, Failure, and Rollback:

    • Success Handler: Triggers only when the parent rule completes successfully. Use this to chain logic, like copying our order details, that depends on the parent record existing first.
    • Failure Handler: Triggers only if the parent rule encounters an error. Use it to send alerts, log errors, or trigger compensating actions.
    • Rollback Handler: Used when working with systems that don't support standard database transactions, such as a third-party REST API. It executes actions in reverse chronological order to manually undo the rule execution up to that point.

    Click Success. The Success Handlers dialog opens. Click Create. The Action dialog opens:

    1. In the Type field, select Rule.
    2. In the Rule field, select OrderDetail (Insert) Copy Order Detail.
    3. Save.
    4. In the Bindings panel, click + Binding and map the following:
      • Source Column: Select OrderID (the unique ID of the order currently being copied).
      • Rule Column: Select BindOrderID (the alias we created in the rule to identify the source record).
    5. Save.
  6. Exit the open dialogs and navigate to the Visual Workflow Editor by clicking the icon. The event diagram is now more complex than the Delete Order Details event we reviewed earlier. Notice the following:

    • Logic Layer: Both the Copy Order and Copy Order Lines rules run through the business layer, which is why both rule blocks show the + icon. Double-click either block to navigate directly to the parent events it triggers.
    • Conditional paths: You can see the branching paths for Success and Failure. The Success path shows the Copy Order Lines rule being triggered, while the Failure path shows no subsequent events, confirming that the process stops if the primary copy fails.

Practice time: Add a copy control

Now that your copy rules and event are ready, let's put them to work in the UI. Add a Copy button to the Orders panel and link it to the Copy Order event you just created. Once the button is added, test it: you should see the confirmation message with the dynamic order number, and upon success, a new order will appear using the next available order number.

Validations

Validation rules are used to ensure that data entered into App Builder is valid before it gets saved. In Lesson 7, we covered a simple "required field" validation. Now, we'll explore a more complex validation scenario, including custom logic and dynamic error messages.

Like other rules, validation rules must be attached to an event to be used. The choice of event depends on the use case:

  • Intrinsic event: Use this when you want the validation to run automatically during standard operations (e.g., preventing a save if data is invalid).

  • Custom event: Use this when you want to trigger the validation on demand from a specific control (e.g., a button click).

Let's create a validation rule to ensure that a discount on an order is never greater than 10%. We want this to run every time an OrderDetail record is saved, so we will attach it to the intrinsic save event.

  1. In App Workbench > Rules, click + Rule.

  2. In the Name field, enter OrderDetail (Validate Discount).

  3. In the Purpose field, select Validation.

  4. In the Target field, select OrderDetail.

  5. Click Create.

  6. In the Tables tab, select the Discount column (you can deselect OrderDetailID).

  7. In the Where tab, click + Where Clause. A dialog opens where you can create WHERE clauses:

    Where clause dialog

    1. In the Left Expression field, enter OD.Discount.

    2. In the Operator field, select >.

    3. In the Right Expression field, enter 0.1.

  8. Click Save.

The rule is now created. It is designed to find "bad" data (records where the discount is over 10%).

Dynamic threshold

In the last step, we hard-coded a 10% limit. Now, let's make it configurable: we'll store the threshold as an application parameter so the business can update it from the UI without touching the rule itself. Apply the following steps using skills from previous lessons:

  1. Add a DiscountMaximum column to the Parameter table. Set the Logical Data Type to Percent and the scale to 4 to capture precise values like 12.25%.

  2. Navigate to the Parameter page. On the table edit screen, the Pages button provides a shortcut: it lists all pages that currently use this table, letting you jump directly to the right page.

  3. Add DiscountMaximum as a numeric control to the page and populate it with a value such as .3 to allow discounts of up to 30%.

  4. Return to App Workbench > Rules and open the OrderDetail (Validate Discount) rule.

  5. In the Tables tab, click + Tables and add the Parameter table. Select the DiscountMaximum column.

  6. In the Where tab, click the pencil icon to edit the WHERE clause. In the Right Expression field, replace 0.1 with P.DiscountMaximum.

Whatever value is set on the Parameter page now controls the threshold for this validation.

Attach the validation rule

Now, let's attach the validation rule to the intrinsic save event for OrderDetail.

  1. In App Workbench > Rules, find the OrderDetail (Source) business object and open it.

  2. In the Rule panel, click Events. The All Events (OrderDetail (Source)) page opens.

  3. Find the Save row. Notice there are two designer buttons: Table Event Detail and Rule Event Detail. We want to register this validation using Rule Event Detail. This is an important distinction: it ensures the validation only applies when this specific business rule is utilized, rather than imposing a global restriction on the entire OrderDetail table. This keeps the app flexible. For example, you could later build a Manager Approval page. By using a different business object there, a supervisor could authorize higher discounts without being blocked by the standard rule we're building here. Click Rule Event Detail for the Save event.

  4. In the Validations panel, click Register. The Validation dialog opens:

    Validation dialog

  5. In the Type field, select Rule.

  6. In the Rule field, select OrderDetail (Validate Discount), the validation rule we've just created.

  7. In the Binding field, select Implicit.

    Note

    Implicit binding validates against the data currently "in memory" on the user's screen. This is the value the user has typed, before it has been saved to the database. We use implicit binding here because we want to stop the user from saving a new record while the Discount value is invalid (that is, greater than the configured threshold). On the other hand, Explicit binding validates against the data that is already saved in the database.

    To illustrate, imagine a record was saved with an invalid discount of 50%. A user opens that record, corrects the Discount field to a valid value like 7%, and submits it. If the validation event uses explicit binding, it will check the database, see the original 50% value, and fail the validation. However, if the validation event uses implicit binding, it will check the in-memory value (7%), see that it is not greater than the threshold, and pass the validation, allowing the save.

    Read more in Implicit and explicit binding.

  8. In the Failure field, select Fail on data returned.

    Note

    App Builder validations can be configured in two ways. The default, which we are using, is Fail on data returned. This means the validation rule is written to find the bad data (for example, Discount is greater than the threshold). If the rule finds any records matching this "bad" criteria, it fails and shows the error message. The alternative is Fail on no data returned, which is used when a rule is written to find good data.

  9. In the Severity field, select Error.

    Note

    The Severity field determines how forcefully App Builder interacts with the user when a validation triggers:

    • Error: The most critical level. Indicates a major issue and prevents the save entirely. The user sees a red notification and must correct the data before they can proceed.
    • Warning: A soft block. Alerts the user to a potential issue with a yellow notification, but gives them the choice to either cancel and fix it or proceed and save the record anyway.
    • Information: Purely informational. Provides a blue notification after the event runs to give the user helpful context, but never stops or interrupts the workflow.
  10. In the Message field, enter: Discount cannot be greater than {{ DiscountMaximum }}. Using dynamic substitution here ensures the error message automatically reflects whatever threshold is currently set on the Parameter page.

  11. Click Save to exit the dialog.

The validation is attached. To test it, go to the Orders page preview, select an order, and try to edit an OrderDetail item to have a discount greater than the threshold you set. When you click to save, you should see the dynamic error message.

For the {{ DiscountMaximum }} placeholder to resolve correctly, the column must be available in the OrderDetail (Source) business object. Since DiscountMaximum lives in a separate Parameter table, you need to add it explicitly:

  1. Return to App Workbench > Rules and open the OrderDetail (Source) rule.

  2. In the Tables tab, click + Tables and add the Parameter table. Select the DiscountMaximum column.

The column name in the rule must match the placeholder in your validation message exactly. If you had used a different name in the message (for example, {{ MaxDiscount }}), you would need to alias the column in the rule to match.

Further reading

For more techniques on how to ensure data integrity, see Validation tips.

Further learning

This concludes this deep dive into the details of App Builder's business layer. See Appendix A for a closer look at the data layer, or Appendix C for the UI layer.