Skip to main content

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

  1. Create a new embed template
  2. Enable "Custom UI" mode
  3. 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 = `
<h3>${block.name}</h3>
<select id="block-${block.id}">
<option value="">Choose ${block.name}</option>
</select>
`;

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 data
  • product.sku - Current SKU
  • product.configurationCode - Configuration string
  • product.isConfigurationCodeValid - Validation state

Options (Examples)

  • optionSets.blocks - All option blocks
  • optionSets.selectedOptions - Currently selected
  • optionSets.allOptions - All available options

Pricing (Examples - if enabled)

  • pricing.prices - Current price data
  • pricing.quantity - Selected quantity
  • pricing.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