This note is based on patterns from these 5 samples:
sample/arcgis-experience-builder-sdk-resources/widgets/demo-functionsample/arcgis-experience-builder-sdk-resources/widgets/map-viewsample/arcgis-experience-builder-sdk-resources/widgets/feature-layer-functionsample/arcgis-experience-builder-sdk-resources/widgets/add-layerssample/arcgis-experience-builder-sdk-resources/widgets/show-unit-tests
A widget is working when it:
- appears in the Experience Builder widget panel,
- can be dropped into a page,
- renders without runtime errors,
- can save and read settings/config,
- and (if used) connects to map/data sources correctly.
Inside your Experience Builder install, place custom widgets under:
client/your-extensions/widgets/<your-widget-name>/
The sample repo README also shows cloning into client/sdk-sample for reference samples.
Use this structure as your base:
<your-widget-name>/
manifest.json
config.json
icon.svg
src/
config.ts
runtime/
widget.tsx
translations/
default.ts
setting/
setting.tsx
translations/
default.ts
The simplest working shape is shown by demo-function.
At minimum, include:
name,label,type: "widget"versionandexbVersionmatching your ExB versiondefaultSizetranslatedLocales
If you use map APIs/components, add map dependency info:
dependency: "jimu-arcgis"(seemap-view)properties.canCreateMapView: trueif your widget creates a map view (seemap-view)
Example:
{
"name": "my-widget",
"label": "My Widget",
"type": "widget",
"version": "1.19.0",
"exbVersion": "1.19.0",
"translatedLocales": ["en"],
"defaultSize": { "width": 400, "height": 300 }
}Use immutable config typing like demo-function and add-layers.
src/config.ts:
import type { ImmutableObject } from 'jimu-core'
export interface Config {
text: string
}
export type IMConfig = ImmutableObject<Config>config.json:
{
"text": "Hello"
}src/runtime/widget.tsx (minimal function widget):
import { React, type AllWidgetProps } from 'jimu-core'
import type { IMConfig } from '../config'
export default function Widget (props: AllWidgetProps<IMConfig>) {
return (
<div className='jimu-widget'>
{props.config?.text}
</div>
)
}From demo-function, the key pattern is onSettingChange.
src/setting/setting.tsx:
import { React } from 'jimu-core'
import type { AllWidgetSettingProps } from 'jimu-for-builder'
import type { IMConfig } from '../config'
export default function Setting (props: AllWidgetSettingProps<IMConfig>) {
const onTextChange = (evt: React.FormEvent<HTMLInputElement>) => {
props.onSettingChange({
id: props.id,
config: props.config.set('text', evt.currentTarget.value)
})
}
return <input defaultValue={props.config.text} onChange={onTextChange} />
}- In settings, use
MapWidgetSelectorand saveuseMapWidgetIds. - In runtime, render
JimuMapViewComponentand captureonActiveViewChange.
Use this when your widget interacts with another map widget.
- In settings, use
DataSourceSelectorrestricted toWebMap. - In runtime, wrap UI in
DataSourceComponent. - On data source creation, build a map view via
MapViewManager.createJimuMapView(...).
Use this when your widget itself owns/creates the view.
For data-driven widgets:
- Settings:
- use
DataSourceSelectorwithDataSourceTypes.FeatureLayer - use
FieldSelectorto choose one or more fields
- use
- Runtime:
- keep query state (
where,outFields,pageSize) - render through
DataSourceComponent - check status with
DataSourceStatus.Loadedbefore reading records
- keep query state (
This is the standard ExB pattern for querying and rendering records.
If your widget needs ArcGIS JS API classes (for example FeatureLayer), load them inside an action handler:
loadArcGISJSAPIModules(['esri/layers/FeatureLayer']).then(([FeatureLayer]) => {
// use module
})This keeps startup lighter and avoids loading modules before user action.
Use jimu-for-test helpers:
widgetRender()andwrapWidget(...)- mock ArcGIS module loading with
jest.mock('jimu-core', ...) - test render, props/state mapping, click events, and async API behavior
This sample is a good template for tests that touch ExB props and ArcGIS module calls.
- Create widget folder under
client/your-extensions/widgets/. - Add
manifest.json,config.json,src/runtime/widget.tsx. - Add
src/setting/setting.tsxand wireonSettingChange. - If map/data is needed, wire
MapWidgetSelectororDataSourceSelector. - Start Experience Builder and verify the widget appears and renders.
- If you added tests, run
npm run testinclient.
- ExB version mismatch between your install and
manifest.json. - Missing
dependency: "jimu-arcgis"for map-related widgets. - No selected data source/map widget in settings, so runtime has nothing to use.
- Reading records before data source status is loaded.
- Not calling
onSettingChange, so config changes never persist.