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
- The button runs in the shopper's browser. It collects a photo and consent, and talks only to your own route.
- Your route at
/api/tryonholds the API key. It looks the product up in your catalogue, uploads the photo and starts the try-on. - Clothsy AI makes the image. The button polls your route and shows the result.
1. Install and add your key
npm install clothsy-aiCreate 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.
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.
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:
| Request | What happens |
|---|---|
POST /api/tryon | Takes 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_KEYon 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. requestIdis 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.
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:
<TryOnButton
productId={product.id}
endpoint="/api/tryon"
label="Try it on"
className="product-tryon"
/>/* app/globals.css */
.product-tryon {
--clothsy-accent: #7c3aed; /* your brand colour */
}| Custom property | Controls |
|---|---|
--clothsy-accent | Button and highlight colour |
--clothsy-accent-contrast | Text on the accent colour |
--clothsy-bg | Dialog background |
--clothsy-text | Main text colour |
--clothsy-muted | Secondary text |
--clothsy-border | Borders and dividers |
--clothsy-error | Error messages |
--clothsy-backdrop | The shade behind the dialog |
--clothsy-font | Font family |
--clothsy-radius | Corner radius of buttons and inputs |
--clothsy-radius-lg | Corner 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 whenstateis"error", otherwisenull;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.
"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'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't create your try-on. Please try another photo.</p> : null}
</form>
);
}Deploying on Vercel
- Add
CLOTHSY_API_KEYunder Settings → Environment Variables for Production (and Preview, if you want try-on there too), then redeploy. - Keep
export const maxDuration = 60in 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. - Photos from
TryOnButtonare 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.

