Skip to main content

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

IDComponentIconDescription
textcv-module-textcursor-textCustom text input and templates
imagescv-module-imagesimageTabbed image sources (upload, Pixabay, AI, QR)
designscv-module-graphicscircles-three-plusGraphics/clipart catalog
designs_<id>cv-module-graphics(configured)Dynamic design library (see Dynamic Designs)
manage-layerscv-module-layersstackLayer management and reorder
text-layerscv-module-text-layerstext-align-leftText layer editing (accordion)
my-designscv-module-my-designsfloppy-diskSave/load designs
productscv-module-productsstorefrontProduct catalog browser
layoutscv-module-layoutsgrid-fourLayout templates
names-numberscv-module-names-numberslist-numbersNames & numbers bulk entry
bulk-variationscv-module-bulk-variationsbulk-variationsBulk order entry with variation dropdowns (size, color, etc.)
color-selectioncv-color-selectioncolor-selectionColor selection panel for product-level colorizable elements
property-controlcv-property-controlExternal single-property element control (guide)
element-toolbarcv-element-toolbarElement 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

PropertyTypeDescription
namestringDisplay name shown in the mainbar and panel header.
iconstringImage URL (PNG, SVG, JPG) for the mainbar icon. Rendered at 24×24px.
categoriesstring[]Categories to include from the graphics catalog. Only items matching these categories are shown.

dynamicDesigns Option

PropertyTypeDescription
dynamicDesignsRecord<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 in disabledModules and initialActiveModule just like any built-in module.
  • Omitting designs from mainBarModules while using designs_* 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

PropertyTypeRequiredDescription
idstringYesUnique module ID. Referenced in disabledModules and initialActiveModule.
elementstringYesCustom element tag name. Must be registered in the DOM before the customizer renders.
iconstringNoPhosphor icon name for the mainbar. Default: 'sparkle'.
labelstringNoDisplay 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

  1. registerModule(moduleId, target) resolves the module ID to its component tag name
  2. Creates the component element and appends it to the target container
  3. Sets the chamevo property (and graphics for the designs module)
  4. Subscribes to store changes so the module stays reactive to view switches, product loads, etc.
  5. Returns a cleanup function that removes the element and unsubscribes when called

registerModule() Signature

registerModule(moduleId: string, target: string | HTMLElement): Promise<() => void>
ParameterTypeDescription
moduleIdstringBuilt-in module ID or custom module id from mainBarModules.
targetstring | HTMLElementCSS selector or DOM element to mount into.
Returns() => voidCleanup 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