Build a fully custom virtual try-on experience using Genlook’s backend endpoints
Choose this approach when you want to build a completely bespoke try-on experience. You design the upload screens, loading states, and result displays, while Genlook’s backend quietly handles the AI generation.
The Genlook app block still needs to be installed in your Shopify theme.
Here is a minimal, vanilla JavaScript implementation tying the core endpoints together:
async function tryOn(photoFile, productId) { const base = "/apps/proxy_genlook-x/public"; // 1. Ensure the store has credits const credits = await fetch(`${base}/check-credits`).then((r) => r.json()); if (!credits.allowed) throw new Error("No generation credits available"); // 2. Upload the user's photo (using our 3-step signed-URL flow) const { uploadUrl, uploadKey } = await fetch(`${base}/prepare-upload`, { method: "POST", }).then((r) => r.json()); await fetch(uploadUrl, { method: "PUT", headers: { "Content-Type": "application/octet-stream" }, body: photoFile, }); const { fileId } = await fetch(`${base}/upload-complete`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ uploadKey }), }).then((r) => r.json()); // 3. Kick off the AI generation const { jobId } = await fetch(`${base}/fitting-room`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ userImageId: fileId, productId }), }).then((r) => r.json()); // 4. Poll for the final result for (let i = 0; i < 60; i++) { const status = await fetch(`${base}/generation/${jobId}`).then((r) => r.json()); if (status.status === "COMPLETED") return status.resultImageUrl; if (status.status === "FAILED") throw new Error(status.errorMessage); // Wait 2 seconds before polling again await new Promise((resolve) => setTimeout(resolve, 2000)); } throw new Error("The generation request timed out");}
If you’re using our SDK alongside your custom endpoints, you can use Genlook.cabin.fetch(url, options) instead of
manually constructing the base proxy path. Read more about it in the Custom Button
guide.