An Aggregate is OutSystems' drag-and-drop visual query builder. Open a screen → right-click → Fetch data from Database, drag in an entity, and OutSystems generates the SQL automatically. The result is available as AggregateNameVariable.List (a record list) and AggregateNameVariable.Count (total rows).
What Are OutSystems Aggregates?
Aggregates are OutSystems' visual, database-agnostic query tool. Instead of writing SQL SELECT statements, you drag entities onto a canvas, add filters and sorts visually, and OutSystems generates optimized SQL at compile time. The platform only fetches the columns you actually use in your UI, reducing data transfer automatically.
This tutorial covers the fundamentals: creating aggregates on screens, accessing their data in action flows, and displaying results in List widgets. It does NOT cover joins (see outsystems-aggregate-joins), group by (see outsystems-aggregate-group-by), or performance optimization (see outsystems-aggregate-performance).
Prerequisites
- At least one Entity defined in your module's Data tab (see outsystems-entities-relationships tutorial)
- A Reactive Web app open in Service Studio or ODC Studio
- A screen created under Interface tab → UI Flows → MainFlow → [your screen]
Step-by-step guide
Create a Screen Aggregate via Fetch data from Database
Create a Screen Aggregate via Fetch data from Database
In Service Studio, open your screen in the Screen Editor. Right-click anywhere on the canvas → Fetch data from Database. A new Aggregate editor opens as a tab within the screen. Alternatively: in the Interface tab, expand your screen → right-click → Fetch data from Database. A third option: drag the entity directly from the Data tab onto the screen canvas. This auto-creates both the Aggregate and a List widget bound to it in one step. The Aggregate editor shows four tabs: Sources, Filters, Sorts, Groups.
Expected result: An Aggregate node (named 'GetEmployees' or similar) appears nested under your screen in the Interface tab tree, and the Aggregate editor opens.
Add an entity source and preview data
Add an entity source and preview data
In the Aggregate editor, the Sources tab is active by default. Drag the Employee entity from the Data tab (left panel or right panel) into the Sources area. OutSystems immediately queries the database and shows live preview rows in the bottom preview pane. The Aggregate is now named 'GetEmployees' (auto-named from the entity). You can rename it by clicking the name at the top. In the bottom column list, you can see all available columns from Employee. Unused columns appear grayed out — OutSystems will not fetch them unless you use them in the UI (this is the auto-optimization behavior).
Expected result: The preview pane shows actual rows from the Employee table with all columns listed. The Aggregate editor title shows the entity name.
Add a calculated attribute using an expression
Add a calculated attribute using an expression
In the Aggregate editor, scroll to the right edge of the column list → click the + icon (Add Attribute) or double-click an empty area. Name it 'FullName'. In the Expression field, write: `Employee.FirstName + " " + Employee.LastName` Click OK. The preview pane immediately shows the computed FullName column. Other expression examples: - `If(Employee.IsActive, "Active", "Inactive")` — conditional text - `IntegerToText(Employee.Id)` — type conversion - `DiffDays(Employee.HireDate, CurrDate())` — days since hire Calculated attributes are computed in the database query (not in application memory) when they use simple expressions.
1/* Calculated attribute expressions in Aggregate editor */23/* Full name from two columns */4Employee.FirstName + " " + Employee.LastName56/* Status label from Boolean */7If(Employee.IsActive, "Active", "Inactive")89/* Days since hire */10DiffDays(Employee.HireDate, CurrDate())1112/* Formatted hire date */13FormatDate(Employee.HireDate, "MMM dd, yyyy")Expected result: A FullName column appears in the Aggregate preview pane showing computed values for each row.
Bind the Aggregate output to a List widget on the screen
Bind the Aggregate output to a List widget on the screen
Close the Aggregate editor tab to return to the Screen editor. In the Toolbox (left panel), drag a List widget onto the screen canvas. In the Properties panel → Source, click the Expression editor icon and select: `GetEmployees.List` Inside the List widget, drag an Expression widget from the Toolbox. Set its Value property to: `GetEmployees.List.Current.Employee.FirstName + " " + GetEmployees.List.Current.Employee.LastName` The List widget iterates over each record. Inside the list body, use `GetEmployees.List.Current.<Entity>.<Attribute>` syntax to access fields. Alternatively, drag a Table widget instead of List for a grid layout — bind Source to `GetEmployees.List` and OutSystems auto-generates column bindings.
Expected result: The List widget on the screen shows employee names when you click the Preview button (1-Click Publish → Open in Browser), or when you use the Preview Data feature in Service Studio.
Set Max Records and access the Count property
Set Max Records and access the Count property
In the Aggregate editor, click the Aggregate node (the name at the top). In the Properties panel → Max Records, enter 20. This limits the query to 20 rows — critical for performance (see outsystems-aggregate-performance). To access the total count of records (regardless of Max Records limit), use `GetEmployees.Count` in expressions or action flows. Note: Count returns the full dataset size, not the count of rows fetched. For pagination, use the Start Index property: set it to a screen variable (e.g., `StartIndex`) and increment by Max Records per page. Action flow to access data: Start → GetEmployees Aggregate → If GetEmployees.List.Empty → [True] Message 'No employees found' → [False] Assign EmployeeList = GetEmployees.List → End
1/* Properties on Aggregate node */2Max Records: 203Start Index: StartIndexVar /* Integer local variable, starts at 0 */45/* Accessing Aggregate output in action flow */6GetEmployees.List /* RecordList of Employee */7GetEmployees.List.Current /* Current record in a For Each loop */8GetEmployees.Count /* Total rows in DB matching filters */9GetEmployees.List.Empty /* Boolean: True when no records returned */1011/* Pagination expression */12StartIndex = StartIndex + MaxRecords /* Move to next page */Expected result: Aggregate is limited to 20 rows. GetEmployees.Count accurately reflects the total number of employees matching any filters, usable in a 'Showing X of Y records' label.
Refresh the Aggregate after a data change
Refresh the Aggregate after a data change
After a user creates, updates, or deletes a record, you must refresh the Aggregate so the screen displays current data. OutSystems does not auto-refresh. In your Save button's Client Action flow: Start → CreateEmployee(NewEmployee) → Refresh Data (drag from Toolbox) → select GetEmployees → End The Refresh Data node re-executes the Aggregate query and updates the bound List widget automatically. For Server-side data changes (e.g., in a Server Action called from Client Action): Client Action → Server Action (CreateEmployee) → back in Client Action → Refresh Data (GetEmployees) Alternative: after a CreateEmployee call in a Server Action within the same Client Action flow, the Refresh Data node in the Client Action will re-query the updated data.
Expected result: After clicking Save and creating a new employee, the list on screen immediately updates to include the new record without a full page reload.
Complete working example
1/* ======================================================2 Screen: EmployeeList3 Aggregate: GetEmployees4 ====================================================== */56/* --- Aggregate: GetEmployees --- */7Source: Employee (Data tab → Entities → Database → Employee)8Max Records: 209Start Index: StartIndex /* local Integer variable, default 0 */10Fetch: At Start1112/* Columns used (OutSystems only fetches these): */13Employee.Id14Employee.FirstName15Employee.LastName16Employee.Email17Employee.IsActive18Employee.HireDate1920/* Calculated attribute: FullName */21Expression: Employee.FirstName + " " + Employee.LastName2223/* --- List widget binding (Screen Editor) --- */24List.Source: GetEmployees.List25List.Item.Name: GetEmployees.List.Current.Employee.FullName26List.Item.Email: GetEmployees.List.Current.Employee.Email2728/* --- Count label binding --- */29Expression: "Showing " + IntegerToText(GetEmployees.List.Length) + " of " + IntegerToText(GetEmployees.Count)3031/* --- SaveEmployee Client Action flow (simplified) --- */32/* Start33 → CreateEmployee(NewEmployee) [Server Action]34 → Refresh Data: GetEmployees [Client Action node]35 → Message "Employee saved!" [Feedback Message]36 → End */3738/* --- Pagination Client Action (NextPage) --- */39/* Start40 → Assign: StartIndex = StartIndex + 2041 → Refresh Data: GetEmployees42 → End */4344/* --- Empty state check in action flow --- */45/* If(GetEmployees.List.Empty)46 [True] → Show empty state widget47 [False] → Show list widget */Common mistakes
Why it's a problem: Accessing GetEmployees.List.Current outside of a For Each loop or List widget context
How to avoid: The .Current property is only valid inside a For Each loop body (in an action flow) or within a List widget's template (in the screen editor). To access the first record directly, use GetEmployees.List.First() in an action flow, or check that the list is not empty first.
Why it's a problem: Forgetting to add a Refresh Data node after creating or updating records
How to avoid: OutSystems Aggregates do not auto-refresh when underlying data changes. After any Create, Update, or Delete Server Action call, add a Refresh Data node in the calling Client Action flow and select the Aggregate to re-execute. Without this, the screen shows stale data.
Why it's a problem: Leaving Max Records blank, assuming OutSystems will paginate automatically
How to avoid: OutSystems does NOT paginate automatically. An Aggregate with no Max Records limit fetches every matching row. On a table with 100,000 rows, this causes slow queries and potential timeouts. Always set Max Records explicitly.
Why it's a problem: Creating an Aggregate in a Client Action to fetch sensitive data
How to avoid: Client Action aggregates execute the query in the user's browser session context. For sensitive data, place the Aggregate inside a Server Action. Server Actions run on the server and data never passes through client-side code before your security checks.
Best practices
- Always set Max Records on every Aggregate — leaving it blank fetches ALL rows in the table, which can be thousands of records. Set it to the maximum you will display plus a small buffer.
- Check GetEmployees.List.Empty before accessing .Current or showing data — accessing .Current on an empty list raises a runtime error. Use an If widget with condition GetEmployees.List.Empty to show an empty state.
- Use calculated attributes inside the Aggregate for simple computations instead of computing them in action flows — the database does the work, not the application server.
- Rename Aggregates to describe what they fetch: 'GetActiveEmployees' rather than 'GetEmployees1'. This makes action flows readable when you have multiple aggregates on one screen.
- Place Aggregates in Server Actions (not Client Actions) when the data is sensitive — Client Action aggregates run queries from the browser context, while Server Action aggregates run server-side.
- Use the Preview Data feature (bottom pane in Aggregate editor) to verify your query returns expected results before building UI. You can filter preview data by entering test values.
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I'm building an OutSystems O11 Reactive Web app. I have an Employee entity with these attributes: [list your attributes]. Write me the steps to create a Screen Aggregate that fetches active employees, adds a FullName calculated attribute, and displays them in a List widget with a count label showing 'Showing X of Y records'. Include the exact Service Studio UI paths for each step.
In my OutSystems module, I have a screen called EmployeeList and an Employee entity. Show me how to create a Screen Aggregate that fetches employees with Max Records = 20, sorted by LastName ascending, and describe how to bind it to a Table widget on the screen.
Frequently asked questions
What is the difference between a Screen Aggregate and an Aggregate in a Server Action?
A Screen Aggregate is nested directly under a screen and runs automatically when the screen loads (or on demand if you set Fetch to 'Only on demand'). It is data-fetching logic tied to the screen lifecycle. An Aggregate placed in a Server Action is part of a reusable action that can be called from anywhere — it runs when the Server Action is invoked, not on screen load. Use Server Action aggregates for data fetched in response to user actions (button clicks, form submissions) rather than on initial page load.
Can I use an Aggregate in a Client Action?
Yes. You can drag an Aggregate node into a Client Action flow from the Toolbox. However, Client Action aggregates execute in the browser context using the user's session — they are appropriate for non-sensitive, user-specific data. For sensitive data or queries requiring server-side security, use a Server Action containing the Aggregate and call it from your Client Action.
How do I get the total count of rows including rows beyond Max Records?
Use the .Count property: GetEmployees.Count. This returns the total number of rows matching the Aggregate's filters, regardless of the Max Records limit. For example, if there are 500 employees but Max Records = 20, GetEmployees.List.Length returns 20 (rows fetched) while GetEmployees.Count returns 500 (total matching rows). Use GetEmployees.Count to build 'Page X of Y' pagination UI.
Why does my Aggregate show 'Test/Preview is not available' in the Aggregate editor?
The preview pane requires an active connection to a published environment. If you have unpublished changes (red 1-Click Publish button), the preview may be unavailable. Publish the module first, then the Aggregate editor will connect to the live database and show real preview data. If the issue persists, check your Service Studio connection to the environment via the Environments panel.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation