Primary reference: https://ui5.sap.com/#/topic/549c8e9c70ce4715bdacde98e24c0b55 FPM Explorer: https://sapui5.hana.ondemand.com/test-resources/sap/fe/core/fpmExplorer/index.html Only for OData V4 — the flexible programming model is not available for V2
The flexible programming model (FPM) removes the old choice between "pure Fiori Elements" and "freestyle UI5". You use the Fiori Elements framework as the base and extend exactly what you need — nothing more.
Three extension levels, in order of preference:
- Building Blocks — reuse Fiori Elements components in your own views
- Extension Points — add custom sections, columns, or actions via manifest.json
- Controller Extensions — override or augment standard lifecycle/edit-flow behavior
Building Blocks — use Fiori Elements components in custom views
Building blocks are the recommended way to extend. They keep framework compliance (draft handling, navigation, personalization) while letting you add custom UI.
Table Building Block
<!-- custom section fragment: app/orders/ext/CustomSection.fragment.xml -->
<core:FragmentDefinition
xmlns="sap.m"
xmlns:core="sap.ui.core"
xmlns:macros="sap.fe.macros">
<macros:Table
id="CustomOrderTable"
metaPath="/Orders"
readOnly="false"
enableExport="true"
personalization="Sort,Filter,Column" />
</core:FragmentDefinition>
FilterBar Building Block
<macros:FilterBar
id="OrderFilterBar"
metaPath="/Orders/@com.sap.vocabularies.UI.v1.SelectionFields"
search="true" />
Chart Building Block
<macros:Chart
id="OrdersChart"
metaPath="/Orders/@com.sap.vocabularies.UI.v1.Chart" />
Field Building Block
<macros:Field
metaPath="/Orders/totalAmount"
id="amountField" />
Extension Points — add to existing floorplan pages
Custom Section on Object Page
In manifest.json:
"OrdersObjectPage": {
"type": "Component",
"name": "sap.fe.templates.ObjectPage",
"options": {
"settings": {
"entitySet": "Orders",
"content": {
"body": {
"sections": {
"CustomApprovalSection": {
"template": "orders.ext.CustomApprovalSection",
"position": { "placement": "After", "anchor": "GeneralFacet" },
"title": "Approval History",
"visible": true
}
}
}
}
}
}
}
Fragment: app/orders/ext/CustomApprovalSection.fragment.xml:
<core:FragmentDefinition
xmlns="sap.m"
xmlns:core="sap.ui.core"
xmlns:macros="sap.fe.macros">
<VBox>
<macros:Table
id="ApprovalHistoryTable"
metaPath="/Orders/approvalHistory"
readOnly="true" />
</VBox>
</core:FragmentDefinition>
Custom Column in List Report table
"OrdersList": {
"settings": {
"entitySet": "Orders",
"controlConfiguration": {
"@com.sap.vocabularies.UI.v1.LineItem": {
"columns": {
"CustomStatusColumn": {
"header": "Custom Status",
"template": "orders.ext.CustomStatusColumn",
"position": { "placement": "After", "anchor": "DataField::status" }
}
}
}
}
}
}
Custom Action in toolbar
"actions": {
"CustomExport": {
"press": "orders.ext.ExtensionController.onCustomExport",
"enabled": true,
"text": "Export to Excel"
}
}
Controller Extensions — override standard behavior
// app/orders/ext/ExtensionController.js
sap.ui.define([
'sap/ui/core/mvc/ControllerExtension',
'sap/ui/core/mvc/OverrideExecution'
], (ControllerExtension, OverrideExecution) => {
return ControllerExtension.extend('orders.ext.ExtensionController', {
metadata: {
methods: {
// override standard edit-flow hook
onBeforeSave: { public: true, final: false, overrideExecution: OverrideExecution.After }
}
},
// Runs before the user saves — validate or enrich data
onBeforeSave() {
const context = this.base.getModel().bindContext('/Orders(...)');
// custom validation logic
return Promise.resolve();
},
// Custom action handler (wired in manifest actions)
onCustomExport() {
const table = this.base.byId('fe::table::Orders::LineItem');
// export logic
},
// Lifecycle hook — runs when Object Page is ready
onAfterRendering() {
// DOM-safe customizations
}
});
});
Wire in manifest.json:
"extends": {
"extensions": {
"sap.ui.controllerExtensions": {
"sap.fe.templates.ObjectPage.ObjectPageController": {
"controllerName": "orders.ext.ExtensionController"
}
}
}
}
When to use what
| Need | Approach |
|---|---|
| Add a table/chart/filter from your own data | Building Block |
| Add a whole new section to Object Page | Extension Point + custom fragment |
| Add a column to an existing table | Extension Point controlConfiguration |
| Add a toolbar button with custom logic | Extension Point action + controller extension |
| Override save/edit/navigation flow | Controller Extension |
| Completely custom page in the navigation flow | Custom Page (last resort) |
Common mistakes to avoid
-
❌ Using freestyle SAPUI5 for the whole app when a floorplan covers 80% of the use case
-
✅ Use floorplan + building blocks/extension points for the remaining 20%
-
❌ Building blocks used outside OData V4 context — they require V4 metadata
-
✅ Flexible programming model is V4 only
-
❌ Overriding the entire ObjectPageController when only one method needs changing
-
✅ Only override specific hooks, leave the rest to the framework
-
❌ Using
sap.ui.getCore().byId(...)to find controls — breaks with framework updates -
✅ Use
this.base.byId(...)in controller extensions -
❌ Putting custom section fragments inside
webapp/at root level — hard to maintain -
✅ Keep all extensions in
app/<name>/ext/folder with clear naming