Help us improve
Share bugs, ideas, or general feedback.
From ui5
Enforces SAP UI5 coding standards including async loading, ComponentSupport, CSP compliance, OData binding, i18n, TypeScript events, CAP integration, and form layouts (Form with ColumnLayout).
npx claudepluginhub anthropics/claude-plugins-official --plugin ui5How this skill is triggered — by the user, by Claude, or both
Slash command
/ui5:ui5-best-practicesThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill enforces UI5 development standards derived from official SAP guidelines. It covers the four critical areas: coding guidelines, tooling integration, CAP integration, and form creation rules.
Develops SAP UI5 applications: freestyle and Fiori Elements apps, custom controls, OData v2/v4 integration, data binding, MVC patterns, routing, QUnit/OPA5 testing, performance, accessibility.
Manages SAPUI5/OpenUI5 projects with UI5 CLI: initializes, configures ui5.yaml, builds apps/libraries, runs HTTP/2 dev servers, handles monorepos, troubleshoots errors.
Generates and configures SAP Fiori Elements/Freestyle SAPUI5 apps with VS Code extensions: Page Editor, annotations, Service Modeler, ABAP/Cloud Foundry deployment, and AI-assisted development.
Share bugs, ideas, or general feedback.
This skill enforces UI5 development standards derived from official SAP guidelines. It covers the four critical areas: coding guidelines, tooling integration, CAP integration, and form creation rules.
NEVER access UI5 framework objects globally (e.g., sap.m.Button). Always declare dependencies explicitly for asynchronous loading.
// ❌ WRONG - Global access
var oButton = new sap.m.Button();
// ✅ CORRECT - Explicit dependency
sap.ui.define(["sap/m/Button"], function(Button) {
var oButton = new Button();
});
// ✅ CORRECT - Dynamic loading with sap.ui.require
sap.ui.require(["sap/m/MessageBox"], function(MessageBox) {
MessageBox.show("Hello");
});
// ❌ WRONG - Global namespace
const button: sap.m.Button;
// ✅ CORRECT - Import module
import Button from "sap/m/Button";
const button: Button;
<!-- ✅ Controls are auto-loaded by tag -->
<m:Button text="Click Me"/>
<!-- ✅ For formatters/types, use core:require -->
<ObjectListItem
core:require="{
Currency: 'sap/ui/model/type/Currency'
}"
number="{
parts: ['invoice>Price', 'view>/currency'],
type: 'Currency'
}"/>
Why: Ensures proper async loading, improves performance in production builds.
Reference: UI5 documentation page "Require Modules in XML View and Fragment"
Use sap/ui/core/ComponentSupport for declarative initialization of the initial (root) component:
<!-- index.html -->
<script id="sap-ui-bootstrap"
src="resources/sap-ui-core.js"
data-sap-ui-on-init="module:sap/ui/core/ComponentSupport"
data-sap-ui-async="true"
data-sap-ui-resource-roots='{ "my.app": "./" }'>
</script>
<body class="sapUiBody">
<div data-sap-ui-component
data-name="my.app"
data-id="container">
</div>
</body>
Reference: UI5 documentation page "Declarative API for Initial Components"
Note: Nested components should be managed via component usages (declared in the manifest.json of the containing component)
ALWAYS use data binding in views to connect UI controls to data or i18n models.
Priority order:
sap/ui/model/odata/type/*) - Preferredsap/ui/model/type/*) - Only when no OData equivalent<!-- ❌ WRONG - Custom formatter for standard formatting -->
<Text text="{path: 'price', formatter: '.formatCurrency'}"/>
<!-- ✅ CORRECT - Use OData type with format options -->
<Text text="{
path: 'price',
type: 'sap.ui.model.odata.type.Decimal',
formatOptions: {
style: 'currency',
currencyCode: 'EUR'
}
}"/>
<!-- ✅ CORRECT - Use grouping for thousands separator -->
<Text text="{
path: 'quantity',
type: 'sap.ui.model.odata.type.Decimal',
formatOptions: {
groupingEnabled: true
}
}"/>
Common OData Types:
sap.ui.model.odata.type.Decimal - Numbers with decimalssap.ui.model.odata.type.String - Text with length constraintssap.ui.model.odata.type.DateTime - Date and timeCommon Simple Types (use only when no OData equivalent):
sap.ui.model.type.DateInterval - Date rangessap.ui.model.type.FileSize - File size formattingExample: For number formatting with thousands separator, prefer sap.ui.model.odata.type.Decimal with formatOptions: {groupingEnabled: true} over sap.ui.model.type.Integer or a custom formatter.
Custom types are needed for special two-way binding scenarios where built-in types don't provide the required validation or conversion logic.
Example: Custom Type for Email Validation with Two-Way Binding
// controller/EmailType.js
sap.ui.define(["sap/ui/model/SimpleType"], function(SimpleType) {
return SimpleType.extend("my.app.type.EmailType", {
formatValue: function(oValue) {
return oValue;
},
parseValue: function(oValue) {
return oValue;
},
validateValue: function(oValue) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (oValue && !emailRegex.test(oValue)) {
throw new sap.ui.model.ValidateException("Invalid email format");
}
}
});
});
Usage in View:
<!-- ❌ WRONG - Formatter doesn't work for two-way binding validation -->
<Input value="{path: 'email', formatter: '.validateEmail'}"/>
<!-- ✅ CORRECT - Custom type enables two-way binding with validation -->
<Input
core:require="{EmailType: 'my/app/type/EmailType'}"
value="{
path: 'email',
type: 'EmailType'
}"/>
Why Custom Types:
ALWAYS use data binding to connect controls to models:
<!-- Property binding -->
<Input value="{/customer/name}"/>
<!-- Aggregation binding -->
<List items="{/products}">
<StandardListItem title="{name}" description="{price}"/>
</List>
<!-- Expression binding -->
<Text text="{= ${quantity} * ${price} }" visible="{= ${stock} > 0 }"/>
When modifying .properties files, follow the appropriate workflow based on your project type:
For development and testing:
i18n.properties (base file) onlyProduction translation workflows:
i18n_de.properties, i18n_fr.properties, etc.)
Why: Professional translation workflows generate localized files from the base i18n.properties file. Manual edits to localized files will be overwritten during the translation process.
NEVER use inline scripts or inline styles in HTML. They violate the recommended CSP settings for UI5 applications.
<!-- ❌ WRONG - Violates CSP -->
<script>
alert("Hello");
</script>
<style>
.error { color: red; }
</style>
<div style="color: red;">Styled text</div>
<!-- ✅ CORRECT - External files -->
<script src="controller/Main.controller.js"></script>
<link rel="stylesheet" href="css/style.css">
<!-- ✅ CORRECT - CSS classes -->
<div class="errorText">Styled text</div>
Requirements:
<script> tags violate CSP<style> tags violate CSPstyle attributes violate CSPReference: UI5 documentation page "Content Security Policy"
For UI5 1.115.0 and above, import and use the specific event type from the control's module.
Pattern: <ControlName>$<EventName>Event (notice the "Event" suffix)
// ✅ CORRECT - Import specific event type
import { Button$PressEvent } from "sap/m/Button";
import { Table$RowSelectionChangeEvent } from "sap/ui/table/Table";
import Controller from "sap/ui/core/mvc/Controller";
export default class MainController extends Controller {
public onPress(event: Button$PressEvent): void {
const button = event.getSource(); // Correctly typed as Button
// ...
}
public onRowSelectionChange(event: Table$RowSelectionChangeEvent): void {
// Correctly typed: getParameter is known and return value inferred
const selectedContext = event.getParameter("rowContext");
// ...
}
}
UI5 < 1.115.0: Control-specific event types are NOT available. Use the generic Event type:
import Event from "sap/ui/base/Event";
import Controller from "sap/ui/core/mvc/Controller";
export default class MainController extends Controller {
public onPress(event: Event): void {
// Generic Event type for UI5 < 1.115.0
// ...
}
}
Benefits: Static type checking and autocompletion for event parameters without manual casting.
ALWAYS use the get_api_reference tool to get information on UI5 controls and APIs. This provides direct access to the official UI5 API Reference for the UI5 version in use.
Usage: get_api_reference with project path
Returns: Official API documentation for controls, classes, and namespaces
ALWAYS use the run_ui5_linter tool to identify issues. It detects deprecated APIs, accessibility issues, and other potential bugs.
Usage: run_ui5_linter with project path
Returns: List of issues with severity levels
To apply fixes suggested by the linter:
fix parameter of the run_ui5_linter toolWhen interacting with the UI5 CLI's development server:
CRITICAL: The server does NOT serve a default index file.
# ❌ WRONG - Will not work
http://localhost:8080/
# ✅ CORRECT - Must reference files by full path
http://localhost:8080/index.html
After making code changes, ALWAYS run the project's linter if available:
npm run lint # Standard
npm run eslint # Alternative
eslint . # Direct ESLint call
npm run ui5-lint # UI5 Linter if configured
ui5lint . # UI5 Linter if available as CLI tool
Why: Linters catch common issues before committing:
Fix all linting errors before committing.
When creating a UI5 project within a CAP (Cloud Application Programming Model) project:
ALWAYS create UI5 projects within the app/ directory of the CAP project root.
cap-project/
├── app/ # ← UI5 apps go here
│ └── my-ui5-app/
├── srv/ # CAP services
├── db/ # Database models
└── package.json
Get service information:
cds compile '*' # Get definitions
cds compile '*' --to serviceinfo # Get services and endpoints
When creating the UI5 project, ALWAYS provide:
ALWAYS run in CAP project root:
npm i -D cds-plugin-ui5
This plugin automatically handles serving the UI5 applications.
# ❌ WRONG - Never run separate UI5 server
cd app/my-ui5-app
ui5 serve # Don't do this!
npm start # Don't do this!
# ✅ CORRECT - Run from CAP project root
cds watch # Serves both backend and UI5 apps
# or
cds run # Alternative command
Why: Single command serves both backend services and all UI5 applications from the same origin (http://localhost:4004).
NEVER configure ui5-middleware-simpleproxy in ui5.yaml:
# ❌ WRONG - No proxy needed
server:
customMiddleware:
- name: ui5-middleware-simpleproxy # Don't add this!
Why: cds watch ensures UI and service are served from the same origin, making a proxy unnecessary.
Check the CAP launch page (typically http://localhost:4004) for:
<!-- ❌ AVOID - SimpleForm -->
<form:SimpleForm>
<Label text="Name"/>
<Input value="{name}"/>
</form:SimpleForm>
<!-- ✅ CORRECT - Use Form with ColumnLayout -->
<form:Form editable="true">
<form:layout>
<form:ColumnLayout
columnsM="2"
columnsL="3"
columnsXL="4"/>
</form:layout>
<form:formContainers>
<form:FormContainer title="Personal Data">
<form:formElements>
<form:FormElement label="Name">
<form:fields>
<Input value="{name}"/>
</form:fields>
</form:FormElement>
</form:formElements>
</form:FormContainer>
</form:formContainers>
</form:Form>
ALWAYS use these defaults unless requested differently:
For additional information, consult these UI5 documentation pages:
get_api_reference tool)