Catalog3D

Catalog3D for developers

One small API.
A complete room experience.

Add Catalog3D to a product page while Catalog3D owns room upload, processing, presentation, and object targeting inside its isolated application.

A small browser loader for adding Catalog3D to a product page. The merchant page controls placement, dimensions, responsive layout, and appearance.

Five-minute integration

<div id="catalog3d-room" style="height:680px"></div>

<script src="https://catalog3d.ai/embed/v1/catalog3d.js"></script>
<script>
  Catalog3D.mount({
    target: "#catalog3d-room",
    siteId: "your-publishable-site-id",
    productId: "your-product-id",
    locale: "en",
    appearance: {
      theme: "auto",
      accentColor: "#274d3d",
      fontFamily: "Inter, system-ui, sans-serif",
    },
  }).then((handle) => {
    window.catalog3dRoom = handle;
  });
</script>

The promise resolves when Catalog3D is ready. Mounting replaces the target's children, so keep any poster or fallback markup in a sibling element.

Bundler-based apps can skip the tag and import the package instead:

npm install @catalog3d/embed
import { mount } from "@catalog3d/embed";

After catalog3d:room-ready, a host chatbot can submit a plain-language removal intent:

await window.catalog3dRoom.requestRemoval({
  description: "remove the floor lamp beside the sofa",
});

Product identity is immutable. To display another product, call destroy() and mount a new instance.

Product-page example

examples/product-page is the canonical integration and visual debugging surface. It demonstrates:

  • a responsive merchant-owned product page;
  • sizing and placing Catalog3D inside a media gallery;
  • appearance tokens;
  • ready, room-ready, and error events;
  • a host-owned assistant using requestRemoval({ description });
  • cleanup with destroy().

Run it locally:

npm install
npm run build
npm run example

Then open http://127.0.0.1:4174/examples/product-page/.

Documentation

Declarative alternative

<script src="https://catalog3d.ai/embed/v1/catalog3d.js"></script>
<catalog3d-room
  site-id="your-publishable-site-id"
  product-id="your-product-id"
  locale="en"
  theme="auto"
  accent-color="#274d3d"
  font-family="Inter, system-ui, sans-serif"
  style="display:block;height:680px">
</catalog3d-room>

Attributes are initial configuration. Replace the element to change product identity.

Development

npm install
npm run verify   # typecheck, tests, build, package-consumer checks

dist/ is committed because it is the source for the served tag. Rebuild and commit it with any source change.

License

Code is available under the MIT License. Product-page demo media is Catalog3D example content; see the example's asset notice.

Integration guide

View source

Add Catalog3D to a product page with a publishable site id, a product id, and an optional appearance configuration. Use normal page CSS to size and place it.

1. Register the merchant site

Ask Catalog3D for a publishable siteId registered for the merchant's HTTPS origins and products. Never place an API secret in browser code.

For local development, use an explicitly registered loopback origin such as http://127.0.0.1:4174. Origin matching includes the scheme and non-default port.

2. Add a sized target

<section class="product-media">
  <img class="product-poster" src="/media/chair.webp" alt="Marlow armchair" />
  <div id="catalog3d-room"></div>
</section>

Mounting replaces the target's children, so keep any poster image or <noscript> fallback in a sibling element rather than inside the target.

.product-media {
  width: min(100%, 760px);
}

#catalog3d-room {
  width: 100%;
  min-height: 420px;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  border-radius: 22px;
}

The merchant controls placement and size. Catalog3D fills the target and applies a 420-pixel minimum height.

3. Load and mount

<script src="https://catalog3d.ai/embed/v1/catalog3d.js"></script>
<script type="module">
  const target = document.querySelector("#catalog3d-room");

  target.addEventListener("catalog3d:ready", () => {
    console.info("Catalog3D mounted");
  });

  target.addEventListener("catalog3d:room-ready", () => {
    console.info("The shopper's room is ready");
  });

  target.addEventListener("catalog3d:error", (event) => {
    console.error(event.detail.code, event.detail.message);
  });

  const handle = await window.Catalog3D.mount({
    target,
    siteId: "your-publishable-site-id",
    productId: "your-product-id",
    locale: "en",
    appearance: {
      theme: "auto",
      accentColor: "#274d3d",
      fontFamily: "Inter, system-ui, sans-serif",
    },
  });
</script>

Attach listeners before mounting so early errors are observable.

4. Connect a host chatbot

After catalog3d:room-ready, pass a shopper's plain-language removal request:

await handle.requestRemoval({
  description: "remove the small table beside the window",
});

Resolution confirms that Catalog3D accepted the request.

5. Clean up or change products

handle.destroy();

Configuration is immutable. Destroy and mount again to show another product or variant. This starts a new Catalog3D room experience. For normal product pages, full page navigation is often the simplest lifecycle.

Product-page architecture

Keep merchant commerce UI—title, price, variants, quantity, cart, recommendations, and navigation—in the product page. Place Catalog3D in the product media area.

See examples/product-page for the complete reference.

API reference

View source

The stable browser global is window.Catalog3D, installed by the browser tag. The same API is available as named exports for bundlers:

import { mount, version, Catalog3DError } from "@catalog3d/embed";

TypeScript declarations are published in dist/index.d.ts.

Catalog3D.version

The loader's semantic version string. The current version is 1.3.0.

Catalog3D.mount(options)

function mount(options: Catalog3DMountOptions): Promise<Catalog3DHandle>;

Mounts Catalog3D in the target. The promise resolves when the experience is ready and rejects with Catalog3DError if mounting fails.

type Catalog3DMountOptions = {
  target: Element | string;
  siteId: string;
  productId: string;
  variantId?: string;
  locale?: "en" | "de" | "fr";
  appearance?: {
    theme?: "light" | "dark" | "auto";
    accentColor?: string;
    fontFamily?: string;
  };
};
  • target is an element or selector for exactly one merchant-owned container. Mounting replaces the target's existing children. Put a poster image or a <noscript> fallback in a sibling element, not inside the target.
  • siteId is the publishable merchant registration id: 3-64 characters matching [a-z0-9][a-z0-9_-]*.
  • productId identifies an authorized published product: 1-192 characters matching [A-Za-z0-9][A-Za-z0-9._:-]*.
  • variantId optionally identifies a variant in that product family, in the same format as productId.
  • locale defaults to en.
  • appearance is immutable and bounded; see Appearance.

Only one active mount may own a target. Product and appearance configuration are fixed for the lifetime of the mount.

Unknown options are rejected, not ignored. { local: "de" } or { appearance: { accentcolor: "#639" } } fails with INVALID_CONFIG rather than mounting with the wrong configuration.

mount() rejects with INVALID_CONFIG when called without a browser environment, so importing the loader into a server-rendered page is safe.

Catalog3DHandle

type Catalog3DHandle = {
  destroy(): void;
  requestRemoval(request: { description: string }): Promise<void>;
};

destroy()

Removes the iframe and loader-owned wrapper, event listeners, timers, and pending intent requests. Calling it more than once is safe.

requestRemoval({ description })

Submits a plain-language object-removal request after catalog3d:room-ready. Descriptions are trimmed and must contain 1-500 characters. The promise resolves when Catalog3D accepts the request.

Only one removal may be in flight at a time. A second call while one is pending rejects immediately with BUSY.

Catalog3DError

class Catalog3DError extends Error {
  readonly code: Catalog3DErrorCode;
}

The message is safe to log or display. Use code for application decisions. See Events and errors.

Declarative element

<catalog3d-room
  site-id="merchant-site"
  product-id="product-123"
  variant-id="variant-456"
  locale="en"
  theme="auto"
  accent-color="#274d3d"
  font-family="Inter, system-ui, sans-serif">
</catalog3d-room>

Attributes are initial configuration. The element does not observe identity or appearance mutations; replace it to reconfigure.

Child content is treated as a placeholder and is replaced when the embed mounts, so <catalog3d-room><p>Loading your room…</p></catalog3d-room> works. Moving the element in the DOM — a carousel, a tab panel, a framework reorder — keeps the mount; removing it destroys the mount.

Events and errors

View source

Catalog3D dispatches DOM CustomEvent objects on the mount target.

Events

catalog3d:ready

Catalog3D is ready. Catalog3D.mount() resolves at the same lifecycle point. The event has no detail payload (detail is null).

It fires once per mount. If the iframe reloads afterwards the loader re-initializes it silently; the event does not fire again.

catalog3d:room-ready

The shopper's room is ready for room-dependent actions such as object removal. The event has no detail payload (detail is null).

It may fire more than once, so handlers should be idempotent. A room-dependent request can still reject with ROOM_NOT_READY if the room changes after an earlier event.

catalog3d:error

type Catalog3DErrorDetail = {
  code: Catalog3DErrorCode;
  message: string;
};

The error event is used for both lifecycle failures and rejected commands.

Stable error codes

  • BUSY: another incompatible request is active. The loader raises this itself when requestRemoval() is called while one is already in flight.
  • FRAME_LOAD_FAILED: Catalog3D could not load. Handle this and TIMEOUT as unavailable states.
  • INTERNAL_ERROR: Catalog3D could not complete the operation safely.
  • INVALID_CONFIG: public mount configuration failed validation.
  • INVALID_REQUEST: a command payload failed validation.
  • ORIGIN_DENIED: the page origin is not registered for the site.
  • PRODUCT_NOT_FOUND: the product is unavailable or not authorized.
  • ROOM_NOT_READY: a room-dependent command was sent too early.
  • SITE_NOT_FOUND: the publishable site registration is unavailable.
  • TARGET_IN_USE: the target already owns a Catalog3D mount.
  • TARGET_NOT_FOUND: the target element or selector did not resolve.
  • TIMEOUT: the frame or command did not acknowledge within its public limit.

Treat messages as shopper-safe explanations, but branch on stable codes:

try {
  await room.requestRemoval({ description });
} catch (error) {
  if (error.code === "ROOM_NOT_READY") {
    showMessage("Upload a room before requesting an edit.");
  } else if (error.code === "BUSY") {
    showMessage("Catalog3D is already working on another edit.");
  } else {
    showMessage(error.message);
  }
}

Appearance, sizing, and placement

View source

Style the target container with normal page CSS and use the appearance option to align Catalog3D with the surrounding product page.

Container sizing

Use ordinary CSS on the target or its parent:

.catalog3d-product-media {
  width: 100%;
  max-width: 760px;
  aspect-ratio: 1 / 1;
  min-height: 420px;
  overflow: hidden;
  border-radius: 22px;
}

@media (max-width: 460px) {
  .catalog3d-product-media {
    height: 420px;
    aspect-ratio: auto;
  }
}

Catalog3D fills the target's width and height and uses a 420-pixel minimum height. Size and place the target itself. On a viewport narrower than 420 pixels, use a fixed 420-pixel height instead of combining the minimum height with a square aspect ratio. The target can sit in a grid, carousel, dialog, product gallery, or full-width section.

Appearance tokens

appearance: {
  theme: "auto",
  accentColor: "#274d3d",
  fontFamily: "Inter, system-ui, sans-serif",
}
  • theme is light, dark, or auto and defaults to auto.
  • accentColor is an opaque three- or six-digit hex color. Catalog3D derives the related interface colors.
  • fontFamily is a bounded CSS family stack up to 200 characters. The named font must be available to Catalog3D.

Appearance is fixed for the lifetime of the mount. Only the documented fields are accepted.

Responsive integration

Let the product page decide when columns collapse. Keep the target mounted while temporarily showing another gallery image if the current experience should be preserved. The reference product page demonstrates this pattern.

React and Next.js

View source

Catalog3D is framework-independent. Mount it when the client component is ready and destroy it during cleanup.

Bundler-based apps can install the package and import mount directly instead of loading the browser tag:

npm install @catalog3d/embed

The examples below use the browser tag, which keeps the loader out of the application bundle. Either route is supported; the API is identical. Importing the package during server rendering is safe — it registers nothing without a document, and mount() rejects with INVALID_CONFIG rather than throwing.

React component

import { useEffect, useRef } from "react";
import type {
  Catalog3DHandle,
  Catalog3DMountOptions,
} from "@catalog3d/embed";

type Catalog3DRoomProps = Omit<Catalog3DMountOptions, "target"> & {
  sdkReady: boolean;
};

export function Catalog3DRoom({ sdkReady, ...options }: Catalog3DRoomProps) {
  const targetRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const target = targetRef.current;
    if (!sdkReady || !target || !window.Catalog3D) return;

    let disposed = false;
    let handle: Catalog3DHandle | undefined;

    window.Catalog3D.mount({ ...options, target })
      .then((mounted) => {
        if (disposed) mounted.destroy();
        else handle = mounted;
      })
      .catch((error) => {
        if (!disposed) console.error("Catalog3D failed to mount", error);
      });

    return () => {
      disposed = true;
      handle?.destroy();
    };
  }, [
    sdkReady,
    options.siteId,
    options.productId,
    options.variantId,
    options.locale,
    options.appearance?.theme,
    options.appearance?.accentColor,
    options.appearance?.fontFamily,
  ]);

  return <div ref={targetRef} className="catalog3d-room" />;
}

Changing an immutable option runs cleanup and mounts a new instance. Avoid passing a newly created options object as the only effect dependency.

Next.js App Router

Load the browser script with next/script, and keep the mount component on the client:

"use client";

import { useState } from "react";
import Script from "next/script";

export default function ProductPage() {
  const [sdkReady, setSdkReady] = useState(false);

  return (
    <>
      <Script
        src="https://catalog3d.ai/embed/v1/catalog3d.js"
        strategy="afterInteractive"
        onReady={() => setSdkReady(true)}
      />
      <Catalog3DRoom
        sdkReady={sdkReady}
        siteId="your-publishable-site-id"
        productId="your-product-id"
        locale="en"
      />
    </>
  );
}

Catalog3D mounts in the browser. Product metadata, pricing, structured data, and commerce controls can remain server-rendered.

Strict Mode

Development Strict Mode may run an effect setup-cleanup-setup cycle. Always destroy the first handle when its promise resolves after disposal. Never disable cleanup or retain a handle for a target that React has removed.

Security and privacy

View source

Use the loader only on origins registered for the merchant site. Origin matching includes the scheme and non-default port. Register loopback origins explicitly for local development.

The siteId is publishable. Keep API secrets and other server credentials out of browser configuration. Load the production tag from https://catalog3d.ai and use only the documented API and events.

Catalog3D runs in an isolated frame. Style and position the target container rather than attempting to inspect or modify the embedded page.

Content Security Policy

Merchants normally need to allow:

script-src https://catalog3d.ai
frame-src https://catalog3d.ai

Apply the site's existing nonce or hash policy to inline integration code, or place mount code in a merchant-owned external script.

Merchants with a restrictive style-src-attr policy should include the embed in their CSP verification.

Versioning and migration

View source

The loader follows semantic versioning. The stable browser URL is versioned by major API generation:

https://catalog3d.ai/embed/v1/catalog3d.js

Backward-compatible fixes and additions may ship within v1. Breaking changes use a new major URL and include a migration guide.

Stores that require an exact version should use the npm package and commit their lockfile:

npm install @catalog3d/embed

Compatibility commitments

  • Existing valid mount configuration retains its meaning within v1.
  • Events and error codes documented for v1 remain stable.
  • New optional configuration or handle methods may be added in a minor release.
  • The documented API defines the compatibility surface.

Product changes

V1 configuration is immutable. Use destroy() and mount again:

let room = await Catalog3D.mount(firstProduct);

async function showProduct(nextProduct) {
  room.destroy();
  room = await Catalog3D.mount(nextProduct);
}

Remounting starts a new room experience. Prefer normal product-page navigation when the shopper should not carry the current room session between products.