ChamevoJS API (Headless)
Use @chamevo/core to get full canvas orchestration (product loading, view switching, pricing, history) while building your own custom UI.
When to use: You want complete control over the user interface but don't want to manage canvas internals yourself. ChamevoJS handles the data model, view lifecycle, pricing, and serialization.
Installation
npm install @chamevo/core
FabricJS v7 is a peer dependency:
npm install fabric@^7.0.0
Setup
import { ChamevoJS, ChamevoCanvas } from '@chamevo/core';
// 1. Create a <canvas> element
const canvasEl = document.getElementById('my-canvas') as HTMLCanvasElement;
// 2. Create the ChamevoCanvas (FabricJS wrapper)
const canvas = new ChamevoCanvas(canvasEl, {
width: 900,
height: 600,
backgroundColor: '#ffffff',
});
// 3. Create the ChamevoJS orchestrator
const chamevo = new ChamevoJS({
canvas,
options: {
stageWidth: 900,
stageHeight: 600,
fonts: [
{ name: 'Arial' },
{ name: 'Roboto', url: 'google' },
],
},
});
Loading Products
await chamevo.loadProduct({
title: 'T-Shirt',
views: [
{
title: 'Front',
thumbnail: '/images/front-thumb.jpg',
elements: [
{
type: 'image',
source: '/images/tshirt-front.png',
title: 'T-Shirt Front',
parameters: { left: 450, top: 300, draggable: false },
},
],
},
{
title: 'Back',
elements: [
{
type: 'image',
source: '/images/tshirt-back.png',
title: 'T-Shirt Back',
parameters: { left: 450, top: 300, draggable: false },
},
],
},
],
});
Events
ChamevoJS uses a typed event emitter. Subscribe with .on() and unsubscribe with .off():
// Element selection — update your custom UI
chamevo.on('elementSelect', ({ element }) => {
if (element) {
showToolbar(element);
} else {
hideToolbar();
}
});
// Element modifications — sync with your state
chamevo.on('elementModify', ({ element, changes }) => {
console.log(`${element.title} changed:`, changes);
});
// Price updates
chamevo.on('priceChange', ({ price, singleProductPrice, pricingRulesPrice, breakdown }) => {
updatePriceDisplay(price);
// breakdown: PricingLineItem[] — one entry per matched pricing rule
});
// View switches
chamevo.on('viewSelect', ({ view, index }) => {
updateViewTabs(index);
});
// Product loaded
chamevo.on('productCreate', ({ product }) => {
console.log(`Loaded: ${product.title} with ${product.views.length} views`);
});
// Loading state
chamevo.on('loading', ({ isLoading }) => {
toggleSpinner(isLoading);
});
See the Events Guide for the complete event reference.
ChamevoJS Methods
Product Lifecycle
| Method | Returns | Description |
|---|---|---|
loadProduct(product, replaceInitialElements?) | Promise<void> | Load a CVProduct or legacy CVView[]. Pass replaceInitialElements: true to preserve user-added elements across the load; when omitted it falls back to mainOptions.replaceInitialElements. |
getProduct() | CVProduct | null | Get the current product with all element states serialized. |
getOrder() | { product, usedFonts, usedColors } | Get product + font/color metadata for order processing. |
reset() | void | Remove all user-added elements across all views. |
abortProductLoading() | void | Cancel an in-progress product load. |
Views
| Method | Returns | Description |
|---|---|---|
selectView(index) | Promise<void> | Switch to a view by index. Saves current view state first. |
| Property | Type | Description |
|---|---|---|
views | ChamevoView[] | All view instances for the current product. |
currentView | ChamevoView | null | The currently active view. |
currentViewIndex | number | Index of the active view. |
productCreated | boolean | Whether a product has been fully loaded. |
Elements
Elements are added/modified via the canvas instance:
// Add a text element
await canvas.addElement('text', 'Hello World', 'My Text', {
fontSize: 32,
fill: '#333333',
left: 400,
top: 300,
draggable: true,
resizable: true,
});
// Add an image element
await canvas.addElement('image', '/images/logo.png', 'Logo', {
left: 450,
top: 200,
scaleX: 0.5,
scaleY: 0.5,
});
// Modify an element by title
canvas.setElementOptions('My Text', { fill: '#ff0000', fontSize: 48 });
// Remove an element by title
canvas.removeElement('My Text');
To get serialized element data:
// All elements in current view
const elements = chamevo.getElements();
// Only text elements in view 0
const textElements = chamevo.getElements(0, 'text');
// Only image elements across all views
const imageElements = chamevo.getElements(undefined, 'image');
Image Uploads
| Method | Returns | Description |
|---|---|---|
handleImageUpload(source, title, params?) | Promise<ICVElement | null> | Add an uploaded image with before/after hook events. Returns null if cancelled by a beforeImageUpload listener. |
// Add an uploaded image (fires beforeImageUpload / afterImageUpload hooks)
const element = await chamevo.handleImageUpload(
'https://example.com/photo.png',
'User Photo',
{ isCustom: true },
);
if (!element) {
console.log('Upload was cancelled by a hook');
}
Options
| Property | Type | Description |
|---|---|---|
currentOptions | ChamevoOptions | Fully resolved options (DEFAULTS -> main -> view -> printArea). |
optionsService | OptionsService | Access the options cascade service directly. |
// Read resolved options
const maxPrice = chamevo.currentOptions.maxPrice;
// Update global options at runtime
chamevo.optionsService.updateMainOptions({ maxPrice: 100 });
Pricing
| Property | Type | Description |
|---|---|---|
currentPrice | number | Total price (with quantity multiplier). |
singleProductPrice | number | Per-unit price from element prices. |
pricingRulesPrice | number | Additional price from pricing rules. |
orderQuantity | number | Quantity multiplier. |
| Method | Returns | Description |
|---|---|---|
calculatePrice(considerQuantity?) | number | Recalculate and emit price. |
setOrderQuantity(qty) | void | Set quantity and recalculate price. |
getPricingBreakdown() | PricingLineItem[] | Returns the current breakdown — one entry per matched pricing rule, with property, target, condition, and amount fields. |
Services
ChamevoJS exposes its internal services for advanced use:
chamevo.optionsService // OptionsService — configuration cascade
chamevo.fontService // FontService — font loading
chamevo.pricingService // PricingService — pricing rules
chamevo.uploadService // UploadService — file validation & upload
Fonts
// Load fonts before using them
await chamevo.fontService.loadFont('Roboto', 'google');
await chamevo.fontService.loadFont('Custom Font', '/fonts/custom.woff2');
History (Undo/Redo)
History is managed by the canvas:
canvas.history.undo();
canvas.history.redo();
console.log(canvas.history.canUndo); // boolean
console.log(canvas.history.canRedo); // boolean
Building a Custom UI
Here's a pattern for wiring ChamevoJS to a custom UI:
// View tabs
function renderViewTabs() {
chamevo.views.forEach((view, i) => {
const tab = document.createElement('button');
tab.textContent = view.title;
tab.classList.toggle('active', i === chamevo.currentViewIndex);
tab.onclick = () => chamevo.selectView(i);
viewBar.appendChild(tab);
});
}
// Element toolbar
chamevo.on('elementSelect', ({ element }) => {
if (!element) {
toolbar.style.display = 'none';
return;
}
toolbar.style.display = 'block';
// Show relevant controls based on element type
if (element.type?.startsWith('text') || element.type?.startsWith('cv-')) {
showTextControls(element);
} else {
showImageControls(element);
}
});
// Undo/redo buttons
undoBtn.onclick = () => canvas.history.undo();
redoBtn.onclick = () => canvas.history.redo();
// Save button
saveBtn.onclick = () => {
const product = chamevo.getProduct();
console.log(JSON.stringify(product));
};
Next Steps
- Core Canvas — Direct canvas manipulation without the orchestrator
- Events Guide — Complete event reference
- Elements Guide — All element types and parameters