Skip to content

Show your own product photos

To show the design on photos of your own rather than the blank’s, pass them as mockups. Each is a view of its own, shown in the strip like a blank’s and switched to with setView(). Pixonaut never learns what they show:

mockups: [
{ url: '/img/tee-white.jpg', label: 'White', viewId: 'white', placementRectPx: { x: 115, y: 90, w: 300, h: 380 } },
{ url: '/img/tee-green.jpg', label: 'Green', viewId: 'green', placementRectPx: { x: 115, y: 90, w: 300, h: 380 }, shadingStrength: 0.6 },
],
initialView: 'white',
  • localizedLabels (optional, { fr: 'Blanc' }) is the photo’s label per language, by its code: the strip shows the one in the designer’s language, else label.

  • viewId names the photo’s view for setView(), initialView and getViews(). Left out, the view is named by its place in the list, as a string ('0', '1'), which moves when you reorder the list. Your photos have no variants, so setVariant() rejects with a TypeError.

  • The photo is fetched by your customer’s browser, from your own URL. It never reaches the print file, so a wrong or missing photo is a display problem, never a bad print.

  • placementRectPx is measured in that photo, in its own pixels: where the printed area sits on the garment as photographed. Photos from one shoot usually share a rect.

  • photoSizePx (optional, { w, h }) is the size of the photo the rect was measured in, for when your URL serves a smaller copy of it — a downscaled preview of a larger original. The rect is then read in that size and placed on whatever size of image arrives. Leave it out when the rect is in the served image’s own pixels. A zero, negative or infinite edge makes the entry one that cannot be drawn.

  • Nothing here is judged against your design. A photo cannot change what gets cut, so there is no rule to refuse one. Labels and order are shown as you give them; only an entry that cannot be drawn at all is skipped.

  • shadingStrength (optional, 0 to 1) sets how strongly the photo’s folds, shadows and highlights show through the design: 0 draws it flat, and leaving it out uses the same default as a mockup photo in the studio. Shading needs the photo’s bytes, so a photo served without CORS headers shows the design flat. A value outside 0 to 1 makes the entry one that cannot be drawn.

  • warp (optional) wraps the design round a mug or a bottle in the photo: { kind: 'cylinder', curvature: 0.8, edgeFalloff: 0.2 }. A curvature of 1 puts the edges of placementRectPx on the product’s sides; edgeFalloff (0 to 1, default 0) darkens the print as the surface turns away. The rect stays the print’s size at the front of the product, so a strongly wrapped print looks narrower than it. Leave warp out, or pass null, for a flat product; a curvature of 0 is refused, and makes the entry one that cannot be drawn, with a line in the console naming it. The wrap needs no photo bytes, so it shows even where shading cannot; the selection handles stay on the flat print.

  • displacementStrength (optional, just above 0 to 1) moves the design with the folds and creases of a t-shirt or a bag in the photo, read from the photo’s own light and shade. Leave it out, or pass null, to leave the design where it is. A value outside that range, 0 included, is read as none: the entry is still drawn, unmoved, with a line in the console naming it. Like shading it needs the photo’s bytes, so a photo served without CORS headers shows the design unmoved; the selection handles stay on the unmoved print.

  • A photo that will not load leaves the design shown without a backdrop, still printable.

A mount naming a blank shows its views by itself (see Views and variants). To pick its photos yourself, the photos and print areas marked on a blank in the studio can be your mockups. GET /v1/blanks/{id}/mockups answers each placement’s views, and each view’s photos with a mount: the photo’s url, its view’s placementRectPx, its photoSizePx, and its look settings, named as mockups names them. Pass each one with its view’s label, or a colour name of your own:

// On your server, with your integration key (ik_…); `apiBase` is the
// address of the Pixonaut API, as you pass it to the designer.
const res = await fetch(`${apiBase}/v1/blanks/${blankId}/mockups`, {
headers: { authorization: `Bearer ${process.env.PIXONAUT_INTEGRATION_KEY}` },
});
const { placements } = await res.json();
const front = placements.find((p) => p.id === 'front');
const flat = front.views.find((v) => v.label === 'Flat');
const mockups = flat.photos
.filter((photo) => photo.mount !== null)
.map((photo) => ({ label: flat.label, ...photo.mount }));
  • The url opens with no key and is served at the size of the stored preview, which may be smaller than the photo the rect was marked on; photoSizePx says which size that was, and the designer places the rect on whatever size arrives.
  • It stays valid for at least 30 days, and at most 60. Any cache, a CDN in front of your store included, may keep the photo that long. Read the blank’s mockups again well inside 30 days, say weekly or whenever your page cache refreshes, and replace the mockups you stored with the new answer. Within one 30-day window every read answers the same url, so a cached copy is reused.
  • It stops opening once the photo is replaced on the blank, or the mockup removed; the next read answers the new photo’s url.
  • mount is null while a photo has not finished uploading: leave that mockup out.
  • With a publishable key (pk_…) the same read works from your page in the customer’s browser, which sends its page’s origin. A server-side fetch sends none, so a publishable key is refused there unless you send an origin header naming one of the key’s allowed origins.

Omit mockups and the blank’s views are shown, when the mount names a blank; naming none, no backdrop is drawn.

Questions? Write to [email protected].

Status · Terms of service · Privacy policy · Subprocessors