From ui5-modernization
Fixes UI5 control renderer issues reported by the UI5 linter that cannot auto-fix, including missing renderer declarations, missing apiVersion:2, and non-static renderer properties.
How this skill is triggered — by the user, by Claude, or both
Slash command
/ui5-modernization:fix-control-rendererThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill fixes Control renderer issues that the UI5 linter detects but cannot auto-fix because they require understanding of the control's rendering behavior and module dependencies.
This skill fixes Control renderer issues that the UI5 linter detects but cannot auto-fix because they require understanding of the control's rendering behavior and module dependencies.
| Rule ID | Message Pattern | This Skill's Action |
|---|---|---|
no-deprecated-control-renderer-declaration | Control '...' is missing a renderer declaration | Add renderer: null or import renderer |
no-deprecated-control-renderer-declaration | Deprecated declaration of renderer '...' for control '...' | Import renderer module and assign directly |
no-deprecated-api | Use of deprecated renderer detected. Define explicitly the {apiVersion: 2} | Add apiVersion: 2 to renderer object |
no-deprecated-api | "sap/ui/core/IconPool" module must be imported when using RenderManager's icon() method | Add IconPool import |
no-deprecated-api | Override of deprecated method 'rerender' in control '...' | Remove override, move code to lifecycle hooks |
ui5-class-declaration | The control renderer must be a static property | Make renderer property static |
Apply this skill when you see linter output like:
MyControl.js:5:1 error Control 'my.app.control.MyControl' is missing a renderer declaration no-deprecated-control-renderer-declaration
MyControl.js:10:5 error Deprecated declaration of renderer 'my.app.control.MyControlRenderer' for control 'my.app.control.MyControl' no-deprecated-control-renderer-declaration
MyControl.js:15:5 error Use of deprecated renderer detected. Define explicitly the {apiVersion: 2} parameter no-deprecated-api
MyControl.js:20:5 error "sap/ui/core/IconPool" module must be imported when using RenderManager's icon() method no-deprecated-api
MyControl.js:25:5 warning The control renderer of 'MyControl' must be a static property ui5-class-declaration
UI5's rendering framework evolved from an immediate DOM manipulation model (apiVersion 1) to a semantic rendering model (apiVersion 2 and 4). The key differences:
oRm.write(), oRm.writeAttribute(), etc.oRm.openStart(), oRm.openEnd(), oRm.text(), oRm.close()Without explicit apiVersion, UI5 assumes legacy rendering which causes synchronous loading and performance issues.
In UI5 1.x, if a control doesn't declare a renderer property, the framework automatically tries to load a renderer module by appending Renderer to the control's module path. For example, for sap/m/Button, it checks whether sap/m/ButtonRenderer exists — if so, that module is loaded and used as the renderer.
This implicit auto-discovery is removed in modern UI5. Every control must explicitly declare its renderer. If a control relied on auto-discovery and has no renderer property, it will break at runtime. The linter flags this as no-deprecated-control-renderer-declaration with the message "Control '...' is missing a renderer declaration".
The modernization workflow is: check whether a <ControlName>Renderer module exists at the default path → if yes, import it via sap.ui.define → assign it to the renderer property.
Problem: Control class doesn't declare a renderer at all.
// Before - triggers no-deprecated-control-renderer-declaration
sap.ui.define([
"sap/ui/core/Control"
], function(Control) {
"use strict";
return Control.extend("my.app.control.MyControl", {
metadata: {
properties: { ... }
}
// No renderer declaration!
});
});
Fix Strategy A - No rendering needed (control is abstract or uses child controls):
// After - explicitly declare no renderer
sap.ui.define([
"sap/ui/core/Control"
], function(Control) {
"use strict";
return Control.extend("my.app.control.MyControl", {
metadata: {
properties: { ... }
},
renderer: null
});
});
Fix Strategy B - Renderer exists in separate file (including implicit auto-discovery):
In UI5 1.x, many controls rely on the framework's implicit auto-discovery — they have no renderer property, but a <ControlName>Renderer.js file exists at the default path and gets loaded automatically. Since this auto-discovery is removed in modern UI5, you need to make the import explicit.
How to find the renderer:
Renderer. For my/app/control/MyControl, check for my/app/control/MyControlRenderer.MyControlRenderer.js in the same directory as the control).renderer: null) or Fix Strategy C (inline renderer).// After - import and assign the renderer module
sap.ui.define([
"sap/ui/core/Control",
"./MyControlRenderer"
], function(Control, MyControlRenderer) {
"use strict";
return Control.extend("my.app.control.MyControl", {
metadata: {
properties: { ... }
},
renderer: MyControlRenderer
});
});
Important: After importing the renderer, also check whether the renderer module itself has apiVersion: 2. If not, that's a separate linter finding — see section 3 "Missing apiVersion in Renderer" below.
Fix Strategy C - Add inline renderer:
// After - define renderer inline with apiVersion: 2
sap.ui.define([
"sap/ui/core/Control"
], function(Control) {
"use strict";
return Control.extend("my.app.control.MyControl", {
metadata: {
properties: { ... }
},
renderer: {
apiVersion: 2,
render: function(oRm, oControl) {
oRm.openStart("div", oControl);
oRm.class("myControl");
oRm.openEnd();
// Render content here
oRm.close("div");
}
}
});
});
Problem: Renderer declared as string causes synchronous loading.
// Before - triggers no-deprecated-control-renderer-declaration
sap.ui.define([
"sap/ui/core/Control"
], function(Control) {
"use strict";
return Control.extend("my.app.control.MyControl", {
metadata: { ... },
renderer: "my.app.control.MyControlRenderer" // String = sync loading!
});
});
Fix Strategy: Import the renderer module and assign directly.
// After - import renderer module
sap.ui.define([
"sap/ui/core/Control",
"my/app/control/MyControlRenderer"
], function(Control, MyControlRenderer) {
"use strict";
return Control.extend("my.app.control.MyControl", {
metadata: { ... },
renderer: MyControlRenderer
});
});
Problem: Renderer object or function without apiVersion declaration.
// Before - triggers no-deprecated-api
renderer: {
render: function(oRm, oControl) {
oRm.write("<div");
oRm.writeControlData(oControl);
oRm.write(">");
oRm.write("</div>");
}
}
// OR - function without apiVersion
renderer: function(oRm, oControl) {
oRm.write("<div>");
oRm.write("</div>");
}
Fix Strategy: Add apiVersion: 2 and convert to semantic rendering API.
// After - with apiVersion: 2 and semantic methods
renderer: {
apiVersion: 2,
render: function(oRm, oControl) {
oRm.openStart("div", oControl);
oRm.openEnd();
oRm.close("div");
}
}
apiVersion 1 to apiVersion 2 Method Conversions:
| Old Method (apiVersion 1) | New Method (apiVersion 2) |
|---|---|
oRm.write("<tag") | oRm.openStart("tag") or oRm.voidStart("tag") |
oRm.write(">") | oRm.openEnd() or oRm.voidEnd() |
oRm.write("</tag>") | oRm.close("tag") |
oRm.write(text) | oRm.text(text) |
oRm.writeControlData(oCtrl) | Pass control as 2nd arg: oRm.openStart("div", oControl) |
oRm.addClass("cls") | oRm.class("cls") |
oRm.writeAttribute("name", val) | oRm.attr("name", val) |
For the complete conversion table with examples, read references/renderer-api-mapping.md.
Problem: Using oRm.icon() without importing IconPool.
// Before - triggers no-deprecated-api
sap.ui.define([
"sap/ui/core/Control"
], function(Control) {
"use strict";
return Control.extend("my.app.control.MyControl", {
renderer: {
apiVersion: 2,
render: function(oRm, oControl) {
oRm.openStart("div", oControl);
oRm.openEnd();
oRm.icon("sap-icon://accept"); // IconPool not imported!
oRm.close("div");
}
}
});
});
Fix Strategy: Add IconPool to the imports. The import is required even though it's not directly referenced in code.
// After - IconPool imported
sap.ui.define([
"sap/ui/core/Control",
"sap/ui/core/IconPool" // Required for oRm.icon()
], function(Control, IconPool) {
"use strict";
return Control.extend("my.app.control.MyControl", {
renderer: {
apiVersion: 2,
render: function(oRm, oControl) {
oRm.openStart("div", oControl);
oRm.openEnd();
oRm.icon("sap-icon://accept");
oRm.close("div");
}
}
});
});
Problem: Overriding rerender() method no longer works in UI5 1.121+.
// Before - triggers no-deprecated-api
sap.ui.define([
"sap/ui/core/Control"
], function(Control) {
"use strict";
return Control.extend("my.app.control.MyControl", {
renderer: { ... },
rerender: function() {
// Custom logic before rerendering
this._prepareForRender();
Control.prototype.rerender.apply(this, arguments);
// Custom logic after rerendering
this._finishRender();
}
});
});
Fix Strategy: Move pre-render logic to onBeforeRendering() and post-render logic to onAfterRendering().
// After - use lifecycle hooks instead
sap.ui.define([
"sap/ui/core/Control"
], function(Control) {
"use strict";
return Control.extend("my.app.control.MyControl", {
renderer: { ... },
onBeforeRendering: function() {
// Called before each render (initial + re-renders)
this._prepareForRender();
},
onAfterRendering: function() {
// Called after each render (initial + re-renders)
this._finishRender();
}
});
});
Run linter with --details to get additional context:
npx @ui5/linter --details
Identify the error pattern from linter output (rule ID + message)
Determine the control's rendering needs:
<ControlName>Renderer.js in the same directory (UI5 1.x auto-discovery path)oRm.icon()?Apply the appropriate transformation:
<ControlName>Renderer.js exists at the default path (auto-discovery). If yes, import and assign it. If no, add renderer: null or create an inline rendererapiVersion: 2 and convert render methodsstatic keyword (ES6 classes)Verify the fix by re-running the linter
Given linter output:
npx @ui5/linter --details
MyControl.js:5:1 error Deprecated declaration of renderer 'my.app.control.MyControlRenderer' for control 'my.app.control.MyControl' no-deprecated-control-renderer-declaration
Details: Defining the control renderer by its name may lead to synchronous loading of the control renderer module.
MyControl.js:20:5 error Use of deprecated renderer detected. Define explicitly the {apiVersion: 2} parameter in the renderer object no-deprecated-api
Details: See: https://ui5.sap.com/#/topic/c9ab34570cc14ea5ab72a6d1a4a03e3f
Before:
sap.ui.define([
"sap/ui/core/Control"
], function(Control) {
"use strict";
return Control.extend("my.app.control.MyControl", {
metadata: {
properties: {
text: { type: "string", defaultValue: "" }
}
},
renderer: "my.app.control.MyControlRenderer"
});
});
// MyControlRenderer.js (separate file)
sap.ui.define([], function() {
"use strict";
var MyControlRenderer = {};
MyControlRenderer.render = function(oRm, oControl) {
oRm.write("<div");
oRm.writeControlData(oControl);
oRm.addClass("myControl");
oRm.writeClasses();
oRm.write(">");
oRm.writeEscaped(oControl.getText());
oRm.write("</div>");
};
return MyControlRenderer;
});
After:
sap.ui.define([
"sap/ui/core/Control",
"./MyControlRenderer"
], function(Control, MyControlRenderer) {
"use strict";
return Control.extend("my.app.control.MyControl", {
metadata: {
properties: {
text: { type: "string", defaultValue: "" }
}
},
renderer: MyControlRenderer
});
});
// MyControlRenderer.js (separate file) - updated
sap.ui.define([], function() {
"use strict";
var MyControlRenderer = {
apiVersion: 2
};
MyControlRenderer.render = function(oRm, oControl) {
oRm.openStart("div", oControl);
oRm.class("myControl");
oRm.openEnd();
oRm.text(oControl.getText());
oRm.close("div");
};
return MyControlRenderer;
});
Controls that extend these base classes do NOT need a renderer declaration:
sap/ui/core/mvc/Viewsap/ui/core/XMLCompositesap/ui/core/webc/WebComponentsap/uxap/BlockBaseapiVersion: 4 is also valid and provides additional optimizations for modern UI5
When converting from apiVersion 1 to 2, ensure all write() calls are properly converted to semantic methods
The IconPool import is needed at module load time for icon font registration, even if IconPool variable is not used in code
no-globals errors in non-renderer JavaScript files (e.g., controllers, utilities), use fix-js-globals — it handles sap.ui.define dependency additions and global access replacementLibrary.init() / Lib.init() apiVersion errors ("Deprecated call to ... Use the {apiVersion: 2} parameter"), use fix-library-init — it handles library initialization, not renderer objectsclaude plugin install ui5-modernization@claude-plugins-officialFixes partially deprecated API usage in UI5 apps that the UI5 linter detects but cannot auto-fix. Provides specific fix patterns for each deprecated variant.
Converts UI5 (SAPUI5/OpenUI5) projects to TypeScript with step-by-step guidance on setup, code conversion, and test migration. Preserves comments, avoids `any`, and uses proper UI5 types.
Enforces SAP UI5 coding standards including async loading, ComponentSupport, CSP compliance, OData binding, i18n, TypeScript events, CAP integration, and form layouts (Form with ColumnLayout).