Skip to main content

Overview

EverShop’s widget system provides a flexible way to create reusable, configurable UI components that can be added to pages through the admin panel. Widgets consist of two components: a setting component for configuration and a display component for rendering.

Widget Manager

The WidgetManager is a singleton that manages widget registration, updates, and retrieval. Location: packages/evershop/src/lib/widget/widgetManager.ts:66

Widget Registration

registerWidget()

Register a new widget type.
import { registerWidget } from '@evershop/evershop/src/lib/widget/widgetManager';

registerWidget({
  type: 'text_block',
  name: 'Text Block',
  description: 'A simple text block widget',
  enabled: true,
  component: '/path/to/TextBlock.js',
  settingComponent: '/path/to/TextBlockSetting.js'
});
Location: packages/evershop/src/lib/widget/widgetManager.ts:341

Signature

function registerWidget(widget: Widget): boolean

Parameters

widget
Widget
required

Returns

  • true if the widget was successfully registered
  • false if a widget with the same type already exists

Validation Rules

Widget registration enforces several validation rules:
  1. Widget type must be alphanumeric with underscores only
  2. Component paths must be valid, existing .js files
  3. Component filenames must start with an uppercase letter
  4. Widget types must be unique
  5. Cannot register widgets after getAllWidgets() is called (manager is frozen)

updateWidget()

Update properties of an existing widget.
import { updateWidget } from '@evershop/evershop/src/lib/widget/widgetManager';

updateWidget('text_block', {
  name: 'Enhanced Text Block',
  description: 'Updated description',
  component: '/path/to/EnhancedTextBlock.js'
});
Location: packages/evershop/src/lib/widget/widgetManager.ts:352

Signature

function updateWidget(
  widgetType: string,
  updates: Partial<Widget>
): boolean

Use Case

Third-party extensions can override core widget components:
// In a third-party extension's bootstrap file
updateWidget('product_carousel', {
  component: '/extensions/my-extension/components/CustomProductCarousel.js',
  settingComponent: '/extensions/my-extension/components/CustomProductCarouselSettings.js'
});
Implementation: packages/evershop/src/lib/widget/widgetManager.ts:165

removeWidget()

Remove a widget from the registry.
import { removeWidget } from '@evershop/evershop/src/lib/widget/widgetManager';

removeWidget('text_block');
Location: packages/evershop/src/lib/widget/widgetManager.ts:365

Signature

function removeWidget(widgetType: string): boolean

Widget Retrieval

getWidget()

Get a single widget by type.
import { getWidget } from '@evershop/evershop/src/lib/widget/widgetManager';

const widget = getWidget('text_block');
if (widget) {
  console.log(widget.name);
  console.log(widget.component);
}
Location: packages/evershop/src/lib/widget/widgetManager.ts:373

Signature

function getWidget(widgetType: string): Widget | undefined

hasWidget()

Check if a widget is registered.
import { hasWidget } from '@evershop/evershop/src/lib/widget/widgetManager';

if (hasWidget('text_block')) {
  console.log('Text block widget is available');
}
Location: packages/evershop/src/lib/widget/widgetManager.ts:382

Signature

function hasWidget(widgetType: string): boolean

getAllWidgets()

Get all registered widgets.
import { getAllWidgets } from '@evershop/evershop/src/lib/widget/widgetManager';

const widgets = getAllWidgets();
widgets.forEach(widget => {
  console.log(`${widget.name} (${widget.type})`);
});
Location: packages/evershop/src/lib/widget/widgetManager.ts:304
Calling getAllWidgets() freezes the widget manager. After this call, no further registrations, updates, or removals are allowed. This typically happens during the application bootstrap phase.

Signature

function getAllWidgets(): Widget[]

Returns

Array of widgets with additional generated keys:
interface WidgetWithKeys extends Widget {
  settingComponentKey: string;  // Generated unique key
  componentKey: string;         // Generated unique key
}
Implementation: packages/evershop/src/lib/widget/widgetManager.ts:304

getEnabledWidgets()

Get only enabled widgets.
import { getEnabledWidgets } from '@evershop/evershop/src/lib/widget/widgetManager';

const enabledWidgets = getEnabledWidgets();
Location: packages/evershop/src/lib/widget/widgetManager.ts:321

Signature

function getEnabledWidgets(): Widget[]

Widget Components

Display Component

The display component renders the widget on the frontend.
// TextBlock.js
import React from 'react';

export default function TextBlock({ content, title }) {
  return (
    <div className="text-block">
      {title && <h2>{title}</h2>}
      <div dangerouslySetInnerHTML={{ __html: content }} />
    </div>
  );
}

Settings Component

The settings component provides an admin UI for configuring the widget.
// TextBlockSetting.js
import React from 'react';
import { Input } from '@components/common/form/fields/Input';
import { Textarea } from '@components/common/form/fields/Textarea';

export default function TextBlockSetting({ widget, updateWidget }) {
  return (
    <div>
      <Input
        name="title"
        label="Title"
        value={widget.settings?.title || ''}
        onChange={(e) => updateWidget({
          ...widget,
          settings: {
            ...widget.settings,
            title: e.target.value
          }
        })}
      />
      <Textarea
        name="content"
        label="Content"
        value={widget.settings?.content || ''}
        onChange={(e) => updateWidget({
          ...widget,
          settings: {
            ...widget.settings,
            content: e.target.value
          }
        })}
      />
    </div>
  );
}

Freezing Behavior

The widget manager has a built-in immutability feature:
// During bootstrap phase
registerWidget({ /* ... */ });  // ✓ Works

// After bootstrap (when widgets are retrieved)
const widgets = getAllWidgets();  // Freezes the manager

registerWidget({ /* ... */ });  // ✗ Throws error
updateWidget('type', { /* ... */ });  // ✗ Throws error
removeWidget('type');  // ✗ Throws error
Implementation: packages/evershop/src/lib/widget/widgetManager.ts:88
This ensures widgets are only modified during the bootstrap phase, preventing runtime modifications that could cause inconsistencies.

Best Practices

1

Register in Bootstrap Files

Always register widgets in module bootstrap files (bootstrap.js), not at runtime.
2

Use Descriptive Types

Widget types should be clear and unique (e.g., product_carousel, image_slider, text_block).
3

Follow Component Naming

Component filenames must start with uppercase (e.g., TextBlock.js, not textBlock.js).
4

Validate Settings

Settings components should validate user input and provide helpful error messages.
5

Make Widgets Reusable

Design widgets to be flexible and configurable through settings rather than hard-coded.
6

Handle Missing Data

Display components should gracefully handle missing or invalid settings.

Complete Example

// modules/cms/bootstrap.js
import { registerWidget } from '@evershop/evershop/src/lib/widget/widgetManager';
import path from 'path';

export default function bootstrap() {
  // Register a custom widget
  registerWidget({
    type: 'featured_products',
    name: 'Featured Products',
    description: 'Display a grid of featured products',
    enabled: true,
    component: path.resolve(__dirname, 'components/FeaturedProducts.js'),
    settingComponent: path.resolve(__dirname, 'components/FeaturedProductsSetting.js')
  });
}
// components/FeaturedProducts.js
import React from 'react';
import { useQuery } from '@apollo/client';

export default function FeaturedProducts({ count, categoryId }) {
  const { data, loading } = useQuery(FEATURED_PRODUCTS_QUERY, {
    variables: { count: count || 4, categoryId }
  });

  if (loading) return <div>Loading...</div>;

  return (
    <div className="featured-products">
      <h2>Featured Products</h2>
      <div className="product-grid">
        {data.products.map(product => (
          <ProductCard key={product.id} product={product} />
        ))}
      </div>
    </div>
  );
}
// components/FeaturedProductsSetting.js
import React from 'react';

export default function FeaturedProductsSetting({ widget, updateWidget }) {
  return (
    <div>
      <label>
        Product Count:
        <input
          type="number"
          min="1"
          max="12"
          value={widget.settings?.count || 4}
          onChange={(e) => updateWidget({
            ...widget,
            settings: {
              ...widget.settings,
              count: parseInt(e.target.value, 10)
            }
          })}
        />
      </label>
    </div>
  );
}

Build docs developers (and LLMs) love