registerWidget
Register a new widget in the widget manager during the bootstrap phase.
Import
import { registerWidget } from '@evershop/evershop/lib/widget';
Syntax
registerWidget(widget: Widget): boolean
Parameters
widget
Type: Widget
| Property | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Unique widget type id. Must match /^[a-zA-Z_][a-zA-Z0-9_]*$/ — letters, digits and underscores, and it may not start with a digit. |
name | string | Yes | Display name in the admin panel and page-builder palette. |
component | string | Yes | Absolute path to the storefront component (a .js file with an uppercase basename). |
settingComponent | string | Yes | Absolute path to the admin settings component (same file rules). |
previewComponent | string | Yes | Absolute path to the palette hover-preview component (same file rules). Registration throws without it. |
enabled | boolean | Yes | Whether the widget is offered to merchants. |
defaultSettings | Record<string, any> | Yes | Initial settings for a newly-added instance. Validated against schema at registration. |
description | string | No | Short explanation shown in the palette. |
category | WidgetCategory | No | One of 'content', 'commerce', 'navigation', 'marketing', 'layout'. |
icon | string | No | A lucide-react icon name from the curated map (e.g. Columns, Type, Image). Unknown names fall back to Layers. |
schema | WidgetSchemaDefinition | No | JSON Schema (draft-07) for the settings object. Optional for backward compatibility — omitting it logs a warning and will become an error in a future version. |
graphql | { typeDefs: string; settingsType: string } | No | SDL fragment plus the name of the settings type that joins the WidgetSettings union. |
defaultSettings, not default_settingsThe field is camelCase. TypeScript rejects default_settings outright; in plain JavaScript it is accepted and ignored, leaving defaultSettings undefined. If the widget also declares a schema, registration then throws — undefined is not an object — so the mistake surfaces at boot. Without a schema it fails silently, and every new instance starts with no settings.
Return Value
Returns boolean:
trueif the widget was successfully registeredfalseif a widget with the same type already exists (a warning is logged)
Invalid input throws rather than returning false.
Examples
Basic Usage
import path from 'path';
import { registerWidget } from '@evershop/evershop/lib/widget';
export default function bootstrap() {
registerWidget({
type: 'banner_slider',
name: 'Banner Slider',
description: 'Display a banner slider',
category: 'marketing',
enabled: true,
settingComponent: path.resolve(
import.meta.dirname,
'components/BannerSliderSetting.js'
),
component: path.resolve(
import.meta.dirname,
'components/BannerSlider.js'
),
previewComponent: path.resolve(
import.meta.dirname,
'components/BannerSliderPreview.js'
),
defaultSettings: {
slides: [],
autoplay: true
}
});
}
__dirname does not exist in extensionsExtensions are ESM, where __dirname is undefined — path.resolve(undefined, …) throws. Use import.meta.dirname.
Complete Widget Registration
import path from 'path';
import { registerWidget } from '@evershop/evershop/lib/widget';
export default function bootstrap() {
registerWidget({
type: 'product_carousel',
name: 'Product Carousel',
description: 'Display products in a carousel',
category: 'commerce',
icon: 'Image',
enabled: true,
settingComponent: path.resolve(
import.meta.dirname,
'components/ProductCarouselSetting.js'
),
component: path.resolve(
import.meta.dirname,
'components/ProductCarousel.js'
),
previewComponent: path.resolve(
import.meta.dirname,
'components/ProductCarouselPreview.js'
),
defaultSettings: {
limit: 10,
autoplay: true,
interval: 5000
},
schema: {
type: 'object',
additionalProperties: true,
properties: {
limit: { type: 'integer', minimum: 1, maximum: 50 },
autoplay: { type: 'boolean' },
interval: { type: 'integer', minimum: 1000 }
}
},
graphql: {
typeDefs: `
type ProductCarouselSettings {
limit: Int
autoplay: Boolean
interval: Int
}
`,
settingsType: 'ProductCarouselSettings'
}
});
}
The preview component
previewComponent renders the hover-preview card in the page-builder Widgets palette. It receives no props, so it must render a self-contained stylized mock (rectangles, lines, placeholder text) that works without runtime data or context. It is bundled into the admin build under the key admin_widget_preview_<type>.
import React from 'react';
export default function ProductCarouselPreview() {
return (
<div className="flex gap-2">
{[0, 1, 2].map((i) => (
<div key={i} className="h-16 w-12 rounded bg-gray-200" />
))}
</div>
);
}
Validation Rules
Every rule below throws on violation.
Widget type
- Must match
/^[a-zA-Z_][a-zA-Z0-9_]*$/— letters, digits, underscores; cannot start with a digit; no spaces or dashes. - Cannot be empty.
- A duplicate type does not throw: registration is skipped and
falseis returned with a warning.
Component paths
component,settingComponentandpreviewComponentare all required.- Each must resolve to an existing file with a
.jsextension (paths point at compiled output, not the.tsxsource). - Each base filename must start with an uppercase letter.
Schema and default settings
schemamust compile under AJV — an invalid JSON Schema throwsWidget "<type>" has an invalid JSON Schema: ….defaultSettingsmust validate againstschema, otherwiseWidget "<type>" has defaultSettings that don't match its schema: ….
GraphQL block
graphql.typeDefsmust parse as SDL.graphql.settingsTypemust be one of the object types declared ingraphql.typeDefs, otherwisegraphql.settingsType "<name>" is not declared in graphql.typeDefs.- Two widgets declaring the same SDL type name collide when the schema is assembled and throw at build time.
Registry lock
- Must be called during bootstrap. After
getAllWidgets()orgetEnabledWidgets()runs, the manager is frozen and any further registration throws.
Bootstrap Location
Widgets must be registered in the extension's bootstrap file:
import path from 'path';
import { registerWidget } from '@evershop/evershop/lib/widget';
export default function bootstrap() {
registerWidget({
type: 'custom_widget',
name: 'Custom Widget',
category: 'content',
enabled: true,
settingComponent: path.resolve(
import.meta.dirname,
'components/CustomWidgetSetting.js'
),
component: path.resolve(
import.meta.dirname,
'components/CustomWidget.js'
),
previewComponent: path.resolve(
import.meta.dirname,
'components/CustomWidgetPreview.js'
),
defaultSettings: {}
});
}
See Also
- updateWidget - Update an existing widget
- removeWidget - Remove a widget
- getAllWidgets - Query the widget registry
- Widget Development - Full widget guide