# Conditional Logic: If Widget, Switch Widget, and Expression Patterns

- Tool: OutSystems
- Difficulty: Beginner
- Time required: 20-25 min
- Compatibility: OutSystems 11 and ODC
- Last updated: March 2026

## TL;DR

OutSystems has three ways to express conditional logic: the If widget on screens (shows/hides UI based on a Boolean expression), the If node in action flows (branches logic), and the If() function in expressions (inline ternary). The Switch node handles multiple-branch conditions in action flows, equivalent to a switch/case statement.

## Conditional Logic in OutSystems

OutSystems provides conditional logic at three levels: the screen layout (If widget), the action flow (If node and Switch node), and inline expressions (If() function). Understanding which tool to use where — and the important OutSystems-specific syntax differences from JavaScript — eliminates the most common beginner mistakes and TrueChange errors.

## Before you start

- Service Studio with a Reactive Web App open
- A screen with at least one Local Variable and one widget
- Familiarity with the Screen Editor and Action Flow Editor

## Step-by-step guide

### 1. Use the If widget to conditionally show UI on a screen

The If widget is a layout element that renders either its True branch or False branch based on a Boolean expression. It does NOT hide the element with CSS — it removes it from the DOM when False.

In the Screen Editor, search the Toolbox for 'If' and drag it onto the canvas. In the Properties Panel:
- Condition: enter your Boolean expression, e.g. User.IsLoggedIn

The Widget Tree shows two branches inside the If widget: True Branch and False Branch. Drag UI elements into each branch. Only the branch matching the condition is rendered.

Common use cases:
- Show 'Admin' menu only if CheckAdminRole() = True
- Show loading spinner while data fetches: GetEmployees.IsLoading
- Hide empty state message: not GetEmployees.List.Empty
- Conditional form sections: SelectedCountry = Entities.Country.US

```
/* If widget condition examples */
User.IsLoggedIn
CheckAdminRole()
not GetEmployees.List.Empty
CurrentStep = 3
Order.Status = Entities.OrderStatus.Pending
SelectedCountryId <> NullIdentifier()
GetOrders.IsLoading
```

**Expected result:** The True branch content is visible and the False branch is completely absent from the rendered HTML when the condition is False.

### 2. Use the If node in action flows for branching logic

In the Action Flow Editor (for both Client and Server Actions), the If node creates a binary branch. Drag 'If' from the Toolbox onto the flow between two nodes.

The If node has:
- Condition (Boolean expression): set in Properties Panel
- True connector (right side): flow when condition is True
- False connector (bottom): flow when condition is False

Example — save or navigate based on form validity:
Start → If: Form.Valid → [True] SaveEmployee Server Action → Destination → [False] End

Nesting: connect multiple If nodes in sequence. The False branch of one If can connect to the next If node for an else-if chain:
Start → If: Status = 'Active' → [True] ... → [False] → If: Status = 'Pending' → [True] ... → [False] → end default case

```
/* Action flow with nested If nodes */
Start
  --> If: Order.TotalAmount > 1000
       [True]  --> Assign: DiscountRate = 0.15
       [False] --> If: Order.TotalAmount > 500
                    [True]  --> Assign: DiscountRate = 0.10
                    [False] --> Assign: DiscountRate = 0.05
  --> Assign: FinalAmount = Order.TotalAmount * (1 - DiscountRate)
  --> End
```

**Expected result:** The action flow branches correctly at runtime. Set a breakpoint on the If node in the Debugger to verify which branch executes.

### 3. Use the If() function for inline conditional expressions

The If() function returns one of two values based on a condition. Use it anywhere an expression is expected — widget properties, Assign node values, Aggregate filter conditions, FormatDate arguments.

Syntax: If(BooleanCondition, TrueValue, FalseValue)

Critical rule: TrueValue and FalseValue MUST be the same data type. OutSystems does not auto-cast — passing a Text True value and an Integer False value causes a TrueChange type error.

Examples:
- If(Employee.IsActive, "Active", "Inactive") → Text
- If(Order.Amount > 100, Order.Amount * 0.9, Order.Amount) → Decimal
- If(User.Age >= 18, True, False) → same as just User.Age >= 18
- If(GetOrder.IsLoading, "Loading...", IntegerToText(GetOrder.List.Length) + " orders") → Text

```
/* If() expression examples */

/* Text conditional */
If(Employee.IsActive, "Active", "Inactive")

/* Decimal conditional */
If(Order.Amount > 100, Order.Amount * 0.9, Order.Amount)

/* Nested If() - equivalent to else-if */
If(Age >= 65, "Senior", If(Age >= 18, "Adult", "Minor"))

/* Used in FormatDate */
FormatDate(If(Order.ShippedDate = NullDate(), CurrDate(), Order.ShippedDate), "dd/MM/yyyy")

/* Used in Aggregate filter */
Order.Status = If(ShowAllOrders, Entities.OrderStatus.All, Entities.OrderStatus.Pending)

/* Conditional CSS class */
If(IsSelected, "selected active", "")
```

**Expected result:** Expressions using If() compile without TrueChange errors. The Expression widget on screen dynamically shows 'Active' or 'Inactive' based on Employee.IsActive.

### 4. Use the Switch node for multi-branch conditions in action flows

The Switch node is the OutSystems equivalent of a switch/case statement. Use it when you have three or more branches based on the same value.

Drag 'Switch' from the Toolbox into an action flow. In the Properties Panel, add cases:
- Click '+' for each case
- Case 1: Expression = Order.Status = Entities.OrderStatus.Pending, connect to 'Send pending reminder' logic
- Case 2: Expression = Order.Status = Entities.OrderStatus.Shipped, connect to 'Send tracking email' logic  
- Case 3: Expression = Order.Status = Entities.OrderStatus.Cancelled, connect to 'Send cancellation notice' logic
- Otherwise (default): connect to logging or no-op End

Switch evaluates cases top-to-bottom and takes the FIRST match. Unlike some languages, there is no fallthrough — only one branch executes.

When to use Switch over nested Ifs:
- 3+ branches on the same variable → Switch
- 2 branches or unrelated conditions → If node chain

```
/* Switch node cases - evaluates top to bottom, first match wins */
Case 1: Order.Status = Entities.OrderStatus.Pending
        --> Send pending reminder email
Case 2: Order.Status = Entities.OrderStatus.Shipped
        --> Send tracking number SMS
Case 3: Order.Status = Entities.OrderStatus.Cancelled
        --> Send cancellation refund notice
Otherwise:
        --> LogMessage: "Unhandled order status: " + Order.Status
```

**Expected result:** The action flow executes the matching case branch. Only one branch fires per execution regardless of how many cases exist.

### 5. Apply conditional logic to widget properties beyond Visible

The If widget handles conditional rendering, but you can also apply conditional logic to any widget property that accepts an expression:

- Style Classes: If(IsHighlighted, "highlight-row", "") — applies a CSS class conditionally
- Enabled: SelectedCountryId <> NullIdentifier() — disables a button until a precondition is met
- Mandatory: If(RequiresPhone, True, False) — makes a field mandatory based on context
- Value in Expression widget: If(Employee.IsActive, "Active", "Inactive") — text content

For Button Enabled, set the Enabled expression in the Button's Properties Panel:
Enabled = Form.Valid and SelectedCountryId <> NullIdentifier()

This creates a reactive UI where the button only becomes clickable when the form is valid and a country is selected — no Client Action needed.

```
/* Widget property conditional expressions */

/* Button Enabled */
Form.Valid and SelectedCountryId <> NullIdentifier()

/* Input Mandatory */
If(DeliveryMethod = Entities.DeliveryMethod.Home, True, False)

/* Dynamic CSS class */
If(Row.IsOverdue, "row-overdue text-danger", "")

/* List item background color via Style Class */
If(Mod(GetEmployees.List.CurrentRowNumber, 2) = 0, "row-even", "row-odd")
```

**Expected result:** Widget properties update reactively as the bound variables change, without requiring Client Action logic for simple show/disable/style patterns.

## Complete code example

File: `ConditionalLogic_patterns.txt`

```outsystems-pseudocode
/* ============================================================
   IF WIDGET PATTERNS (Screen Editor)
   ============================================================ */

If (Condition = User.IsAdmin)
  True Branch:  Admin Dashboard Container
  False Branch: Standard User Dashboard Container

If (Condition = GetOrders.IsLoading)
  True Branch:  Spinner widget
  False Branch: If (not GetOrders.List.Empty)
                  True:  Table widget bound to GetOrders.List
                  False: BlankSlate 'No orders found'

/* ============================================================
   IF NODE PATTERNS (Action Flows)
   ============================================================ */

/* Client Action: ApplyDiscount */
Start
  --> If: Order.TotalAmount > 1000
       [True]  --> Assign: DiscountRate = 0.15
       [False] --> If: Order.TotalAmount > 500
                    [True]  --> Assign: DiscountRate = 0.10
                    [False] --> Assign: DiscountRate = 0.05
  --> Assign: FinalAmount = Order.TotalAmount * (1 - DiscountRate)
  --> End

/* ============================================================
   IF() EXPRESSION PATTERNS
   ============================================================ */

/* Inline ternary for display */
If(Employee.IsActive, "Active", "Inactive")

/* Nested If() for three outcomes */
If(Score >= 90, "A", If(Score >= 70, "B", If(Score >= 50, "C", "F")))

/* Null safety pattern */
If(Order.ShippedDate = NullDate(), "Not yet shipped",
   "Shipped: " + FormatDate(Order.ShippedDate, "dd/MM/yyyy"))

/* ============================================================
   SWITCH NODE PATTERN
   ============================================================ */
Switch on Order.Status:
  Pending    --> send reminder
  Shipped    --> send tracking info
  Delivered  --> request review
  Cancelled  --> process refund
  Otherwise  --> log unknown status
```

## Common mistakes

- **Using If(condition, True, False) when just 'condition' works** — undefined Fix: If the TrueValue is True and FalseValue is False, the If() call is redundant. Use the Boolean expression directly: Button.Enabled = IsFormValid is cleaner than Button.Enabled = If(IsFormValid, True, False).
- **Mismatched types in If() expression (Text True value, Integer False value)** — undefined Fix: TrueChange will show 'Invalid data type' error. Ensure both values are the same type. Use IntegerToText() or TextToInteger() to convert. Example: If(IsNew, "New", IntegerToText(RecordCount)) — both are now Text.
- **Using a Screen Aggregate inside an If widget condition that runs before data loads** — undefined Fix: Use GetEmployees.IsLoading and GetEmployees.List.Empty to handle loading and empty states. Wrap the data-dependent If condition in an outer If checking IsLoading first to prevent accessing .List.Current on an empty list.
- **Placing the If node's False branch logic above the True branch in the flow visually** — undefined Fix: In Service Studio's Action Flow Editor, the True connector goes right and the False connector goes down by default. When arranging complex flows, keep the happy path going right and exception/alternative paths going down for readability — this matches the visual convention OutSystems developers expect.

## Best practices

- Use the If widget (not CSS visibility) for conditional rendering of substantial UI sections — it removes the element from the DOM and avoids rendering overhead.
- Use Switch nodes when you have three or more branches on the same variable — it is more readable and maintainable than chained If nodes.
- Keep If() expression nesting to two levels maximum. Three or more nested If() calls should be moved to a Assign node in an action flow or a local Server Action function.
- Both values in an If() expression must be the same data type — use conversion functions (IntegerToText, TextToInteger) if needed.
- For widget Enabled and Mandatory properties, use direct Boolean expressions rather than If(condition, True, False) — If(x, True, False) is identical to just x.
- Add an Otherwise branch to every Switch node to handle unexpected values — log the unhandled case to Service Center for debugging.

## Frequently asked questions

### What is the difference between the If widget and setting Visible = False on a widget?

The If widget completely removes the non-rendered branch from the DOM. Setting Visible = False hides the element but leaves it in the DOM. Use If widget for conditional content that should not be rendered at all (cleaner HTML, better performance). Use Visible = False for elements that need to toggle frequently with animation or when keeping the DOM position matters.

### Can I use the Switch node in a Client Action, or only in Server Actions?

The Switch node is available in both Client Actions and Server Actions. Drag it from the Toolbox in the Action Flow Editor for either action type. The behavior is identical regardless of execution context.

### How do I express an 'and' / 'or' condition in an OutSystems expression?

OutSystems uses lowercase 'and' and 'or' operators (not && or ||). Example: Employee.IsActive and Employee.DepartmentId = SelectedDeptId. For negation, use 'not': not Employee.IsActive. These are OutSystems expression keywords, not JavaScript operators.

### Is there a null/nil check equivalent in OutSystems expressions?

Yes, but it depends on the type. For entity identifiers: use = NullIdentifier() or = NullTextIdentifier(). For dates: = NullDate(). For binary data: = NullBinaryData(). For text: = '' (empty string — OutSystems has no null text). For integers: = 0 (the default, not null). OutSystems intentionally avoids null for most types to prevent null reference exceptions.

---

Source: https://www.rapidevelopers.com/outsystems-tutorial/outsystems-if-condition-logic
© RapidDev — https://www.rapidevelopers.com/outsystems-tutorial/outsystems-if-condition-logic
