Option Blocks
Option blocks are the core configurator building blocks. Each block type represents a different UI control that drives 3D model changes, material swaps, visibility toggles, and more. Supports 15 block types for headless integration.
Supported Block Types
dropdown
select-material
thumbnail-selector
checkbox
toggle-switch
carousel
text-input
number-input
file-upload
pattern-designer
design-canvas
hotspot
fixed-material
section-header
scenery
Select Material — authoring modes
The select-material block exposes two authoring modes via a top-level materialSource field:
- Manual (
materialSource: 'manual', default) — variants are authored individually indropdownVariants, each with its own target, material, and pricing. Supports Price Group / Variable / Price Table linking. - From Category (
materialSource: 'category') — runtime variants are auto-populated from every material inmaterialCategoryId. The block uses a shared target that can span multiple objects —categoryTargetObjectIds(string[]) and per-modelcategoryTargetParts(Record<modelId, string[]>) — applied to every synthesized variant (legacy single-objectcategoryTargetObjectId/categoryTargetPartNamesare still read as a fallback). Pricing flows through the standard Pricing system (Price Group / Variable / Price Table / Unique Price) — link a pricing block to the category-mode Select Material block and author one price per material. Variant IDs at runtime areauto:{materialId}.
Public share viewers fetch referenced category materials at first paint via GET /share/:projectId/:token/category-materials?ids=cat1,cat2, with GET /share/:projectId/:token/category-materials/:categoryId still available for a single category.
List Option Blocks
GET/projects/:id/option-blocks
const response = await fetch(BASE_URL + '/projects/proj_123/option-blocks', {
headers: { 'Authorization': 'Bearer ' + accessToken }
});
const data = await response.json();
// {
// "success": true,
// "blocks": [
// {
// "id": "blk_001",
// "type": "dropdown",
// "name": "Frame Color",
// "visible": true,
// "enabled": true,
// "dropdownVariants": [
// {
// "id": "var_a",
// "label": "Black",
// "value": "black",
// "materialData": { "baseColor": "#1a1a1a", "metallic": 0.8, "roughness": 0.2 },
// "targetPartNames": ["frame_body", "frame_legs"]
// },
// {
// "id": "var_b",
// "label": "Silver",
// "value": "silver",
// "materialData": { "baseColor": "#C0C0C0", "metallic": 0.95, "roughness": 0.1 },
// "targetPartNames": ["frame_body", "frame_legs"]
// }
// ],
// "defaultDropdownValue": "black"
// },
// {
// "id": "blk_002",
// "type": "thumbnail-selector",
// "name": "Fabric Type",
// "visible": true,
// "enabled": true,
// "thumbnailStyle": "grid",
// "thumbnailSize": "60x60",
// "thumbnailShape": "rounded",
// "dropdownVariants": [
// {
// "id": "var_c",
// "label": "Linen",
// "value": "linen",
// "thumbnailUrl": "https://...",
// "materialData": { "baseColor": "#f0e6d3", "roughness": 0.9 }
// }
// ]
// },
// {
// "id": "blk_003",
// "type": "toggle-switch",
// "name": "Armrests",
// "visible": true,
// "enabled": true,
// "dropdownVariants": [
// { "id": "var_d", "label": "Without", "value": "without",
// "visibilityConfig": { "targetObjectIds": ["model_armrest"], "action": "hide" } },
// { "id": "var_e", "label": "With Armrests", "value": "with",
// "visibilityConfig": { "targetObjectIds": ["model_armrest"], "action": "show" } }
// ]
// }
// ]
// }Create Option Block
POST/projects/:id/option-blocks
// Create a Dropdown block with material-swapping variants
const response = await fetch(BASE_URL + '/projects/proj_123/option-blocks', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'dropdown',
name: 'Wood Finish',
dropdownVariants: [
{
id: crypto.randomUUID(),
label: 'Oak',
value: 'oak',
targetPartNames: ['desk_top', 'desk_drawer'],
materialData: {
baseColor: '#8B6F47',
metallic: 0.0,
roughness: 0.8,
textureMap: 'https://cdn.example.com/textures/oak.jpg',
normalMap: 'https://cdn.example.com/textures/oak_normal.jpg'
}
},
{
id: crypto.randomUUID(),
label: 'Walnut',
value: 'walnut',
targetPartNames: ['desk_top', 'desk_drawer'],
materialData: {
baseColor: '#5C3D2E',
metallic: 0.0,
roughness: 0.75,
textureMap: 'https://cdn.example.com/textures/walnut.jpg'
}
}
],
defaultDropdownValue: 'oak'
})
});
// { "success": true, "block": { "id": "blk_new", ... } }Create Checkbox Block
POST/projects/:id/option-blocks
// Checkbox — multiple selections, each toggling 3D object visibility
const response = await fetch(BASE_URL + '/projects/proj_123/option-blocks', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'checkbox',
name: 'Optional Accessories',
checkboxAlignment: 'vertical',
showSelectAll: true,
dropdownVariants: [
{
id: crypto.randomUUID(),
label: 'Cup Holder',
value: 'cup-holder',
visibilityConfig: { targetObjectIds: ['model_cup_holder'], action: 'show' }
},
{
id: crypto.randomUUID(),
label: 'Monitor Stand',
value: 'monitor-stand',
visibilityConfig: { targetObjectIds: ['model_monitor_arm'], action: 'show' }
},
{
id: crypto.randomUUID(),
label: 'Cable Tray',
value: 'cable-tray',
visibilityConfig: { targetObjectIds: ['model_cable_tray'], action: 'show' }
}
],
defaultCheckboxValues: ['cup-holder']
})
});Create Carousel Block
POST/projects/:id/option-blocks
// Carousel — swipe/arrow navigation through variants
const response = await fetch(BASE_URL + '/projects/proj_123/option-blocks', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'carousel',
name: 'Upholstery',
carouselArrowStyle: 'filled',
carouselAnimation: 'slide',
carouselLoop: true,
carouselShowCounter: true,
carouselShowLabel: true,
dropdownVariants: [
{ id: crypto.randomUUID(), label: 'Velvet Red', value: 'velvet-red',
thumbnailUrl: 'https://...', materialData: { baseColor: '#8B0000', roughness: 0.9 } },
{ id: crypto.randomUUID(), label: 'Leather Brown', value: 'leather-brown',
thumbnailUrl: 'https://...', materialData: { baseColor: '#654321', roughness: 0.6, metallic: 0.1 } }
]
})
});Create Text Input Block
POST/projects/:id/option-blocks
// Text Input — user-typed text rendered as a texture on a 3D part
const response = await fetch(BASE_URL + '/projects/proj_123/option-blocks', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'text-input',
name: 'Engraving',
textInputTargets: [{
id: crypto.randomUUID(),
label: 'Custom Text',
targetPartName: 'engraving_plate',
placeholder: 'Enter up to 20 characters',
maxLength: 20,
fontFamily: 'Roboto',
fontSize: 48,
fontColor: '#1a1a1a',
textAlign: 'center',
canvasWidth: 1024,
canvasHeight: 256,
backgroundColor: '',
// Restrict consumer input. 'numbers' triggers the mobile numeric keypad
// and strips any non-digit characters on every change. Use 'any' (or
// omit the field) for unrestricted text. For decimal math / pricing,
// use the dedicated number-input option block instead.
contentType: 'numbers',
// Let consumers swap the font, but only between these three.
// Omit (or set to undefined) to expose every built-in font.
allowConsumerFontFamily: true,
consumerAllowedFonts: ['Roboto', 'Montserrat', 'Playfair Display']
}],
textInputShowLivePreview: true
})
});Create Hotspot Block
POST/projects/:id/option-blocks
// Hotspot — interactive 3D marker with popup content
const response = await fetch(BASE_URL + '/projects/proj_123/option-blocks', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
type: 'hotspot',
name: 'Adjustable Mechanism',
hotspotPosition: { x: 0.5, y: 1.2, z: 0.3 },
hotspotTitle: 'Tilt Mechanism',
hotspotDescription: 'Multi-angle tilt lock supports 5 positions from 90-135 degrees.',
hotspotIcon: 'info',
hotspotTrigger: 'click',
hotspotAnimationStyle: 'pulse',
hotspotImageUrl: 'https://cdn.example.com/mechanism.jpg',
hotspotLinkUrl: 'https://example.com/specs',
hotspotLinkLabel: 'View Specifications'
})
});Update Option Block
PUT/projects/:id/option-blocks/:blockId
const response = await fetch(BASE_URL + '/projects/proj_123/option-blocks/blk_001', {
method: 'PUT',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Updated Block Name',
visible: false,
dropdownVariants: [/* updated variants */]
})
});Delete Option Block
DELETE/projects/:id/option-blocks/:blockId
const response = await fetch(BASE_URL + '/projects/proj_123/option-blocks/blk_001', {
method: 'DELETE',
headers: { 'Authorization': 'Bearer ' + accessToken }
});Reorder Option Blocks
POST/projects/:id/option-blocks/reorder
const response = await fetch(BASE_URL + '/projects/proj_123/option-blocks/reorder', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + accessToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
blockIds: ['blk_003', 'blk_001', 'blk_002'] // New order
})
});
// { "success": true, "blocks": [/* blocks with updated order */] }Continue reading
Conditional LogicDefine show / hide / disable rules that react to selections, including 3D object and part targeting.Pricing & CPQConfigure CPQ pricing — base prices, price groups, 1D/2D tables, unique modular prices, variables, and formulas.Contact FormsBuild and read contact forms and their 12 field types for lead capture and add-to-cart flows.3D ViewerDrive the 3D viewer — camera, lighting, scene presets, and runtime configuration selections.
