{
  "name": "Cloudinary — E-commerce Image Pipeline (AI generative fill → square → WebP)",
  "nodes": [
    {
      "parameters": {},
      "id": "22220000-0000-4a01-9001-000000000001",
      "name": "Start",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        240,
        320
      ]
    },
    {
      "parameters": {
        "jsCode": "// Stand in for your product catalog. Each row = one product to ingest + process.\n// Swap this for an HTTP Request to your PIM, a spreadsheet read, etc.\n// (Source URLs here are public Cloudinary demo images.)\nreturn [\n  { json: { name: 'Leather Bag', source_url: 'https://res.cloudinary.com/demo/image/upload/samples/ecommerce/leather-bag-gray.jpg', public_id: 'n8n_blog/products/leather-bag' } },\n  { json: { name: 'Accessories Bag', source_url: 'https://res.cloudinary.com/demo/image/upload/samples/ecommerce/accessories-bag.jpg', public_id: 'n8n_blog/products/accessories-bag' } },\n  { json: { name: 'Analog Watch', source_url: 'https://res.cloudinary.com/demo/image/upload/samples/ecommerce/analog-classic.jpg', public_id: 'n8n_blog/products/analog-watch' } }\n];"
      },
      "id": "22220000-0000-4a01-9001-000000000002",
      "name": "Product Catalog (sample)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        520,
        320
      ]
    },
    {
      "parameters": {
        "resource": "upload",
        "operation": "uploadUrl",
        "url": "={{ $json.source_url }}",
        "resource_type": "image",
        "additionalFields": {
          "public_id": "={{ $json.public_id }}",
          "tags": "n8n-blog,product"
        }
      },
      "id": "22220000-0000-4a01-9001-000000000007",
      "name": "Ingest Product",
      "type": "n8n-nodes-cloudinary.cloudinary",
      "typeVersion": 1,
      "position": [
        820,
        320
      ]
    },
    {
      "parameters": {
        "resource": "transform",
        "operation": "cropImage",
        "transformPublicId": "={{ $json.public_id }}",
        "type": "upload",
        "cropBy": "dimensions",
        "cropWidth": 1200,
        "cropHeight": 1200,
        "cropFocus": "auto",
        "cropGenerativeFill": true,
        "cropGenerativeFillPrompt": "extend the surrounding surface and background"
      },
      "id": "22220000-0000-4a01-9001-000000000003",
      "name": "Square + AI Fill (1200×1200)",
      "type": "n8n-nodes-cloudinary.cloudinary",
      "typeVersion": 1,
      "position": [
        1120,
        320
      ]
    },
    {
      "parameters": {
        "resource": "transform",
        "operation": "convertImage",
        "transformPublicId": "={{ $('Ingest Product').item.json.public_id }}",
        "type": "upload",
        "continueFromTransformation": "={{ $json.transformation }}",
        "convertFormat": "webp"
      },
      "id": "22220000-0000-4a01-9001-000000000004",
      "name": "Convert → WebP",
      "type": "n8n-nodes-cloudinary.cloudinary",
      "typeVersion": 1,
      "position": [
        1420,
        320
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "language": "javaScript",
        "jsCode": "// One catalog-ready row per product: a square, AI-filled, WebP delivery URL.\nconst name = $('Product Catalog (sample)').item.json.name;\nreturn { json: {\n  product: name,\n  public_id: $json.public_id,\n  transformation: $json.transformation,\n  catalog_image_url: $json.secure_url,\n} };"
      },
      "id": "22220000-0000-4a01-9001-000000000005",
      "name": "Catalog Row",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1720,
        320
      ]
    },
    {
      "parameters": {
        "content": "## 🛍️ E-commerce Image Pipeline\n\n**Inconsistent product photos in → a uniform, catalog-ready set out** — automatically, for every product.\n\n### What it does (once per product)\n1. **Product Catalog (sample)** emits a list of products (source URL + target public ID). One item per product.\n2. **Ingest Product** uploads each into *your* Cloudinary account (so everything downstream runs on your cloud).\n3. **Square + AI Fill** uses **Image: Crop** with **Generative Fill** on: instead of cropping the product, Cloudinary *extends* the canvas to a perfect `1200×1200` square and **extends the existing surface and background with AI** (prompt: *“extend the surrounding surface and background”*). No more odd aspect ratios or letterboxing.\n4. **Convert → WebP** chains onto that transformation (via **Continue From Transformation** = `{{ $json.transformation }}`) and delivers a modern, lightweight `WebP`.\n5. **Catalog Row** outputs one `{product, catalog_image_url}` row, ready to push to your storefront.\n\n### Why it matters\nEvery product page gets identically-shaped, optimized hero images without a designer touching Photoshop — and it scales to thousands of SKUs by changing only the input list.\n\n### Make it yours\n- **Real catalog:** replace *Product Catalog (sample)* with an HTTP Request to your PIM, a spreadsheet read, or an **Asset → Search Assets** over an existing folder.\n- **Tune the look:** edit the **Generative Fill Prompt**, the square size, or add a watermark step.\n- **Publish:** wire *Catalog Row* into your commerce platform's product-image API.\n\n> ⚠️ **Generative Fill** is a generative-AI effect — it costs extra transformation credits and may render asynchronously on the first request. The first load of a brand-new URL can take a moment; it's cached thereafter.\n\n### Setup\n👉 Select (or create) your **Cloudinary API** credential on the Cloudinary nodes (Cloud name + API Key + Secret from the [Console](https://console.cloudinary.com/settings/api-keys)). Not included in this template.\n\n💾 **Then press `Cmd/Ctrl+S` to save before running.** That one save persists the credential to *every* Cloudinary node at once, so a full **Execute Workflow** run finds credentials everywhere. Skip the save and the run can fail node-after-node with *\"Node does not have any credentials set\"* — even though the credential looks selected. (If **Execute step** works but **Execute Workflow** doesn't, you forgot the save.)",
        "height": 1300,
        "width": 600
      },
      "id": "22220000-0000-4a01-9001-000000000006",
      "name": "README",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        200,
        560
      ]
    }
  ],
  "connections": {
    "Start": {
      "main": [
        [
          {
            "node": "Product Catalog (sample)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Product Catalog (sample)": {
      "main": [
        [
          {
            "node": "Ingest Product",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ingest Product": {
      "main": [
        [
          {
            "node": "Square + AI Fill (1200×1200)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Square + AI Fill (1200×1200)": {
      "main": [
        [
          {
            "node": "Convert → WebP",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert → WebP": {
      "main": [
        [
          {
            "node": "Catalog Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {}
}
