| name | fix-control-renderer |
| description | Fix Control renderer issues that UI5 linter reports but cannot auto-fix. Use this skill when linter outputs these rules:
- `no-deprecated-control-renderer-declaration` - For missing renderer declaration, string-based renderer declaration, implicit renderer auto-discovery (removed in modern UI5)
- `no-deprecated-api` - For missing apiVersion:2 in renderer objects, missing IconPool import when using oRm.icon(), deprecated rerender() override. NOTE: For Library.init() apiVersion errors, use fix-library-init instead.
- `ui5-class-declaration` - For non-static renderer property in ES6 classes
Trigger on: "missing a renderer declaration", "Deprecated declaration of renderer", "deprecated renderer", "apiVersion" (in renderer context), "IconPool", "rerender", "renderer must be a static property"
Converts legacy renderer patterns to modern apiVersion: 2 format with proper module imports.
|
Fix Control Renderer Issues
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.
Linter Rules Handled
| 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 |
When to Use
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
Background: Why apiVersion: 2?
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:
- apiVersion 1 (deprecated): Direct DOM manipulation via
oRm.write(), oRm.writeAttribute(), etc.
- apiVersion 2: Semantic methods like
oRm.openStart(), oRm.openEnd(), oRm.text(), oRm.close()
- apiVersion 4: Same as 2, with additional performance optimizations (for modern UI5)
Without explicit apiVersion, UI5 assumes legacy rendering which causes synchronous loading and performance issues.
Implicit Renderer Auto-Discovery (Removed in modern UI5)
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.
Fix Strategy
1. Missing Renderer Declaration
Problem: Control class doesn't declare a renderer at all.
sap.ui.define([
"sap/ui/core/Control"
], function(Control) {
"use strict";
return Control.extend("my.app.control.MyControl", {
metadata: {
properties: { ... }
}
});
});
Fix Strategy A - No rendering needed (control is abstract or uses child controls):
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:
- Derive the expected renderer path: take the control's module path and append
Renderer. For my/app/control/MyControl, check for my/app/control/MyControlRenderer.
- Look for the file in the project (e.g.,
MyControlRenderer.js in the same directory as the control).
- If the renderer file exists, import it and assign it. If it doesn't exist, use Fix Strategy A (
renderer: null) or Fix Strategy C (inline renderer).
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:
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();
oRm.close("div");
}
}
});
});
2. String-Based Renderer Declaration
Problem: Renderer declared as string causes synchronous loading.
sap.ui.define([
"sap/ui/core/Control"
], function(Control) {
"use strict";
return Control.extend("my.app.control.MyControl", {
metadata: { ... },
renderer: "my.app.control.MyControlRenderer"
});
});
Fix Strategy: Import the renderer module and assign directly.
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
});
});
3. Missing apiVersion in Renderer
Problem: Renderer object or function without apiVersion declaration.
renderer: {
render: function(oRm, oControl) {
oRm.write("<div");
oRm.writeControlData(oControl);
oRm.write(">");
oRm.write("</div>");
}
}
renderer: function(oRm, oControl) {
oRm.write("<div>");
oRm.write("</div>");
}
Fix Strategy: Add apiVersion: 2 and convert to semantic rendering API.
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.
4. Missing IconPool Import
Problem: Using oRm.icon() without importing IconPool.
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");
oRm.close("div");
}
}
});
});
Fix Strategy: Add IconPool to the imports. The import is required even though it's not directly referenced in code.
sap.ui.define([
"sap/ui/core/Control",
"sap/ui/core/IconPool"
], 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");
}
}
});
});
5. Deprecated rerender() Override
Problem: Overriding rerender() method no longer works in UI5 1.121+.
sap.ui.define([
"sap/ui/core/Control"
], function(Control) {
"use strict";
return Control.extend("my.app.control.MyControl", {
renderer: { ... },
rerender: function() {
this._prepareForRender();
Control.prototype.rerender.apply(this, arguments);
this._finishRender();
}
});
});
Fix Strategy: Move pre-render logic to onBeforeRendering() and post-render logic to onAfterRendering().
sap.ui.define([
"sap/ui/core/Control"
], function(Control) {
"use strict";
return Control.extend("my.app.control.MyControl", {
renderer: { ... },
onBeforeRendering: function() {
this._prepareForRender();
},
onAfterRendering: function() {
this._finishRender();
}
});
});
Implementation Steps
-
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:
- Does the control need custom rendering?
- Is there an existing separate renderer file? Check the default path:
<ControlName>Renderer.js in the same directory (UI5 1.x auto-discovery path)
- Does the renderer use
oRm.icon()?
-
Apply the appropriate transformation:
- For missing declaration: Check if
<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 renderer
- For string declaration: Convert to module import
- For missing apiVersion: Add
apiVersion: 2 and convert render methods
- For IconPool: Add the import to sap.ui.define dependencies
- For rerender override: Move logic to lifecycle hooks
- For non-static: Add
static keyword (ES6 classes)
-
Verify the fix by re-running the linter
Example Fix Session
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"
});
});
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
});
});
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;
});
Notes
-
Controls that extend these base classes do NOT need a renderer declaration:
sap/ui/core/mvc/View
sap/ui/core/XMLComposite
sap/ui/core/webc/WebComponent
sap/uxap/BlockBase
-
apiVersion: 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
Related Skills
- fix-js-globals: For
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 replacement
- fix-pseudo-modules: If renderer code also has enum or DataType pseudo module imports, use fix-pseudo-modules for those specific issues
- fix-library-init: For
Library.init() / Lib.init() apiVersion errors ("Deprecated call to ... Use the {apiVersion: 2} parameter"), use fix-library-init — it handles library initialization, not renderer objects