Methods reference
Methods are called directly on the element (const tg = document.getElementById('tg')).
Synchronous reads
Section titled “Synchronous reads”Instant, served from the cached last selection_change. Return null / [] until the
first event arrives.
getSelection()
Section titled “getSelection()”Current configuration:
{ parts: [{ partId, partLabel, variantId, label, sku, modelName }], shareUrl }.
getSkus()
Section titled “getSkus()”The SKUs of the current configuration, one per configurable part — the cart payload:
["OSLO-BODY-WALNUT", "OSLO-LEGS-CHROME"].
getShareUrl()
Section titled “getShareUrl()”Deep-link URL that rebuilds the current configuration exactly. Persist it on the order line — it is the one field worth storing beyond the SKUs.
Configuration
Section titled “Configuration”Async — each returns a Promise.
getOptions()
Section titled “getOptions()”Enumerate every part + all its variants with select()-ready handles. Resolves to
[{ partId, partKey, label, defaultVariantId, inPicker, link?, visibility?, variants: [{ variantId, handle, label, sku, swatch, swatchImage, modelName, isDefault, available, disabledReason, blockedBy }] }].
label is the panel’s customer-facing heading for that option. inPicker tells you
whether the published panel offers the part to visitors — it is false for parts a
link group drives, since the driver represents them. Filter
on it and your UI matches the published panel without re-deriving the rule:
const options = (await tg.getOptions()).filter((o) => o.inPicker);link ({ role: 'driver' | 'driven', groupId, … }) and visibility are there when you
want to render the relationship yourself instead of hiding it.
Blocked options explain themselves. While a variant cannot be picked, available is
false and disabledReason says why: 'rule' — the current configuration
rules it out — or 'stock' — you reported it out of stock through
setAvailability(). blockedBy lists the selections a rule blocks it
on, as [{ partId, partKey, variantId, handle }], so your panel can say what to change:
for (const v of option.variants) { if (v.disabledReason === 'rule' && v.blockedBy.length) { hint(v, `Not available with ${v.blockedBy.map((b) => b.handle).join(' + ')}`); } else if (v.disabledReason === 'stock') { hint(v, 'Out of stock'); }}A rule outranks stock when both apply. blockedBy is empty for stock, and for a variant that
is ruled out only because picking it would leave no valid configuration.
Options that move carry two more fields: a variant that opens or closes the model reports
animation: { clipName, mode: 'toggle' }, and the option reports where the selected
variant currently stands as animationState: { open }. Read them to draw your control in
the right position on first paint, then drive it with
setAnimationState().
select(partKey, variantKey)
Section titled “select(partKey, variantKey)”Drive a selection from your own UI. Keys are the URL handles (select('body', 'walnut')).
Resolves with { ok, selection } after the model swap settles; rejects on unknown
handle or timeout. An option that cannot be picked right now — the ones getOptions()
reports with available: false — resolves
{ ok: false, error: 'blocked', reason, blockedBy, selection } and changes nothing. When the option carries its own recorded camera angle, the camera eases
there; an option sharing its part’s angle leaves the camera where it is.
setAnimationState(partKey, open)
Section titled “setAnimationState(partKey, open)”Open or close an option that moves — an oven door, a drawer. Separate from select():
the choice and its state are two different things, so a host that only wants the door
open does not re-pick the door.
await tg.setAnimationState('door', true);Idempotent — setting a state the model already holds plays nothing. Resolves with
{ ok, open }; { ok: false } when the selected variant has no open/close animation,
and rejects on an unknown handle. getOptions() reports which variants carry one and
where the selected one stands.
getGroups()
Section titled “getGroups()”Enumerate the product’s module slots — visibility groups,
where one whole part is shown at a time (Shelf 1 with its 22 alternative modules).
getOptions() reports group membership but cannot change it; this is the read that lets
your own panel offer the slots.
Resolves to [{ groupId, label, activePartId, members: [{ partId, partKey, label, thumb, isActive, available, disabledReason, blockedBy }] }].
partKeyis the handle to pass toshowPart()—nullwhen the part has no stable handle, in which case it cannot be driven from the host.thumbis the module’s product shot, generated in the panel from the part’s own model and served from the CDN — see Module images. It isnullwhen none has been generated; render the label instead.availableisfalsewhile a rule blocks that module;disabledReasonis then'rule'andblockedBynames the selections to change, exactly as on a variant.
showPart(partKey)
Section titled “showPart(partKey)”Show one member of a module slot, e.g. showPart('shelf1-two-doors'). The group is
resolved from the member itself and membership is re-checked, so an unknown handle
resolves to { ok: false } and changes nothing. A module a rule blocks resolves
{ ok: false, error: 'blocked', reason: 'rule', blockedBy }.
const groups = await tg.getGroups();await tg.showPart(groups[0].members[1].partKey);getProducts()
Section titled “getProducts()”A configurator can hold more than one product — a two-seater and a one-seater of the same
sofa. Resolves to [{ productId, key, label, active }]: key is what ?product= carries
and what selectProduct() takes, active marks the product on screen. Empty when the
project has none.
selectProduct(productKey)
Section titled “selectProduct(productKey)”Switch the product from your own panel, e.g. selectProduct('one-seater'). It is the same
switch as the built-in panel’s product header: matching choices carry over, the camera moves
to that product and the share link gains ?product=. Resolves with
{ ok, product, selection } after the new product has loaded, and getOptions() then lists
its parts. An unknown key resolves { ok: false } and changes nothing.
const products = await tg.getProducts();if (products.length > 1) await tg.selectProduct(products[1].key);reset()
Section titled “reset()”Back to the published default. Resolves with { ok, selection }.
Commerce sync
Section titled “Commerce sync”setAvailability(map)
Section titled “setAvailability(map)”Push live stock: setAvailability({ body: { walnut: false } }) — keys are the same handles
getOptions() returns; false = out of stock. Out-of-stock swatches dim, strike through
and become unselectable in the built-in picker; getOptions() then reports each variant’s
available. Resolves with { ok, applied }.
setPrices(prices)
Section titled “setPrices(prices)”Push price display from your catalogue:
tg.setPrices({ currency: 'EUR', locale: 'de-DE', items: { 'SKU-1': 129.9 }, total: 259.8 });Keys are the same SKUs the events report. Numbers are Intl-formatted with
currency/locale; strings are shown verbatim; total is optional (auto-summed when
every shown price is numeric). Prices appear in the viewer’s summary. Pass null to clear.
Resolves with { ok, applied }.
Viewer surface
Section titled “Viewer surface”With controls="off" — named after <video controls> — the viewer draws none of its own controls:
no AR button, no snapshot button, no ruler icon, no reset-view button. Your page draws them instead.
One switch covers the whole surface, so a control added to the viewer later never needs a new
attribute here.
getControls()
Section titled “getControls()”What the viewer’s controls would be offering right now:
const { ar, photo, dimensions, resetView } = tg.getControls() ?? {};ar is whether native AR can be launched for the current configuration. dimensions is { enabled, on } — enabled follows the project’s WebAR settings → Dimensions
switch, on is whether the overlay is currently drawn. resetView is true while the camera has
left its opening framing. Returns null before the viewer reports.
The togenar:controls event fires on every change, with the same shape in detail — bind your
buttons to it and they track the viewer exactly:
tg.addEventListener('togenar:controls', (e) => { arBtn.hidden = !e.detail.ar; measureBtn.hidden = !e.detail.dimensions.enabled; measureBtn.classList.toggle('is-on', e.detail.dimensions.on); resetBtn.hidden = !e.detail.resetView;});setDimensions(on?)
Section titled “setDimensions(on?)”Show or hide the product’s measurement overlay. Omit the argument to flip it.
tg.setDimensions(true);const { on } = await tg.toggleDimensions();measureBtn.classList.toggle('is-on', on === true);Resolves with { ok, on } — render on on your own button. Measurements must be enabled for
the project (WebAR settings → Dimensions); otherwise it resolves with
{ ok: false, error: 'dimensions disabled' }. Inside an AR session it resolves with
{ ok: false, error: 'unavailable in AR' }.
Add controls="off" to the embed to take the viewer’s own controls down and draw your own:
<togenar-embed project="…" picker="off" controls="off"></togenar-embed>Camera & AR
Section titled “Camera & AR”resetCamera()
Section titled “resetCamera()”Ease the camera back to its opening framing. Resolves with { ok }. Show your own reset button
while getControls().resetView is true — that is exactly when the viewer would have shown its own.
focusPart(partKey)
Section titled “focusPart(partKey)”Apply the camera angle recorded for a part. Call it when your own panel changes section, not
when a swatch is picked. Pass null for a section that is not a part to ease back to the
opening framing.
tg.focusPart('door');tg.focusPart(null);A part with no recorded angle eases back to the opening framing. Resolves with
{ ok, moved, partId }; moved is false when the project does not move the camera on part
change. An unknown handle resolves with { ok: false, error: 'unknown part handle' }.
enterAR()
Section titled “enterAR()”Launch AR (iOS Quick Look / Android Scene Viewer / WebXR). Call from your own click
handler — WebXR may require an in-page user gesture. Resolves with { ok }.
isArAvailable()
Section titled “isArAvailable()”Whether native AR can be launched for the current configuration — synchronous, returns a boolean. Use it to decide whether to render your own AR button at all, so the button never appears on a configuration or a plan that cannot enter AR.
addedToCart(detail?)
Section titled “addedToCart(detail?)”Tell us your cart call succeeded. Call it from your own “Add to cart” handler, right after your store confirms the add. Fire-and-forget; safe to call on every add, AR or not.
This is what makes AR measurable on Android, where no button of ours can be drawn inside the AR session. Without this call, an AR-driven Android sale is not attributed.
addBtn.addEventListener('click', async () => { await addToCart(tg.getSkus()); tg.addedToCart({ value: 1249.5, currency: 'EUR', quantity: 2 });});Pass value (major units) and currency (ISO 4217) to have the add counted in the Revenue
block of Analytics. quantity and sku are optional.
See AR Add to Cart and Revenue tracking.
purchased(detail)
Section titled “purchased(detail)”Tell us the order was placed. Call it on your order-confirmation page, with the embed present.
tg.purchased({ value: 1249.5, currency: 'EUR', orderId: '1001' });value, currency and orderId are required. orderId deduplicates: a refreshed
confirmation page counts once.
See Revenue tracking.
Sharing & capture
Section titled “Sharing & capture”getSnapshot()
Section titled “getSnapshot()”PNG data-URL of the currently-configured model (the summary hero image). Resolves
{ ok, url } — best-effort, ok: false when capture is unavailable.
getShareLink()
Section titled “getShareLink()”Short device-adaptive share link (…/s/{code}) for the current configuration — the same
link the built-in summary’s Share box and QR encode. Resolves { ok, url } (falls back to
the full launcher URL).
getQr(params?)
Section titled “getQr(params?)”QR PNG data-URL for the desktop “scan to view in AR” flow. Defaults to the short share
link; pass { text, size } to encode something else. Resolves { ok, url }.
Timing
Section titled “Timing”Async methods can be called before the viewer is ready — requests queue and flush once the viewer’s bridge is up. Default timeout: 30 s.
