Declare entities with key fields, built-in scalar types, virtual fields, and computed elements.
Every way to declare fields in CDS — scalars, virtual elements, calculated fields, custom types, and how to define reusable types that keep your schema consistent.
An entity in CDS maps to a database table. Every entity must have at least one key field.
entity OrderItems {
key order : Association to Orders;
key lineNo : Integer;
product: String(50);
qty : Integer;
}
Avoid compound keys where possible. UUID keys are simpler, and CAP handles them automatically.
entity Products {
key ID : UUID;
status : String(20) default 'active';
stock : Integer default 0;
taxRate: Decimal default 0.20;
}
entity Person {
key ID : UUID;
name : String not null; // required
nickname : String; // nullable (default)
age : Integer; // nullable
}
Virtual elements exist in the service layer but are not persisted to the database. They are computed or populated by handlers at runtime.
entity Books {
key ID : UUID;
title : String(100);
price : Decimal(10,2);
stock : Integer;
// Virtual — computed in a handler, not stored in DB
virtual discount : Decimal(10,2);
virtual isAvailable : Boolean;
}
In your handler, populate them:
this.after('READ', 'Books', books => {
for (const book of books) {
book.discount = book.price > 30 ? book.price * 0.1 : 0;
book.isAvailable = book.stock > 0;
}
});
Calculated elements are persisted — the value is computed from other fields and stored in the DB:
entity OrderItems {
key ID : UUID;
quantity : Integer;
unitPrice: Decimal(10,2);
// Calculated: stored in DB, recomputed on INSERT/UPDATE
totalPrice = quantity * unitPrice : Decimal(10,2);
}
Calculated vs Virtual: Calculated = stored in DB, computed from other fields. Virtual = not in DB, computed in handler at read time.
Define reusable types to keep your schema consistent:
// db/types.cds
namespace com.bookshop.types;
type BookTitle : String(100);
type Price : Decimal(10, 2);
type ISBN : String(13);
type Status : String(20) enum {
active = 'active';
inactive = 'inactive';
archived = 'archived';
};
Use them in entities:
using { com.bookshop.types } from './types';
entity Books {
key ID : UUID;
title : types.BookTitle;
price : types.Price;
isbn : types.ISBN;
status: types.Status default 'active';
}
Enums enforce that a field can only have specific values:
type Priority : Integer enum {
low = 1;
medium = 2;
high = 3;
urgent = 4;
}
entity Tasks {
key ID : UUID;
title : String(200);
priority: Priority default 2;
}
CAP validates enum values on INSERT and UPDATE automatically. Trying to set priority = 99 will get rejected.
Structured types let you define a record shape and reuse it:
type Address {
street : String(100);
city : String(50);
country : String(50);
zip : String(10);
}
entity Customers {
key ID : UUID;
name : String(100);
billingAddress : Address;
shippingAddress : Address;
}
In HANA: Structured types are stored as a JSON column. In SQLite, CAP flattens them:
billingAddress_street,billingAddress_city, etc.
entity Products {
key ID : UUID;
tags : array of String(50);
}
Arrays are stored as JSON in both SQLite and HANA. They are useful for lightweight tagging, but not for data you need to filter or join on. For those cases, use a separate entity with an association.
entity Users {
key ID : UUID;
@mandatory
email : String(254);
@assert.range: [18, 120]
age : Integer;
@assert.format: '^[A-Z]{2}[0-9]{6}$'
passport: String(8);
}
| Annotation | What it does |
|---|---|
@mandatory |
Field must be provided on CREATE |
@assert.range |
Number must be in [min, max] |
@assert.format |
String must match regex |
@assert.unique |
Value must be unique across all rows |
Use UUID keys everywhere — they work offline, don't require a sequence, and are safe for distributed systems.
Define shared types in a types.cds file — then use them across all entities for consistency.
Avoid over-normalizing — if a piece of data is always read with its parent and never queried independently, consider structured types instead of a separate entity.
Use enums instead of magic strings — Status.active is clearer than 'active' and CAP validates it.
@mandatory and @assert.range to a field?In Lesson 7 we build on this to define proper service definitions — controlling what operations are allowed and how entities are projected.
Sign in for progress tracking & Q&A →