Custom & External Modules
ChamevoJS modules (text, images, designs, etc.) are displayed in the mainbar and rendered inside the sidebar panel. You can register your own web components as modules and mount any module outside the customizer into arbitrary DOM elements.
Built-in Module IDs
| ID | Component | Icon | Description |
|---|---|---|---|
text | cv-module-text | cursor-text | Custom text input and templates |
images | cv-module-images | image | Tabbed image sources (upload, Pixabay, AI, QR) |
designs | cv-module-graphics | circles-three-plus | Graphics/clipart catalog |
designs_<id> | cv-module-graphics | (configured) | Dynamic design library (see Dynamic Designs) |
manage-layers | cv-module-layers | stack | Layer management and reorder |
text-layers | cv-module-text-layers | text-align-left | Text layer editing (accordion) |
my-designs | cv-module-my-designs | floppy-disk | Save/load designs |
products | cv-module-products | storefront | Product catalog browser |
layouts | cv-module-layouts | grid-four | Layout templates |
names-numbers | cv-module-names-numbers | list-numbers | Names & numbers bulk entry |
bulk-variations | cv-module-bulk-variations | bulk-variations | Bulk order entry with variation dropdowns (size, color, etc.) |
color-selection | cv-color-selection | color-selection | Color selection panel for product-level colorizable elements |
property-control | cv-property-control | — | External single-property element control (guide) |
element-toolbar | cv-element-toolbar | — | Element editing toolbar |
Use these IDs in mainBarModules, disabledModules, and initialActiveModule options.
Choosing Which Modules to Show
By default, all modules are shown. Control which modules appear with mainBarModules:
customizer.options = {
mainBarModules: ['text', 'images', 'designs', 'manage-layers'],
initialActiveModule: 'text',
};
Module Ordering
The order of IDs in mainBarModules is the order they appear in the nav. You can freely mix built-in IDs, designs_<id> dynamic libraries, and custom module descriptors:
mainBarModules: [
'products', // built-in
'text', // built-in
'designs_clipart', // dynamic designs library
'images', // built-in
{ id: 'quote', element: 'my-quote-module', icon: 'calculator', label: 'Quote' }, // custom
'manage-layers', // built-in
'bulk-variations', // built-in
],
Module Display Mode
Control whether the module panel opens as a sidebar or a floating dialog:
customizer.moduleDisplay = 'sidebar'; // default — panel opens beside the canvas
customizer.moduleDisplay = 'dialog'; // panel floats over the canvas, can be dragged
On small screens (sm layout) the dialog always slides up as a bottom sheet regardless of this setting.
Disable modules per view with disabledModules:
const product = {
title: 'Mug',
views: [
{
title: 'Front',
elements: [...],
options: {
disabledModules: ['layouts', 'names-numbers'],
},
},
],
};
Dynamic Designs
Dynamic Designs let you configure multiple separate design libraries, each appearing as its own named module in the mainbar. Every library reuses the cv-module-graphics component but filters to a specific set of categories.
Configuration
Define your libraries with the dynamicDesigns option, then reference them in mainBarModules using designs_<id> entries — where <id> matches the key in dynamicDesigns:
const chamevo = new ChamevoJS({
canvas,
options: {
mainBarModules: ['text', 'images', 'designs_clipart', 'designs_sports', 'layouts'],
dynamicDesigns: {
clipart: {
name: 'Clipart',
icon: '/images/icons/clipart.svg',
categories: ['Animals', 'Doodles'],
},
sports: {
name: 'Sports',
icon: '/images/icons/sports.png',
categories: ['Sports Logos', 'Team Badges'],
},
},
},
});
DynamicDesignLibrary Interface
| Property | Type | Description |
|---|---|---|
name | string | Display name shown in the mainbar and panel header. |
icon | string | Image URL (PNG, SVG, JPG) for the mainbar icon. Rendered at 24×24px. |
categories | string[] | Categories to include from the graphics catalog. Only items matching these categories are shown. |
dynamicDesigns Option
| Property | Type | Description |
|---|---|---|
dynamicDesigns | Record<string, DynamicDesignLibrary> | Map of library IDs to their configuration. Each key becomes a designs_<id> module entry. |
Notes
- The
designs_<id>ID can be used indisabledModulesandinitialActiveModulejust like any built-in module. - Omitting
designsfrommainBarModuleswhile usingdesigns_*entries removes the default all-categories library entirely. - Each library shares the same underlying graphics catalog — only category filtering differs.
Registering Custom Modules
Add your own web components as mainbar modules by including a CustomModule descriptor in the mainBarModules array:
customizer.options = {
mainBarModules: [
'text',
'images',
{
id: 'quote',
element: 'my-quote-module',
icon: 'calculator',
label: 'Get Quote',
},
],
};
CustomModule Interface
| Property | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique module ID. Referenced in disabledModules and initialActiveModule. |
element | string | Yes | Custom element tag name. Must be registered in the DOM before the customizer renders. |
icon | string | No | Phosphor icon name for the mainbar. Default: 'sparkle'. |
label | string | No | Display label for the mainbar and panel header. Falls back to id. |
Creating a Custom Module Element
Your custom element receives a chamevo property — the ChamevoJS instance — giving full access to the API:
class MyQuoteModule extends HTMLElement {
set chamevo(instance) {
this._chamevo = instance;
this.render();
}
render() {
if (!this._chamevo) return;
const product = this._chamevo.getProductSnapshot();
this.innerHTML = `
<div style="padding: 16px;">
<h3>Quote for ${product?.title ?? 'Product'}</h3>
<p>Elements: ${this._chamevo.canvas.getElements().length}</p>
<button id="quoteBtn">Request Quote</button>
</div>
`;
this.querySelector('#quoteBtn').addEventListener('click', () => {
// Your quote logic here
});
}
}
customElements.define('my-quote-module', MyQuoteModule);
The module appears in the mainbar with its configured icon and opens in the sidebar panel (or dialog on mobile) like any built-in module.
Disabling a Custom Module Per View
Custom module IDs work with disabledModules just like built-in ones:
views: [
{
title: 'Back',
options: { disabledModules: ['quote'] },
elements: [...],
},
]
Mounting Modules Outside the Customizer
Use registerModule() to mount any module — built-in or custom — into an external DOM element anywhere on the page:
<cv-customizer id="customizer" options='...'></cv-customizer>
<!-- Your own layout -->
<div id="my-sidebar"></div>
<div id="my-layers-panel"></div>
const customizer = document.querySelector('#customizer');
customizer.addEventListener('cvReady', async () => {
// Mount built-in modules into external containers
const cleanupText = await customizer.registerModule('text', '#my-sidebar');
const cleanupLayers = await customizer.registerModule('manage-layers', '#my-layers-panel');
});
How It Works
registerModule(moduleId, target)resolves the module ID to its component tag name- Creates the component element and appends it to the target container
- Sets the
chamevoproperty (andgraphicsfor the designs module) - Subscribes to store changes so the module stays reactive to view switches, product loads, etc.
- Returns a cleanup function that removes the element and unsubscribes when called
registerModule() Signature
registerModule(moduleId: string, target: string | HTMLElement): Promise<() => void>
| Parameter | Type | Description |
|---|---|---|
moduleId | string | Built-in module ID or custom module id from mainBarModules. |
target | string | HTMLElement | CSS selector or DOM element to mount into. |
| Returns | () => void | Cleanup function — call to unmount the module. |
Mounting Custom Modules Externally
Custom modules registered in mainBarModules can also be mounted externally:
customizer.options = {
mainBarModules: [
'text',
{ id: 'quote', element: 'my-quote-module', icon: 'calculator', label: 'Quote' },
],
};
customizer.addEventListener('cvReady', async () => {
// Mount your custom module into an external panel
const cleanup = await customizer.registerModule('quote', '#external-quote-panel');
});
Cleanup
Always store cleanup functions and call them when you're done:
const cleanups = [];
customizer.addEventListener('cvReady', async () => {
cleanups.push(await customizer.registerModule('text', '#panel-1'));
cleanups.push(await customizer.registerModule('images', '#panel-2'));
});
// Later — unmount all external modules
function unmountAll() {
cleanups.forEach(fn => fn());
cleanups.length = 0;
}
Modules are also automatically cleaned up when cv-customizer is disconnected from the DOM.
React Example
import { useEffect, useRef } from 'react';
function ExternalTextModule() {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const customizer = document.querySelector('cv-customizer');
if (!customizer || !containerRef.current) return;
let cleanup: (() => void) | undefined;
customizer.addEventListener('cvReady', async () => {
cleanup = await customizer.registerModule('text', containerRef.current!);
});
return () => cleanup?.();
}, []);
return <div ref={containerRef} />;
}
Next Steps
- Options Reference —
mainBarModules,disabledModules,initialActiveModule - Module Configs — Per-module configuration
- Full Customizer — Complete integration guide