How CAP maps CDS service definitions to OData V4 — metadata, system query options ($filter, $expand, $select, $top), and Batch.
How CAP maps your CDS service to OData V4 — the metadata document, all system query options, batch requests, error response format, and how to call your API from JavaScript without a library.
OData (Open Data Protocol) is a REST-based standard for querying and manipulating data. Version 4 is the current standard, supported natively by SAP Fiori, Excel, Power BI, and many other tools.
CAP automatically serves your service as an OData V4 API — you don't write any OData code. The CDS definition is all you need.
Every OData service exposes a metadata document at $metadata:
GET /odata/v4/catalog/$metadata
This is an XML document (EDMX format) that describes every entity, field type, key, navigation, action, and function in your service. Client tools (Fiori, Excel) read this to understand your API shape.
<EntityType Name="Books">
<Key><PropertyRef Name="ID"/></Key>
<Property Name="ID" Type="Edm.Guid" Nullable="false"/>
<Property Name="title" Type="Edm.String" MaxLength="100"/>
<Property Name="price" Type="Edm.Decimal" Precision="10" Scale="2"/>
</EntityType>
CAP generates this automatically from your CDS model. You never write EDMX manually.
OData provides powerful built-in query capabilities via URL parameters:
$filter — filter resultsGET /odata/v4/catalog/Books?$filter=price lt 20
GET /odata/v4/catalog/Books?$filter=price ge 10 and price le 50
GET /odata/v4/catalog/Books?$filter=contains(title, 'Clean')
GET /odata/v4/catalog/Books?$filter=stock gt 0 and author eq 'Adams'
Filter operators:
| Operator | Meaning | Example |
|---------|---------|---------|
| eq | equals | status eq 'active' |
| ne | not equals | status ne 'archived' |
| lt le | less than / or equal | price lt 20 |
| gt ge | greater than / or equal | stock ge 5 |
| and or not | logical | a gt 1 and b lt 10 |
| contains(f, v) | string contains | contains(title,'CAP') |
| startswith(f,v) | string starts with | startswith(name,'S') |
| endswith(f,v) | string ends with | endswith(email,'.com') |
$select — choose fieldsGET /odata/v4/catalog/Books?$select=ID,title,price
Response only includes the listed fields. Reduces payload size — critical for mobile apps.
$orderby — sort resultsGET /odata/v4/catalog/Books?$orderby=price asc
GET /odata/v4/catalog/Books?$orderby=title desc,price asc
$top and $skip — paginationGET /odata/v4/catalog/Books?$top=10&$skip=0 # page 1
GET /odata/v4/catalog/Books?$top=10&$skip=10 # page 2
GET /odata/v4/catalog/Books?$top=10&$skip=20 # page 3
$count — total record countGET /odata/v4/catalog/Books?$count=true&$top=10
Response includes @odata.count: 1234 — the total count before pagination.
$expand — include related entities# Include author details with each book
GET /odata/v4/catalog/Books?$expand=author
# Nested expand
GET /odata/v4/catalog/Orders?$expand=items($expand=product)
# Expand with filter on the nested set
GET /odata/v4/catalog/Authors?$expand=books($filter=price lt 20)
GET /odata/v4/catalog/Books
?$filter=stock gt 0
&$select=ID,title,price
&$orderby=price asc
&$top=5
&$count=true
&$expand=author($select=name)
{
"@odata.context": "$metadata#Books",
"@odata.count": 42,
"value": [
{
"ID": "abc-123",
"title": "Clean Code",
"price": 35.00,
"author": {
"name": "Robert C. Martin"
}
}
]
}
Always wrapped in { "value": [...] }. Single entity reads (by key) return the object directly without "value".
{
"error": {
"code": "404",
"message": "Books with ID 'xyz' not found.",
"target": "ID",
"details": []
}
}
CAP returns this format automatically for validation errors, not-found, and authorization failures.
OData $batch lets you send multiple requests in one HTTP call — critical for performance in Fiori apps:
POST /odata/v4/catalog/$batch
Content-Type: multipart/mixed; boundary=batch_boundary
--batch_boundary
Content-Type: application/http
GET Books?$select=ID,title HTTP/1.1
--batch_boundary
Content-Type: application/http
GET Authors HTTP/1.1
--batch_boundary--
CAP handles batch requests automatically. Fiori apps use $batch internally for every UI interaction.
No OData library needed for simple cases:
// Fetch all books with price filter
const response = await fetch(
'/odata/v4/catalog/Books?$filter=price lt 20&$select=ID,title,price',
{ headers: { 'Accept': 'application/json' } }
);
const { value: books } = await response.json();
// Create a book
await fetch('/odata/v4/catalog/Books', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: 'New Book', price: 19.99 })
});
For Fiori/UI5 apps, use sap.ui.model.odata.v4.ODataModel — it handles $batch, CSRF tokens, and metadata automatically.
$top and $skip?$expand to include the author with each book?@odata.context and value wrapper in responses?In Lesson 10 we learn about CAP's generic handlers — what CAP handles automatically so you don't have to write repetitive CRUD code.
Sign in for progress tracking & Q&A →