Your First Product
Build a working t-shirt customizer in under 10 minutes. By the end of this tutorial you'll have a two-sided product with editable text and image upload.
Prerequisites
- Node.js >= 18
- A package manager (npm, pnpm, or yarn)
1. Set Up the Project
mkdir my-customizer && cd my-customizer
npm init -y
npm install @chamevo/customizer
Create an index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>T-Shirt Customizer</title>
<style>
body { margin: 0; font-family: sans-serif; }
#customizer-container { width: 100%; height: 100vh; }
</style>
</head>
<body>
<div id="customizer-container"></div>
<script type="module" src="main.js"></script>
</body>
</html>
2. Define the Product
Create main.js:
import { createCustomizer } from '@chamevo/customizer';
// Define a t-shirt with front and back views
const tshirtProduct = {
title: 'Classic T-Shirt',
views: [
{
title: 'Front',
thumbnail: 'https://your-domain.com/images/tshirt-front-thumb.jpg',
elements: [
{
type: 'image',
source: 'https://your-domain.com/images/tshirt-front.png',
title: 'T-Shirt Front',
parameters: {
left: 450,
top: 300,
draggable: false,
removable: false,
resizable: false,
zChangeable: false,
},
},
],
options: {
stageWidth: 900,
stageHeight: 600,
},
},
{
title: 'Back',
thumbnail: 'https://your-domain.com/images/tshirt-back-thumb.jpg',
elements: [
{
type: 'image',
source: 'https://your-domain.com/images/tshirt-back.png',
title: 'T-Shirt Back',
parameters: {
left: 450,
top: 300,
draggable: false,
removable: false,
resizable: false,
zChangeable: false,
},
},
],
},
],
};
The product data model is simple:
- A product has one or more views (front, back, sleeve, etc.)
- Each view has elements (images, text, SVGs)
- Each element has a
type,source,title, andparameters
3. Initialize the Customizer
Add this below the product definition in main.js:
// Create the customizer and load the product
const customizer = createCustomizer('#customizer-container', {
stageWidth: 900,
stageHeight: 600,
fonts: [
{ name: 'Arial' },
{ name: 'Lobster', url: 'google' },
{ name: 'Playfair Display', url: 'google' },
],
});
customizer.addEventListener('cvReady', async (e) => {
const { chamevo, canvas } = e.detail;
// Load the t-shirt product
await customizer.loadProduct(tshirtProduct);
console.log('Customizer ready!');
});
createCustomizer() does three things:
- Registers all
cv-*web components - Creates a
<cv-customizer>element with your options - Appends it to the target container
4. Add Editable Text
Let's pre-place an editable text element on the front view. Add it to the front view's elements array:
// Add this as the second element in the Front view
{
type: 'text',
source: 'Your Text Here',
title: 'Custom Text',
parameters: {
left: 450,
top: 350,
fill: '#333333',
fontSize: 32,
fontFamily: 'Lobster',
draggable: true,
resizable: true,
rotatable: true,
removable: true,
editable: true,
colors: '#000000', // enables color picker
},
}
Users can now double-click the text to edit it, drag it around, resize it, and change its color.
5. Listen to Events
Track what the user is doing:
customizer.addEventListener('cvReady', async (e) => {
const { chamevo } = e.detail;
await customizer.loadProduct(tshirtProduct);
// Track element changes
chamevo.on('elementModify', ({ element, changes }) => {
console.log(`${element.title} modified:`, changes);
});
// Track price changes
chamevo.on('priceChange', ({ price, singleProductPrice }) => {
console.log(`Price: $${price.toFixed(2)}`);
});
// Track view switches
chamevo.on('viewSelect', ({ view, index }) => {
console.log(`Switched to ${view.title} (view ${index})`);
});
});
6. Get the Customized Product
When the user is done, retrieve their customization:
// Add a "Save" button handler
document.getElementById('save-btn')?.addEventListener('click', async () => {
const product = await customizer.getProduct();
console.log('Customized product:', product);
// Send to your server
await fetch('/api/save-design', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(product),
});
});
getProduct() returns a CVProduct with all views and their current element states serialized. You can later reload this product to restore the user's design.
7. Add Print Areas (Optional)
Print areas define the printable region on each view. Elements are constrained within print areas:
// Add printAreas to the Front view
{
title: 'Front',
elements: [ /* ... */ ],
printAreas: [
{
printingBox: {
left: 310,
top: 140,
width: 280,
height: 320,
},
},
],
}
Complete Example
import { createCustomizer } from '@chamevo/customizer';
const tshirtProduct = {
title: 'Classic T-Shirt',
views: [
{
title: 'Front',
thumbnail: 'https://your-domain.com/images/tshirt-front-thumb.jpg',
elements: [
{
type: 'image',
source: 'https://your-domain.com/images/tshirt-front.png',
title: 'T-Shirt Front',
parameters: {
left: 450, top: 300,
draggable: false, removable: false,
resizable: false, zChangeable: false,
},
},
{
type: 'text',
source: 'Your Text Here',
title: 'Custom Text',
parameters: {
left: 450, top: 350,
fill: '#333333', fontSize: 32, fontFamily: 'Lobster',
draggable: true, resizable: true,
rotatable: true, removable: true, editable: true,
colors: '#000000',
},
},
],
printAreas: [
{
printingBox: { left: 310, top: 140, width: 280, height: 320 },
},
],
options: { stageWidth: 900, stageHeight: 600 },
},
{
title: 'Back',
thumbnail: 'https://your-domain.com/images/tshirt-back-thumb.jpg',
elements: [
{
type: 'image',
source: 'https://your-domain.com/images/tshirt-back.png',
title: 'T-Shirt Back',
parameters: {
left: 450, top: 300,
draggable: false, removable: false,
resizable: false, zChangeable: false,
},
},
],
},
],
};
const customizer = createCustomizer('#customizer-container', {
stageWidth: 900,
stageHeight: 600,
fonts: [
{ name: 'Arial' },
{ name: 'Lobster', url: 'google' },
{ name: 'Playfair Display', url: 'google' },
],
});
customizer.addEventListener('cvReady', async (e) => {
const { chamevo } = e.detail;
await customizer.loadProduct(tshirtProduct);
chamevo.on('priceChange', ({ price }) => {
document.getElementById('price').textContent = `$${price.toFixed(2)}`;
});
});
Next Steps
- Full Customizer Guide — Framework recipes (React, Vue, Angular) and layout customization
- ChamevoJS API — Build your own UI with the headless API
- Elements Guide — All element types and parameters
- Configuration — Full options reference