<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Zhang Jun</title>
    <description>The latest articles on DEV Community by Zhang Jun (@jun_builds).</description>
    <link>https://dev.to/jun_builds</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3996111%2F8e28ffc1-c377-4636-a71a-77470b2bf6ea.png</url>
      <title>DEV Community: Zhang Jun</title>
      <link>https://dev.to/jun_builds</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jun_builds"/>
    <language>en</language>
    <item>
      <title>Build a multi-step image pipeline in n8n: turn one product photo into a cut-out, packshot and web size</title>
      <dc:creator>Zhang Jun</dc:creator>
      <pubDate>Wed, 28 Oct 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/jun_builds/build-a-multi-step-image-pipeline-in-n8n-turn-one-product-photo-into-a-cut-out-packshot-and-web-mak</link>
      <guid>https://dev.to/jun_builds/build-a-multi-step-image-pipeline-in-n8n-turn-one-product-photo-into-a-cut-out-packshot-and-web-mak</guid>
      <description>&lt;p&gt;Almost every product photo ends up needing the same three versions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;transparent cut-out&lt;/strong&gt; for ads, marketplaces and anything you composite later,&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;packshot&lt;/strong&gt; with white space around it for the catalogue,&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;web size&lt;/strong&gt; that loads fast on the product page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By hand that is a few minutes per photo, which is fine for five photos and not for five hundred. n8n is the obvious next step, and it is also where these workflows tend to sprawl: one HTTP Request node per service, binary data dragged from node to node, the same file uploaded once per branch.&lt;/p&gt;

&lt;p&gt;This tutorial builds the whole thing as one small n8n workflow. A photo lands in a Google Drive folder, and a minute later a Google Sheet has three new rows, one per variant, each with a permanent URL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Google Drive: new photo
  → Download Photo
  → Upload Once ─┬─ Cut-out         remove_bg               (AI)
                 ├─ White Packshot  pad 120 px of #ffffff   (deterministic)
                 └─ Web Size        resize to ≤ 1200 px     (deterministic)
  → Merge → One Row Per Variant → Google Sheets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One idea keeps it small: &lt;strong&gt;upload once, then refer to the image by its id&lt;/strong&gt;. All three branches point at the same asset, so nothing is uploaded twice. Each branch is its own job with its own output and URL, so a branch that fails costs only its own row.&lt;/p&gt;

&lt;p&gt;The image steps run on &lt;a href="https://imagestep.dev/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=product-trio" rel="noopener noreferrer"&gt;ImageStep&lt;/a&gt;, an image API for automations. &lt;em&gt;Disclosure: I build it.&lt;/em&gt; The workflow, plus a Node.js version of the same pipeline, is MIT-licensed in the &lt;a href="https://github.com/imagestep/imagestep-sdk/tree/main/recipes/product-trio" rel="noopener noreferrer"&gt;recipes folder of the public SDK repo&lt;/a&gt;. Everything below comes from there.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;n8n&lt;/strong&gt;, Cloud or self-hosted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Drive and Google Sheets credentials&lt;/strong&gt; in n8n.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An ImageStep API key&lt;/strong&gt; from &lt;a href="https://imagestep.dev/keys?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=product-trio" rel="noopener noreferrer"&gt;imagestep.dev/keys&lt;/a&gt;. The Free plan includes 200 deterministic ops a month, and new accounts get a one-time $3 welcome credit for AI ops. Step 4 shows the exact price of a run before anything is charged.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A product photo&lt;/strong&gt; (JPEG, PNG, WebP or HEIC). If you don't have one handy, use &lt;a href="https://github.com/imagestep/imagestep-sdk/blob/main/recipes/examples/product.jpg" rel="noopener noreferrer"&gt;&lt;code&gt;recipes/examples/product.jpg&lt;/code&gt;&lt;/a&gt;, a 1024 × 1024 picture of a camping mug. It's the one in the pictures below.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Install the ImageStep node and add your key
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;n8n Cloud and recent editors&lt;/strong&gt;: click &lt;strong&gt;+&lt;/strong&gt; on the canvas, search &lt;strong&gt;ImageStep&lt;/strong&gt; in the nodes panel, pick the one with the verified badge and click &lt;strong&gt;Install&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-hosted&lt;/strong&gt;: &lt;strong&gt;Settings → Community Nodes → Install&lt;/strong&gt;, package &lt;code&gt;n8n-nodes-imagestep&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then create an &lt;strong&gt;ImageStep API&lt;/strong&gt; credential. Paste your key into &lt;strong&gt;API Key&lt;/strong&gt; and leave &lt;strong&gt;Base URL&lt;/strong&gt; at &lt;code&gt;https://api.imagestep.dev&lt;/code&gt;. The credential test reads your key's usage, which only a valid key can do, so a green test means the key works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Import the workflow
&lt;/h2&gt;

&lt;p&gt;Download the template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-LO&lt;/span&gt; https://raw.githubusercontent.com/imagestep/imagestep-sdk/main/recipes/product-trio/n8n-template.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Import it with &lt;strong&gt;Import from File&lt;/strong&gt; in a new workflow's &lt;strong&gt;⋯&lt;/strong&gt; menu. You can also open the file, copy all of it, click an empty canvas and paste. Every credential in it is a &lt;code&gt;REPLACE_ME&lt;/code&gt; and every folder or sheet id a &lt;code&gt;REPLACE_WITH_…&lt;/code&gt;, so open these nodes once and pick your own:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Node&lt;/th&gt;
&lt;th&gt;What to set&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;New Product Photo&lt;/strong&gt; (Google Drive Trigger)&lt;/td&gt;
&lt;td&gt;your Drive credential and the folder to watch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Download Photo&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;the same Drive credential&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Upload Once&lt;/strong&gt;, &lt;strong&gt;Cut-out (remove_bg)&lt;/strong&gt;, &lt;strong&gt;White Packshot (pad)&lt;/strong&gt;, &lt;strong&gt;Web Size (resize)&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;your ImageStep credential&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Append Manifest Row&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;your Sheets credential and the spreadsheet&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Give the sheet a header row with these columns: &lt;code&gt;file&lt;/code&gt;, &lt;code&gt;variant&lt;/code&gt;, &lt;code&gt;assetId&lt;/code&gt;, &lt;code&gt;publicUrl&lt;/code&gt;, &lt;code&gt;width&lt;/code&gt;, &lt;code&gt;height&lt;/code&gt;, &lt;code&gt;jobId&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: What each node does
&lt;/h2&gt;

&lt;p&gt;You can run the workflow as imported. This section is what you need when you adapt it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;New Product Photo → Download Photo.&lt;/strong&gt; The trigger checks the folder every minute for new files. Download Photo puts each file's bytes on the binary property &lt;code&gt;data&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Upload Once.&lt;/strong&gt; Resource &lt;strong&gt;Asset&lt;/strong&gt;, Operation &lt;strong&gt;Upload&lt;/strong&gt;, Binary Property &lt;code&gt;data&lt;/code&gt;, &lt;strong&gt;Wait Until Ready&lt;/strong&gt; on, collection &lt;code&gt;product-trio&lt;/code&gt;. It sends the bytes and waits until ImageStep has finished reading the file. Its output is the asset, and the branches only need its &lt;code&gt;assetId&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The three branches.&lt;/strong&gt; Each one is Resource &lt;strong&gt;Operation&lt;/strong&gt;, Operation &lt;strong&gt;Run&lt;/strong&gt;, with &lt;strong&gt;Input&lt;/strong&gt; set to &lt;strong&gt;Asset IDs&lt;/strong&gt; and the Asset IDs field set to &lt;code&gt;{{ $json.assetId }}&lt;/code&gt;. Only the op and its JSON parameters differ:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Node&lt;/th&gt;
&lt;th&gt;Op&lt;/th&gt;
&lt;th&gt;Parameters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cut-out (remove_bg)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;remove_bg&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;White Packshot (pad)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;pad&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{"top": 120, "bottom": 120, "left": 120, "right": 120, "background": "#ffffff"}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web Size (resize)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;resize&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{"width": 1200, "fit": "inside", "withoutEnlargement": true}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All three have &lt;strong&gt;Store Result&lt;/strong&gt;, &lt;strong&gt;Wait for Result&lt;/strong&gt; and &lt;strong&gt;Publish Outputs&lt;/strong&gt; turned on. Store Result is the switch that matters, and it is off by default. With it off, a deterministic op like &lt;code&gt;pad&lt;/code&gt; hands the bytes straight back and stores nothing, and an AI op like &lt;code&gt;remove_bg&lt;/code&gt; returns asset ids without publishing them. That is right for a one-off conversion and wrong here, because we want every variant kept as an asset with its own permanent URL. With it on, each branch runs as a job, waits for it (240 seconds for the cut-out, 120 for the other two) and publishes the output.&lt;/p&gt;

&lt;p&gt;A branch's output looks like this (trimmed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jobId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"job_…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"COMPLETED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"op"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pad"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"outputs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"assetId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ast_…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"publicUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://cdn.imagestep.dev/…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1264&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1264&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Merge&lt;/strong&gt; has three inputs, one per branch, and passes all three results on as one list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One Row Per Variant&lt;/strong&gt; is a Code node. It turns each job into a sheet row and names the variant after the op:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;New Product Photo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;VARIANT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;remove_bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cutout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;packshot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;web&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;$input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VARIANT&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;op&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;op&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;assetId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assetId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;publicUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;publicUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;jobId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;jobId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Append Manifest Row&lt;/strong&gt; appends the rows and matches fields to columns by name.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Price it before you run it
&lt;/h2&gt;

&lt;p&gt;Open &lt;strong&gt;Cut-out (remove_bg)&lt;/strong&gt; and turn on &lt;strong&gt;Dry Run&lt;/strong&gt;. Activate the workflow and drop one photo into the folder. With Dry Run on, that node creates and charges nothing, and its output is the price:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dryRun"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"estimate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"estimatedCredits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"creditBalance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"sufficientCredit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Credits are a fixed 10,000 to the dollar, so &lt;code&gt;estimatedCredits&lt;/code&gt; divided by 10,000 is the price in USD. The price of each background-removal model is listed on &lt;a href="https://imagestep.dev/pricing?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=product-trio" rel="noopener noreferrer"&gt;imagestep.dev/pricing&lt;/a&gt;. To use a different one, set &lt;strong&gt;Options → Model&lt;/strong&gt; on the Cut-out node.&lt;/p&gt;

&lt;p&gt;The two deterministic branches do run on this pass (on the Free plan they use two of the month's 200 ops). So the sheet gets their two rows, plus a cut-out row with nothing but the file name. Delete those rows, then turn Dry Run off.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Run it
&lt;/h2&gt;

&lt;p&gt;Drop a photo into the folder. Within a minute or so the sheet has three new rows. For &lt;code&gt;examples/product.jpg&lt;/code&gt; they look like this, each with its own &lt;code&gt;assetId&lt;/code&gt;, &lt;code&gt;publicUrl&lt;/code&gt; and &lt;code&gt;jobId&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;file&lt;/th&gt;
&lt;th&gt;variant&lt;/th&gt;
&lt;th&gt;width&lt;/th&gt;
&lt;th&gt;height&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;product.jpg&lt;/td&gt;
&lt;td&gt;cutout&lt;/td&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;product.jpg&lt;/td&gt;
&lt;td&gt;packshot&lt;/td&gt;
&lt;td&gt;1264&lt;/td&gt;
&lt;td&gt;1264&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;product.jpg&lt;/td&gt;
&lt;td&gt;web&lt;/td&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;td&gt;1024&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two of those numbers are worth a second look:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The packshot is 1264 px&lt;/strong&gt;: the 1024 px photo plus 120 px of white on each side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The web size is 1024 px, not 1200&lt;/strong&gt;: &lt;code&gt;withoutEnlargement: true&lt;/code&gt; means resize never upscales. A 3000 px photo comes out 1200 px wide, and a smaller one is left as it is.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cut-out is a PNG with an alpha channel, the same size as the input.&lt;/p&gt;

&lt;p&gt;This is what a real run of the recipe made from that photo (thumbnails). From top to bottom: the upload, the cut-out, the packshot and the web size.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1yiijtybmpdz3w97s2yy.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1yiijtybmpdz3w97s2yy.webp" alt="The upload: a dark green enamel mug on a grey background" width="320" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpvgbozkqmqaysjyaj954.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpvgbozkqmqaysjyaj954.webp" alt="remove_bg: the same mug on transparency" width="320" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdry18icbltx4375q85t8.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdry18icbltx4375q85t8.webp" alt="pad: the photo with white on every side" width="320" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr3uvfbyl3olgswnrxwzv.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr3uvfbyl3olgswnrxwzv.webp" alt="resize: the web size, no larger than the upload" width="320" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before you point the folder at a thousand photos, run it on one. The template's ImageStep nodes are checked against the node's field definitions by the recipes' tests, but your Drive folder and your sheet are your own setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same pipeline as a script
&lt;/h2&gt;

&lt;p&gt;For a cron job or CI, or to try it without n8n, the recipe folder has &lt;code&gt;trio.mjs&lt;/code&gt;: the same pipeline as a short script on the JavaScript SDK. You need Node 20+ and pnpm.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/imagestep/imagestep-sdk.git
&lt;span class="nb"&gt;cd &lt;/span&gt;imagestep-sdk/recipes
pnpm &lt;span class="nb"&gt;install
export &lt;/span&gt;&lt;span class="nv"&gt;IMAGESTEP_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;is_sk_…
node product-trio/trio.mjs examples/product.jpg &lt;span class="nt"&gt;--dry-run&lt;/span&gt;   &lt;span class="c"&gt;# uploads, prices the AI op, stops&lt;/span&gt;
node product-trio/trio.mjs examples/product.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At its core is one upload and three jobs started together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;240&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;cutout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;packshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;web&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeBg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pad&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#ffffff&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;source&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inside&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;withoutEnlargement&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wait&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then it publishes each output with &lt;code&gt;client.assets.publish&lt;/code&gt; and prints one JSON line per variant, plus a summary line a program can read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uploaded product.jpg → ast_… (1024×1024)
dry run: remove_bg … credits · pad and resize are deterministic ops, priced by plan (balance …)
{"variant":"cutout","assetId":"ast_…","publicUrl":"https://cdn.imagestep.dev/…","width":1024,"height":1024,"jobId":"job_…"}
{"variant":"packshot","assetId":"ast_…","publicUrl":"https://cdn.imagestep.dev/…","width":1264,"height":1264,"jobId":"job_…"}
{"variant":"web","assetId":"ast_…","publicUrl":"https://cdn.imagestep.dev/…","width":1024,"height":1024,"jobId":"job_…"}
{"recipe":"product-trio","sourceAssetId":"ast_…","variants":[…]}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Options: &lt;code&gt;--pad 160&lt;/code&gt;, &lt;code&gt;--width 1600&lt;/code&gt;, &lt;code&gt;--collection shop&lt;/code&gt;. &lt;code&gt;IMAGESTEP_BASE_URL&lt;/code&gt; points the script at another API host.&lt;/p&gt;

&lt;h2&gt;
  
  
  Branch or chain?
&lt;/h2&gt;

&lt;p&gt;Branches are the right shape when the variants don't depend on each other. If you want the packshot padded around the &lt;em&gt;cut-out&lt;/em&gt; rather than the raw photo, you need a chain instead. Pass the cut-out's &lt;code&gt;assetId&lt;/code&gt; to a second &lt;code&gt;pad&lt;/code&gt;, or send both steps as one job (&lt;code&gt;steps: [remove_bg, pad]&lt;/code&gt;) and save it as a preset once it deserves a name. The &lt;a href="https://imagestep.dev/docs/recipes?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=product-trio#marketplace-cutout" rel="noopener noreferrer"&gt;marketplace cut-out recipe&lt;/a&gt; is that chain, finished.&lt;/p&gt;

&lt;h2&gt;
  
  
  When it goes wrong
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A branch's output has no &lt;code&gt;publicUrl&lt;/code&gt;, or bytes instead of an asset id&lt;/strong&gt;: Store Result is off on that node. Turn it on, together with Publish Outputs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;asset_not_found&lt;/code&gt; (404)&lt;/strong&gt;: the branch isn't getting the upload's id. Check that Input is &lt;strong&gt;Asset IDs&lt;/strong&gt;, that the field is &lt;code&gt;{{ $json.assetId }}&lt;/code&gt;, and that the branch is wired straight from Upload Once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The credential test is red&lt;/strong&gt;: the key is wrong or revoked, or Base URL was changed. It should be &lt;code&gt;https://api.imagestep.dev&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;insufficient_credit&lt;/code&gt; (402)&lt;/strong&gt;: the AI op costs more than your balance. The job is refused when you submit it, before anything runs, and the error carries a top-up link. The dry run's &lt;code&gt;sufficientCredit&lt;/code&gt; tells you in advance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The wait times out&lt;/strong&gt;: the job keeps running. Raise &lt;strong&gt;Wait Seconds&lt;/strong&gt;, or turn off Wait for Result and collect the result later with &lt;strong&gt;Job → Wait&lt;/strong&gt; or the &lt;strong&gt;ImageStep Trigger&lt;/strong&gt;. The trigger is a signed webhook and needs n8n on a public &lt;code&gt;https://&lt;/code&gt; URL, so a local n8n on &lt;code&gt;localhost:5678&lt;/code&gt; needs &lt;code&gt;WEBHOOK_URL&lt;/code&gt; set to a tunnel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retry or not?&lt;/strong&gt; Every ImageStep error carries a &lt;code&gt;retryable&lt;/code&gt; flag. &lt;code&gt;rate_limited&lt;/code&gt; (429), &lt;code&gt;provider_unavailable&lt;/code&gt; (503) and &lt;code&gt;internal_error&lt;/code&gt; (500) are worth retrying. &lt;code&gt;invalid_param&lt;/code&gt; and &lt;code&gt;insufficient_credit&lt;/code&gt; are not: a retry fails the same way.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where to go from here
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://github.com/imagestep/imagestep-sdk/tree/main/recipes" rel="noopener noreferrer"&gt;recipes folder&lt;/a&gt; has more pipelines, each as a Node script and an n8n template. Among them: a social carousel with the same character on every card, one product placed in new scenes at every ad size, HTML/CSS cards rendered from sheet rows, and phone photos filed by EXIF date and GPS.&lt;/li&gt;
&lt;li&gt;Docs, live prices and the pictures from real runs are on &lt;a href="https://imagestep.dev/docs/recipes?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=product-trio" rel="noopener noreferrer"&gt;imagestep.dev&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next in this series: the same character on every AI-generated image, from a Google Sheet.&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>automation</category>
      <category>ecommerce</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
