AutomationFlowsGeneral › Backup n8n Workflows to Gitea Git Repo

Backup n8n Workflows to Gitea Git Repo

Original n8n title: Backup Workflows to Git Repository on Gitea

Backup workflows to git repository on Gitea. Uses n8n, scheduleTrigger, stickyNote, httpRequest. Scheduled trigger; 20 nodes.

Cron / scheduled trigger★★★★☆ complexity20 nodesn8nHTTP Request
General Trigger: Cron / scheduled Nodes: 20 Complexity: ★★★★☆ Added:
Backup n8n Workflows to Gitea Git Repo — n8n workflow card showing n8n, HTTP Request integration

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

This workflow follows the HTTP Request → n8n 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
{
  "id": "Ef2uEM6H19K2DGUO",
  "meta": {
    "templateId": "2532",
    "templateCredsSetupCompleted": true
  },
  "name": "Backup workflows to git repository on Gitea",
  "tags": [
    {
      "id": "UWNX4AzSneYNvTQI",
      "name": "Gitea",
      "createdAt": "2025-01-28T23:10:06.823Z",
      "updatedAt": "2025-01-28T23:10:06.823Z"
    },
    {
      "id": "4b7Bs9T0Cagsg5tT",
      "name": "Git",
      "createdAt": "2025-01-28T23:10:26.545Z",
      "updatedAt": "2025-01-28T23:10:26.545Z"
    },
    {
      "id": "HiN3ehC2KkAp5kVs",
      "name": "Backup",
      "createdAt": "2025-01-28T23:10:38.878Z",
      "updatedAt": "2025-01-28T23:10:38.878Z"
    }
  ],
  "nodes": [
    {
      "id": "639582ef-f13e-4844-bd10-647718079121",
      "name": "Globals",
      "type": "n8n-nodes-base.set",
      "position": [
        600,
        240
      ],
      "parameters": {
        "values": {
          "string": [
            {
              "name": "repo.url",
              "value": "https://git.vdm.dev"
            },
            {
              "name": "repo.name",
              "value": "workflows"
            },
            {
              "name": "repo.owner",
              "value": "n8n"
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "9df89713-220e-43b9-b234-b8f5612629cf",
      "name": "n8n",
      "type": "n8n-nodes-base.n8n",
      "position": [
        840,
        240
      ],
      "parameters": {
        "filters": {},
        "requestOptions": {}
      },
      "credentials": {
        "n8nApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "4b2d375c-a339-404c-babd-555bd2fc4091",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        380,
        240
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 45
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "ea026e96-0db1-41fd-b003-2f2bf4662696",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2620,
        300
      ],
      "parameters": {
        "height": 80,
        "content": "Workflow changes committed to the repository"
      },
      "typeVersion": 1
    },
    {
      "id": "9c402daa-6d03-485d-b8a0-58f1b65d396d",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2260,
        180
      ],
      "parameters": {
        "height": 80,
        "content": "Check if there are any changes in the workflow"
      },
      "typeVersion": 1
    },
    {
      "id": "1d9216d9-bf8d-4945-8a58-22fb1ffc9be8",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1800,
        580
      ],
      "parameters": {
        "height": 80,
        "content": "Create a new file for the workflow"
      },
      "typeVersion": 1
    },
    {
      "id": "60a3953b-d9f1-4afd-b299-e314116b96c6",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1300,
        200
      ],
      "parameters": {
        "height": 80,
        "content": "Check if file exists in the repository"
      },
      "typeVersion": 1
    },
    {
      "id": "f2340ad0-71a1-4c74-8d90-bcb974b8b305",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        780,
        180
      ],
      "parameters": {
        "height": 80,
        "content": "Get all workflows"
      },
      "typeVersion": 1
    },
    {
      "id": "617bea19-341a-4e9d-b6fd-6b417e58d756",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        500,
        180
      ],
      "parameters": {
        "height": 80,
        "content": "Set variables"
      },
      "typeVersion": 1
    },
    {
      "id": "72f806d7-e30a-470b-9ba2-37fdc35de3c8",
      "name": "SetDataUpdateNode",
      "type": "n8n-nodes-base.set",
      "position": [
        1920,
        240
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "0a6b769a-c66d-4784-92c7-a70caa28e1ba",
              "name": "item",
              "type": "object",
              "value": "={{ $node[\"ForEach\"].json }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "bca5e2c4-7aa3-48df-9e5f-b31977970c28",
      "name": "SetDataCreateNode",
      "type": "n8n-nodes-base.set",
      "position": [
        1220,
        640
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "0a6b769a-c66d-4784-92c7-a70caa28e1ba",
              "name": "item",
              "type": "object",
              "value": "={{ $node[\"ForEach\"].json }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "bf74b1ea-e066-462b-9c3d-ed4a44a09a33",
      "name": "Base64EncodeUpdate",
      "type": "n8n-nodes-base.code",
      "position": [
        2140,
        240
      ],
      "parameters": {
        "language": "python",
        "pythonCode": "import json\nimport base64\nfrom js import Object\n\n# Assuming _input.all() returns a JavaScript object\njs_object = _input.all()\n\n# Convert the JsProxy object to a Python dictionary\ndef js_to_py(js_obj):\n    if isinstance(js_obj, (str, int, float, bool)) or js_obj is None:\n        # Base types are already Python-compatible\n        return js_obj\n    elif isinstance(js_obj, list):\n        # Convert lists recursively\n        return [js_to_py(item) for item in js_obj]\n    elif hasattr(js_obj, \"__iter__\") and not isinstance(js_obj, str):\n        # Handle JsProxy objects (JavaScript objects or arrays)\n        if hasattr(js_obj, \"keys\"):\n            # If it has keys, treat it as a dictionary\n            return {key: js_to_py(js_obj[key]) for key in Object.keys(js_obj)}\n        else:\n            # Otherwise, treat it as a list\n            return [js_to_py(item) for item in js_obj]\n    else:\n        # Fallback for other types\n        return js_obj\n\n# Convert the JavaScript object to a Python dictionary\ninput_dict = js_to_py(js_object)\n\n# Step 0: get the correct data set of the workflow\ninner_data = input_dict[0].get('json').get('item')\n\n# Step 1: Convert the dictionary to a pretty-printed JSON string\njson_string = json.dumps(inner_data, indent=4)\n\n# Step 2: Encode the JSON string to bytes\njson_bytes = json_string.encode('utf-8')\n\n# Step 3: Convert the bytes to a base64 string\nbase64_string = base64.b64encode(json_bytes).decode('utf-8')\n\n# Step 5: Create the return object with the base64 string and its SHA-256 hash\nreturn_object = {\n    \"item\": base64_string\n}\n\n# Return the object\nreturn return_object"
      },
      "typeVersion": 2
    },
    {
      "id": "2d817c66-5aa0-45c9-b851-4b5e3dbecca4",
      "name": "Base64EncodeCreate",
      "type": "n8n-nodes-base.code",
      "position": [
        1520,
        640
      ],
      "parameters": {
        "language": "python",
        "pythonCode": "import json\nimport base64\nfrom js import Object\n\n# Assuming _input.all() returns a JavaScript object\njs_object = _input.all()\n\n# Convert the JsProxy object to a Python dictionary\ndef js_to_py(js_obj):\n    if isinstance(js_obj, (str, int, float, bool)) or js_obj is None:\n        # Base types are already Python-compatible\n        return js_obj\n    elif isinstance(js_obj, list):\n        # Convert lists recursively\n        return [js_to_py(item) for item in js_obj]\n    elif hasattr(js_obj, \"__iter__\") and not isinstance(js_obj, str):\n        # Handle JsProxy objects (JavaScript objects or arrays)\n        if hasattr(js_obj, \"keys\"):\n            # If it has keys, treat it as a dictionary\n            return {key: js_to_py(js_obj[key]) for key in Object.keys(js_obj)}\n        else:\n            # Otherwise, treat it as a list\n            return [js_to_py(item) for item in js_obj]\n    else:\n        # Fallback for other types\n        return js_obj\n\n# Convert the JavaScript object to a Python dictionary\ninput_dict = js_to_py(js_object)\n\n# Step 0: get the correct data set of the workflow\ninner_data = input_dict[0].get('json').get('item')\n\n# Step 1: Convert the dictionary to a pretty-printed JSON string\njson_string = json.dumps(inner_data, indent=4)\n\n# Step 2: Encode the JSON string to bytes\njson_bytes = json_string.encode('utf-8')\n\n# Step 3: Convert the bytes to a base64 string\nbase64_string = base64.b64encode(json_bytes).decode('utf-8')\n\n# Step 4: Create the return object with the base64 string in 'item'\nreturn_object = {\n    \"item\": base64_string\n}\n\n# Return the object\nreturn return_object"
      },
      "typeVersion": 2
    },
    {
      "id": "41a7da89-1c8c-4100-8c30-d0788962efc1",
      "name": "Exist",
      "type": "n8n-nodes-base.if",
      "position": [
        1640,
        260
      ],
      "parameters": {
        "options": {
          "ignoreCase": false
        },
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "or",
          "conditions": [
            {
              "id": "16a9182d-059d-4774-ba95-654fb4293fdb",
              "operator": {
                "type": "object",
                "operation": "notExists",
                "singleValue": true
              },
              "leftValue": "={{ $json.error }}",
              "rightValue": 404
            }
          ]
        }
      },
      "executeOnce": false,
      "typeVersion": 2.2,
      "alwaysOutputData": false
    },
    {
      "id": "ab9246eb-a253-4d76-b33b-5f8f12342542",
      "name": "Changed",
      "type": "n8n-nodes-base.if",
      "position": [
        2360,
        240
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "e0c66624-429a-4f1f-bf7b-1cc1b32bad7b",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "leftValue": "={{ $json.item }}",
              "rightValue": "={{ $('GetGitea').item.json.content }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "4278a176-6496-4817-82f8-591539619673",
      "name": "PutGitea",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2700,
        360
      ],
      "parameters": {
        "url": "={{ $('Globals').item.json.repo.url }}/api/v1/repos/{{ $('Globals').item.json.repo.owner }}/{{ $('Globals').item.json.repo.name }}/contents/{{ encodeURIComponent($('GetGitea').item.json.name) }}",
        "method": "PUT",
        "options": {},
        "sendBody": true,
        "authentication": "genericCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "content",
              "value": "={{ $('Base64EncodeUpdate').item.json.item }}"
            },
            {
              "name": "sha",
              "value": "={{ $('GetGitea').item.json.sha }}"
            }
          ]
        },
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "12307a61-e7cc-42f9-a7c7-8abbcab9e3ab",
      "name": "GetGitea",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        1380,
        260
      ],
      "parameters": {
        "url": "={{ $('Globals').item.json.repo.url }}/api/v1/repos/{{ encodeURIComponent($('Globals').item.json.repo.owner) }}/{{ encodeURIComponent($('Globals').item.json.repo.name) }}/contents/{{ encodeURIComponent($json.name) }}.json",
        "options": {},
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "24fda439-bb23-4392-a297-d8070907f9e6",
      "name": "PostGitea",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1920,
        640
      ],
      "parameters": {
        "url": "={{ $('Globals').item.json.repo.url }}/api/v1/repos/{{ $('Globals').item.json.repo.owner }}/{{ $('Globals').item.json.repo.name }}/contents/{{ encodeURIComponent($('ForEach').item.json.name) }}.json",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "authentication": "genericCredentialType",
        "bodyParameters": {
          "parameters": [
            {
              "name": "content",
              "value": "={{ $json.item }}"
            }
          ]
        },
        "genericAuthType": "httpHeaderAuth"
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "43a60315-d381-4ac4-be4c-f6a158651a00",
      "name": "ForEach",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        1060,
        240
      ],
      "parameters": {
        "options": {}
      },
      "executeOnce": false,
      "typeVersion": 3
    },
    {
      "id": "88578dc4-2398-48d0-b0ba-2198b35bb994",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        380,
        440
      ],
      "parameters": {
        "width": 560,
        "height": 1620,
        "content": "### **\ud83d\udccc Setup Guide for Backup Workflows to Git Repository on Gitea**\n\n#### **\ud83d\udd27 1. Configure Global Variables**\nGo to the **Globals** node and update the following:\n- **`repo.url`** \u2192 `https://your-gitea-instance.com` *(Replace with your actual Gitea URL)*\n- **`repo.name`** \u2192 `workflows` *(Repository name where backups will be stored)*\n- **`repo.owner`** \u2192 `octoleo` *(Gitea account that owns the repository)*\n\n\ud83d\udccc **These settings define where workflows will be backed up.**\n\n---\n\n#### **\ud83d\udd11 2. Set Up Gitea Authentication**\n1\ufe0f\u20e3 **In Gitea:**\n- Generate a **Personal Access Token** under **Settings \u2192 Applications \u2192 Generate Token**\n- Ensure the token has **repo read/write permissions**\n\n2\ufe0f\u20e3 **In the Credentials Manager:**\n- Create a new **Gitea Token** credential\n- Set the **Name** as `Authorization`\n- Set the **Value** as:\n```\nBearer YOUR_PERSONAL_ACCESS_TOKEN\n```\n\ud83d\udccc **Ensure there is a space after `Bearer` before the token!**\n\n---\n\n#### **\ud83d\udd17 3. Connect Gitea Credentials to Git Nodes**\n- Open each of these **three Git nodes**:\n- **GetGitea** \u2192 Retrieves existing repository data\n- **PutGitea** \u2192 Updates workflows\n- **PostGitea** \u2192 Adds new workflows\n\n- Assign the **Gitea Token** credential to each node.\n\n\ud83d\udccc **These nodes handle pushing your workflows to Gitea.**\n\n---\n\n#### **\ud83c\udf10 4. Set Up API Credentials for Workflow Retrieval**\n- Locate the API request node that **fetches workflows**.\n- Add your **API authentication credentials** (Token or Basic Auth).\n\n\ud83d\udccc **This ensures the workflow can fetch all available workflows from your system.**\n\n---\n\n#### **\ud83d\udee0\ufe0f 5. Test & Activate the Workflow**\n\u2705 **Run the workflow manually** \u2192 Check that workflows are being backed up correctly.\n\u2705 **Review the Gitea repository** \u2192 Ensure the files are updated.\n\u2705 **Enable the scheduled trigger** \u2192 Automates backups at defined intervals.\n\n\ud83d\udccc **The workflow automatically checks for changes before committing updates!**\n\n---\n\n### **\ud83d\ude80 Done! Your Workflows Are Now Backed Up Securely!**\n\ud83d\udcac Have issues? **Reach out on the forum for help!**"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "84ba3f3f-fbc8-4792-8e28-198f515fef4e",
  "staticData": {
    "node:Schedule Trigger": {
      "recurrenceRules": []
    }
  },
  "connections": {
    "n8n": {
      "main": [
        [
          {
            "node": "ForEach",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Exist": {
      "main": [
        [
          {
            "node": "SetDataUpdateNode",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "SetDataCreateNode",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Changed": {
      "main": [
        [
          {
            "node": "PutGitea",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "ForEach",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "ForEach": {
      "main": [
        [],
        [
          {
            "node": "GetGitea",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Globals": {
      "main": [
        [
          {
            "node": "n8n",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GetGitea": {
      "main": [
        [
          {
            "node": "Exist",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PutGitea": {
      "main": [
        [
          {
            "node": "ForEach",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PostGitea": {
      "main": [
        [
          {
            "node": "ForEach",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Globals",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SetDataCreateNode": {
      "main": [
        [
          {
            "node": "Base64EncodeCreate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SetDataUpdateNode": {
      "main": [
        [
          {
            "node": "Base64EncodeUpdate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Base64EncodeCreate": {
      "main": [
        [
          {
            "node": "PostGitea",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Base64EncodeUpdate": {
      "main": [
        [
          {
            "node": "Changed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "triggerCount": 1
}

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

How this works

Safeguard your n8n workflows from loss or corruption by automatically backing them up to a Gitea-hosted Git repository, ensuring you can always recover or version-control your automations with ease. This is ideal for n8n users managing complex setups who need reliable data protection without manual exports. The key step involves a scheduled cron trigger that fetches all workflows via n8n's API and commits them to Git using HTTP requests, creating a seamless daily or periodic sync.

Use this workflow for routine backups in production environments where workflow integrity is critical, such as in team-based automation projects. Avoid it for one-off exports or if you lack a Gitea instance, opting instead for n8n's built-in export feature. Common variations include adjusting the cron schedule for hourly runs or adding filters to backup only specific workflow tags.

About this workflow

Backup workflows to git repository on Gitea. Uses n8n, scheduleTrigger, stickyNote, httpRequest. Scheduled trigger; 20 nodes.

Source: https://github.com/Zie619/n8n-workflows — original creator credit. Request a take-down →

More General workflows → · Browse all categories →

Related workflows

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

General

Backup n8n Workflows to Bitbucket. Uses scheduleTrigger, n8n, splitInBatches, httpRequest. Scheduled trigger; 9 nodes.

n8n, HTTP Request
General

Create Threads on Bluesky. Uses httpRequest, scheduleTrigger, stickyNote, splitInBatches. Scheduled trigger; 20 nodes.

HTTP Request
General

Datetime Todoist. Uses scheduleTrigger, todoist, dateTime, stickyNote. Scheduled trigger; 19 nodes.

Todoist, Crypto, Item Lists +1
General

Http Schedule. Uses scheduleTrigger, httpRequest, stickyNote, @horka. Scheduled trigger; 18 nodes.

HTTP Request, @Horka
General

💥workflow n8n d'Auto-Post sur les réseaux sociaux - vide. Uses scheduleTrigger, googleSheets, httpRequest, stickyNote. Scheduled trigger; 15 nodes.

Google Sheets, HTTP Request