UI Extensibility - How to develop UI Extensions
Read our introduction to UI Extensibility and how to configure an UI extension before diving into this article.
- 1 1. Introduction
- 2 2. Extension contract
- 3 3. Security - Extension signature
- 4 4. Anchors reference
- 4.1 4.1. bo.page – custom page in the back office
- 4.2 4.2. bo.orders.action – bulk action on orders
- 4.3 4.3. bo.order.action – action on order detail
- 4.4 4.4. sa.page - custom page on the store application
- 4.5 4.5. bo.customer.tab – custom tab on Customer Profile page
- 4.6 4.6. bo.customer.information – custom block on Customer Profile page
- 5 5. Translating your extension
- 6 6. Using OneStock API with your extensions
- 7 7. OneStock Design system
- 8 8. Hosting, monitoring and database
1. Introduction
OneStock UI Extensions let you plug custom web applications into specific places (“anchors”) of the OneStock backoffice and store application.
A new page in the back office
A cta for an order detail that will open a popup
A cta for a bulk action in the order list that will open a popup
A new page in the store application
1.1. Key concepts: Extension vs Anchor
An extension is:
Identified by a single extension_id
Secured by a secret hash
Owned and hosted by you (public HTTPS URL)
An anchor is:
A location in the UI where OneStock can display your extension
Examples :
bo.page,bo.order.action,bo.orders.action
1.2. Add an extension to your environment
To add an extension to your environment:
Define the extension itself
Provide to your OneStock contact:A name for the extension
A description
A public HTTPS URL where your extension is hosted
An icon (png, jpg, gif up to 100KB
Configure one or more anchors for this extension
For each injection point you want to use:A name : that name that will be displayed in the anchor
A slug : used to construct the url in the backoffice for pages and also to translate your name in different languages in the backoffice
Choose the anchor type, e.g.:
bo.pagebo.order.actionbo.orders.action
Choose roles who can access this extension. You can either allow only for roles or disable for roles.
Client admin
Headquarters
Customer service
Configuration Manager
An icon (png, jpg, gif up to 100KB)
The path to load for this anchor : that would be the relative path from your domain to access this anchor actions. Ex:
/bulk_edit_orders
All these anchors will share the same
extension_idand secret, which means:You verify signatures in the same way regardless of anchor
You can reuse the same codebase (single UI project) behind multiple entry URLs and anchors
OneStock will:
Create the extension entity
Generate and share a secret hash (for the signature)
Provide the
extension_id
2. Extension contract
This section is the formal contract between OneStock and your extension.
The contract has three parts:
URL parameters (sent in the iframe URL)
Handshake via
postMessage(extension ⇄ OneStock)Security: extension signature verification
Ressource :
2.1. URL parameters
When OneStock opens your extension, it calls your public URL with query parameters:
Parameter | Type | Description |
|---|---|---|
| string | Unique id of the configured extension instance |
| string | Id of the logged OneStock user |
| string | Id of the OneStock account / tenant |
| string | User language code set in the OneStock UI (e.g. |
| string | User timezone (e.g. |
| string | User locale (e.g. |
| string | URL of the OneStock UI that embeds your extension iframe |
Your extension should:
Parse these query parameters on load.
Use
parent_url(or a known back office host) to restrictpostMessageorigins.
2.2. Handshake mechanism
When the extension iframe finishes loading, it must:
Notify OneStock that it is ready
Receive contextual data from OneStock (order ID(s), API URL, signature…)
2.2.1. Step 1 – Extension → OneStock: extension_ready
From your extension JavaScript, as soon as you are ready to receive data, send:
// Example: send 'extension_ready' to the parent window
window.parent.postMessage(
{ type: "extension_ready" },
"*" // or use parent_url for stricter security
);For production, you should replace
"*"with the exact parent origin (derived fromparent_url), to avoid accepting messages from other origins.
2.2.2. Step 2 – OneStock → Extension: onestock_data
After receiving extension_ready, OneStock sends a message back to your iframe via postMessage with:
type: "onestock_data"data: { ... }– payload containing shared data
Use this pattern to receive the message:
let sharedData = {};
window.addEventListener("message", handleMessage);
function handleMessage(event) {
// Optional but recommended: restrict origin:
// if (event.origin !== "https://<your-onestock-backoffice-host>") return;
if (!event.data || typeof event.data !== "object") return;
if (event.data.type === "onestock_data") {
// event.data.data is the OneStock payload (e.g. order_id, order_ids, user info…)
sharedData = event.data.data;
// Your code here: check signature and save data in context
console.log("Received OneStock data:", sharedData);
}
}3. Security - Extension signature
If your extension gives access to sensitive external data (e.g. your own APIs), you must:
Validate the OneStock extension signature before establishing a session.
Reject or limit access if the signature is missing or invalid.
3.1. Inputs required for signature verification
To validate the signature you need:
Secret hash
Provided by OneStock during extension installation.
You will have a different hash for every OneStock instance you are using the extension on (qualif, training, production)
Payload
Concatenation of the extension id and user id with two
#characters:${extension_id}##${user_id}extension_id– from URL parametersuser_id– from URL parameters
Signature
Shared in the handshake data (
extension_signatureinsharedData)Format :
t=timestamp,h0=h[0],h1=h[1],h2=h[2]where:
timestamp– Unix timestamp in secondsh[0]– HMAC-SHA256 of${timestamp}.${payload}using the latest hashh[1]– same with the previous hashh[2]– same with the oldest hash
3.2. Encryption details
Algorithm: HMAC-SHA256
Message to sign:
${timestamp}.${payload}where:
timestamp– taken from the signature stringpayload–${extension_id}##${user_id}
3.3. Example of signature verification in Node.js
Below is an example you can adapt. It:
Accepts a list of current and previous keys
Rejects signatures older than 6 hours
Compares against each
h0,h1,h2value
const crypto = require("crypto");
/**
* Attempts to verify the extension signature against a list of keys.
*
* @param {object} req - Object containing signature, extension_id and user_id.
* @param {array} secretKeys - Array of secret keys (current + previous).
* @returns {boolean} - True if the signature is valid with any key.
*/
function verifyWithPreviousKeys(req, secretKeys) {
for (const key of secretKeys) {
if (checkExtensionSignature(req, key)) {
return true;
}
}
return false;
}
/**
* Checks the validity of the extension signature to ensure the request
* is from a trusted OneStock source.
*
* @param {object} req - { extension_signature, extension_id, user_id }.
* @param {string} secretKey - Secret key used to generate the signature.
* @returns {boolean} - True if the signature is valid, else false.
*/
function checkExtensionSignature(req, secretKey) {
const signatureHeader = req.extension_signature; // e.g. "t=...,h0=...,h1=...,h2=..."
const body = `${req.extension_id}##${req.user_id}`;
if (!signatureHeader) return false;
const signatureParts = signatureHeader.split(",");
if (signatureParts.length < 2) return false;
const timestamp = signatureParts[0].replace("t=", "");
// Reject if older than 6 hours
const nowInSeconds = Math.floor(Date.now() / 1000);
if (nowInSeconds - parseInt(timestamp, 10) > 60 * 60 * 6) {
return false;
}
const payload = `${timestamp}.${body}`;
const expectedSignature = crypto
.createHmac("sha256", secretKey)
.update(payload)
.digest("hex");
// Compare against each h0, h1, h2...
for (let i = 1; i < signatureParts.length; i++) {
const h = signatureParts[i].replace(`h${i - 1}=`, "");
if (h === expectedSignature) {
return true;
}
}
return false;
}
// Example usage
const req = {
extension_signature:
"t=timestamp,h0=hash0,h1=eb2a2ab3d29587fba099451925bdbd9637814711c97bc79c6a6e86f898884796",
user_id: "user_id",
extension_id: "extension_id",
};
const secretKeys = ["oldKey1", "oldKey2", "currentKey"];
const isVerified = verifyWithPreviousKeys(req, secretKeys);
console.log(`Extension signature verification result: ${isVerified}`);3.4. Secure your extension
Once the signature validated, you have to implement a session handler of your choice such as JWT with a short expiration date for the token availabilities.
Other options exists depending on your IT availabilities. If your enterprise have an existing Single Sign On server, this is a good option to use and implement and complement to our signature verification for even more security to your data access.
4. Anchors reference
This section defines, per anchor:
Where it appears in OneStock
How the extension is displayed
Additional data you receive
Extra messages you can send back to OneStock
4.1. bo.page – custom page in the back office
Behavior
Adds a new menu entry in the OneStock back office.
The menu entry:
Uses the configured icon and name.
Payload
You receive:
Common fields (
api_url,extension_signature, etc.)No additional fields specific to
bo.page.
4.1.1. Update back office breadcrumb (v19)
Your extension can update the OneStock breadcrumb when the user navigates inside your page by sending:
window.parent.postMessage(
{
type: "extension_breadcrumb_update",
breadcrumb_items: [
{ name: "Books-music-video", path: "/books-music-video", params: "" },
{
name: "Blade Runner 2048",
path: "/books-music-video/1005199002506",
params: "",
},
],
},
"*" // restrict to backoffice origin in production
);4.1.2. Listen to breadcrumb clicks (v19)
When a breadcrumb item is clicked in OneStock, your extension receives a navigation event:
window.addEventListener("message", (event) => {
if (!event.data || typeof event.data !== "object") return;
if (event.data.type === "onestock_navigation") {
const { path } = event.data.data;
// Your code: route within your extension to the given path
console.log("Navigate to path:", path);
}
});Example payload from OneStock:
{
"type": "onestock_navigation",
"data": { "path": "/books-music-video" }
}4.2. bo.orders.action – bulk action on orders
Behavior
Adds a custom bulk action in the back office order list.
When the user selects multiple orders and triggers your action:
OneStock opens your extension in a modal iframe.
You receive the list of order IDs.
Payload
In
sharedData:Common fields (
api_url,extension_signature, …)order_ids– comma‑separated list of selected order IDs
Closing the modal
Your extension can request OneStock to close the modal:
window.parent.postMessage(
{
type: "extension_close",
},
"*" // restrict in production
);4.3. bo.order.action – action on order detail
Behavior
Adds a custom action button on the order detail page.
When triggered:
OneStock opens your extension in a modal iframe.
You receive the current order ID.
Payload
In
sharedData:Common fields (
api_url,extension_signature, …)order_id– the current order ID
Closing the modal
Same as for bo.orders.action:
window.parent.postMessage(
{
type: "extension_close",
},
"*" // restrict in production
);4.4. sa.page - custom page on the store application
Behaviour
Adds a new menu entry in the store application.
You can set the store application extension to be displayed in the menu section of your choice.
In the bellow configuration we are displaying our extension in the admin section of the menu0
The available menus and section can listed in the Backoffice > Store app > Menu configuration page.
Replacemenu_keyandmenu_sectionwith the correct values depending on your store app configurations.{ "name": "store-app-extension", "injection_points": [ { "anchor": "sa.page", "icon": "/9j/4Q/+RXhp...", "name": "SA Extension", "path": "/sa-page", "params": { "menu": { "0": "admin", "{{menu_key}}": "{{menu_section}}" } }, "rank": 1, "slug": "sa-main-page" } ], "url": "https://myextensionurl.com", "test_url": "https://myextensionurl.com" }
Payload
In
sharedData:Common fields (
api_url,extension_signature, …)endpoint_id: the id of the stock location
4.5. bo.customer.tab – custom tab on Customer Profile page
Behavior
Adds a custom tab on the customer profile page.
When triggered:
OneStock opens your extension in a tab and you can add your iframe.
You receive the customer ID.
Payload
In
sharedData:Common fields (
api_url,extension_signature, …)customerId,customerExternalId
4.6. bo.customer.information – custom block on Customer Profile page
Behavior
Adds a custom information block on the customer profile page.
When triggered:
OneStock opens your extension in a block when the customer profile page is openned
You receive the customer ID.
Payload
In
sharedData:Common fields (
api_url,extension_signature, …)customerId,customerExternalId
5. Translating your extension
5.1. Translate the content of your extension
It is up to you to use the lang parameter received in the URL parameters to correctly translate your extension with a library such as i18n.
The lang value available from the Backoffice are:
Lang | Value |
|---|---|
French |
|
English |
|
Deutch |
|
Italian |
|
Spanish |
|
Greek |
|
Russian |
|
5.2. Backoffice placeholders
For each anchor, the backoffice will display its name by default in the menu for a page, or in a button for an action. You can add translation keys in the backoffice translation configuration page.
Key format :
extensions.{extension_name}.{slug}Where
extension_name(spaces are allowed) is the name of your extensionWhere
slugis the slug for your anchor.
Ex: If you have an extension called Hello World with 3 anchors:
bo.page : with a slug
pagebo.orders.action : with a slug
orders-actionbo.order.action : with a slug
order-action
You can create 3 translation keys:
extensions.Hello World.pageextensions.Hello World.orders-actionextensions.Hello World.order-action
6. Using OneStock API with your extensions
To use OneStock APIs the extension must manage the session after the signature verification with the security approach of your choice, and then expose a backend proxy to call OneStock APIs using and application user that would be saved in your project environment.