Headless Configurator
Use Mimeeq as a headless configuration engine - all the logic and data without any UI. Like any headless CMS, it provides the backend functionality while you build the frontend.
What is Headless Mode?
A headless configurator provides:
- Configuration logic - Rules, dependencies, validation
- Product data - Options, attributes, SKUs
- Real-time updates - Prices, availability, validity
- No UI - You build the interface
Think of it as an API for product configuration.
Use Cases
- Complex products without visuals - Server configurations, insurance packages
- Custom interfaces - Build your own unique UI
- Multi-channel - Power websites, apps, kiosks from one source
- Integration scenarios - Embed configuration logic into existing systems
Setup
Step 1: Create a Headless Template
- Create a new embed template
- Enable "Custom UI" mode
- Save the template
Step 2: Hide the Embed
<!-- Hidden configurator engine -->
<div style="display: none;">
<mmq-embed
id="config-engine"
short-code="YOUR_PRODUCT_CODE"
template="headless_template">
</mmq-embed>
</div>
<script src="https://cdn.mimeeq.com/read_models/embed/app-embed.js" async></script>
Step 3: Use the Engine
document.addEventListener('mimeeq-app-loaded', () => {
console.log('Configuration engine ready');
// Access all Mimeeq data and methods
// Build your custom UI
});
Working with Data
Get Product Structure
// Get all option blocks
window.mimeeqApp.observers.optionSets.blocks.subscribe(({ newValue }) => {
console.log('Product structure:', newValue);
newValue.forEach(block => {
console.log(`Block: ${block.name}`);
console.log(`Options: ${block.options.length}`);
console.log(`Required: ${block.required}`);
});
});
// Get product metadata
window.mimeeqApp.observers.product.mainProductData.subscribe(({ newValue }) => {
if (newValue) {
console.log('Product:', newValue.metadata.name);
console.log('Code:', newValue.metadata.code);
}
});
Track Configuration State
// Current configuration
window.mimeeqApp.observers.product.configurationCode.subscribe(({ newValue }) => {
console.log('Configuration:', newValue);
});
// Selected options
window.mimeeqApp.observers.optionSets.selectedOptions.subscribe(({ newValue }) => {
console.log('Selected:', newValue);
});
// Validation state
window.mimeeqApp.observers.product.isConfigurationCodeValid.subscribe(({ newValue }) => {
console.log('Valid:', newValue);
});
// Current SKU
window.mimeeqApp.observers.product.sku.subscribe(({ newValue }) => {
console.log('SKU:', newValue);
});
Set Options
// Select by block name and option code
window.mimeeqApp.actions.markOptionByBlockNameAndOptionCode(
'Processor',
'intel_i7'
);
// Set complete configuration
window.mimeeqApp.actions.setConfigurationCode(
'Processor-intel_i7&Memory-16gb&Storage-512gb'
);
Simple Example: Product Configurator
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: system-ui, sans-serif;
max-width: 600px;
margin: 40px auto;
padding: 20px;
}
.config-block {
margin: 20px 0;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
.config-block h3 {
margin-top: 0;
}
select {
width: 100%;
padding: 8px;
font-size: 16px;
}
.status {
margin-top: 30px;
padding: 20px;
background: #f5f5f5;
border-radius: 8px;
}
.status.invalid {
background: #fee;
border: 1px solid #fcc;
}
</style>
</head>
<body>
<!-- Hidden engine -->
<div style="display: none;">
<mmq-embed
id="engine"
short-code="YOUR_PRODUCT"
template="headless">
</mmq-embed>
</div>
<h1>Product Configurator</h1>
<div id="options"></div>
<div id="status" class="status"></div>
<script src="https://cdn.mimeeq.com/read_models/embed/app-embed.js" async></script>
<script>
document.addEventListener('mimeeq-app-loaded', () => {
let blocks = [];
let isValid = true;
// Build UI from product structure
window.mimeeqApp.observers.optionSets.blocks.subscribe(({ newValue }) => {
blocks = newValue;
buildUI(blocks);
});
// Update status
window.mimeeqApp.observers.product.sku.subscribe(({ newValue }) => {
updateStatus('sku', newValue);
});
window.mimeeqApp.observers.pricing.prices.subscribe(({ newValue }) => {
if (newValue) {
updateStatus('price', `${newValue.currency} ${newValue.price}`);
}
});
window.mimeeqApp.observers.product.isConfigurationCodeValid.subscribe(({ newValue }) => {
isValid = newValue;
updateStatus('valid', newValue);
});
function buildUI(blocks) {
const container = document.getElementById('options');
container.innerHTML = '';
blocks.forEach(block => {
const blockEl = document.createElement('div');
blockEl.className = 'config-block';
blockEl.innerHTML = `
`;
const select = blockEl.querySelector('select');
block.options.forEach(option => {
const optEl = document.createElement('option');
optEl.value = option.code;
optEl.textContent = option.name;
select.appendChild(optEl);
});
select.addEventListener('change', (e) => {
if (e.target.value) {
window.mimeeqApp.actions.markOptionByBlockNameAndOptionCode(
block.name,
e.target.value
);
}
});
container.appendChild(blockEl);
});
}
function updateStatus(type, value) {
const status = document.getElementById('status');
if (type === 'valid') {
status.className = value ? 'status' : 'status invalid';
}
const currentHTML = status.innerHTML;
const lines = currentHTML.split('<br>').filter(l => !l.includes(type));
if (value !== null && value !== undefined) {
lines.push(`<strong>${type}:</strong> ${value}`);
}
status.innerHTML = lines.join('<br>');
}
});
</script>
</body>
</html>
Use with Your Framework
React Example
function ConfiguratorEngine({ productCode, onConfigChange }) {
const [blocks, setBlocks] = useState([]);
const [selectedOptions, setSelectedOptions] = useState({});
useEffect(() => {
const loadEngine = () => {
// Subscribe to data
window.mimeeqApp.observers.optionSets.blocks.subscribe(({ newValue }) => {
setBlocks(newValue);
});
window.mimeeqApp.observers.optionSets.selectedOptions.subscribe(({ newValue }) => {
const options = {};
newValue.forEach(opt => {
options[opt.blockName] = opt.code;
});
setSelectedOptions(options);
onConfigChange(options);
});
};
if (window.mimeeqApp) {
loadEngine();
} else {
document.addEventListener('mimeeq-app-loaded', loadEngine);
}
}, []);
const selectOption = (blockName, optionCode) => {
window.mimeeqApp.actions.markOptionByBlockNameAndOptionCode(
blockName,
optionCode
);
};
return (
<>
{/* Hidden engine */}
<div style={{ display: 'none' }}>
<mmq-embed
id="engine"
short-code={productCode}
template="headless"
/>
</div>
{/* Your custom UI */}
<div className="configurator">
{blocks.map(block => (
<div key={block.id}>
<h3>{block.name}</h3>
<select
value={selectedOptions[block.name] || ''}
onChange={(e) => selectOption(block.name, e.target.value)}
>
<option value="">Select...</option>
{block.options.map(opt => (
<option key={opt.id} value={opt.code}>
{opt.name}
</option>
))}
</select>
</div>
))}
</div>
</>
);
}
Available Data Points
These are just some examples of available observers. For the complete list of all observers and their documentation, see the Observers Reference.
Product Information (Examples)
product.mainProductData
- Complete product dataproduct.sku
- Current SKUproduct.configurationCode
- Configuration stringproduct.isConfigurationCodeValid
- Validation state
Options (Examples)
optionSets.blocks
- All option blocksoptionSets.selectedOptions
- Currently selectedoptionSets.allOptions
- All available options
Pricing (Examples - if enabled)
pricing.prices
- Current price datapricing.quantity
- Selected quantitypricing.priceType
- Price type (retail, wholesale, etc.)
Important: This is not a complete list. Mimeeq provides many more observers for basket data, modular products, AR features, translations, and more. Always check the full API documentation for all available data streams.
Best Practices
Memory Management
// Store subscriptions for cleanup
const subscriptions = [];
// Subscribe
const sub = window.mimeeqApp.observers.product.sku.subscribe(handler);
subscriptions.push(sub);
// Cleanup when done
subscriptions.forEach(sub => sub.unsubscribe());
Error Handling
// Always check for null values
window.mimeeqApp.observers.product.mainProductData.subscribe(({ newValue }) => {
if (newValue) {
// Safe to use
}
});
Performance
- Subscribe only to data you need
- Store values locally instead of accessing
.value
repeatedly - Unsubscribe when components unmount
Next Steps
- Custom UI Guide - Build complete interfaces
- API Reference - All available methods
- Observers Reference - All data streams