Entities in OutSystems are database tables created in the Data tab. Add attributes with data types, then link entities by setting an attribute's type to another entity's Identifier. OutSystems auto-creates the foreign key index and CRUD actions for every entity you define.
Data Modeling Fundamentals in OutSystems
Before you can query data with Aggregates or build screens, you need a solid data model. In OutSystems, every database table is called an Entity and lives in the Data tab. You define attributes (columns), set their types, and create relationships by referencing other entities — no SQL DDL required. OutSystems compiles your visual model into real SQL Server tables (O11) or Aurora PostgreSQL tables (ODC) and automatically generates CRUD Server Actions for each entity. This tutorial walks you through building a complete data model from scratch, including relationships and a Static Entity for status codes.
Prerequisites
- Service Studio installed and connected to an OutSystems environment (O11), or ODC Studio open
- A Reactive Web or Mobile app created (or an existing app module open)
- Basic understanding of what a database table and primary key are
Step-by-step guide
Open the Data tab and create your first entity
Open the Data tab and create your first entity
In Service Studio, click the Data tab in the top-right panel (the cylinder icon). Expand Entities → Database. Right-click on Database → Add Entity. Name it 'Department'. Service Studio automatically creates an Id attribute (Long Integer, auto-increment primary key). This Id attribute cannot be deleted or renamed — it is the entity's primary key. For ODC Studio: the path is identical — Data tab → Entities → right-click → Add Entity.
Expected result: A 'Department' entity appears under Data tab → Entities → Database with a single Id attribute visible.
Add attributes to the entity
Add attributes to the entity
Right-click the Department entity → Add Entity Attribute. Name it 'Name' and set Data Type to Text. In the Properties panel (right side), set Is Mandatory to Yes and Length to 100. Repeat to add a 'Description' attribute (Text, Is Mandatory: No, Length: 500) and a 'CreatedOn' attribute (Date Time, Is Mandatory: Yes). Data type reference: use Text for names/labels, Integer for counts, Decimal for prices, Boolean for flags, Date for calendar dates, DateTime for timestamps, Binary Data for files.
Expected result: Department entity now has Id, Name (mandatory), Description, and CreatedOn (mandatory) attributes shown in the entity editor.
Create a second entity and add a foreign key relationship
Create a second entity and add a foreign key relationship
Right-click Database → Add Entity. Name it 'Employee'. Add these attributes: - FirstName (Text, Mandatory, Length 100) - LastName (Text, Mandatory, Length 100) - Email (Email, Mandatory) - HireDate (Date, Mandatory) - IsActive (Boolean, Mandatory) - DepartmentId — set Data Type to 'Department Identifier' Setting DepartmentId's type to 'Department Identifier' is how OutSystems creates a foreign key. An index is auto-created. If DepartmentId Is Mandatory = Yes, joins to Department will default to Only With (INNER JOIN). If Is Mandatory = No, joins default to With or Without (LEFT JOIN).
Expected result: Employee entity has a DepartmentId attribute with type 'Department Identifier'. An arrow appears between Employee and Department in the Entity Diagram.
Create a Many-to-Many relationship using a junction entity
Create a Many-to-Many relationship using a junction entity
For Many-to-Many relationships — for example, an Employee can have multiple Skills, and a Skill can belong to multiple Employees — create a junction entity. Right-click Database → Add Entity. Name it 'EmployeeSkill'. Add attributes: - EmployeeId (Employee Identifier, Mandatory) - SkillId (Skill Identifier, Mandatory) - ProficiencyLevel (Integer, Mandatory) First create a 'Skill' entity with a Name attribute, then add EmployeeId and SkillId to EmployeeSkill. The junction entity's Id is still the auto-generated primary key. There is no composite PK in OutSystems — you enforce uniqueness via a CreateOrUpdate pattern or a unique index (available in Service Center).
Expected result: EmployeeSkill entity exists with EmployeeId and SkillId attributes. The Entity Diagram shows arrows from EmployeeSkill to both Employee and Skill.
Create a Static Entity for lookup values
Create a Static Entity for lookup values
Static Entities are design-time lookup tables — equivalent to enum values stored in the database. Right-click Entities → Add Static Entity. Name it 'EmploymentStatus'. OutSystems auto-creates four attributes: Id (Text Identifier), Label (Text), Order (Integer), Is_Active (Boolean). Right-click EmploymentStatus → Add Record. Add records: 'FullTime' (Label: Full Time, Order: 1), 'PartTime' (Label: Part Time, Order: 2), 'Contractor' (Label: Contractor, Order: 3). In the Employee entity, add a 'StatusId' attribute with Data Type set to 'EmploymentStatus Identifier'. Access values in expressions: `Entities.EmploymentStatus.FullTime`
Expected result: EmploymentStatus static entity appears under Entities (with a different icon — a table with a lock). Employee entity has a StatusId attribute referencing it.
View and verify the Entity Diagram
View and verify the Entity Diagram
Right-click anywhere in the Data tab → Add Entity Diagram. Drag all your entities onto the diagram canvas. OutSystems draws arrows for all relationships. The direction of the arrow indicates the FK direction (arrow points from the entity holding the FK to the referenced entity). Verify your model: Department ← Employee (Employee holds DepartmentId FK). Employee ← EmployeeSkill and Skill ← EmployeeSkill (junction table arrows). Employee → EmploymentStatus (StatusId FK). To save the diagram, it auto-saves as part of your module. Click the 1-Click Publish button (green button at top center) to deploy the schema to the database.
Expected result: Entity Diagram shows all entities with relationship arrows. After publishing, the tables are created in the database and CRUD Server Actions (CreateEmployee, GetEmployee, UpdateEmployee, DeleteEmployee, etc.) appear under Logic tab → Server Actions automatically.
Complete working example
1/* --- Entity: Department --- */2Department.Id Long Integer (auto PK)3Department.Name Text(100) Mandatory4Department.Description Text(500)5Department.CreatedOn DateTime Mandatory67/* --- Entity: EmploymentStatus (Static) --- */8EmploymentStatus.Id EmploymentStatus Identifier (auto PK)9EmploymentStatus.Label Text10EmploymentStatus.Order Integer11EmploymentStatus.Is_Active Boolean12/* Records: FullTime, PartTime, Contractor */1314/* --- Entity: Employee --- */15Employee.Id Long Integer (auto PK)16Employee.FirstName Text(100) Mandatory17Employee.LastName Text(100) Mandatory18Employee.Email Email Mandatory19Employee.HireDate Date Mandatory20Employee.IsActive Boolean Mandatory21Employee.DepartmentId Department Identifier Mandatory /* FK → Department */22Employee.StatusId EmploymentStatus Identifier Mandatory /* FK → EmploymentStatus */2324/* --- Entity: Skill --- */25Skill.Id Long Integer (auto PK)26Skill.Name Text(100) Mandatory2728/* --- Entity: EmployeeSkill (junction) --- */29EmployeeSkill.Id Long Integer (auto PK)30EmployeeSkill.EmployeeId Employee Identifier Mandatory /* FK → Employee */31EmployeeSkill.SkillId Skill Identifier Mandatory /* FK → Skill */32EmployeeSkill.ProficiencyLevel Integer Mandatory3334/* --- Auto-generated CRUD Actions (Logic tab) --- */35CreateDepartment(Department) → DepartmentId36GetDepartment(Id) → Department37UpdateDepartment(Department) → (void)38DeleteDepartment(Id) → (void)39CreateOrUpdateDepartment(Department) → DepartmentId40GetForUpdateDepartment(Id) → Department (with row lock)4142/* --- Static Entity access in expressions --- */43If(Employee.StatusId = Entities.EmploymentStatus.FullTime, "FT", "Other")Common mistakes
Why it's a problem: Setting a FK attribute as Not Mandatory when it should be Mandatory
How to avoid: If every Employee must belong to a Department, set DepartmentId as Mandatory. This enforces the relationship at the database level and ensures Aggregates join correctly with Only With (INNER JOIN). Optional FKs (With or Without / LEFT JOIN) should only be used when the relationship is genuinely optional.
Why it's a problem: Trying to create a composite primary key in a junction entity
How to avoid: OutSystems does not support composite PKs. Every entity gets a single auto-increment Id. To prevent duplicate Employee+Skill combinations in EmployeeSkill, use the CreateOrUpdate pattern: Aggregate to check for existing record → If exists → UpdateEmployeeSkill → Else → CreateEmployeeSkill.
Why it's a problem: Deleting an attribute from a published entity without checking for usages
How to avoid: Before deleting an attribute, right-click it → Find Usages (Ctrl+F7). Remove all usages (screens, aggregates, actions) before deleting. TrueChange will show errors for every broken reference. You cannot publish while TrueChange shows errors.
Why it's a problem: Using a regular Entity for values that should be a Static Entity
How to avoid: If your lookup values (status codes, types, categories) are defined by the developer and do not change at runtime, use a Static Entity. This allows you to reference values in expressions as Entities.EmploymentStatus.FullTime, gives compile-time checking, and avoids unnecessary database queries.
Best practices
- Name entities in singular PascalCase (Employee, not Employees or EMPLOYEE) — OutSystems uses the name for auto-generated CRUD actions and code references
- Set Is Mandatory correctly — mandatory FK attributes generate INNER JOINs in Aggregates; optional FK attributes generate LEFT JOINs. Choose based on your business rules, not convenience.
- Keep entity attributes to under 30 per entity. If you need more, consider splitting the entity into a primary entity and a detail entity.
- Use Static Entities for small, stable lookup lists (status codes, categories). Use regular entities for user-managed reference data that changes at runtime.
- Always set Text attribute Length explicitly — the default of 50 characters is often too short for names and email addresses.
- Create an Entity Diagram immediately after defining your entities. It serves as living documentation for the whole team.
- Avoid storing computed values in entities when they can be derived in an Aggregate — for example, FullName can be FirstName + ' ' + LastName rather than a stored column.
- After modifying an entity's attributes on a published app, check the TrueChange tab for broken references before publishing — renaming an attribute breaks all existing code that references it.
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 these entities: [list your entities and their fields]. Please describe the correct relationships (One-to-Many, Many-to-Many) I should define, which attributes should be Mandatory, and which should use Static Entities. Also suggest any junction entities needed for Many-to-Many relationships.
In my OutSystems module, I need a data model for [your domain, e.g. 'a project management app with Projects, Tasks, and Users']. Generate the entity definitions including all attributes, data types, Is Mandatory settings, and foreign key relationships. Format as a list I can follow in Service Studio's Data tab.
Frequently asked questions
Does OutSystems create the database table automatically when I add an entity?
Not immediately — the table is created when you click the 1-Click Publish button (green button at the top of Service Studio). Publishing compiles your .OML file into .NET code and runs the SQL DDL to create or alter the table. Until you publish, the entity exists only in your module definition.
Can I rename an entity after it has been published and has data?
Yes. OutSystems tracks the underlying table name separately from the entity name you see in Service Studio. Renaming the entity in Service Studio renames it in code and the visual editor, but the actual SQL table name in the database uses the physical name (which you can see in Service Center). TrueChange will auto-update all references across your module when you rename.
What is the difference between a Structure and an Entity in OutSystems?
An Entity is persisted to the database and has auto-generated CRUD actions. A Structure is a non-persisted compound data type — like a C# struct or TypeScript interface — used to pass grouped data between actions (for example, an API response shape). Structures live under Data tab → Structures and are never saved to the database.
How do I add a unique constraint to an entity attribute like Email?
OutSystems does not have a visual unique constraint option in Service Studio. For O11, you can create a unique index via Service Center → Factory → Modules → your module → Entities → Add Index and check 'Unique'. In practice, many teams enforce uniqueness at the application logic level: Aggregate to check for existing email before CreateEmployee, then raise a user exception if found.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation