- name
- b2c-business-manager-extensions
- description
- Build Business Manager extension cartridges with custom admin tools, menu items, and dialog actions. Use this skill whenever the user needs to create bm_* cartridges, add menu actions or dialog buttons in BM, configure bm_extensions.xml, or extend admin pages with form overlays. Also use when customizing the BM interface for back-office workflows -- even if they just say 'add a button to BM' or 'custom admin page'.
# Business Manager Extensions Skill
This skill guides you through creating Business Manager (BM) extension cartridges to customize the admin interface.
## Overview
BM extensions add custom functionality to Business Manager through a single `bm_extensions.xml` descriptor plus a controller that renders ISML pages:
| Extension Type | Purpose |
|----------------|---------|
| **Menu Items** | Add a navigation section (a container in the Admin or a site's menu) |
| **Menu Actions** | Add a functional link/page under a menu item — wired to a controller node |
| **Dialog Actions** | Add buttons to existing BM pages at defined extension points |
| **Form Extensions** | Add fields to existing BM search forms (order/customer search) |
## ⚠️ Schema essentials (read first)
These are the most common mistakes. The descriptor uses the **`bmmodules/2007-12-11`** schema (`bmext.xsd`), not the `extensibility/*` namespace.
- **Namespace** is `http://www.demandware.com/xml/bmmodules/2007-12-11`.
- `name`, `short_description`, and `description` are **child elements** with a required `xml:lang` attribute — NOT attributes and NOT resource keys. Localize by repeating the element per language. There is no `name="..."` attribute.
- `<icon>` is a plain string **element** holding a path (`<icon>icons/q.png</icon>`), NOT `<icon path="..."/>`.
- `position` is ascending: **lower numbers appear higher** in the menu. Core BM items run 2000–12000, so custom items typically use a large number (e.g. `99999`) to sit at the bottom, or `-1` to float a "Home" action to the top.
- The dialog-action extension-point attribute is **`xp_ref`** (underscore), and dialog actions link to a menu action via a child `<menuactions><menuaction-ref action-id="..."/></menuactions>` element — there is no `menuaction-ref` attribute.
- Use the file encoding `ISO-8859-1` to match platform samples.
To confirm any detail against the authoritative schema:
```bash
b2c docs schema bmext
```
## File Structure
```
/bm_my_extension
/cartridge
bm_extensions.xml # Extension definitions (required)
bm_my_extension.properties # Cartridge properties (required)
/controllers
MyExtension.js # Controller for menu actions
/templates
/default
/extensions
mypage.isml # Custom BM pages
/static
/default
/icons
my-icon.png # Menu icons
```
`bm_my_extension.properties` is a standard cartridge marker file:
```properties
## cartridge.properties for cartridge bm_my_extension
demandware.cartridges.bm_my_extension.multipleLanguageStorefront=true
demandware.cartridges.bm_my_extension.id=bm_my_extension
```
## Basic bm_extensions.xml
```xml
<?xml version="1.0" encoding="ISO-8859-1" ?>
<extensions xmlns="http://www.demandware.com/xml/bmmodules/2007-12-11"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.demandware.com/xml/bmmodules/2007-12-11 bmext.xsd">
<!-- Menu Item: a container in the Administration menu (site="false") -->
<menuitem id="acme_id01" position="99999" site="false">
<name xml:lang="x-default">ACME Tools</name>
<short_description xml:lang="x-default">ACME admin tools</short_description>
<description xml:lang="x-default">Tools and utilities from the ACME team</description>
<icon>icons/acme.png</icon>
</menuitem>
<!-- Menu Action: a page under the menu item, wired to a controller node.
position="-1" floats it to the top of the section. -->
<menuaction id="acme-home" menupath="acme_id01" position="-1" site="false">
<name xml:lang="x-default">Home</name>
<short_description xml:lang="x-default">ACME Home</short_description>
<exec pipeline="ACME" node="Start"/>
<sub-pipelines>
<pipeline name="ACME-Start"/>
</sub-pipelines>
<icon>icons/acme.png</icon>
</menuaction>
</extensions>
```
`pipeline="ACME"` resolves to the controller `ACME.js`; `node="Start"` resolves to its exported `Start` function (which must be marked `Start.public = true`).
## Menu Items
Top-level navigation containers. `site="false"` places the item in the **Administration** menu; `site="true"` (the default) places it in a **site** menu.
```xml
<menuitem id="acme_id01" position="99999" site="false">
<name xml:lang="x-default">ACME Tools</name>
<name xml:lang="de">ACME Werkzeuge</name>
<short_description xml:lang="x-default">ACME admin tools</short_description>
<icon>icons/acme.png</icon>
</menuitem>
```
| Attribute | Required | Default | Description |
|-----------|----------|---------|-------------|
| `id` | Yes | - | Unique identifier. Prefix to avoid collisions with core BM IDs. |
| `site` | No | `true` | `true` = site menu, `false` = Administration menu |
| `position` | No | - | Sort order — **lower = higher**. Use a large value to sit at the bottom. |
| `type` | No | `BM` | `BM` or `CSC` (Customer Service Center) |
Child elements: `name` (required, localized), `short_description`, `description` (localized), `icon` (string path), `sldsIconName` (an SLDS utility icon name, preferred in the modern BM UI), `exec`.
## Menu Actions
Functional pages under a menu item. Wire each to a controller node via `<exec>` and register every controller node it uses in `<sub-pipelines>`.
```xml
<menuaction id="acme-export" menupath="acme_id01" position="20" site="false">
<name xml:lang="x-default">Product Export</name>
<short_description xml:lang="x-default">Export products to CSV</short_description>
<exec pipeline="ACME" node="Export"/>
<sub-pipelines>
<pipeline name="ACME-Export"/>
<pipeline name="ACME-Download"/>
</sub-pipelines>
<parameters>
<parameter name="defaultFormat" value="csv"/>
</parameters>
<icon>icons/export.png</icon>
</menuaction>
```
| Attribute | Required | Default | Description |
|-----------|----------|---------|-------------|
| `id` | Yes | - | Unique identifier |
| `menupath` | No | - | Parent menu item `id` (or a core BM menu/item `id` to attach to it) |
| `site` | No | `true` | Must match the parent menu item |
| `position` | No | - | Sort order (lower = higher; `-1` floats to top) |
| `extern` | No | `false` | `true` opens the action in a new window |
Key child elements: `name` (required), `short_description`, `description`, `extended_description`, `exec`, `sub-pipelines`, `parameters`, `icon`.
> **Note:** `<exec>` takes only `pipeline` and `node` (both required) — there is no `https` attribute. The `apis`, `scapis`, and `required-features` elements in the schema are **not supported for custom menu actions**.
### The `NoPermissionCheck` idiom
BM checks that the logged-in user has permission for the menu action whose `sub-pipelines` register the requested controller node. For endpoints that must work **without** a module permission check — keepalive pings, service workers, polling/AJAX endpoints called by your page's JavaScript — register their nodes under a dedicated action with the reserved id `NoPermissionCheck` and **no `<exec>`**:
```xml
<menuaction id="NoPermissionCheck" site="false">
<name xml:lang="x-default">Dummy</name>
<short_description xml:lang="x-default">Holds pipelines that do not require a permission check</short_description>
<sub-pipelines>
<pipeline name="ACME-Keepalive"/>
<pipeline name="ACME-ServiceWorker"/>
<pipeline name="ACME-Poll"/>
</sub-pipelines>
</menuaction>
```
Without this, those nodes 403 for users who haven't been granted the module. Only register nodes here that are genuinely safe to expose to any authenticated BM user.
## Dialog Actions
Add buttons to existing BM pages at a named extension point (`xp_ref`).
```xml
<dialogaction id="acme-order-sync" xp_ref="OrderPage-OrderDetails" position="100">
<name xml:lang="x-default">Sync Order</name>
<exec pipeline="ACME" node="OrderSync"/>
<parameters>
<parameter name="OrderNo"/>
</parameters>
<menuactions>
<menuaction-ref action-id="acme-home"/>
</menuactions>
</dialogaction>
```
| Attribute | Required | Default | Description |
|-----------|----------|---------|-------------|
| `id` | Yes | - | Unique identifier |
| `xp_ref` | Yes | - | Extension point ID (note the underscore) |
| `position` | No | - | Sort order |
| `class` | No | - | Optional CSS class |
| `extern` | No | `false` | `true` opens in a new window |
Child elements: `name` (optional, localized), `exec` (**required**), `parameters`, `menuactions` (one or more `<menuaction-ref action-id="..."/>` linking to the menu action that owns the permission check). Dialog actions do **not** take an `icon` child.
Common extension points: `OrderPage-OrderDetails`, `ProductPage-General`, `ProductPage-Images`, `CustomerPage-Profile`, `ContentPage-General`, `CatalogPage-CategoryGeneral`.
## Form Extensions
Add fields to existing BM search forms (e.g. order or customer search).
```xml
<formextension id="order-search">
<valueinput type="string" name="invoiceNumber">
<label xml:lang="x-default">Invoice Number</label>
<label xml:lang="de">Rechnungsnummer</label>
</valueinput>
<valueinput type="string" name="syncStatus" defaultvalue="">
<label xml:lang="x-default">Sync Status</label>
<option>All</option>
<option>Synced</option>
<option>Pending</option>
<option>Failed</option>
</valueinput>
<valueinput type="int" name="minQuantity">
<label xml:lang="x-default">Minimum Quantity</label>
</valueinput>
</formextension>
```
`id` matches the target form (e.g. `order-search`, `customer-search`). `valueinput` requires `type` (`string`, `int`, or `double`) and `name`; `defaultvalue` is optional. Each `valueinput` has localized `<label>` children and optional `<option>` children for dropdowns.
## Controller Example
Controllers live in `controllers/` and export functions marked `.public = true`. Use `*/cartridge/...` requires so logic can be shared, and gate page nodes on the BM session.
```javascript
'use strict';
var ISML = require('dw/template/ISML');
var URLUtils = require('dw/web/URLUtils');
var Logger = require('dw/system/Logger');
var log = Logger.getLogger('bm', 'ACME');
/**
* Render the dashboard. Wired to <exec pipeline="ACME" node="Start"/>.
*/
function Start() {
// BM nodes should only be reachable in an authenticated BM session
if (!session.userAuthenticated) {
response.setStatus(403);
return;
}
ISML.renderTemplate('extensions/acme/dashboard', {
stats: getStats()
});
}
Start.public = true;
module.exports.Start = Start;
/**
* Process an action, then redirect back to the dashboard.
*/
function Process() {
var action = request.httpParameterMap.action.stringValue;
try {
performAction(action);
response.redirect(URLUtils.url('ACME-Start', 'message', 'Done'));
} catch (e) {
Ver no GitHub