Embedding the widget
Drop the visualizer into any storefront as a web component.
The widget is a framework-agnostic web component: one script tag and one element, no build-tool integration required. It handles the photo upload, the corner editor for marking areas, and displaying the finished render — your product customizer drives it through the JS API.
The shape of it
<script src="https://your-instance.example/viewa-widget.js" async></script>
<viewa-visualizer publishable-key="pk_…"></viewa-visualizer>Load the script from your API base URL — the same host you call the API on. That is a complete embed. The widget authenticates with your publishable key — safe in the browser because keys are domain-locked — and walks the customer through the whole journey on its own.
If you are serving viewa-widget.js yourself — from your own CDN or asset
pipeline rather than from us — add base-url. It defaults to the origin the script
came from, which in the embed above is the API host; served from your own domain, that
default points every call at your storefront. Your origin also has to be on the key's
allowlist, or every call answers 403 origin_not_allowed.
Add a product and it visualizes that product with no JavaScript at all:
<viewa-visualizer publishable-key="pk_…" product="YOUR-SKU-123"></viewa-visualizer>Attributes
| Attribute | What it does |
|---|---|
publishable-key | Required. A pk_ key whose allowlist includes this origin |
base-url | The API host. Defaults to the origin the script itself came from |
capabilities | Which pipeline steps this instance may take. Defaults to all three |
product | The product to render: a prod_… id or a registered SKU alias |
materials | slot:material pairs, comma or space separated. Omitted slots use the product's default |
product-name | Overrides the name in the upload prompt. Optional — the widget looks it up |
units | m (default), cm or in. Display only; the API always measures in meters |
auto-render | on (default) or off. Off means you call visualize() |
max-areas | How many areas the customer may mark, 1–20. 1 is single-area mode |
ui | Which controls the widget draws for itself. See Theming |
logo | The mark on the upload panel: a URL for your own, or none. Defaults to the Viewa lockup |
image | Resume from a photo you already have (img_…) |
area-set | Resume from areas already measured (aset_…) |
Only publishable-key and base-url reset the widget when they change. Everything else
is presentation, so a customizer can rewrite product on every product change without
throwing away the photo the customer just uploaded.
materials is the markup half of setSelection, for a page that has one product and one
finish rather than a picker:
<viewa-visualizer
publishable-key="pk_…"
product="YOUR-SKU-123"
materials="slot_fabric:CHARCOAL-LINEN"
></viewa-visualizer>Both halves take refs, so a registered SKU alias works in either position. The slot is required: a material on its own does not say which part of the product it dresses.
Splitting the journey across pages
capabilities is a subset of upload, areas and render. It is how a storefront
spreads the flow over more than one page — and what a plugin's settings screen maps
checkboxes onto.
<!-- page one: collect the photo and stop -->
<viewa-visualizer publishable-key="pk_…" capabilities="upload"></viewa-visualizer>
<!-- page two: mark up the photo page one captured -->
<viewa-visualizer
publishable-key="pk_…"
capabilities="areas render"
image="img_…"
></viewa-visualizer>The set is contiguous, and it also says where to begin. A run that does not start at
upload cannot obtain its own photo, so it has to be handed one:
| Hand it | And the widget resumes at |
|---|---|
image | The corner editor, on that photo |
area-set | The measured areas, ready to render |
An area set carries the id of the image it was measured on, so resuming a fully marked-up scene needs one attribute, not two. Setting both only makes the widget resume twice.
Where those ids come from is your session, cart line or order record. Read them off
getStatus() when the customer finishes and store them against the line.
A widget whose capabilities start after upload with no image or area-set has
nothing to show, and says so. That is a template mistake rather than a customer-facing
one, so it also logs to the console instead of leaving you guessing.
What the widget owns vs what you own
| The widget handles | Your page handles |
|---|---|
| Photo upload and validation | Product and material choice UI |
| Corner editing and measurement | Passing the selection via setSelection(…) |
| Render request, polling, display | Where the widget sits in your layout |
| Visitor token storage | Persisting ids against the cart line |
The widget never reads your catalog. Product choice is your customizer's job, which is why there is no picker inside it — see Catalog for building one.
Where to put it
The widget fills the width of its container and holds one height across every step of the
journey, so it drops into a product gallery slot without the page shifting as the customer
works through it. Set --viewa-aspect to match your slot.