9.3 Rule Inputs, Component Parameters & Two-Way Binding
Key Takeaways
Rule Inputs (ri!) establish the public parameter interface of a SAIL rule, enabling parent interfaces, process models, and record views to pass data into and receive modified data back from the component.
Interactive SAIL components implement two-way data binding through complementary value (read state) and saveInto (write state) parameters.
a!save can transform save!value or update several interface state targets; preparing audit fields does not itself persist an audit log.
Task edits flow through ac! node inputs and their configured saves to process variables; start forms use process parameters.
Modularity best practices dictate building 'pure' UI components that rely strictly on Rule Inputs rather than querying global database entities directly, maximizing component reusability across multiple applications.
Rule Inputs, Component Parameters & Two-Way Binding
Quick Summary: In Appian SAIL, interfaces communicate with the broader application ecosystem through Rule Inputs (
ri!), which define the public data contract of an interface. Interactive components achieve two-way data binding through pairedvalueandsaveIntoparameters. Whilevaluereads and displays the current state,saveIntocaptures user modifications. By leveraginga!save()and the contextualsave!valueoperator, developers transform data during user interaction and seamlessly return updated records to process models upon task submission.
The Architectural Role of Rule Inputs (ri!)
Every SAIL interface is fundamentally an expression rule that evaluates to a component tree. To make interfaces dynamic and reusable, Appian provides Rule Inputs (ri!) as parameter definitions that define the external interface contract.
+--------------------------------------------------------------------------+
| PARENT CONTEXT (Process Model / Site) |
| |
| Process Variable: pv!customerRecord (Type: Customer CDT) |
+------------------------------------+-------------------------------------+
| 1. Passes data in (Read Binding)
| 4. Returns data out (Write Binding)
v
+------------------------------------+-------------------------------------+
| SAIL INTERFACE CONTRACT |
| |
| Rule Input: ri!customer (Type: Customer CDT, Array: False) |
| |
| +------------------------------------------------------------------+ |
| | Component: a!textField( | |
| | label: "Customer Name", | |
| | value: ri!customer.name, <-- 2. Reads from ri! | |
| | saveInto: ri!customer.name <-- 3. Writes to ri! | |
| | ) | |
| +------------------------------------------------------------------+ |
+--------------------------------------------------------------------------+
Scoping Comparison: ri! vs. local!
local!(Local Variables): Internal, private, and ephemeral. Exists solely within the interface during runtime execution. Destroyed when the interface is closed.ri!(Rule Inputs): Explicit inputs supplied by the caller, with persistence determined by that caller and its mappings. Functions as the gateway through which parent callers (such as a Process Model User Input Task, a Record View, or a parent Interface) inject data into the interface and receive updated data back.
Configuring Rule Inputs
When creating Rule Inputs in the Appian Designer interface editor, developers specify three critical attributes:
- Name: Standardized in
camelCase(e.g.,ri!customer,ri!orderItems,ri!isReadOnly). - Type: Can be bound to primitive system types (
Text,Integer,Date), Custom Data Types (CDTs), Record Types, or generic types (Any Type). - Array Configuration ("List"): Dictates whether the rule input accepts a single scalar value (
Array: false) or a collection of values (Array: true). For instance, a child interface rendering a purchase order line-item grid requires an array rule input of typeOrderLineItem.
Two-Way Data Binding: The Mechanics of value and saveInto
Unlike traditional web frameworks that rely on hidden form fields or imperative change listeners, Appian SAIL implements a reactive, two-way data binding architecture. Every interactive input component features two complementary parameters: value and saveInto.
a!textField(
label: "Primary Contact Phone",
value: ri!customer.phoneNumber,
saveInto: ri!customer.phoneNumber,
required: true
)
The 5-Step Lifecycle of a SAIL User Interaction
- Component Rendering (Read Phase): The interface engine evaluates the component's
valueparameter (ri!customer.phoneNumber). The server translates this into a JSON payload, and the client displays the string"555-0199". - User Modification (Event Phase): The user edits the phone number to "555-0100" and triggers the field's configured save event.
- Save Execution (Write Phase): The client transmits the change event to the server. The server executes the expression defined in
saveInto, assigning the new value directly intori!customer.phoneNumber. - Re-Evaluation (Reactivity Phase): The server re-evaluates the interface expression tree to incorporate the newly updated state across all dependent variables.
- DOM Update (Render Phase): The client updates the display with the new evaluated state.
Common Data Binding Misconfigurations
| Configuration | User Experience / System Behavior | Root Cause |
|---|---|---|
value defined, saveInto omitted | There is no durable interface-state save target; edits may revert on evaluation | The component has a read binding but no write target to capture state |
saveInto defined, value omitted | User can type, but entered text vanishes immediately after focus shifts | The component writes the change to the variable, but does not read it back to display |
Mismatched types in value and saveInto | Runtime expression error or type coercion failure | Attempting to save a text string into an integer or date variable without casting |
Advanced Save Logic with a!save() and save!value
In simple form fields, passing a direct variable reference to saveInto (e.g., saveInto: ri!customer.name) suffices. However, enterprise applications frequently require transforming data before saving, setting automated audit timestamps, or updating multiple related variables simultaneously. Developers accomplish this using a!save().
Syntax of a!save()
a!save(target, value)
target: The variable (ri!orlocal!) where the data must be stored.value: The expression defining what will be written to the target.
The save!value Context Operator
Within the scope of a component's saveInto parameter, Appian provides a special contextual operator: save!value.
save!value represents the new raw value generated by the user's interaction (e.g., the exact text entered into a text box, the choice selected in a dropdown, or the date picked in a calendar).
Common Enterprise Save Patterns
Pattern 1: Data Transformation on Input
Enforcing uppercase formatting on a vehicle identification number (VIN) or postal code:
a!textField(
label: "Vehicle Identification Number (VIN)",
value: ri!vehicle.vin,
saveInto: a!save(ri!vehicle.vin, upper(save!value))
)
Pattern 2: Multi-Variable Audit Metadata
When an underwriter modifies an approval limit, the interface prepares the new limit, timestamp, and user identifier in its state; a later persistence operation must save them:
a!floatingPointField(
label: "Credit Approval Limit",
value: ri!creditProfile.approvalLimit,
saveInto: {
ri!creditProfile.approvalLimit,
a!save(ri!creditProfile.lastModifiedTimestamp, now()),
a!save(ri!creditProfile.lastModifiedBy, loggedInUser()),
a!save(ri!creditProfile.isAuditFlagged, true)
}
)
Pattern 3: Conditional Save Execution
Conditionally clearing or populating variables based on user selection:
a!dropdownField(
label: "Payment Method",
choiceLabels: {"Credit Card", "Bank Transfer", "Cash"},
choiceValues: {"CC", "ACH", "CASH"},
value: local!paymentMethod,
saveInto: {
local!paymentMethod,
if(
save!value = "CASH",
a!save(local!requiresPhysicalReceipt, true),
a!save(local!requiresPhysicalReceipt, false)
)
}
)
Data Flow Between Interfaces and Process Models
A process start form can receive parameterized process variables through its interface inputs and save submitted values back to those parameters. A User Input Task uses node inputs for editable form data. The reusable interface itself uses ri! inputs rather than directly naming process variables.
For a task, define ac!customer initialized from pv!customer. Map the interface's customer input to ac!customer on the Forms tab. Configure the node input's save or an appropriate node output to update pv!customer when the task completes. Read-only values can be supplied directly for display, but the task form cannot save directly into pv! variables.
| Boundary | Example configuration |
|---|---|
| Process to node | ac!customer initialized from pv!customer |
| Node to interface | rule!APP_customerForm(customer: ac!customer) |
| Component edit | value and saveInto target ri!customer.name |
| Node completion | Configured save returns ac!customer into pv!customer |
| Durable persistence | Subsequent authorized Write Records or data-store write |
Check the actual node input, rule input, and process variable after submitting a test task. A form can appear to work while editing only a local copy. Also refresh mappings when interface inputs change and avoid competing saves into the same process variable.
Submission and Validation
Use a button with submit: true to complete a task form after its saves and validations succeed. A non-submit button can update interface state without advancing the workflow. The validate default follows submit: true for submitting buttons, false otherwise. Configure both deliberately for a wizard or a cancel action.
a!buttonWidget(
label: "Submit Customer Profile",
value: "SUBMITTED",
saveInto: ri!actionTaken,
submit: true,
style: "SOLID",
validate: true
)
A button can set an action result before completing the form. The following process gateway must interpret that result, and an authorization check must still govern the business change. Merely hiding a button or displaying a read-only field does not secure the operation.
Realistic Scenario: Customer Profile Update Interface
An insurance enterprise constructs an interface allowing agents to update policyholder contact records:
- Interface Definition:
INS_uiUpdateCustomerProfiledefines two rule inputs:ri!customer(Type:INS_CustomerCDT, Array: false)ri!isUnderwritingReview(Type:Boolean, Array: false)
- Component Composition:
- First Name and Last Name inputs bind
value: ri!customer.firstNameandsaveInto: ri!customer.firstName. - Email Address field validates formatting:
a separate validation rule that checks the intended email contract; a simple wildcard is not complete email validation. - Child Address Interface: The form embeds a modular child interface
rule!INS_uiAddressForm(address: ri!customer.address, readOnly: false).
- First Name and Last Name inputs bind
- Process Model Integration: In the
Review Customer Claimprocess model, a User Input Task initializes an ac! node input from pv!claimCustomer, maps that node input to ri!customer, and configures its save back into pv!claimCustomer. On submission the modified data reaches the process variable in engine memory, ready for the subsequentWrite to Data Store Entitysmart service node.
Exam Traps & Binding Guidelines
Warning
Common Exam Trap 1: Attempting to reference pv! variables directly within a SAIL interface. Interfaces do not have access to the pv! namespace. An expression like value: pv!customerName will throw an immediate syntax error. Process data must enter an interface through a Rule Input (ri!) mapped on the task form.
Warning
Common Exam Trap 2: Using save!value outside of the saveInto parameter. save!value is a contextual keyword that is instantiated only during a save event. Calling save!value inside value, validations, or instructions triggers an undefined variable error.
Tip
Design Best Practice: Keep child interfaces 'pure'. A child interface should receive data through its rule inputs and communicate modifications back through those same rule inputs, avoiding direct calls to process smart services or application-specific mutations. This makes the child interface universally embeddable across Sites, Portals, and Record Views.
Source checked 2026-10-18: Appian interface/process mappings.
A developer attempts to reference pv!assignedAnalyst directly inside a SAIL interface expression. Why does this expression fail to evaluate?
Process variables can only be referenced inside a!formLayout() button configurations.
Process variables are accessible only when the user holds System Administrator privileges.
Process variables must be converted to XML before an interface can parse them.
SAIL interfaces do not have access to the pv! namespace; process variables must be mapped to Rule Inputs (ri!) on the task form.
A developer needs to ensure that whenever a user enters a postal code into a text field, the text is automatically transformed and saved in uppercase letters. Which saveInto configuration achieves this requirement?
saveInto: upper(ri!address.postalCode)
saveInto: a!save(ri!address.postalCode, upper(save!value))
saveInto: { ri!address.postalCode, save!value: upper() }
saveInto: a!save(save!value, upper(ri!address.postalCode))
A paragraph field displays ri!notes but has no saveInto target. What is missing from the data contract?
A new process model for every keystroke
A requirement that the field become a grid
A guarantee that all text will automatically persist to the database
A save target that updates interface state and can flow back through the caller mapping
Sections you finish are checked off in the contents.