The Store Commerce app in Microsoft Dynamics 365 Commerce delivers a modern, unified platform for retail point-of-sale (POS) operations. What makes it powerful is its ability to go beyond standard functionality, allowing retailers to build custom pages and views that align with their unique workflows, surface tailored data, and integrate seamlessly with external systems.
By extending the app through custom page development, businesses can streamline associate-level tasks, personalize customer interactions, and adapt the POS to evolving retail strategies. This blog highlights how custom views are structured in the Store Commerce environment, the role of the manifest.json configuration, and explains the benefits of tailoring Dynamics 365 Commerce to fit your business.
Why go for custom page development in Dynamics 365 Commerce?
Custom page development enables retailers to enhance their POS experience beyond the standard, out-of-the-box capabilities. This includes scenarios such as:
- Creating a custom checkout page to align with specific business workflows
- Enhancing associate efficiency by surfacing tailored data directly in the POS
- Integrating with external systems for a seamless in-store experience
- Personalizing customer journeys with custom Dynamics 365 Commerce pages
For example, in this project, we developed a custom view called “Custom Page” to demonstrate how Dynamics 365 Commerce development can deliver targeted features while maintaining a native POS experience.
Registering the custom page in the D365 Store Commerce app
To integrate a custom page into the D365 Store Commerce app, it must be registered using the manifest.json file, which defines the page’s metadata and integration details. Below is the manifest.json configuration used for the “Custom Page” in the Dynamics 365 Store Commerce app.
1. manifest.json
{
“$schema”: “./devDependencies/schemas/manifestSchema.json”,
“name”: “CustomApp.Commerce”,
“publisher”: “CustomApp”,
“version”: “1.0.00”,
“minimumPosVersion”: “9.29.0.0”,
“dependencies”: [
{
“alias”: “knockout”,
“format”: “amd”,
“modulePath”: “Libraries/knockout”
}
],
“components”: {
“create”: {
“views”: [
{
“name”: “Custom Page”,
“description”: “Custom Page View – Store Commerce”,
“title”: “Custom Page”,
“pageName”: “CustomPageView”,
“phonePageName”: “CustomPageView”,
“viewDirectory”: “CustomPageView/Views/”,
“viewControllerPath”: “CustomPageView/Views/CustomPageView”
}
] ,
“operations”: [
{
“name”: “Custom Page View Operation”,
“description”: “Custom Page View”,
“operationId”: “9030”,
“operationRequestFactoryPath”: “CustomPageView/Operations/CustomPageViewOperationRequestFactory”,
“operationRequestHandlerPath”: “CustomPageView/Operations/CustomPageViewOperationRequestHandler”
}
]
}
}
}
2. Explanation of manifest.json
- Metadata: Defines the extension name (Confiz.Commerce), publisher (Confiz), and version (3.4.03). The minimumPosVersion (9.29.0.0) ensures compatibility with Store Commerce D365.
- Dependencies: Includes Knockout.js for data binding, critical for the Dynamics 365 Commerce custom UX design for storefronts.
- Components: The create.views section registers the custom page with:
- Name, description, and title should be set to “Custom Page” for identification in the D365 Store Commerce app.
- PageName (CustomPageView) and phonePageName (CustomPageView) for desktop and mobile POS interfaces.
- ViewDirectory (CustomPage/Views/) and viewControllerPath (CustomPage/Views/CustomPageView) to locate the view files.
- Components: The create.operations section registers to call the custom page in the D365 Store Commerce app.
This configuration ensures the custom page integrates seamlessly into the Store Commerce app D365 POS menu. Please see the image below:
Key code files required for Dynamics 365 Commerce custom modules
The custom page was built using the Dynamics 365 Commerce custom modules and templates within the Store Commerce POS SDK, leveraging Knockout.js for data binding. Below are the key files involved, with placeholders for your code:
1. CustomPageView.ts
This file defines the view’s logic and UI bindings for the D365 Store Commerce app.
// CustomPageView.ts
import * as Views from “PosApi/Create/Views”;
import CustomPageViewModel from “./CustomPageViewModel”;
import { ICustomPageViewExtensionViewModelOptions } from “./NavigationContracts”;
import { ObjectExtensions, StringExtensions } from “PosApi/TypeExtensions”;
import ko, { Observable } from “knockout”;
/**
* The controller for CustomPageView.
*/
export default class CustomPageView extends Views.CustomViewControllerBase {
public readonly viewModel: CustomPageViewModel;
public string1: ko.Observable<string>;
public string2: ko.Observable<string>;
public string3: ko.Observable<string>;
public string4: ko.Observable<string>;
public string5: ko.Observable<string>;
public customList: ko.ObservableArray<CustomData>;
public _option: ICustomPageViewExtensionViewModelOptions;
constructor(context: Views.ICustomViewControllerContext, state: Views.ICustomViewControllerBaseState,
options?: ICustomPageViewExtensionViewModelOptions) {
super(context);
// Initial values for Basic Info
this.string1 = ko.observable(“”);
this.string2 = ko.observable(“”);
this.string3 = ko.observable(“”);
this.string4 = ko.observable(“”);
this.string5 = ko.observable(“”);
this.customList = ko.observableArray<CustomData>([]);
// Initialize the view model.
this.viewModel = new CustomPageViewModel(context, this.state, options);
this._option = options;
this.state.title = this.viewModel.title();
}
public loadData = (): Promise<void> => {
// Mock data for demonstration purposes
const data: CustomData[] = [
{ string1: “89027”, string2: “7489794546548”, string3: “Pending”, string4: “Good”, string5: “02-01-2025” },
{ string1: “89028”, string2: “7489794546548”, string3: “Approved”, string4: “Good”, string5: “01-01-2025” },
{ string1: “89029”, string2: “7489794547848”, string3: “Submitted”, string4: “Good”, string5: “02-01-2025” },
{ string1: “89030”, string2: “7489794549848”, string3: “Cancelled”, string4: “Bad”, string5: “01-01-2025” }
];
// Apply search filters
const filteredData = data.filter(item =>
(StringExtensions.isNullOrWhitespace(this.string1()) || item.string1.includes(this.string1())) &&
(StringExtensions.isNullOrWhitespace(this.string2()) || item.string2.includes(this.string2())) &&
(StringExtensions.isNullOrWhitespace(this.string3()) || item.string3.includes(this.string3())) &&
(StringExtensions.isNullOrWhitespace(this.string4()) || item.string4.includes(this.string4())) &&
(StringExtensions.isNullOrWhitespace(this.string5()) || item.string5.includes(this.string5()))
);
this.customList(filteredData);
return Promise.resolve();
};
/**
* Function to close the form.
*/
public formClose = (): void => {
this.context.navigator.navigateBack(); // Close the current view
};
/**
* Bind the HTML element to the view controller.
*
* @param {HTMLElement} element DOM element.
*/
public onReady(element: HTMLElement): void {
// Customized binding
// Fetch the data when the page is ready
this.loadData();
ko.applyBindings(this, element);
}
/**
* Called when the object is disposed.
*/
public dispose(): void {
ObjectExtensions.disposeAllProperties(this);
}
}
interface CustomData {
string1: string;
string2: string;
string3: string;
string4: string;
string5: string;
}
Take control of your business operations
Discover how Confiz services can simplify your complex workflows and improve decision-making.
Get a Free Quote2. CustomPageView.html
This HTML file defines the UI structure, bound to the Knockout view model for Dynamics 365 Commerce custom UX design.
<!– CustomPageView.html –>
<!DOCTYPE html>
<html lang=”en” xmlns=”http://www.w3.org/1999/xhtml”>
<head>
<meta charset=”utf-8″ />
<title>Search ERX ABUD History</title>
<style>
body {
font-family: Arial, sans-serif;
font-size: 20px;
width:100px;
}
.search-section {
height: auto;
display: ruby;
padding: 10px;
}
.section-search1 {
font-size: 22px;
font-weight: bold;
margin-top: 20px;
}
.table-section {
padding: 10px;
}
.input-section {
font-size: 22px !important;
}
.search-btn {
font-size: 22px !important;
font-weight: bold;
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f4f4f4;
}
button {
padding: 5px 10px;
background-color: #007BFF;
color: #fff;
border: none;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<div>
<div class=”search-section”>
<div class=”section-search1″>
<label style=”font-weight: bold; font-size: large” for=”string1Search”>String 1:</label>
<input class=”input-section” type=”text” id=”string1Search” data-bind=”value: string1, valueUpdate: ‘input'” />
</div>
<div class=”section-search1″>
<label style=”font-weight: bold; font-size: large” for=”string2Search”>String 2:</label>
<input class=”input-section” type=”text” id=”string2Search” data-bind=”value: string2, valueUpdate: ‘input'” />
</div>
<div class=”section-search1″>
<label style=”font-weight: bold; font-size: large” for=”string3Search”>String 3:</label>
<input class=”input-section” type=”text” id=”string3Search” data-bind=”value: string3, valueUpdate: ‘input'” />
</div>
<div class=”section-search1″>
<label style=”font-weight: bold; font-size: large” for=”string4Search”>String 4:</label>
<input class=”input-section” type=”text” id=”string4Search” data-bind=”value: string4, valueUpdate: ‘input'” />
</div>
<button class=”search-btn” data-bind=”click: loadData”>Search</button>
<button class=”search-btn” data-bind=”click: loadData”>Refresh</button>
</div>
<div class=”table-section”>
<table>
<thead>
<tr>
<th>Sr.</th>
<th>String 1</th>
<th>String 2</th>
<th>String 3</th>
<th>String 4</th>
<th>String 5</th>
</tr>
</thead>
<tbody data-bind=”foreach: customList”>
<tr>
<td data-bind=”text: $index() + 1″></td>
<td data-bind=”text: string1″></td>
<td data-bind=”text: string2″></td>
<td style=”font-weight: bold”>
<span data-bind=”text: string3″></span>
</td>
<td data-bind=”text: string4″></td>
<td data-bind=”text: string5″></td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
</html>
3. CustomPageViewModel.ts
This service file handles external data or logic for the custom page.
// CustomPageViewModel.ts
import { ICustomViewControllerContext, ICustomViewControllerBaseState } from “PosApi/Create/Views”;
import { ICustomPageViewExtensionViewModelOptions } from “./NavigationContracts”;
import KnockoutExtensionViewModelBase from “./BaseClasses/KnockoutExtensionViewModelBase”;
import ko, { Observable } from “knockout”;
export default class CustomPageViewModel extends KnockoutExtensionViewModelBase {
public title: Observable<string>;
private _context: ICustomViewControllerContext;
constructor(context: ICustomViewControllerContext, state: ICustomViewControllerBaseState,
options?: ICustomPageViewExtensionViewModelOptions) {
super();
this._context = context;
this.title = ko.observable(“Custom Page View”);
}
}
4. CustomPageViewOperationRequest.ts
This operation service file handles external data or logic for the custom page.
// CustomPageViewOperationRequest.ts
import { ExtensionOperationRequestBase } from “PosApi/Create/Operations”;
import CustomPageViewOperationResponse from “./CustomPageViewOperationResponse”;
/**
* Operation request for skipping fiscalization.
*/
export default class CustomPageViewOperationRequest<TResponse extends CustomPageViewOperationResponse> extends ExtensionOperationRequestBase<TResponse> {
// The operation identifier.
private static readonly OPERATION_ID: number = 9030;
constructor(correlationId: string) {
super(CustomPageViewOperationRequest.OPERATION_ID, correlationId);
}
}
5. CustomPageViewOperationRequestFactory.ts
This operation service file handles external data or logic for the custom page.
// CustomPageViewOperationRequestFactory.ts
import CustomPageViewOperationRequest from “./CustomPageViewOperationRequest”;
import CustomPageViewOperationResponse from “./CustomPageViewOperationResponse”;
import { ExtensionOperationRequestFactoryFunctionType, IOperationContext } from “PosApi/Create/Operations”;
import { ClientEntities } from “PosApi/Entities”;
let getOperationRequest: ExtensionOperationRequestFactoryFunctionType<CustomPageViewOperationResponse> =
/**
* Gets an instance of SkipFiscalizationOperationRequest.
* @param {IOperationContext} context The operation constext.
* @param {number} operationId The operation Id.
* @param {string[]} actionParameters The action parameters.
* @param {string} correlationId A telemetry correlation ID, used to group events logged from this request together with the calling context.
* @return {SkipFiscalizationOperationRequest<TResponse>} Instance of SkipFiscalizationOperationRequest.
*/
function (
context: IOperationContext,
operationId: number,
actionParameters: string[],
correlationId: string
): Promise<ClientEntities.ICancelableDataResult<CustomPageViewOperationRequest<CustomPageViewOperationResponse>>> {
let operationRequest: CustomPageViewOperationRequest<CustomPageViewOperationResponse> =
new CustomPageViewOperationRequest<CustomPageViewOperationResponse>(correlationId);
return Promise.resolve(<ClientEntities.ICancelableDataResult<CustomPageViewOperationRequest<CustomPageViewOperationResponse>>>{
canceled: false,
data: operationRequest
});
};
export default getOperationRequest;
6. CustomPageViewOperationRequestHandler.ts
This operation service file handles external data or logic for the custom page.
// CustomPageViewOperationRequestHandler.ts
import { ExtensionOperationRequestType, ExtensionOperationRequestHandlerBase } from “PosApi/Create/Operations”;
import { ClientEntities } from “PosApi/Entities”;
import ko, { Observable } from “knockout”;
import CustomPageViewOperationRequest from “./CustomPageViewOperationRequest”;
import CustomPageViewOperationResponse from “./CustomPageViewOperationResponse”;
/**
* Request handler for the CustomPageViewOperationRequest class.
*/
export default class CustomPageViewOperationRequestHandler<TResponse extends CustomPageViewOperationResponse>
extends ExtensionOperationRequestHandlerBase<TResponse> {
public dialogResult: Observable<string>;
/**
* Gets the supported request type.
* @return {RequestType<TResponse>} The supported request type.
*/
public supportedRequestType(): ExtensionOperationRequestType<TResponse> {
return CustomPageViewOperationRequest;
}
/**
* Executes the request handler asynchronously.
* @param {SkipFiscalizationOperationRequest<TResponse>} request The request.
* @return {Promise<ICancelableDataResult<TResponse>>} The cancelable async result containing the response.
*/
public executeAsync(request: CustomPageViewOperationRequest<TResponse>): Promise<ClientEntities.ICancelableDataResult<TResponse>> {
this.context.navigator.navigate(“CustomPageView”);
return Promise.resolve({ canceled: true, data: undefined });
}
}
7. CustomPageViewOperationResponse.ts
This operation service file handles external data or logic for the custom page.
// import { Response } from “PosApi/Create/RequestHandlers”;
/**
* Operation response
*/
export default class CustomPageViewOperationResponse extends Response {
}
The workflow for custom page development
Here are the steps to approach custom page development in Dynamics 365 Commerce:
- Set up the environment: Install the Store Commerce POS SDK, configure Node.js, and use Visual Studio Code to align with Microsoft Dynamics 365 Commerce.
- Build the view: Develop with Knockout.js and keep logic encapsulated in a ViewModel for maintainability.
- Implement the UI: Apply Microsoft’s POS design system with lightweight HTML/CSS for a native Store Commerce look.
- Test thoroughly: Use Jest for unit tests and sandbox/E2E tests across desktop and mobile POS.
- Package and deploy: Bundle as a Commerce Runtime (CRT) extension, validate manifest.json, and deploy into the Store Commerce app.
Accelerate growth at an unprecedented pace
Discover how Confiz can help you take control of your daily operations, increasing growth and revenue.
Book a Free ConsultationBest practices for developing Dynamics 365 Commerce custom pages
To ensure your custom page development is a success and you don’t run into errors, here are some best practices to follow:
- Follow UI guidelines: Reuse Microsoft’s patterns to ensure the extension feels native.
- Keep logic lean: Minimize synchronous calls in ViewModels to avoid slowing the POS.
- Design for multiple devices: Use pageName and phonePageName with responsive layouts.
- Build modularly: Structure services, templates, and ViewModels for reuse in future Dynamics 365 Commerce development.
- Enable supportability: Add telemetry, error handling, and consistent naming for easier debugging and maintenance.
Common challenges and how to solve them
Custom page development can be challenging, so we’ve outlined some common issues along with their solutions to guide you through the process.
1. Maintaining UI consistency
- Challenge: Custom styling looked out of place.
- Solution: Reuse POS components and apply scoped CSS only where necessary.
2. Device compatibility
- Challenge: Layout broke on smaller POS screens.
- Solution: Configure phonePageName and validate with a responsive design.
3. Binding errors
- Challenge: Knockout.js mis-bindings disrupted flows.
- Solution: Add strict typing, null checks, and logging for traceability.
4. Performance bottlenecks
- Challenge: Large data sets slowed the POS.
- Solution: Implement pagination, async services, and debounce user input.
Business benefits of custom pages in Dynamics 365 Commerce
- Tailored workflows: Support specific retail processes, such as checkout flows or associate dashboards.
- Higher productivity: Provide staff with contextual data, reducing clicks and training.
- Enhanced experience: Personalize in-store customer interactions and promotions.
- Scalable foundation: Modular design supports future extensions and integrations.
Partner with Confiz to transform your retail journey
Working with an experienced Microsoft Dynamics 365 partner ensures long-term success. The right partner should:
- Have deep experience with the Store Commerce POS SDK and Dynamics 365 Commerce custom development.
- Provide reliable Microsoft Dynamics 365 Commerce support for upgrades and troubleshooting.
- Demonstrate success in delivering tailored solutions across industries.
- Balance cost with quality through proven delivery practices.
At Confiz, we help organizations unlock the full potential of Dynamics 365 Commerce development, from building custom pages to providing long-term Microsoft Dynamics 365 Commerce support. If you’re looking for a trusted Microsoft Dynamics 365 Commerce partner to guide your next project, our experts are here to help. Contact us at marketing@confiz.com to get started.