Every modern SAPUI5 app has a manifest.json sitting in its webapp folder, quietly running the show. It declares the app's identity, its models, its routing, its i18n files, even which UI5 libraries to load. And yet most developers only touch it when something breaks — a route that doesn't resolve, a model that's suddenly undefined.
Time to fix that. Here's what each section does, with an annotated example you can keep as a reference.
What manifest.json actually is
Think of manifest.json as the app's birth certificate plus instruction manual. When the Component loads, the framework reads this file first and configures everything from it: the app ID and version, the models to instantiate, the routing table, the resource bundle for translations.
Before manifest.json existed, all of this lived in JavaScript — Component.js metadata, manual model creation in init(), routing configured in code. It worked, but every app reinvented the same boilerplate. The descriptor moved that configuration into declarative JSON, which is easier to read, easier to validate, and tooling-friendly (the Fiori generators and editors understand it natively).
The mental model:Component.jsis code,manifest.jsonis configuration. If it describes what the app needs rather than how it behaves, it belongs in the manifest.
The three top-level sections
A manifest has three main blocks. Here's the skeleton:
{
"sap.app": {
// WHO is this app: id, version, title, data sources
},
"sap.ui": {
// HOW it presents: device types, supported themes
},
"sap.ui5": {
// WHAT it needs: models, routing, resources, dependencies
}
}
sap.app holds identity: the app id (which must match the Component namespace), type: "application", version numbers, and the human-readable title pulled from the i18n bundle. It also declares dataSources — named OData services the app consumes.
sap.ui is small but important: technology: "UI5", and deviceTypes declaring whether the app supports desktop, tablet, and phone. This feeds the Fiori launchpad's filtering — a phone-only app won't be offered on desktop.
sap.ui5 is where the real work happens: dependencies, models, routing, resource bundles. Most of your editing time goes here.
Annotated example: the sap.ui5 section
This is the part worth studying line by line:
"sap.ui5": {
"dependencies": {
"minUI5Version": "1.120.0",
"libs": {
"sap.m": {},
"sap.ui.core": {},
"sap.f": {}
}
},
"models": {
"i18n": {
"type": "sap.ui.model.resource.ResourceModel",
"settings": {
"bundleName": "my.app.i18n.i18n",
"supportedLocales": ["en", "de"],
"fallbackLocale": "en"
}
},
"": {
"dataSource": "mainService",
"settings": {
"synchronizationMode": "None",
"operationMode": "Server",
"autoExpandSelect": true
}
}
},
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"async": true,
"viewPath": "my.app.view",
"controlId": "app",
"controlAggregation": "pages"
},
"routes": [
{
"name": "master",
"pattern": "",
"target": "master"
},
{
"name": "detail",
"pattern": "detail/{orderId}",
"target": "detail"
}
],
"targets": {
"master": {
"viewName": "Master",
"viewLevel": 1
},
"detail": {
"viewName": "Detail",
"viewLevel": 2
}
}
},
"resources": {
"css": [
{ "uri": "css/style.css" }
]
}
}
A few things to notice. The i18n model is a named model pointing at the resource bundle — that's why {i18n>title} bindings work everywhere. The unnamed "" model is the default OData model, wired to the mainService data source declared under sap.app. Models declared here are created automatically at startup — no setModel() calls in init() needed.
The routing config sets defaults for every route: which router class, that views are XML and loaded asynchronously, and — critically — controlId plus controlAggregation, which tell the router where to place navigated views. Get controlId wrong and navigation silently does nothing, which is one of the most common manifest debugging sessions.
Route patterns and parameters
The pattern is the hash fragment the route responds to. An empty pattern "" is the default route — what loads when the app opens. "detail/{orderId}" captures a segment into a parameter the detail controller reads:
onInit: function () {
this.getOwnerComponent().getRouter()
.getRoute("detail")
.attachPatternMatched(this._onOrderMatched, this);
},
_onOrderMatched: function (oEvent) {
var sOrderId = oEvent.getParameter("arguments").orderId;
this.getView().bindElement("/Orders('" + sOrderId + "')");
}
Optional parameters use the :param: syntax — "detail/{orderId}/:tab:" matches with or without the tab segment. Query parameters (?filter=open) are available too, though they're less common in Fiori apps where state usually lives in the binding.
If navigation "does nothing" — no error, no view change — check three things in order: the route name matches thenavTocall, thepatternmatches the hash, andcontrolIdpoints at a control that actually exists in the root view.
Data sources: naming your backends
Under sap.app, the dataSources block gives each backend a name:
"sap.app": {
"id": "my.app",
"type": "application",
"dataSources": {
"mainService": {
"uri": "/sap/opu/odata/sap/ZORDER_SRV/",
"type": "OData",
"settings": {
"odataVersion": "2.0"
}
}
}
}
The model then references it by name ("dataSource": "mainService"). This indirection is what makes destinations and flexible deployment work — the URI here is relative, and the approuter or launchpad resolves it against the real backend at runtime. Hardcoding absolute backend URLs in the manifest is a classic mistake that breaks the moment the app moves between systems.
Common pitfalls
Trailing commas. JSON has no mercy — one trailing comma and the whole descriptor fails to parse, usually with an unhelpful error. Use an editor with JSON validation.
Namespace mismatch. The sap.app/id must match the Component's namespace exactly, including case. my.app vs my.App will cost you an afternoon.
Models in init(). If you declare models in the manifest and create them in Component.js init(), the code version wins and the manifest version is silently ignored. Pick one place — the manifest — and delete the code.
Forgetting async: true. Without it, views load synchronously, blocking the UI thread. There's no good reason to leave it off in a new app.
Bottom line
The manifest is the app's single source of truth for configuration: identity in sap.app, device support in sap.ui, and models, routing, and resources in sap.ui5. Learn to read it fluently and half of all "why isn't my app working" mysteries solve themselves in the first five minutes.