Build · WooCommerce

Build a WooCommerce try-on plugin

In one sentence

A WooCommerce plugin calls the Photta API over plain HTTP from a WP-Cron event, polls the job on the following tick, and imports the finished image into the media library.

  • Credits: 5 credits at 2K · 6 credits at 4K
  • Typical time: 1.5–4 min
  • Processing: async, polling

Every figure on this page comes from the Photta API docs · checked

No PHP SDK is needed. WP-Cron submits the job, the next tick polls it, and WP-CLI imports the finished image into the media library.

RESPONSE=$(curl -s -X POST https://api.photta.app/api/v1/tryon/apparel \
  -H "Authorization: Bearer $PHOTTA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "product_type": "top",
  "product_images": [
    "https://cdn.yourstore.com/products/shirt-front.jpg"
  ],
  "product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
  "product_category": "t_shirt",
  "mannequin_id": "mnk_f3a9b1",
  "pose_id": "pose_c2d4e5",
  "background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
  "lock_background": true,
  "resolution": "2K",
  "aspect_ratio": "3:4",
  "body_size": "M",
  "expression": "neutral",
  "user_instruction": "Keep the collar pattern crisp and centered."
}')
ID=$(echo "$RESPONSE" | jq -r '.data.id')

How it works

A cron tick, a poll and a media import

Plan the plugin, schedule the work, submit, poll on the next tick, import.

  1. Step 1

    Plan the plugin

    One queue table, one cron hook, one settings screen for the key. Generation never happens during a page request, so nothing a shopper does can hang on it.

  2. Step 2

    Get a Photta API key

    Read it from the environment in wp-config.php rather than storing it in the options table, so a database export does not carry your credentials.

  3. Step 3

    Schedule the cron event

    Register a recurring hook and take a handful of queued products per run. A site with real traffic gets frequent ticks; a quiet one needs a real system cron.

  4. Step 4

    Submit the product

    Send the product image with wp_remote_post — the same URL, headers and JSON body as the cURL sample. Store the returned id on the queue row.

  5. Step 5

    Poll on the next tick

    Read the status for every row still processing. Because ticks are minutes apart, one poll per tick is usually enough; nothing sleeps inside a request.

  6. Step 6

    Import the image

    Once a job is complete, import the URL into the media library and attach it to the product. WP-CLI does the download so the plugin never buffers the file.

The plugin’s four moving parts

The Photta calls are rendered from the API documentation; wp_remote_post takes the same URL, headers and body.

# wp-config.php holds the key; the plugin reads it with getenv()
# define( 'PHOTTA_API_KEY', getenv( 'PHOTTA_API_KEY' ) );
export PHOTTA_API_KEY="photta_live_xxx"
export PHOTTA_BASE_URL="https://api.photta.app/api/v1"
RESPONSE=$(curl -s -X POST https://api.photta.app/api/v1/tryon/apparel \
  -H "Authorization: Bearer $PHOTTA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "product_type": "top",
  "product_images": [
    "https://cdn.yourstore.com/products/shirt-front.jpg"
  ],
  "product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
  "product_category": "t_shirt",
  "mannequin_id": "mnk_f3a9b1",
  "pose_id": "pose_c2d4e5",
  "background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
  "lock_background": true,
  "resolution": "2K",
  "aspect_ratio": "3:4",
  "body_size": "M",
  "expression": "neutral",
  "user_instruction": "Keep the collar pattern crisp and centered."
}')
ID=$(echo "$RESPONSE" | jq -r '.data.id')
# $ID is data.id from the POST response. Poll every 3 seconds until status is "completed" or "failed".
for i in $(seq 1 120); do
  RESULT=$(curl -s "https://api.photta.app/api/v1/tryon/apparel/$ID" -H "Authorization: Bearer $PHOTTA_API_KEY")
  STATUS=$(echo "$RESULT" | jq -r '.data.status')
  [ "$STATUS" = "completed" ] && break
  [ "$STATUS" = "failed" ] && echo "generation failed" && exit 1
  sleep 3
done
echo "$RESULT" | jq -r '.data.output_url'
# WP-CLI does the download, so the plugin never buffers the file itself
wp media import "$OUTPUT_URL" \
  --post_id="$PRODUCT_ID" \
  --title="Virtual try-on" \
  --featured_image

Questions developers ask

Questions WordPress developers ask

Is there an official PHP SDK?

No. The API is plain REST over HTTPS with a bearer header, so wp_remote_post covers everything without a dependency to keep updated.

How do I make the call from PHP?

wp_remote_post with the Authorization and Content-Type headers and a JSON body. The cURL sample on this page is the same request, field for field.

Why not generate on page load?

A generation takes longer than a page should. Rendering during a request means a shopper waits, a timeout kills it, and the credits are spent either way.

How many products should one run take?

Take a small slice per tick and let the queue drain across ticks. That keeps every run short and keeps you well inside the published rate limit.

What happens to failed jobs?

Mark the row failed and keep the error type from the response. Credits for a failed generation are refunded automatically, so a retry does not pay twice.

A plugin without a dependency tree

Bearer header, JSON body, polling. Everything WordPress already does.