In the last few years, I have built many solutions using the SPFx ListView command set extension. It helped me to customize the SharePoint lists’ and libraries’ command bar and the right-click context menu for different client needs.
This extension is a useful part of the SPFx because it lets us add extra features that SharePoint does not provide by default. With this, we can add our own buttons and perform actions directly from the list or library view.
For example, you can create buttons to trigger approval workflows, update item statuses, and send email notifications, all without leaving the list page.
Based on my experience, in this tutorial, I will explain in complete detail how to create and deploy SPFx ListView command set extension
If you want to learn SharePoint framework development, then check out the SPFx training course.
Create SPFx ListView Command Set Extension
In this section, we’ll start by creating an SPFx ListView command set extension, covering the folder structure and how it works with a simple default example. Follow the steps below!
- Open the command prompt to create a new solution. Create a directory by providing the command below.
md SPFxSiteInspectionLocator
Here, SPFxSiteInspectionLocator is the solution name. I’ll use this solution to show a real-time example later, but you can name it whatever you like.
- Now enter that directory using the command below.
cd SPFxSiteInspectionLocator
- Run the below Yeoman generator command to quickly create a SharePoint client-side solution project with the right toolchain and project structure.
yo @microsoft/sharepoint
- Clicking enter after providing the above command will ask the questions below. Please provide the answers I have provided below.
- What is your solution name? SPFxSiteInspectionLocator
- Which type of client-side component to create? Extension
- Which type of client-side extension to create? ListView Command Set
- Add new Command Set to solution sp-fx-site-inspection-locator.
- What is your Command Set name? InspectionLocatorCommandSet

- Once it creates the project successfully! Provide code . And hitting Enter opens Visual Studio Code.
- Inside the src -> extensions -> inspectionLocatorCommandSet folder, you’ll find two main files:
- manifest.json = defines the extension type, title[alias], and unique ID.
- .ts file = contains the main logic where we’ll write our code to handle the command actions.
- Now, we’ll understand the code present in the manifest.json file.

{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/command-set-extension-manifest.schema.json",
"id": "65b57e7e-7463-43d2-8124-8fe54474f5ee",
"alias": "InspectionLocatorCommandSetCommandSet",
"componentType": "Extension",
"extensionType": "ListViewCommandSet",
"version": "*",
"manifestVersion": 2,
"requiresCustomScript": false,
"items": {
"COMMAND_1": {
"title": { "default": "Command One" },
"iconImageUrl": "icons/request.png",
"type": "command"
},
"COMMAND_2": {
"title": { "default": "Command Two" },
"iconImageUrl": "icons/cancel.png",
"type": "command"
}
}
}
Here, this file contains the following information about this solution:
- id = A unique GUID that defines our extension in SharePoint.
- alias = The internal name used to refer to this command set in our project.
- componentType = It defines the component type: here it’s an Extension.
- extensionType = It specifies the particular extension: ListViewCommandSet, which adds custom buttons to a list or library command bar.
- items = It contains the list of commands we’re adding to the list view. Each command(such as COMMAND_1, COMMAND_2) includes:
- title = The display name of the button in the command bar.
- iconImageUrl = The icon shown for the command.
- type = It defines the command type.
- Next, we’ll understand the “inspectionLocatorCommandSet.ts” file.

import { Log } from '@microsoft/sp-core-library';
import {
BaseListViewCommandSet,
type Command,
type IListViewCommandSetExecuteEventParameters,
type ListViewStateChangedEventArgs
} from '@microsoft/sp-listview-extensibility';
import { Dialog } from '@microsoft/sp-dialog';
export interface IInspectionLocatorCommandSetCommandSetProperties {
sampleTextOne: string;
sampleTextTwo: string;
}
const LOG_SOURCE: string = 'InspectionLocatorCommandSetCommandSet';
export default class InspectionLocatorCommandSetCommandSet extends BaseListViewCommandSet<IInspectionLocatorCommandSetCommandSetProperties> {
public onInit(): Promise<void> {
Log.info(LOG_SOURCE, 'Initialized InspectionLocatorCommandSetCommandSet');
const compareOneCommand: Command = this.tryGetCommand('COMMAND_1');
compareOneCommand.visible = false;
this.context.listView.listViewStateChangedEvent.add(this, this._onListViewStateChanged);
return Promise.resolve();
}
public onExecute(event: IListViewCommandSetExecuteEventParameters): void {
switch (event.itemId) {
case 'COMMAND_1':
Dialog.alert(`${this.properties.sampleTextOne}`).catch(() => {
});
break;
case 'COMMAND_2':
Dialog.alert(`${this.properties.sampleTextTwo}`).catch(() => {
});
break;
default:
throw new Error('Unknown command');
}
}
private _onListViewStateChanged = (args: ListViewStateChangedEventArgs): void => {
Log.info(LOG_SOURCE, 'List view state changed');
const compareOneCommand: Command = this.tryGetCommand('COMMAND_1');
if (compareOneCommand) {
compareOneCommand.visible = this.context.listView.selectedRows?.length === 1;
}
this.raiseOnChange();
}
}
This file contains the main logic for our ListView command set extension. It has three main functions, each serving a specific purpose.
- Initially, we imported the log, which is used for logging messages to the console, providing helpful information during debugging.
- The following are the core SPFx classes and interfaces for building the list view command sets:
- BaseListViewCommandSet,
- Command,
- IListViewCommandSetExecuteEventParameters,
- ListViewStateChangedEventArgs
- Dialog provides methods to display dialogues or alerts in SharePoint.
- onInit() = This method runs when the command set is initialized. It does the following things:
- Logs a message(“Initialized…”) to confirm that the extension has loaded successfully.
- this.tryGetCommand(‘COMMAND_1’); = Gets the reference to the first command.
- compareOneCommand.visible = Sets its visibility to false by default.
- this.context.listView.listViewStateChangedEvent.add(..) = Add an event listener to track when the list selection changes (for example, when a user selects or deselects items).
- onExecute(…) = This method checks which command was triggered using the event.temId.
- If COMMAND_1 is clicked, it displays an alert showing the text from sampleTextOne.
- If COMMAND_2 is clicked, it displays an alert showing the text from sampleTextTwo.
- If an unknown command is triggered, it throws an error message.
- _onListViewStateChanged() = This method runs when the list selection changes.
- At first, it logs that the list view state has changed.
- Checks if COMMAND_1 exists and sets its visibility to true only when one item is selected.
- Calls raisOnChange() to update the command bar and reflect visibility changes immediately.
- To test this SPFx extension locally, we need to provide our SharePoint list or library URL to the “pageUrl” parameter in the serve.json file located in the config folder, as shown below.

{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/spfx-serve.schema.json",
"port": 4321,
"https": true,
"serveConfigurations": {
"default": {
"pageUrl": "https://szg52.sharepoint.com/sites/SPFxEventPlanning/Lists/SiteInspections/AllItems.aspx",
"customActions": {
"65b57e7e-7463-43d2-8124-8fe54474f5ee": {
"location": "ClientSideExtension.ListViewCommandSet.CommandBar",
"properties": {
"sampleTextOne": "One item is selected in the list",
"sampleTextTwo": "This command is always visible."
}
}
}
},
"inspectionLocatorCommandSet": {
"pageUrl": "https://szg52.sharepoint.com/sites/SPFxEventPlanning/Lists/SiteInspections/AllItems.aspx",
"customActions": {
"65b57e7e-7463-43d2-8124-8fe54474f5ee": {
"location": "ClientSideExtension.ListViewCommandSet.CommandBar",
"properties": {
"sampleTextOne": "One item is selected in the list",
"sampleTextTwo": "This command is always visible."
}
}
}
}
}
}
The serve.json file is used when running your SPFx solution locally with the gulp serve command. It defines how and where our extension will be tested in SharePoint.
- port = The local development server port.
- https = Indicates that the local server runs securely using HTTPS.
- serveConfigurations = This section defines one or more configurations for testing our extension. Here, there are two:
- default
- inspectionLocatorCommandSet
- Each configuration contains the same setup and can be run independently when serving our project.
- pageUrl = The SharePoint page where our extension will be loaded during the local testing.
- customActions = It defines how our SPFx extension is injected into the selected SharePoint page. The long key (6f327372-10a2-4a69-beb8-358f9c5b5109) is the GUID of our extension as listed in the manifest. Each custom action includes:
- location = Specifies where the command will appear. Here it’s the Command Bar of the list view.
- properties = Custom property values passed to the extension, like sampleTextOne and sampleTextTwo, which can be accessed in our .ts file.
- Now, we’ll understand the elements.xml and the ClientSideInstance.xml files located in the sharepoint/assets folder.
- The one below is the elements.xml code, which registers our SPFx command sets as a custom action in SharePoint.
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<CustomAction
Title="InspectionLocatorCommandSet"
RegistrationId="100"
RegistrationType="List"
Location="ClientSideExtension.ListViewCommandSet.CommandBar"
ClientSideComponentId="6f327372-10a2-4a69-beb8-358f9c5b5109"
ClientSideComponentProperties="{"sampleTextOne":"One item is selected in the list.", "sampleTextTwo":"This command is always visible."}">
</CustomAction>
</Elements>
- <CustomAction> = This tag declares a new custom action for our command set.
- Title = The display name of our command set.
- RegistrationId=”100″ indicates that this command set applies to generic lists. You can change this if targeting a library, such as “101.”
- RegistrationType=”List” specifies that this extension applies to SharePoint lists.
- Location = Defines where the extension appears; here, it’s the Command Bar of the list view. The possible values are:
- ClientSideExtension.ListViewCommandSet.ContextMenu: The context menu of the item(s).
- ClientSideExtension.ListViewCommandSet.CommandBar: The top command set menu in a list or library.
- ClientSideExtension.ListViewCommandSet: Both the context menu and the command bar.
- ClientSideComponentId = The unique GUID that connects this XML definition to our SPFx command set from our manifest.json.
- ClientSideComponentProperties = It passes custom property values (such as sampleTextOne and sampleTextTwo) that can be accessed within the .ts file code.
- Below is the ClientSideInstance.xml file. It defines an instance of our client-side extension, of how and where it will appear when deployed.
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<ClientSideComponentInstance
Title="InspectionLocatorCommandSet"
Location="ClientSideExtension.ListViewCommandSet.CommandBar"
ListTemplateId="100"
Properties="{"sampleTextOne":"One item is selected in the list.", "sampleTextTwo":"This command is always visible."}"
ComponentId="6f327372-10a2-4a69-beb8-358f9c5b5109" />
</Elements>
- <ClientSideComponentInstance> = This tag creates an instance of our SPFx extension in SharePoint.
- Title = The instance name of our command set.
- Location = Specifies where the command will appear; here, it’s the ListView Command Bar.
- ListTemplateId=”100″ defines the list template ID.
- Properties = Holds the same custom property values you want to pass to the component, e.g., sampleTextOne, sampleTextTwo.
- ComponentId = The GUID that identifies which SPFx component this instance refers to. It must match our manifest.json ID.
- Now, run the gulp serve command, and it will automatically open the SharePoint list /library that you gave in the serve.json file. Once it opens, a pop-up will appear. Click on Load debug scripts, then, as shown below, select an item. You’ll see Command One will be visible. When you click that, a dialog box will open. The same applies to Command Two, but this button will always be visible.

I hope you understand how to create an SPFx list view command set extension and debug it directly in the main SharePoint list. Then, in the section below, I will explain a real-time example for this extension.
SPFx ListView Command Set Extension Example
In the example below, we have a SharePoint list called “SiteInspections” that contains details like the inspector’s name, inspection date, address, city, and region.
When a user selects an item from this list, a custom command called “Map Selected” appears in the command bar. Clicking this button opens a map showing the location of the selected address, making it easy for users to view the inspection site quickly.
If the provided address is incorrect, an error message will be displayed stating “Location not found.”

To achieve this, we are using the Fluent UI React Panel control and the PnP React map control. Let’s implement the above example by following the steps below.
- Open the command prompt where our previous solution was created, and run the command below to use the PnP React map control.
npm install @pnp/spfx-controls-react --save --save-exact
- Then, run the following command to import the React framework into our existing solution.
npm install react react-dom @types/react @types/react-dom --save
Currently, my SPFx version compatibility is:
SPFx : 1.21.1
Node.js : v22
TypeScript : v5.3
React : v17.0.1
I installed the following command to import React. If your SPFx versions match the above one, then please run the command below only.
npm install react@17.0.1 react-dom@17.0.1 @types/react@17.0.2 @types/react-dom@17.0.2 --save
- Then, create a folder named “components” and within that add the MapPanel.tsx file within the src folder as shown below. Add the code below to that file.

import * as React from 'react';
import { Panel, PanelType } from '@fluentui/react';
import { Map, ICoordinates, MapType } from '@pnp/spfx-controls-react/lib/Map';
import { IInspectionItem } from '../InspectionLocatorCommandSetCommandSet';
export interface IMapPanelProps {
isOpen: boolean;
onDismiss: () => void;
items: IInspectionItem[];
}
export interface IMapPanelState {
coordinate?: ICoordinates;
loading: boolean;
error?: string;
}
export default class MapPanel extends React.Component<IMapPanelProps, IMapPanelState> {
constructor(props: IMapPanelProps) {
super(props);
this.state = { loading: false };
}
public async componentDidMount(): Promise<void> {
await this.resolveAddress();
}
public async componentDidUpdate(prevProps: IMapPanelProps): Promise<void> {
if (prevProps.items !== this.props.items && this.props.isOpen) {
await this.resolveAddress();
}
}
private async resolveAddress(): Promise<void> {
const firstItem = this.props.items?.[0];
if (!firstItem) {
this.setState({ loading: false, error: 'No item selected' });
return;
}
const address = [firstItem.Address, firstItem.City, firstItem.Region]
.filter(Boolean)
.join(', ');
if (!address) {
this.setState({ loading: false, error: 'Address is empty' });
return;
}
await this.geocodeWithNominatim(address, firstItem.Title);
}
private async geocodeWithNominatim(address: string, title: string): Promise<void> {
try {
this.setState({ loading: true, error: undefined });
const url = `https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(address)}&limit=1`;
const resp = await fetch(url, { headers: { 'Accept-Language': 'en', 'User-Agent': 'SPFxInspectionLocator/1.0' } });
const data = await resp.json();
console.log("Nominatim response for:", address, data);
if (Array.isArray(data) && data.length > 0) {
const loc = data[0];
const latitude = parseFloat(loc.lat);
const longitude = parseFloat(loc.lon);
this.setState({
coordinate: { latitude, longitude, displayName: title, address },
loading: false,
error: undefined
});
} else {
this.setState({ loading: false, error: 'Location not found for: ' + address });
}
} catch (err) {
console.error('Geocoding error:', err);
this.setState({ loading: false, error: 'Error while fetching coordinates' });
}
}
public render(): React.ReactElement<IMapPanelProps> {
const { isOpen, onDismiss } = this.props;
const { coordinate, loading, error } = this.state;
return (
<Panel
isOpen={isOpen}
onDismiss={onDismiss}
type={PanelType.large}
headerText="Inspection Site Map"
closeButtonAriaLabel="Close"
isLightDismiss
>
<div style={{ height: 600, padding: 10 }}>
{loading && <div>Resolving address...</div>}
{error && <div style={{ color: 'red' }}> {error}</div>}
{!loading && coordinate && (
<Map
titleText={coordinate.displayName}
coordinates={coordinate}
zoom={12}
width="100%"
height={600}
enableSearch={false}
mapType={MapType.standard}
/>
)}
</div>
</Panel>
);
}
}
Here:
- We imported the following things:
- React = For creating the component.
- Panel, PanelType = From Fluent UI, used to display the side panel.
- Map, ICoordinates, MapType = From @pnp/spfx-controls-react, used to show a map.
- IInspectionItem = Interface for list item data.
- IMapPanelProps = Props passed from the parent [panel open state, dismiss function, selected items].
- IMapPanelState = Internal state [coordinates, loading flag, error message].
- constructor = Initializes state with loading: false.
- componentDidMount() = It runs once the panel loads and calls resolveAddress().
- componentDidUpdate() = It re-runs resolveAddress() if selected items change while the panel is open.
- resolveAddress() = This method gets the first selected item.
- Builds a full address using the Address, City, and Region fields.
- If the address exists, it calls geocodeWithNominatim() to retrieve the coordinates.
- geocodeWithNominatim() = It calls the Nominatim (OpenStreetMap) API to get latitude and longitude.
- If a location is found, update the state with coordinates.
- If not found, it sets an error message.
- Handles fetch errors gracefully.
- rener() = Displays a Fluent UI Panel titled “Inspection Site Map.”
- Inside the panel:
- Shows “Resolving address…” while loading.
- Shows an error if geocoding fails.
- Displays a Map when the coordinates are ready.
- Inside the panel:
- Now, we’ll remove those two sample commands, COMMAND_1 and COMMAND_2, and we’ll keep the MAP_SELECTED command in the manifest.json file. SO update it as shown below.
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx/command-set-extension-manifest.schema.json",
"id": "6f327372-10a2-4a69-beb8-358f9c5b5109",
"alias": "InspectionLocatorCommandSetCommandSet",
"componentType": "Extension",
"extensionType": "ListViewCommandSet",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"items": {
"MAP_SELECTED": {
"title": { "default": "Map Selected" },
"iconImageUrl": "./icons/map.png",
"type": "command"
}
}
}
- Then, open the InspectionLocatorCommandSet.ts file and update the default code with the code below.
import { Log } from '@microsoft/sp-core-library';
import {
BaseListViewCommandSet,
type Command,
type IListViewCommandSetExecuteEventParameters,
type ListViewStateChangedEventArgs
} from '@microsoft/sp-listview-extensibility';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import MapPanel from './components/MapPanel';
export interface IInspectionLocatorCommandSetCommandSetProperties {
sampleTextOne: string;
sampleTextTwo: string;
}
export interface IInspectionItem {
Title: string;
Address: string;
City: string;
Region: string;
Status: string;
InspectionDate: string;
InspectorName: string;
PhotoEvidence?: string;
}
const LOG_SOURCE: string = 'InspectionLocatorCommandSetCommandSet';
export default class InspectionLocatorCommandSetCommandSet extends BaseListViewCommandSet<IInspectionLocatorCommandSetCommandSetProperties> {
public onInit(): Promise<void> {
Log.info(LOG_SOURCE, 'Initialized InspectionLocatorCommandSetCommandSet');
this.context.listView.listViewStateChangedEvent.add(this, this._onListViewStateChanged);
return Promise.resolve();
}
public onExecute(event: IListViewCommandSetExecuteEventParameters): void {
switch (event.itemId) {
case 'MAP_SELECTED':
const selectedItems: IInspectionItem[] = event.selectedRows.map(row => ({
Title: row.getValueByName('Title'),
Address: row.getValueByName('Address'),
City: row.getValueByName('City'),
Region: row.getValueByName('Region'),
Status: row.getValueByName('Status'),
InspectionDate: row.getValueByName('InspectionDate'),
InspectorName: row.getValueByName('InspectorName'),
PhotoEvidence: row.getValueByName('PhotoEvidence')
}));
const panelDiv: HTMLElement = document.body.appendChild(document.createElement('div'));
const closePanel = () => {
ReactDOM.unmountComponentAtNode(panelDiv);
panelDiv.remove();
};
const element: React.ReactElement = React.createElement(MapPanel, {
isOpen: true,
onDismiss: closePanel,
items: selectedItems
});
ReactDOM.render(element, panelDiv);
break;
default:
throw new Error('Unknown command');
}
}
private _onListViewStateChanged = (args: ListViewStateChangedEventArgs): void => {
Log.info(LOG_SOURCE, 'List view state changed');
const mapSelectedCommand: Command = this.tryGetCommand('MAP_SELECTED');
if (mapSelectedCommand) {
// This command should be hidden unless exactly one row is selected.
mapSelectedCommand.visible = this.context.listView.selectedRows?.length === 1;
}
// You should call this.raiseOnChage() to update the command bar
this.raiseOnChange();
}
}
Here:
- This time, we additionally imported the following things:
- React and ReactDOM = Added to render the React component (MapPanel) dynamically when a command is executed.
- MapPanel = Imported custom React component that displays the map in a panel.
- Added this new Interface: IInspectionItem, which defines the structure of a SharePoint list item. Includes fields like Title, Address, City, Region, Status, InspectionDate, InspectorName, and optional PhotoEvidence.
- onExecute() = Replaces the old COMMAND_1 or COMMAND_2 logic,added the MAP_SELECTED. When the Map Selected button is clicked:
- Retrieves the selected list item(s).
- Maps each selected row to an IInspectionItem object using getValueByName().
- Creates a new <div> dynamically in the DOM.
- Renders the MapPanel React component inside that div, passing:
- isOpen: true = opens the panel.
- onDismiss: closePanel = unmounts and removes the div when closed.
- items: selectedItems = passes the selected list item(s) to display on the map.
- Updated _onListViewStateChanged()
- Uses the command ID ‘MAP_SELECTED’ instead of ‘COMMAND_1’.
- Keeps the same logic, shows the command only when exactly one row is selected.
Now, save the changes, run ‘gulp serve’, and test it locally, as we did previously. In the section below, we’ll discuss how to deploy our list view command set extension to a particular SharePoint site.
Deploy the SPFx ListView Command Set Extension to SharePoint Online
- To deploy this list view command set extension, run the commands below one by one.
gulp clean
gulp build
gulp bundle --ship
gulp package-solution --ship
- Once those commands run successfully! In the sharepoint > solution folder, the .sppkg file will be created as shown below. This file we need to deploy to the site collection app catalog.

- In case you have not yet created a site collection app catalog in your current SharePoint site, run the code below in VSCode.
Connect-PnPOnline -Url "https://<tenantname>-admin.sharepoint.com" -ClientId "Your Client ID" -Interactive
Add-PnPSiteCollectionAppCatalog -Site "https://<tenantname>.sharepoint.com/sites/SPFxEventPlanning"
Here:
- -ClientId = Provide your application ID, registered in the Microsoft Entra ID, which has SharePoint delegable full control permissions.
- -Url = Provide your SharePoint admin center url.
- Add-PnPSiteCollectionAppCatalog = This PnP command adds the site collection app catalog to the specified site address.
In the image below, you can see that before running the script, my site did not have an app catalog; after running the code, it got created.

- Now, open Apps for SharePoint, then add the .sppkg file to it, and you will see a pop-up as shown below. Click on Deploy.

- Once it is deployed successfully, exit that page. Then, on the home page, click on the +New dropdown, select App.

- It will open the My apps page. Here, select the app that we previously deployed.

- Then, refresh the SharePoint site and open the SharePoint list. Select an item, and you’ll be able to see the “Map Selected” button. By clicking, the map will open, locating the address associated with the selected item.

This way, you can easily deploy your SharePoint list view command set extension to the SharePoint site collection app catalog. This button will be available to all lists and libraries. If you want to restrict, follow this url.
I hope you found this article helpful!. Here, we learned, how to create an SPFx list view command set extension, including a complete explanation of what each file does in this solution, and provide a custom example of adding a button that identifies the location of the selected item and displays it in a map control in the panel.
Then, how to test and deploy this solution to a SharePoint site collection app catalog. Additionally, I gave the code to create a site collection app catalog, if it has not been created yet. Do let me know in the comment below if you still have any questions.
You may also like:
- Create Folders and Subfolders in SharePoint document library using SPFx
- SPFx CRUD Operations using No JavaScript Framework
- Send Email in SPFx using Microsoft Graph API and PnP JS
- Get Current SharePoint Site Information in SPFx using MS Graph API

Hey! I’m Bijay Kumar, founder of SPGuides.com and a Microsoft Business Applications MVP (Power Automate, Power Apps). I launched this site in 2020 because I truly enjoy working with SharePoint, Power Platform, and SharePoint Framework (SPFx), and wanted to share that passion through step-by-step tutorials, guides, and training videos. My mission is to help you learn these technologies so you can utilize SharePoint, enhance productivity, and potentially build business solutions along the way.