Examples
Real-world examples covering common headless configurator workflows.
Complete Headless Configurator
import { createSimplio3DClient } from '@simplio3d/sdk';
async function buildConfigurator(projectId: string, accessToken: string) {
const client = createSimplio3DClient({
apiUrl: process.env.SIMPLIO3D_API_URL,
accessToken
});
// 1. Load all configurator data
const [project, blocks, pricing, formFields] = await Promise.all([
client.getProject(projectId),
client.getOptionBlocks(projectId),
client.getPricingBlocks(projectId),
client.getFormFields(projectId)
]);
// 2. Build selection state from defaults
const selections = {
dropdownSelections: {} as Record<string, string>,
selectMaterialSelections: {} as Record<string, string>,
checkboxSelections: {} as Record<string, string[]>,
toggleSwitchSelections: {} as Record<string, string>,
carouselSelections: {} as Record<string, string>,
};
blocks.forEach(block => {
if (block.defaultDropdownValue && block.type === 'dropdown') {
selections.dropdownSelections[block.id] = block.defaultDropdownValue;
}
if (block.defaultCheckboxValues && block.type === 'checkbox') {
selections.checkboxSelections[block.id] = block.defaultCheckboxValues;
}
});
// 3. Evaluate conditional visibility (takes the GROUPED shape above)
const visibility = await client.evaluateConditions(projectId, { selections });
// 4. Calculate initial price.
// calculatePrice takes a FLAT { blockId: variantValue } map, so flatten the
// single-value groups. checkboxSelections rides as a TOP-LEVEL sibling —
// passing the grouped object matches nothing and silently returns base prices.
const price = await client.calculatePrice(projectId, {
selections: {
...selections.dropdownSelections,
...selections.selectMaterialSelections,
...selections.toggleSwitchSelections,
...selections.carouselSelections,
},
checkboxSelections: selections.checkboxSelections,
variables: {}
});
// Sanity-check that the selections matched pricing blocks at all.
if (Object.keys(selections.dropdownSelections).length > 0 &&
price.matchedBlockIds?.length === 0) {
console.warn('No pricing block matched:', price.unmatchedSelectionKeys);
}
return {
project,
blocks: blocks.filter(b => {
const vis = visibility.find(v => v.blockId === b.id);
return vis ? vis.blockVisible : true;
}),
pricing,
formFields,
price,
selections,
visibility
};
}React Hook: useConfigurator
import { createSimplio3DClient } from '@simplio3d/sdk';
import { useState, useEffect, useCallback, useMemo } from 'react';
function useConfigurator(projectId: string, accessToken: string) {
const client = useMemo(() => createSimplio3DClient({
apiUrl: import.meta.env.VITE_API_URL,
accessToken
}), [accessToken]);
const [blocks, setBlocks] = useState([]);
const [selections, setSelections] = useState({
dropdownSelections: {},
selectMaterialSelections: {},
checkboxSelections: {},
toggleSwitchSelections: {},
carouselSelections: {},
});
const [visibility, setVisibility] = useState([]);
const [price, setPrice] = useState(null);
const [loading, setLoading] = useState(true);
// Load initial data
useEffect(() => {
Promise.all([
client.getOptionBlocks(projectId),
client.getPricingBlocks(projectId)
]).then(([blocks]) => {
setBlocks(blocks);
setLoading(false);
});
}, [client, projectId]);
// Re-evaluate when selections change.
//
// NOTE the two endpoints take DIFFERENT selection shapes:
// evaluateConditions -> the grouped form held in state above
// calculatePrice -> a FLAT { blockId: variantValue } map, with
// multi-select / modular values passed separately
useEffect(() => {
client.evaluateConditions(projectId, { selections })
.then(setVisibility);
client.calculatePrice(projectId, {
selections: {
...selections.dropdownSelections,
...selections.selectMaterialSelections,
...selections.toggleSwitchSelections,
...selections.carouselSelections,
},
checkboxSelections: selections.checkboxSelections,
variables: {},
}).then(setPrice);
}, [client, projectId, selections]);
const setSelection = useCallback((blockId, value) => {
setSelections(prev => ({
...prev,
dropdownSelections: { ...prev.dropdownSelections, [blockId]: value }
}));
}, []);
return { blocks, selections, setSelection, visibility, price, loading };
}E-commerce Integration
// Add configured product to cart with pricing.
//
// `selections` is the GROUPED state your UI holds
// ({ dropdownSelections, selectMaterialSelections, checkboxSelections, … }),
// which is what submitQuote takes. calculatePrice is the one call that needs a
// FLAT { blockId: variantValue } map, so flatten it at that call site only.
async function addToCart(projectId, selections, variables) {
const client = createSimplio3DClient({ apiUrl: API_URL, accessToken });
// Get final price — FLAT selections, checkboxSelections as a sibling.
const price = await client.calculatePrice(projectId, {
selections: {
...selections.dropdownSelections,
...selections.selectMaterialSelections,
...selections.toggleSwitchSelections,
...selections.carouselSelections,
},
checkboxSelections: selections.checkboxSelections,
variables
});
// Get a screenshot of the current configuration
const screenshotUrl = 'https://cdn.example.com/screenshot.png';
// Submit as a quote (or redirect to cart) — this one takes the GROUPED shape.
const submission = await client.submitQuote(projectId, {
formData: { name: user.name, email: user.email },
configuration: {
selections,
variables,
screenshotUrl
}
});
// Or send to your e-commerce platform.
//
// TAX — pick deliberately.
// price.subtotal = pre-tax in BOTH tax modes
// price.totalWithTax = gross in BOTH tax modes
// price.totalPrice = MODE-DEPENDENT (subtotal in exclusive, gross in
// inclusive) — do not use it here
//
// Send subtotal when your commerce platform applies tax itself (the usual
// case for Shopify / WooCommerce). If it charges the number you give it
// verbatim, send totalWithTax — otherwise the shopper is shown
// price.formatted and billed a smaller amount.
await fetch('/api/cart/add', {
method: 'POST',
body: JSON.stringify({
productId: projectId,
price: price.subtotal, // PRE-tax in both modes: platform adds tax
priceWithTax: price.totalWithTax,
displayPrice: price.formatted, // POST-tax, currency-formatted
taxMode: price.taxMode, // 'exclusive' | 'inclusive'
currency: price.currency,
configurationId: submission.id,
thumbnail: screenshotUrl,
options: selections
})
});
}Continue reading
ChangelogVersion history and notable changes to the Simplio3D SDK.IntroductionOverview of the @simplio3d/sdk headless TypeScript client — what it does and how it talks to the Simplio3D platform.InstallationInstall @simplio3d/sdk from npm and add it to a browser or Node.js project.Quick StartCreate a client, load a project, and render a configurator in a few lines of TypeScript.
