AutomationFlowsData & Sheets › Bulk Sync Products From Google Sheets to Odoo with Auto Image Upload

Bulk Sync Products From Google Sheets to Odoo with Auto Image Upload

Bykhaled yasser @khaledyasser01 on n8n.io

What Problem Does It Solve? Manually entering hundreds of products from a spreadsheet into Odoo is slow, repetitive, and prone to human error. Ensuring that barcodes are unique and products aren't duplicated during manual entry is a constant challenge. Uploading product images…

Event trigger★★★★☆ complexity16 nodesGoogle Sheets TriggerOdooHTTP RequestGoogle Sheets
Data & Sheets Trigger: Event Nodes: 16 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #14928 — we link there as the canonical source.

This workflow follows the Google Sheets → Googlesheetstrigger recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "nodes": [
    {
      "id": "sticky-overview",
      "name": "Sticky Note - Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1100,
        -180
      ],
      "parameters": {
        "color": 6,
        "width": 380,
        "height": 340,
        "content": "## Google Sheets to Odoo Syncer\n\n### How it works\n1. Triggers when a new row is added to Google Sheets.\n2. Checks if the product already exists in Odoo via Barcode.\n3. If missing, it fetches the appropriate category.\n4. Checks if an image URL is provided. If yes, it downloads and converts it to base64.\n5. Creates the product in Odoo (with or without the image).\n6. Updates the Google Sheet status to 'Done'.\n\n*Includes an error-handling loop that waits 1 minute and retries if the Google Sheet update fails.*"
      },
      "typeVersion": 1
    },
    {
      "id": "sticky-phase-1",
      "name": "Sticky Note - Phase 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1050,
        200
      ],
      "parameters": {
        "color": 7,
        "width": 1000,
        "height": 280,
        "content": "## Phase 1: Fetch & Verify\nTriggered by new rows. Loops through them and checks Odoo for existing barcodes."
      },
      "typeVersion": 1
    },
    {
      "id": "sticky-phase-2",
      "name": "Sticky Note - Phase 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        50,
        20
      ],
      "parameters": {
        "color": 7,
        "width": 1250,
        "height": 450,
        "content": "## Phase 2: Category, Image Processing & Creation\nRetrieves category, checks for image URL, downloads if present, and creates the product in Odoo."
      },
      "typeVersion": 1
    },
    {
      "id": "sticky-phase-3",
      "name": "Sticky Note - Phase 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1350,
        20
      ],
      "parameters": {
        "color": 7,
        "width": 320,
        "height": 450,
        "content": "## Phase 3: Status & Error Recovery\nUpdates Sheet to 'Done'. If error occurs, waits 1 minute and returns to the loop."
      },
      "typeVersion": 1
    },
    {
      "id": "183ef33f-9a5c-457e-a5b1-c848728d6695",
      "name": "On New Row in Sheets",
      "type": "n8n-nodes-base.googleSheetsTrigger",
      "position": [
        -1000,
        280
      ],
      "parameters": {
        "event": "rowAdded",
        "options": {},
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1xY4o1gv2lvglcIlxNPbM-vuNC6tIysO9rwkse-cnRH8/edit#gid=0",
          "cachedResultName": "\u0627\u0644\u0648\u0631\u0642\u06291"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1xY4o1gv2lvglcIlxNPbM-vuNC6tIysO9rwkse-cnRH8",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1xY4o1gv2lvglcIlxNPbM-vuNC6tIysO9rwkse-cnRH8/edit?usp=drivesdk",
          "cachedResultName": "khaled"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "a69ff7df-c37e-416a-b0e5-eb2614d3d0c9",
      "name": "Process Row Batch",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        -750,
        280
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "3a469a4c-7aff-4cd8-b5d6-55da653e6d80",
      "name": "Check Odoo for Barcode",
      "type": "n8n-nodes-base.odoo",
      "position": [
        -500,
        280
      ],
      "parameters": {
        "limit": 1,
        "options": {
          "fieldsList": []
        },
        "resource": "custom",
        "operation": "getAll",
        "filterRequest": {
          "filter": [
            {
              "value": "={{ $json.Barcode }}",
              "fieldName": "barcode"
            }
          ]
        },
        "customResource": "product.product"
      },
      "credentials": {
        "odooApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "85d563e9-9878-4f68-9031-f87b10788179",
      "name": "If Product Missing",
      "type": "n8n-nodes-base.if",
      "position": [
        -250,
        280
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "213647d7-d6dc-4d55-99c1-9107052c97cb",
              "operator": {
                "type": "string",
                "operation": "empty",
                "singleValue": true
              },
              "leftValue": "={{ $json.id }}",
              "rightValue": ""
            }
          ]
        },
        "looseTypeValidation": true
      },
      "typeVersion": 2.3
    },
    {
      "id": "acddd5e0-4dce-46ca-946e-98dbca0e25cd",
      "name": "Get Odoo Category",
      "type": "n8n-nodes-base.odoo",
      "position": [
        100,
        260
      ],
      "parameters": {
        "limit": 1,
        "options": {},
        "resource": "custom",
        "operation": "getAll",
        "filterRequest": {
          "filter": [
            {
              "value": "={{ $('Process Row Batch').item.json.category }}",
              "fieldName": "name"
            }
          ]
        },
        "customResource": "product.category"
      },
      "credentials": {
        "odooApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "383d89b8-ad0b-4dc1-93b2-4d5d58c074ae",
      "name": "Has Image URL?",
      "type": "n8n-nodes-base.if",
      "position": [
        350,
        260
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "25a10f95-d8a2-4479-9109-0ddca965493f",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $('On New Row in Sheets').item.json['\u0627\u0644\u0635\u0648\u0631\u0647'] }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "07d6f7a7-4641-462f-8bf2-1d5828c451f2",
      "name": "Download Image",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        600,
        140
      ],
      "parameters": {
        "url": "={{ $('On New Row in Sheets').item.json['\u0627\u0644\u0635\u0648\u0631\u0647'] }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "file"
            }
          }
        }
      },
      "typeVersion": 4.3,
      "alwaysOutputData": true
    },
    {
      "id": "0251b632-eb98-4a63-aab0-98ff9113d306",
      "name": "Convert Image to Base64",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        850,
        140
      ],
      "parameters": {
        "options": {},
        "operation": "binaryToPropery",
        "destinationKey": "final_image"
      },
      "typeVersion": 1.1,
      "alwaysOutputData": true
    },
    {
      "id": "f61d1efc-47ed-46ca-b81c-6d05b70daea6",
      "name": "Create Product (With Image)",
      "type": "n8n-nodes-base.odoo",
      "position": [
        1100,
        140
      ],
      "parameters": {
        "resource": "custom",
        "customResource": "product.template",
        "fieldsToCreateOrUpdate": {
          "fields": [
            {
              "fieldName": "=name",
              "fieldValue": "={{ $('Process Row Batch').item.json.Name }}"
            },
            {
              "fieldName": "barcode",
              "fieldValue": "={{ $('Process Row Batch').item.json.Barcode }}"
            },
            {
              "fieldName": "list_price",
              "fieldValue": "={{ $('Process Row Batch').item.json['Sales Price'] }}"
            },
            {
              "fieldName": "standard_price",
              "fieldValue": "={{ $('Process Row Batch').item.json.Cost }}"
            },
            {
              "fieldName": "categ_id",
              "fieldValue": "={{ $('Get Odoo Category').item.json.id }}"
            },
            {
              "fieldName": "taxes_id",
              "fieldValue": "={{ [] }}"
            },
            {
              "fieldName": "supplier_taxes_id",
              "fieldValue": "={{ [] }}"
            },
            {
              "fieldName": "image_1920",
              "fieldValue": "={{ $json.final_image }}"
            }
          ]
        }
      },
      "credentials": {
        "odooApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "16ca4dff-41d1-42d9-af17-25a7118c2cb5",
      "name": "Create Product (No Image)",
      "type": "n8n-nodes-base.odoo",
      "position": [
        1100,
        340
      ],
      "parameters": {
        "resource": "custom",
        "customResource": "product.template",
        "fieldsToCreateOrUpdate": {
          "fields": [
            {
              "fieldName": "=name",
              "fieldValue": "={{ $('Process Row Batch').item.json.Name }}"
            },
            {
              "fieldName": "barcode",
              "fieldValue": "={{ $('Process Row Batch').item.json.Barcode }}"
            },
            {
              "fieldName": "list_price",
              "fieldValue": "={{ $('Process Row Batch').item.json['Sales Price'] }}"
            },
            {
              "fieldName": "standard_price",
              "fieldValue": "={{ $('Process Row Batch').item.json.Cost }}"
            },
            {
              "fieldName": "categ_id",
              "fieldValue": "={{ $('Get Odoo Category').item.json.id }}"
            },
            {
              "fieldName": "taxes_id",
              "fieldValue": "={{ [] }}"
            },
            {
              "fieldName": "supplier_taxes_id",
              "fieldValue": "={{ [] }}"
            }
          ]
        }
      },
      "credentials": {
        "odooApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "5d552216-2f48-45cf-8431-da5c1f4875cb",
      "name": "Mark as Done in Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "onError": "continueErrorOutput",
      "position": [
        1400,
        240
      ],
      "parameters": {
        "columns": {
          "value": {
            "Cost": "={{ $('On New Row in Sheets').item.json.Cost }}",
            "Name": "={{ $('On New Row in Sheets').item.json.Name }}",
            "Weight": "={{ $('On New Row in Sheets').item.json.Weight }}",
            "Barcode": "={{ $('On New Row in Sheets').item.json.Barcode }}",
            "category": "={{ $('On New Row in Sheets').item.json.category }}",
            "row_number": 0,
            "External ID": "={{ $('On New Row in Sheets').item.json['External ID'] }}",
            "Sales Price": "={{ $('On New Row in Sheets').item.json['Sales Price'] }}",
            "Sales Taxes": "={{ $('On New Row in Sheets').item.json['Sales Taxes'] }}",
            "Product Type": "={{ $('On New Row in Sheets').item.json['Product Type'] }}",
            "\u0627\u0644\u062d\u0627\u0644\u0647": "Done",
            "Purchase Taxes": "={{ $('On New Row in Sheets').item.json['Purchase Taxes'] }}",
            "Internal Reference": "={{ $('On New Row in Sheets').item.json['Internal Reference'] }}"
          },
          "schema": [
            {
              "id": "External ID",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "External ID",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Name",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Product Type",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Product Type",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "category",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "category",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Internal Reference",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Internal Reference",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Barcode",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Barcode",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Sales Price",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Sales Price",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Cost",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Cost",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Sales Taxes",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Sales Taxes",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Purchase Taxes",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Purchase Taxes",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Weight",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "Weight",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "\u0627\u0644\u062d\u0627\u0644\u0647",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "\u0627\u0644\u062d\u0627\u0644\u0647",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "row_number",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": true,
              "required": false,
              "displayName": "row_number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Name"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1xY4o1gv2lvglcIlxNPbM-vuNC6tIysO9rwkse-cnRH8/edit#gid=0",
          "cachedResultName": "DATA"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/1xY4o1gv2lvglcIlxNPbM-vuNC6tIysO9rwkse-cnRH8/edit?gid=0#gid=0"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "82c2a98f-c8c8-426e-8974-3daef7e64c8b",
      "name": "Wait on Error",
      "type": "n8n-nodes-base.wait",
      "position": [
        1400,
        400
      ],
      "parameters": {
        "unit": "minutes"
      },
      "typeVersion": 1.1
    }
  ],
  "connections": {
    "Wait on Error": {
      "main": [
        [
          {
            "node": "Process Row Batch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Image": {
      "main": [
        [
          {
            "node": "Convert Image to Base64",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Image URL?": {
      "main": [
        [
          {
            "node": "Download Image",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create Product (No Image)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Odoo Category": {
      "main": [
        [
          {
            "node": "Has Image URL?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Row Batch": {
      "main": [
        [],
        [
          {
            "node": "Check Odoo for Barcode",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If Product Missing": {
      "main": [
        [
          {
            "node": "Get Odoo Category",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Process Row Batch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "On New Row in Sheets": {
      "main": [
        [
          {
            "node": "Process Row Batch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mark as Done in Sheet": {
      "main": [
        [
          {
            "node": "Process Row Batch",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wait on Error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Odoo for Barcode": {
      "main": [
        [
          {
            "node": "If Product Missing",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert Image to Base64": {
      "main": [
        [
          {
            "node": "Create Product (With Image)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Product (No Image)": {
      "main": [
        [
          {
            "node": "Mark as Done in Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Product (With Image)": {
      "main": [
        [
          {
            "node": "Mark as Done in Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

What Problem Does It Solve? Manually entering hundreds of products from a spreadsheet into Odoo is slow, repetitive, and prone to human error. Ensuring that barcodes are unique and products aren't duplicated during manual entry is a constant challenge. Uploading product images…

Source: https://n8n.io/workflows/14928/ — original creator credit. Request a take-down →

More Data & Sheets workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Data & Sheets

This workflow acts as a junior finance research analyst for a UK boutique M&A or corporate finance team. It listens for Slack messages, classifies the request, gathers company or market data, and prod

HTTP Request, Google Drive, Google Docs +5
Data & Sheets

This workflow syncs Toggl Track time entries to Google Sheets and creates monthly tabs automatically.

HTTP Request, Google Sheets
Data & Sheets

This template demonstrates how to build a low-code, AI-powered data analysis workflow in n8n. It enables you to connect to various data sources (such as MySQL, Google Sheets, or local files), process

MySQL, Google Sheets, Read Write File +2
Data & Sheets

This workflow provides a structured way to extract Meta Ads performance data and store it in Google Sheets for reporting, dashboarding, or further analysis.

Google Sheets, HTTP Request
Data & Sheets

AI Money Tracker Chatbot. Uses telegramTrigger, postgres, googleSheets, telegram. Event-driven trigger; 24 nodes.

Telegram Trigger, Postgres, Google Sheets +2