Get started

Next.js

On the App Router, adding try-on takes one route handler, one environment variable and a button. The clothsy-ai package ships all three pieces.

How it fits together

  1. The button runs in the shopper's browser. It collects a photo and consent, and talks only to your own route.
  2. Your route at /api/tryon holds the API key. It looks the product up in your catalogue, uploads the photo and starts the try-on.
  3. Clothsy AI makes the image. The button polls your route and shows the result.

1. Install and add your key

Terminal
npm install clothsy-ai

Create a key under Developer API in the Clothsy AI platform and add it to .env.local. Don't give it a NEXT_PUBLIC_ prefix — that would put it in the browser bundle.

.env.local
CLOTHSY_API_KEY=clothsy_live_...

2. Add the route handler

Create app/api/tryon/route.ts. The only thing you write is resolveProduct: given a product id from the browser, return that product's image URL and title from your own catalogue, or null if there's no such product.

app/api/tryon/route.ts
import { createTryOnRoute } from "clothsy-ai/next";
import { getProduct } from "@/lib/catalog";

export const maxDuration = 60; // gives each request up to 60 s on Vercel

export const { POST, GET } = createTryOnRoute({
  resolveProduct: async (productId) => {
    const product = await getProduct(productId);            // your catalogue
    return product ? { imageUrl: product.imageUrl, title: product.title } : null;
  },
});

What the two handlers do:

RequestWhat happens
POST /api/tryonTakes a multipart form with photo, productId, consent and requestId. Uploads the photo, starts the try-on for the product your resolveProduct returned, and responds with { id }.
GET /api/tryon?id=…Responds with { status, resultUrl, message } for that try-on.
  • The key is read from CLOTHSY_API_KEY on the server and never leaves it.
  • Requests from other websites are refused: both handlers check that the call comes from your own origin.
  • The garment always comes from resolveProduct, never from the browser, so nobody can spend your credits on images you don't sell.
  • requestId is used as the idempotency key, so a double click or a retried request starts one try-on, not two.

3. Add the button

Put TryOnButton on your product page. It's a client component, so you can use it inside a server component as it is.

app/products/[id]/page.tsx
import { TryOnButton } from "clothsy-ai/react";
import { getProduct } from "@/lib/catalog";

export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  const product = await getProduct(id);
  if (!product) return null;

  return (
    <main>
      <h1>{product.title}</h1>
      <img src={product.imageUrl} alt={product.title} />
      <TryOnButton productId={product.id} endpoint="/api/tryon" label="Try it on" />
    </main>
  );
}

When a shopper clicks it, the button:

  • opens a dialog with a photo picker;
  • asks for consent with a checkbox that must be ticked before anything is sent;
  • shrinks the photo in the browser to a 1600-pixel JPEG, which keeps it under the 4 MB limit and drops camera metadata such as location;
  • sends it to your route, polls for the result, and shows the finished image.

That's the whole integration. Link your privacy policy near the button and say that shopper photos are processed by a virtual try-on service; ours is at shopper privacy if you'd like to point to it.

Customising the look

The button and dialog are themed with CSS custom properties such as --clothsy-accent, and accept a className so you can scope your overrides. Set the properties on that class, or on any parent element:

app/products/[id]/page.tsx
<TryOnButton
  productId={product.id}
  endpoint="/api/tryon"
  label="Try it on"
  className="product-tryon"
/>
app/globals.css
/* app/globals.css */
.product-tryon {
  --clothsy-accent: #7c3aed;   /* your brand colour */
}
Custom propertyControls
--clothsy-accentButton and highlight colour
--clothsy-accent-contrastText on the accent colour
--clothsy-bgDialog background
--clothsy-textMain text colour
--clothsy-mutedSecondary text
--clothsy-borderBorders and dividers
--clothsy-errorError messages
--clothsy-backdropThe shade behind the dialog
--clothsy-fontFont family
--clothsy-radiusCorner radius of buttons and inputs
--clothsy-radius-lgCorner radius of the dialog

Build your own UI with useTryOn

Want complete control over the markup? The useTryOn hook gives you the same logic without any UI. It returns:

  • start(file, productId) — sends the photo to your route and starts polling;
  • state — where the try-on is up to: "idle", "preparing" (resizing the photo), "uploading", "processing", "success" or "error";
  • resultUrl — the finished image once it's ready;
  • error — a message you can show the shopper when state is "error", otherwise null;
  • reset() — clears everything for another go.

The hook doesn't draw a consent checkbox, so you must: only call start once the shopper has ticked yours. Also label the result as AI-generated — see AI content label.

app/components/MyTryOn.tsx
"use client";

import { useState } from "react";
import { useTryOn } from "clothsy-ai/react";

export function MyTryOn({ productId }: { productId: string }) {
  const { state, start, reset, resultUrl, error } = useTryOn({ endpoint: "/api/tryon" });
  const [file, setFile] = useState<File | null>(null);
  const [agreed, setAgreed] = useState(false);

  if (resultUrl) {
    return (
      <figure>
        <img src={resultUrl} alt="AI-generated preview of you wearing this item" />
        <figcaption>AI-generated try-on</figcaption>
        <button onClick={reset}>Try another photo</button>
      </figure>
    );
  }

  return (
    <form
      onSubmit={(event) => {
        event.preventDefault();
        if (file && agreed) start(file, productId);
      }}
    >
      <input type="file" accept="image/jpeg,image/png" onChange={(e) => setFile(e.target.files?.[0] ?? null)} />
      <label>
        <input type="checkbox" checked={agreed} onChange={(e) => setAgreed(e.target.checked)} />
        I&apos;m 18 or over, this is a photo of me, and I agree to it being processed to create a virtual try-on.
      </label>
      <button type="submit" disabled={!file || !agreed}>See it on me</button>
      <p role="status">{state}</p>
      {error ? <p role="alert">We couldn&apos;t create your try-on. Please try another photo.</p> : null}
    </form>
  );
}

Deploying on Vercel

  1. Add CLOTHSY_API_KEY under Settings → Environment Variables for Production (and Preview, if you want try-on there too), then redeploy.
  2. Keep export const maxDuration = 60 in the route file. Starting a try-on uploads the photo and waits for the API to accept it, and the extra headroom stops that being cut short by a lower default function limit.
  3. Photos from TryOnButton are already resized in the browser, so they stay well under Vercel's request body limit for functions.

A complete store with a catalogue, route and product page is in Full examples.