OutSystems has 13 primitive types (Text, Integer, Long Integer, Decimal, Boolean, Date, Time, DateTime, Binary Data, Currency, Email, Phone Number, Entity Identifier) and 4 compound types (Record, Structure, List, Object). Expressions use a syntax similar to spreadsheet formulas — functions like FormatDate(), If(), Length(), and Round() are built-in and available everywhere in the platform.
Why OutSystems Expressions Are Different
OutSystems does not use JavaScript or SQL expressions — it has its own expression language used consistently across screen widgets, action flows, Aggregates, and data assignments. Learning this language is foundational: every screen expression, every Aggregate filter, every If node condition, and every Assign node uses it.
This tutorial is a reference guide, not a project-building tutorial. It covers the full type system and the most-used built-in functions organized by category. Keep it bookmarked for day-to-day development.
Prerequisites
- Service Studio installed and a module open (any Reactive Web or Mobile app)
- No prior OutSystems knowledge required — this is a foundational reference
- Basic understanding of what data types are (text, numbers, dates)
Step-by-step guide
Understand primitive data types and their default values
Understand primitive data types and their default values
Every variable, entity attribute, and function parameter in OutSystems has a strict data type. Unlike JavaScript, there is no dynamic typing. The Expression editor enforces type compatibility and TrueChange will show errors for type mismatches. Primitive types and defaults: - Text: default "", configurable length (50 chars for DB columns, unlimited for variables) - Integer: default 0, range -2,147,483,648 to 2,147,483,647 - Long Integer: default 0, range -2^63 to 2^63-1. Used for entity IDs automatically. - Decimal: default 0.0, max 8 decimal places, range ±2^96 - Boolean: default False - Date: default #1900-01-01# - Time: default #00:00:00# - DateTime: default #1900-01-01 00:00:00# - Binary Data: default empty byte array. Use for file content and images. - Currency: same as Decimal, semantic distinction only - Email: Text with email format validation in forms - Phone Number: Text with phone format validation in forms CRITICAL: OutSystems does NOT use NULL for most types. 'Empty' is a default value, not NULL. Only Entity Identifier types can be 'null' via NullIdentifier().
1/* Date literals — always use # delimiters */2#2026-01-15# /* Date */3#14:30:00# /* Time */4#2026-01-15 14:30:00# /* DateTime */56/* Checking for empty values — NOT null */7Length(TextVar) = 0 /* empty text check */8IntVar = 0 /* empty integer check */9DateVar = NullDate() /* empty date check (#1900-01-01#) */10IdentifierVar = NullIdentifier() /* empty entity id check */1112/* Type annotation in Service Studio */13/* Set these in Properties panel when creating variables: */14/* Name: ProductName Type: Text Default: "" */15/* Name: Price Type: Decimal Default: 0.0 */16/* Name: CreatedOn Type: DateTime Default: CurrDateTime() */Expected result: You can identify the correct data type for any piece of data and know its default 'empty' value representation.
Write Text function expressions
Write Text function expressions
Text functions operate on Text type values. Use them in Expression widgets on screens, in Assign node assignments, or as Aggregate filter conditions. Text concatenation uses the + operator. There is no template literal syntax — use multiple + operators. Most commonly used text functions: - Length(t): number of characters - Substr(t, start, length): extract substring, 0-based start index - ToUpper(t) / ToLower(t): case conversion - Trim(t): remove leading and trailing whitespace - Replace(t, search, replace): replace ALL occurrences - Index(t, search, startIndex): find position, returns -1 if not found - NewLine(): inserts a line break character Pros of OutSystems text functions vs JavaScript: they handle empty strings gracefully without throwing exceptions (Length("") = 0, not an error).
1/* Text concatenation */2"Hello, " + Customer.FirstName + " " + Customer.LastName3"Order #" + IntegerToText(Order.Id)45/* Substring */6Substr("Hello World", 6, 5) /* → "World" */7Substr(Employee.LastName, 0, 1) /* → first letter */89/* Length and empty check */10Length(Trim(EmailInput)) > 0 /* non-empty after trim */11If(Length(Description) = 0, "No description", Description)1213/* Case conversion */14ToUpper(Substr(Name, 0, 1)) + Substr(Name, 1, Length(Name) - 1) /* Title Case */15ToLower(Employee.Email) /* normalize for comparison */1617/* Replace */18Replace(PhoneNumber, "-", "") /* remove dashes */19Replace(Description, NewLine(), " ") /* replace newlines with spaces */2021/* Find substring */22Index(ToLower(Email), "@gmail.com", 0) >= 0 /* is Gmail address */23Index(ProductName, SearchTerm, 0) /* returns -1 if not found */Expected result: You can write text manipulation expressions for screen labels, Aggregate calculated attributes, and filter conditions using the built-in function names.
Write Date and DateTime function expressions
Write Date and DateTime function expressions
Date functions are among the most-used expressions in business applications. OutSystems has dedicated functions for date arithmetic, comparison, and component extraction. Date creation and current values: - CurrDate(): today's date (Date type) - CurrDateTime(): current date and time (DateTime type) - CurrTime(): current time (Time type) - NewDate(year, month, day): create a specific Date - NewDateTime(y, mo, d, h, mi, s): create a specific DateTime Date arithmetic: - AddDays(dt, n): add n days (use negative to subtract) - AddMonths(dt, n): add n months - AddYears(dt, n): add n years - DiffDays(dt1, dt2): days between two dates (dt2 - dt1) Date components: - Year(dt), Month(dt), Day(dt): extract components as Integer - Hour(dt), Minute(dt), Second(dt): time components - DayOfWeek(dt): 0=Sunday, 1=Monday, ..., 6=Saturday
1/* Current date/time */2CurrDate() /* today */3CurrDateTime() /* now */45/* Date arithmetic */6AddDays(CurrDate(), 30) /* 30 days from now */7AddDays(CurrDate(), -7) /* 7 days ago */8AddMonths(Contract.StartDate, 12) /* 1 year after start */9AddYears(Employee.BirthDate, 18) /* 18th birthday */1011/* Date difference */12DiffDays(Employee.HireDate, CurrDate()) /* days employed */13DiffDays(CurrDate(), Order.DueDate) /* days until due (negative = overdue) */14DiffDays(StartDate, EndDate) / 7 /* approximate weeks */1516/* Date components */17Year(CurrDateTime()) /* current year as Integer */18Month(Order.OrderDate) /* order month: 1-12 */19Day(CurrDate()) /* day of month: 1-31 */2021/* Conditional date logic */22If(DiffDays(Order.DueDate, CurrDate()) > 0, "Overdue", "On Time")23If(Employee.HireDate > AddYears(CurrDate(), -1), "New Hire", "Established")2425/* Date formatting for display */26"Hired: " + FormatDate(Employee.HireDate, "MMMM dd, yyyy")27/* Format patterns: yyyy=year, MM=month, dd=day, HH=hour, mm=minute */Expected result: You can calculate date differences, add/subtract time periods, extract date components, and format dates for display in screen expressions.
Write Math and Conversion function expressions
Write Math and Conversion function expressions
Math functions operate on numeric types (Integer, Long Integer, Decimal, Currency). Math functions: - Round(n, places): round to decimal places (uses banker's rounding for .5 cases) - Trunc(n): remove decimal part (truncate, not round) - Abs(n): absolute value - Mod(n, m): modulo/remainder - Sqrt(n): square root - Power(n, m): n raised to power m Type conversion is explicit in OutSystems — types do not automatically convert. Use these functions: - IntegerToText(n): Integer → Text for display - TextToInteger(t): Text → Integer (returns 0 if invalid) - DecimalToText(n): Decimal → Text - TextToDecimal(t): Text → Decimal (returns 0.0 if invalid) - DateToText(d): Date → Text (format: "YYYY-MM-DD") - DateTimeToDate(dt): DateTime → Date (drops time component) - BooleanToInteger(b): True=1, False=0
1/* Math operations */2Round(Price * Quantity, 2) /* total with 2 decimals */3Round(TaxRate * 100, 1) /* percentage display */4Trunc(TotalAmount) /* floor without rounding */5Abs(AccountBalance) /* ensure positive value */6Mod(RowIndex, 2) = 0 /* even/odd check (alternate row styling) */7Power(2, 10) /* 1024 */89/* Type conversion — required before concatenation */10"Total: $" + DecimalToText(Round(Price, 2)) /* Decimal to string */11"Item count: " + IntegerToText(GetProducts.Count) /* Integer to string */12"ID: " + IntegerToText(Employee.Id) /* Long Integer to string */1314/* Safe text-to-number conversion */15TextToIntegerValidate(InputValue) /* returns True if convertible */16If(TextToIntegerValidate(AgeInput), TextToInteger(AgeInput), 0)1718/* Date-to-text for sorting or display */19DateToText(CurrDate()) /* "2026-03-30" */20DateTimeToText(CurrDateTime()) /* "2026-03-30 14:30:00" */2122/* Strip time from DateTime */23DateTimeToDate(Order.CreatedAt) /* DateTime → Date, drops HH:MM:SS */Expected result: You can perform calculations with proper rounding, convert between types for display or comparison, and handle invalid input safely.
Write conditional expressions using If()
Write conditional expressions using If()
The If() function is the primary conditional expression in OutSystems. It works everywhere: screen expressions, Aggregate filters, Assign nodes, and calculated attributes. Syntax: `If(BooleanCondition, TrueValue, FalseValue)` IMPORTANT: Both TrueValue and FalseValue must be the SAME data type. If(IsActive, "Active", 1) causes a TrueChange type error — both branches must return Text. If() can be nested for multi-condition logic: `If(Age >= 65, "Senior", If(Age >= 18, "Adult", "Minor"))` For complex multi-branch logic, use a Switch node in action flows instead of deeply nested If() expressions — it is more readable. Boolean operators in conditions: - `and`: both conditions must be true - `or`: at least one condition must be true - `not`: negate a boolean value
1/* Basic If expression */2If(Employee.IsActive, "Active", "Inactive")34/* Numeric condition */5If(Order.TotalAmount > 1000, "Premium", "Standard")67/* Null/empty check */8If(Length(Trim(Employee.MiddleName)) = 0, Employee.FirstName + " " + Employee.LastName,9 Employee.FirstName + " " + Employee.MiddleName + " " + Employee.LastName)1011/* Nested If — three-way */12If(Score >= 90, "A",13 If(Score >= 80, "B",14 If(Score >= 70, "C", "F")))1516/* Date condition */17If(DiffDays(Invoice.DueDate, CurrDate()) > 0, "OVERDUE", "Current")1819/* Combined and/or */20If(Employee.IsActive and Employee.DepartmentId <> NullIdentifier(), "Assigned", "Unassigned")21If(Order.Status = OrderStatus.Pending or Order.Status = OrderStatus.Processing, True, False)2223/* CSS class selection (for styling) */24If(IsSelected, "card selected", "card")25If(Mod(RowIndex, 2) = 0, "row-even", "row-odd")2627/* Static Entity access in condition */28If(Employee.StatusId = Entities.EmploymentStatus.FullTime, "FT", "PT")Expected result: You can write simple and nested If() expressions that compile without TrueChange type errors, using correct boolean operators and consistent output types.
Find and use built-in functions in the Expression editor
Find and use built-in functions in the Expression editor
Every place you write an expression in Service Studio has the Expression editor. Open it by clicking the expression field (the fx icon or any field showing an expression). The Expression editor has: - A text area where you type the expression - A function browser on the left showing all built-in functions organized by category: Text, Date, Math, Numeric, Format, Logic, Data Conversion, URL - An element browser showing your module's variables, aggregates, entities, and site properties - Type-ahead autocomplete as you type function names Useful functions NOT covered above: - `FormatDate(dt, format)`: format DateTime for display. Format string: 'yyyy-MM-dd', 'dd/MM/yyyy', 'MMMM yyyy' - `NullIdentifier()`: returns the null value for any Identifier type - `NullDate()`: returns #1900-01-01# (null equivalent for Date) - `NullTextIdentifier()`: returns "" (null for text-based identifiers) - `NewIdentifier()`: creates a new unique Identifier (O11 GUID-based, ODC auto-increment) - `GetUserId()`: returns the Id of the currently logged-in user
1/* FormatDate — common format strings */2FormatDate(CurrDate(), "yyyy-MM-dd") /* 2026-03-30 */3FormatDate(CurrDate(), "dd/MM/yyyy") /* 30/03/2026 */4FormatDate(CurrDate(), "MMMM dd, yyyy") /* March 30, 2026 */5FormatDate(CurrDateTime(), "HH:mm") /* 14:30 */6FormatDate(CurrDateTime(), "MMM dd 'at' HH:mm") /* Mar 30 at 14:30 */78/* User context */9GetUserId() /* current user's Id */1011/* Null checks and defaults */12If(ManagerId = NullIdentifier(), "No Manager", GetManager.List.Current.Manager.Name)1314/* URL encoding */15EncodeURL(SearchTerm) /* encode for query strings */1617/* Build DateTime from parts */18NewDateTime(2026, 3, 30, 14, 0, 0) /* 2026-03-30 14:00:00 */19BuildDateTime(CurrDate(), #09:00:00#) /* today at 9am */Expected result: You can use the Expression editor's function browser to discover and insert any built-in function with autocomplete, without needing to memorize the full function library.
Complete working example
1/* =====================================================2 OutSystems Expression Function Quick Reference3 Copy-paste these into Expression editor fields4 ===================================================== */56/* === TEXT === */7Length(Trim(EmailVar)) > 0 /* non-empty check */8ToUpper(Substr(Name, 0, 1)) + ToLower(Substr(Name, 1, Length(Name) - 1)) /* Title Case */9Replace(PhoneNumber, "-", "") /* strip dashes */10Index(ToLower(Haystack), ToLower(Needle), 0) >= 0 /* contains check */11Employee.FirstName + " " + Employee.LastName /* concatenation */1213/* === DATE === */14AddDays(CurrDate(), 30) /* 30 days ahead */15AddDays(CurrDate(), -7) /* 7 days ago */16DiffDays(StartDate, EndDate) /* date difference */17Year(CurrDateTime()) /* current year */18FormatDate(CurrDate(), "MMMM dd, yyyy") /* formatted date */19FormatDate(CurrDate(), "yyyy-MM-dd") /* ISO format */20BuildDateTime(CurrDate(), #09:00:00#) /* today at 9am */21DateTimeToDate(Order.CreatedAt) /* strip time */2223/* === MATH === */24Round(Price * Quantity * (1 + TaxRate), 2) /* rounded total */25Trunc(DiffDays(HireDate, CurrDate()) / 365) /* years of service */26Mod(RowIndex, 2) = 0 /* even row check */27Abs(AccountBalance) /* positive value */2829/* === CONVERSION === */30"$" + DecimalToText(Round(Price, 2)) /* price display */31"Ref #" + IntegerToText(Order.Id) /* ID display */32If(TextToIntegerValidate(AgeText), TextToInteger(AgeText), 0) /* safe conversion */33DateToText(CurrDate()) /* "2026-03-30" */3435/* === CONDITIONAL === */36If(IsActive, "Active", "Inactive") /* boolean gate */37If(Balance < 0, "Overdrawn", If(Balance = 0, "Zero", "Positive")) /* three-way */38If(Length(Notes) = 0, "(none)", Notes) /* fallback text */39If(Mod(RowIndex, 2) = 0, "row-even", "row-odd") /* alternating CSS */4041/* === NULL/EMPTY === */42NullDate() /* empty date */43NullIdentifier() /* empty entity id */44NullTextIdentifier() /* "" for text id */45GetUserId() /* current user id */4647/* === STATIC ENTITY ACCESS === */48If(Employee.StatusId = Entities.EmploymentStatus.FullTime, "FTE", "Other")49Employee.StatusId = Entities.Priority.HighCommon mistakes
Why it's a problem: Mixing data types in the If() function branches — If(Condition, 42, "none") causes a type error
How to avoid: Both branches of If() must return the same type. Convert to a consistent type: If(Condition, IntegerToText(42), "none") returns Text in both branches. If you want Integer output: If(Condition, 42, 0) — use 0 instead of 'none'.
Why it's a problem: Using = to check if a date variable is 'not set', comparing to #1900-01-01# as a literal
How to avoid: Use NullDate() instead of the literal: `HireDate = NullDate()` is correct. Hard-coding #1900-01-01# works but is fragile and unclear. Similarly, for empty identifiers use NullIdentifier(), not 0 or -1.
Why it's a problem: Using Length() on a non-Text type to check for empty value
How to avoid: Length() only works on Text type. For Integer, compare to 0: `IntVar = 0`. For Date, compare to NullDate(). For Entity Identifier, compare to NullIdentifier(). For Binary Data, use Length(BinaryVar) after converting — Binary Data Length() works differently.
Why it's a problem: Expecting Decimal math to use standard floating-point rounding
How to avoid: OutSystems uses 'round half to even' (banker's rounding) for the Round() function in logic. 2.5 rounds to 2, 3.5 rounds to 4. For display purposes this is usually fine. If you need standard rounding (2.5 → 3), add a small epsilon: Round(Value + 0.00000001, 2).
Best practices
- Choose Email type for email attributes instead of Text — Email type adds built-in format validation in Form widgets at no extra cost, and communicates intent clearly to other developers.
- Use Long Integer for entity IDs and counts that might exceed 2 billion. OutSystems auto-assigns Long Integer to entity Id attributes. Use Long Integer for any local variable that holds an entity Id.
- Never compare a Date variable to #1900-01-01# directly in expressions — use NullDate() instead. NullDate() is self-documenting and if OutSystems ever changes the null date sentinel, NullDate() will still work.
- Keep nested If() expressions to a maximum of 3 levels — deeper nesting is hard to read and maintain. Move complex conditional logic to a local variable or a separate Client Action with a Switch node.
- Always convert numeric types to Text before concatenation — OutSystems will show a TrueChange error if you try to concatenate a Decimal directly with a Text string. Use DecimalToText() or IntegerToText() explicitly.
- Use TextToIntegerValidate() before TextToInteger() when the input is user-provided — TextToInteger() of an invalid string returns 0 silently, which can cause data quality issues if 0 is a valid ID.
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I'm building an OutSystems Reactive Web app. I need to write an expression that: (1) shows a customer's full name as 'LastName, FirstName', (2) shows their membership duration in years and months from a HireDate attribute, and (3) shows 'NEW' if they joined within the last 30 days, otherwise 'MEMBER'. Write these as OutSystems expression syntax using the built-in functions (not JavaScript).
In my OutSystems module, I have these attributes: Employee.HireDate (Date), Employee.Salary (Decimal), Employee.IsActive (Boolean), Employee.StatusId (EmploymentStatus Identifier). Write screen expression examples for: formatted hire date, salary with currency symbol, status label from Static Entity, and years of service. Use OutSystems expression function syntax.
Frequently asked questions
Does OutSystems have a null type? How do I check if a value is null?
OutSystems does not use NULL for most types — empty values use type-specific defaults (0 for Integer, "" for Text, #1900-01-01# for Date, False for Boolean). Only Entity Identifier types can be null, represented by NullIdentifier(). To check for an unset date, use `HireDate = NullDate()`. To check for an unset entity reference, use `DepartmentId = NullIdentifier()`. To check for empty text, use `Length(Trim(TextVar)) = 0`.
Can I use JavaScript expressions in OutSystems screen widgets?
No. Expression widgets on screens use OutSystems expression syntax, not JavaScript. For client-side JavaScript logic, use a JavaScript node in a Client Action flow — but this is for DOM manipulation and browser APIs, not for data computation. Keep data logic in OutSystems expression syntax; it is validated by TrueChange and works identically on both O11 and ODC.
How do I format a number as currency (with $ sign and commas)?
OutSystems does not have a built-in currency formatter with thousand separators. The common approach is: `"$" + DecimalToText(Round(Amount, 2))` which gives `$1234.56` without commas. For thousand-separator formatting, either use a Forge component (Formatter utilities), write a custom Server Action that formats the number, or use a JavaScript node in a Client Action to format using JavaScript's toLocaleString().
What is the difference between Currency and Decimal data types?
Currency and Decimal are identical in technical behavior — same range, same precision, same expression functions. Currency is a semantic marker that signals to developers that the field represents a monetary value. There is no special formatting or currency-code behavior built into the Currency type. Choose Currency for money fields to communicate intent; the runtime behavior is exactly the same as Decimal.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation