{
  "name": "Turn a product photo into a product video with one Shopify tag",
  "nodes": [
    {
      "parameters": {
        "authentication": "accessToken",
        "topic": "products/update"
      },
      "id": "2dfd21bd-c1c7-4326-bafe-4180a9bbadd9",
      "name": "Product Updated",
      "type": "n8n-nodes-base.shopifyTrigger",
      "typeVersion": 1,
      "position": [
        -544,
        0
      ]
    },
    {
      "parameters": {
        "authentication": "apiKey",
        "operation": "generateVideo",
        "firstFrameUrl": "={{ $json.images[$json.images.length - 1].src }}",
        "duration": "5s",
        "promptId": "e9e92395-4f54-417d-94a3-01a72d9ca90b",
        "videoCallbackUrl": "={{ $execution.resumeUrl }}"
      },
      "id": "3987bfa0-13c3-4160-865c-4ddc803a3252",
      "name": "Generate Video",
      "type": "n8n-nodes-dreem.dreem",
      "typeVersion": 1,
      "position": [
        240,
        0
      ]
    },
    {
      "parameters": {
        "resume": "webhook",
        "httpMethod": "POST",
        "limitWaitTime": true,
        "resumeAmount": 20,
        "resumeUnit": "minutes",
        "options": {}
      },
      "id": "4f76026e-91a6-4ed6-ac24-d86b64c8e073",
      "name": "Wait for Render",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        480,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 1
          },
          "conditions": [
            {
              "leftValue": "={{ $json.body?.status ?? $json.status }}",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "rightValue": "completed"
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "9a0b891b-b50f-4efd-ab10-e59f5e5843b9",
      "name": "Is Complete?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        720,
        0
      ]
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "operation": "sendAndWait",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "message": "=Approve this video for *{{ $('Product Updated').item.json.title }}*? *Upload to Shopify* adds it to the product's media. *Skip* = nothing is published — the link above stays valid for ~12 hours.",
        "approvalOptions": {
          "values": {
            "approvalType": "double",
            "approveLabel": "Upload to Shopify",
            "disapproveLabel": "Skip"
          }
        },
        "options": {
          "limitWaitTime": {
            "values": {
              "resumeAmount": 12,
              "resumeUnit": "hours"
            }
          }
        }
      },
      "id": "70e236d8-5a0d-41ca-94b2-8d9d7028617e",
      "name": "Deliver Video & Approve",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.5,
      "position": [
        1200,
        -144
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 1
          },
          "conditions": [
            {
              "leftValue": "={{ $json.data.approved }}",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "rightValue": true
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "8e577a52-80e9-4acc-9e54-b9e37707459b",
      "name": "Is Approved?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1440,
        -144
      ]
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "text": "=Dreem video render timed out for *{{ $('Product Updated').item.json.title }}*. Nothing was delivered. Check the Dreem dashboard and retry.",
        "otherOptions": {}
      },
      "id": "d42b789a-d2b2-4223-b1f1-bfee80d3e919",
      "name": "Timed Out",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.5,
      "position": [
        1696,
        144
      ]
    },
    {
      "parameters": {
        "content": "## 🟡 Start here - what this template does\n\nTag a Shopify product `dreem-video` → Dreem takes the product's newest **photo on a model** and animates it into a **short video** → the video link lands in Slack with an **Upload to Shopify** button → one click adds it to the product's media.\n\nThe video is only as good as its first frame. Use an **on-model photo** (your garment worn by a model, e.g. made with Dreem's On-Model template) - a model in motion beats a floating packshot.\n\n### 1. Add your own credentials (Settings → Credentials)\nThe template ships **without credentials** - create these three yourself, then select them on the nodes:\n- **Dreem API** - your `dreem_pk_...` key → select on **Generate Video**\n- **Shopify Access Token API** - your shop + Admin token (`read_products` + `write_products`) → select on **Product Updated** + **Swap Tag (Loop Safety)**\n- **Slack** - create a **Slack OAuth2** credential (on n8n Cloud just press *Connect*) → select on **Post Video to Slack**, **Deliver Video & Approve** + **Timed Out**, and pick your channel on each. (Prefer a bot token? Use a *Slack API* credential and switch each Slack node's **Authentication** to *Access Token*.)\n\n### 2. Replace `YOUR-STORE.myshopify.com` in **Stage Shopify Upload** + **Attach Video to Product**, and select your Shopify credential on both nodes.\n### 3. On **Generate Video**: pick a **Video Prompt**, **Duration** (5-10s) + **Dimensions** (`9:16` for Reels/TikTok).\n### 4. Toggle **Active**, then tag a product `dreem-video`.\n\n### ⚠️ Good to know\n- **First frame** - the video animates the product's **newest image** (last in the media list). Make sure that's an on-model photo before tagging.\n- **Tag flow** - `dreem-video` is swapped to `video-done` before generating. Don't disable **Swap Tag** - it prevents infinite re-triggers.\n- **Human gate** - nothing is uploaded to Shopify without a click. **Skip** (or 12h without a click) = the video stays a Slack link only.\n- **Shopify processing** - uploaded videos take ~1 min to appear under Product → Media.",
        "height": 840,
        "width": 1476,
        "color": 7
      },
      "id": "ab58e0f3-4b21-40d1-882d-b305dbfe61f8",
      "name": "Setup — Start Here",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -560,
        -1024
      ]
    },
    {
      "parameters": {
        "content": "### 1️⃣ Product tagged `dreem-video`\nFires on **product updates** and continues only when the product carries the tag `dreem-video` (and has at least one image).\n\n**Do:** select your own **Shopify credential** here and on **Swap Tag (Loop Safety)**.\n**To make a video:** add the tag `dreem-video` to any product whose newest image is an on-model photo.\n\nℹ️ The tag is swapped to `video-done` right away so the workflow can't re-trigger itself.",
        "height": 420,
        "width": 376,
        "color": 4
      },
      "id": "188fdfbf-ef5e-4ee7-8d13-d31a0497a1a3",
      "name": "Note 1 — Trigger",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -464,
        176
      ]
    },
    {
      "parameters": {
        "content": "### 2️⃣ On-model photo → video\nDreem animates the product's newest image into a **5-10s** video clip.\n\n**Do (in the node, before activating):**\n- Select your own **Dreem API credential**.\n- **Start from an on-model photo** - the first frame is the product's **newest image**. Generate the on-model photo first (e.g. with Dreem's On-Model template), THEN tag `dreem-video`. A model in motion beats a static packshot.\n- **Front + back image** - consider providing both (adjustable in the node).\n- **Video Prompt** - pick the motion style (one is pre-selected). Browse all prompts at [dreem.ai](https://dreem.ai).\n- **Duration** → 5s or 10s\n- **Dimensions** → `9:16` for Reels/TikTok, `1:1` / `4:5` for feed\n\nℹ️ Leave **Webhook URL** as `{{ $execution.resumeUrl }}` - it's how Dreem reports the finished video back.",
        "height": 506,
        "width": 576,
        "color": 4
      },
      "id": "3f5f7cdb-22b7-47f3-b829-cfc7f478f594",
      "name": "Note 2 — Generate",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        176
      ]
    },
    {
      "parameters": {
        "content": "### 3️⃣ Wait for the result\nDreem renders in the background and calls back when ready (up to **20 min**). **Wait for Render** pauses the workflow; **Is Complete?** checks it finished.\n\n**Do:** nothing - this part is automatic. If rendering times out, a note is posted to Slack and nothing is delivered.",
        "height": 248,
        "width": 608,
        "color": 5
      },
      "id": "6b7d0c97-3e02-4f89-8815-5c5e22ff9a73",
      "name": "Note 3 — Poll",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        672,
        240
      ]
    },
    {
      "parameters": {
        "content": "### 4️⃣ Deliver + approve in Slack\nThe finished video **link** is posted to Slack, followed by an approval message with two buttons:\n\n✅ **Upload to Shopify** → the video is added to the product's media.\n❌ **Skip** → nothing is published; the link stays valid for ~12 hours.\n\nNo click within **12h** = treated as Skip. Nothing ever reaches Shopify without your approval.\n\n**Do:** select your own **Slack credential** + channel on **Post Video to Slack**, this node and **Timed Out**.\n\n⚠️ Keep **Is Approved?** enabled - if it's disabled, even Skip continues to the upload.",
        "height": 502,
        "width": 372,
        "color": 3
      },
      "id": "83c8788a-5326-4a08-a34a-d527d03962c9",
      "name": "Note 4 — Approve",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1152,
        -800
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 1
          },
          "conditions": [
            {
              "leftValue": "={{ ($json.tags || '').split(',').map(t => t.trim()).includes('dreem-video') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            },
            {
              "leftValue": "={{ ($json.images || []).length }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "ea58923f-a8f4-4228-8e45-671f870d161d",
      "name": "Tagged dreem-video?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -304,
        0
      ]
    },
    {
      "parameters": {
        "authentication": "accessToken",
        "resource": "product",
        "operation": "update",
        "productId": "={{ $json.id }}",
        "updateFields": {
          "tags": "={{ $json.tags.split(',').map(t => t.trim()).filter(t => t !== 'dreem-video').concat('video-done').join(', ') }}"
        }
      },
      "id": "6f86aeb3-a4b0-41a4-b7d4-1df0320995ca",
      "name": "Swap Tag (Loop Safety)",
      "type": "n8n-nodes-base.shopify",
      "typeVersion": 1,
      "position": [
        32,
        0
      ]
    },
    {
      "parameters": {
        "url": "={{ $('Wait for Render').item.json.body?.outputs?.[0]?.files?.[0]?.url ?? $('Wait for Render').item.json.outputs?.[0]?.files?.[0]?.url }}",
        "options": {
          "response": {
            "response": {
              "fullResponse": true,
              "responseFormat": "file"
            }
          }
        }
      },
      "id": "39b1d3a5-9913-47de-9896-b3207ac968be",
      "name": "Download Video",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        1728,
        -144
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://YOUR-STORE.myshopify.com/admin/api/2024-07/graphql.json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "shopifyAccessTokenApi",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ query: 'mutation stagedUploadsCreate($input: [StagedUploadInput!]!) { stagedUploadsCreate(input: $input) { stagedTargets { url resourceUrl parameters { name value } } userErrors { field message } } }', variables: { input: [{ filename: 'dreem-' + $('Product Updated').item.json.id + '.mp4', mimeType: 'video/mp4', resource: 'VIDEO', fileSize: $json.headers['content-length'], httpMethod: 'POST' }] } }) }}",
        "options": {}
      },
      "id": "af8bdf84-8f56-4803-8074-5e9ce9073a84",
      "name": "Stage Shopify Upload",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        2016,
        -144
      ]
    },
    {
      "parameters": {
        "mode": "combine",
        "combineBy": "combineByPosition",
        "options": {}
      },
      "id": "adb5f3cd-763f-459d-bd80-0315b82b04d2",
      "name": "Merge File + Target",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        2352,
        -144
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $json.data.stagedUploadsCreate.stagedTargets[0].url }}",
        "sendBody": true,
        "contentType": "multipart-form-data",
        "bodyParameters": {
          "parameters": [
            {
              "name": "GoogleAccessId",
              "value": "={{ $json.data.stagedUploadsCreate.stagedTargets[0].parameters.find(p => p.name === 'GoogleAccessId').value }}"
            },
            {
              "name": "key",
              "value": "={{ $json.data.stagedUploadsCreate.stagedTargets[0].parameters.find(p => p.name === 'key').value }}"
            },
            {
              "name": "policy",
              "value": "={{ $json.data.stagedUploadsCreate.stagedTargets[0].parameters.find(p => p.name === 'policy').value }}"
            },
            {
              "name": "signature",
              "value": "={{ $json.data.stagedUploadsCreate.stagedTargets[0].parameters.find(p => p.name === 'signature').value }}"
            },
            {
              "parameterType": "formBinaryData",
              "name": "file",
              "inputDataFieldName": "data"
            }
          ]
        },
        "options": {}
      },
      "id": "3c1cabc3-6620-4f32-983d-055d31188c48",
      "name": "Upload Video File",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        2576,
        -144
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://YOUR-STORE.myshopify.com/admin/api/2024-07/graphql.json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "shopifyAccessTokenApi",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ query: 'mutation productCreateMedia($productId: ID!, $media: [CreateMediaInput!]!) { productCreateMedia(productId: $productId, media: $media) { media { id status } mediaUserErrors { field message } } }', variables: { productId: 'gid://shopify/Product/' + $('Product Updated').item.json.id, media: [{ originalSource: $('Stage Shopify Upload').item.json.data.stagedUploadsCreate.stagedTargets[0].resourceUrl, mediaContentType: 'VIDEO', alt: 'Dreem video — ' + $('Product Updated').item.json.title }] } }) }}",
        "options": {}
      },
      "id": "5c2b2eb5-930f-419f-b7a3-436346c50887",
      "name": "Attach Video to Product",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        2800,
        -144
      ]
    },
    {
      "parameters": {
        "content": "### 5️⃣ Upload to Shopify media\nDownloads the finished video → stages an upload slot → pushes the file to Shopify's storage → attaches it to the product.\n\n**Do:** replace `YOUR-STORE.myshopify.com` in **Stage Shopify Upload** + **Attach Video to Product**, and select your own **Shopify credential** on both (token needs `write_products`).\n\nℹ️ Non-destructive - the video is **added** to Product → Media alongside existing media. Shopify takes ~1 min to process it.",
        "height": 380,
        "width": 1330,
        "color": 6
      },
      "id": "7b42e72d-00ac-491a-ae54-edaad16fce13",
      "name": "Note 5 — Shopify Upload",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1968,
        80
      ]
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "text": "=*Dreem video ready* for *{{ $('Product Updated').item.json.title }}*\n{{ $('Wait for Render').item.json.body?.outputs?.[0]?.files?.[0]?.url ?? $('Wait for Render').item.json.outputs?.[0]?.files?.[0]?.url }}",
        "otherOptions": {}
      },
      "id": "b2f2fbcc-dfe6-5aef-80b5-0ab287ac9746",
      "name": "Post Video to Slack",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.5,
      "position": [
        960,
        -320
      ]
    }
  ],
  "connections": {
    "Generate Video": {
      "main": [
        [
          {
            "node": "Wait for Render",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Complete?": {
      "main": [
        [
          {
            "node": "Post Video to Slack",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Timed Out",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Approved?": {
      "main": [
        [
          {
            "node": "Download Video",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait for Render": {
      "main": [
        [
          {
            "node": "Is Complete?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Product Updated": {
      "main": [
        [
          {
            "node": "Tagged dreem-video?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Tagged dreem-video?": {
      "main": [
        [
          {
            "node": "Swap Tag (Loop Safety)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Swap Tag (Loop Safety)": {
      "main": [
        [
          {
            "node": "Generate Video",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Video": {
      "main": [
        [
          {
            "node": "Stage Shopify Upload",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge File + Target",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Stage Shopify Upload": {
      "main": [
        [
          {
            "node": "Merge File + Target",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge File + Target": {
      "main": [
        [
          {
            "node": "Upload Video File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upload Video File": {
      "main": [
        [
          {
            "node": "Attach Video to Product",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Deliver Video & Approve": {
      "main": [
        [
          {
            "node": "Is Approved?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post Video to Slack": {
      "main": [
        [
          {
            "node": "Deliver Video & Approve",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate"
  }
}