AutomationFlowsDevOps › Sync Self-hosted Workflow Backups to Github for Version Control

Sync Self-hosted Workflow Backups to Github for Version Control

ByUcartz Online @ucartz on n8n.io

Automatically back up and sync your n8n workflows to GitHub with unlimited version control. This workflow ensures your repository always reflects the latest state of your n8n instance by creating, updating, and deleting workflow files as needed.

Cron / scheduled trigger★★★★☆ complexity20 nodesGitHubn8n
DevOps Trigger: Cron / scheduled Nodes: 20 Complexity: ★★★★☆ Added:

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

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": "31KRPNmyQMZIp6VO",
  "name": "n8n Workflows \u2192 GitHub Backup (Auto-Sync)",
  "tags": [],
  "nodes": [
    {
      "id": "2048b09a-260d-42cd-b5b6-5848f3065788",
      "name": "README",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -592,
        1104
      ],
      "parameters": {
        "color": 7,
        "width": 820,
        "height": 700,
        "content": "# \ud83d\uddc2\ufe0f n8n \u2192 GitHub sync Workflow versioning\n\n## What this does\nAutomatically backs up all your n8n workflows as JSON files to a GitHub repository on a schedule.\n\n## Features\n- \u2705 Creates new files for new workflows\n- \u2705 Updates existing files when workflows change\n- \u2705 Deletes files for workflows that were renamed or removed\n- \u2705 Skips unchanged workflows (size-based diff check)\n- \u2705 Timestamped commit messages\n\n## Setup (one-time)\n1. **GitHub credential** \u2014 create a Personal Access Token with `repo` scope and add it as a GitHub credential in n8n\n2. **n8n API credential** \u2014 generate an n8n API key under *Settings \u2192 API* and add it as an n8n credential\n3. **GitHub repo** \u2014 create an empty repository on GitHub (e.g. `your-username/n8n-backup`)\n4. Update all GitHub nodes: set your **owner** (GitHub username) and **repository** name\n5. Adjust the **Schedule Trigger** to your preferred backup interval (default: every hour)\n\n## Notes\n- Workflow files are named using the workflow name, lowercased and hyphenated (e.g. `my-workflow.json`)\n- The repo at the top of the canvas links to a working example\n\nContact us if you have any suggestions or need support for this workflow: support@ucartz.com"
      },
      "typeVersion": 1
    },
    {
      "id": "9f9ad33e-c8fd-4cce-9fa9-3a50d7e89b29",
      "name": "Sticky Note \u2014 Fetch GitHub Files",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        448,
        1088
      ],
      "parameters": {
        "color": "#FFFFFF",
        "width": 532,
        "height": 268,
        "content": "### \ud83d\udce5 Step 1 \u2014 Fetch current GitHub repo contents\nLists all files already in the GitHub backup repository.\nThis is used later to:\n- Detect which workflows are new vs existing\n- Identify files to delete (renamed/removed workflows)"
      },
      "typeVersion": 1
    },
    {
      "id": "eacbafe7-43f0-42ec-bdcd-9a863cdb30c7",
      "name": "Sticky Note \u2014 Fetch & Filter Workflows",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1008,
        1088
      ],
      "parameters": {
        "color": "#FFFFFF",
        "width": 820,
        "height": 268,
        "content": "### \u2699\ufe0f Step 2 \u2014 Fetch & filter n8n workflows\nRetrieves all workflows from this n8n instance via API.\n\nThe **Filter Changed Files** node compares each workflow's byte size against the file size stored on GitHub. Only workflows that are **new or modified** pass through \u2014 skipping unchanged ones avoids unnecessary GitHub commits."
      },
      "typeVersion": 1
    },
    {
      "id": "85b82ade-8598-45d2-a7e0-ced7fb165527",
      "name": "Sticky Note \u2014 Commit to GitHub",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        288,
        1472
      ],
      "parameters": {
        "color": "#F2F2F2",
        "width": 700,
        "height": 300,
        "content": "### \ud83d\udcbe Step 3 \u2014 Commit files to GitHub\nFor each changed workflow:\n1. Build the target filename and commit timestamp\n2. Check if the file already exists in the repo\n3. **Update** it (edit) if it exists, or **Create** it (upload) if it's new\n\nFiles are named: `workflow-name.json` (lowercase, hyphenated)"
      },
      "typeVersion": 1
    },
    {
      "id": "39d4e194-b7c1-4598-ada4-cf37d8739509",
      "name": "Sticky Note \u2014 Cleanup Deleted Workflows",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1008,
        1472
      ],
      "parameters": {
        "color": "#FFFFFF",
        "width": 824,
        "height": 304,
        "content": "### \ud83d\uddd1\ufe0f Step 4 \u2014 Clean up deleted/renamed workflows\nAfter committing, compare:\n- **GitHub files** (what's in the repo)\n- **n8n workflows** (what currently exists)\n\nAny GitHub file with **no matching n8n workflow** means the workflow was renamed or deleted \u2014 those files are removed from the repo automatically.\n\n\u26a0\ufe0f Safety: if n8n returns 0 workflows (e.g. API error), the deletion step is aborted to prevent wiping the entire repo."
      },
      "typeVersion": 1
    },
    {
      "id": "402bcfba-fb37-41d1-862d-72a2633c7992",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        272,
        1264
      ],
      "parameters": {
        "rule": {
          "interval": [
            {}
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "6f319899-5007-4c08-926c-fb948e2d1600",
      "name": "List Files in GitHub Repo",
      "type": "n8n-nodes-base.github",
      "position": [
        544,
        1264
      ],
      "parameters": {
        "owner": {
          "__rl": true,
          "mode": "name",
          "value": "YOUR_GITHUB_USERNAME"
        },
        "filePath": "=",
        "resource": "file",
        "operation": "list",
        "repository": {
          "__rl": true,
          "mode": "list",
          "value": "n8n_backup",
          "cachedResultUrl": "https://github.com/YOUR_GITHUB_USERNAME/n8n_backup",
          "cachedResultName": "n8n_backup"
        }
      },
      "credentials": {
        "githubApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1,
      "alwaysOutputData": true
    },
    {
      "id": "149ce3df-686a-427e-b6f5-51b540db0d88",
      "name": "Collect GitHub Filenames",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        720,
        1264
      ],
      "parameters": {
        "options": {},
        "fieldsToAggregate": {
          "fieldToAggregate": [
            {
              "fieldToAggregate": "name"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "fb0e9a96-84fc-43f2-b1e4-2b2946dfecb3",
      "name": "Fetch All n8n Workflows",
      "type": "n8n-nodes-base.n8n",
      "position": [
        1104,
        1264
      ],
      "parameters": {
        "filters": {},
        "requestOptions": {}
      },
      "credentials": {
        "n8nApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "0078cdf4-092d-45be-ba2a-b58c67948941",
      "name": "Filter Changed/New Workflows",
      "type": "n8n-nodes-base.code",
      "position": [
        1296,
        1264
      ],
      "parameters": {
        "jsCode": "// Compare each workflow's current size with its GitHub file size.\n// Only workflows that are NEW or have CHANGED content pass through.\n// This avoids creating unnecessary commits for unmodified workflows.\n\nconst githubFileMap = {};\n$('List Files in GitHub Repo').all().forEach(i => {\n  if (i.json.name) {\n    githubFileMap[i.json.name] = i.json.size;\n  }\n});\n\nconst results = $input.all().map(item => {\n  const workflowJson = JSON.stringify(item.json, null, 2);\n  const fileName = item.json.name.replace(/\\s+/g, '-').toLowerCase() + '.json';\n\n  const currentSize = Buffer.byteLength(workflowJson, 'utf8');\n  const githubSize = githubFileMap[fileName];\n\n  const isNew = githubSize === undefined;\n  const changed = isNew || currentSize !== githubSize;\n\n  return {\n    json: {\n      ...item.json,\n      _fileName: fileName,\n      _currentSize: currentSize,\n      _githubSize: githubSize ?? null,\n      _changed: changed,\n      _isNew: isNew\n    },\n    pairedItem: item.pairedItem\n  };\n});\n\n// Only return workflows that need to be committed\nreturn results.filter(r => r.json._changed || r.json._isNew);"
      },
      "typeVersion": 2
    },
    {
      "id": "c0ccceb3-807e-4f95-a55a-21294d1af105",
      "name": "Convert Workflow to JSON File",
      "type": "n8n-nodes-base.convertToFile",
      "position": [
        1472,
        1264
      ],
      "parameters": {
        "mode": "each",
        "options": {
          "format": true
        },
        "operation": "toJson"
      },
      "typeVersion": 1.1
    },
    {
      "id": "c028c372-7599-409f-b32b-c41dfd81d8ca",
      "name": "Encode as Base64",
      "type": "n8n-nodes-base.extractFromFile",
      "position": [
        1664,
        1264
      ],
      "parameters": {
        "options": {},
        "operation": "binaryToPropery"
      },
      "typeVersion": 1
    },
    {
      "id": "f5e69083-0dbf-46a1-877e-5c030e9dd75d",
      "name": "Build Filename & Commit Date",
      "type": "n8n-nodes-base.set",
      "position": [
        304,
        1680
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "fe4a36ef-9f04-40e3-99bd-cc517a49b440",
              "name": "commitDate",
              "type": "string",
              "value": "={{ $now.format('dd-MM-yyyy/H:mm') }}"
            },
            {
              "id": "b0fe1bcc-e79c-4a6b-b8b4-44222c8bf4e8",
              "name": "fileName",
              "type": "string",
              "value": "={{ $('Fetch All n8n Workflows').item.json.name.replace(/\\s+/g, '-').toLowerCase() }}.json"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "930f04fd-1707-475e-881a-a6fc1c1485cb",
      "name": "File Already Exists in Repo?",
      "type": "n8n-nodes-base.if",
      "position": [
        480,
        1680
      ],
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $('Collect GitHub Filenames').item.json.name }}",
              "value2": "={{ $('Fetch All n8n Workflows').item.json.name.replace(/\\s+/g, '-').toLowerCase() }}.json",
              "operation": "contains"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "d70e8cd5-e429-4238-9917-1c80d4f1f20f",
      "name": "Update Existing File",
      "type": "n8n-nodes-base.github",
      "position": [
        880,
        1680
      ],
      "parameters": {
        "owner": {
          "__rl": true,
          "mode": "name",
          "value": "YOUR_GITHUB_USERNAME"
        },
        "filePath": "={{ $('Fetch All n8n Workflows').item.json.name.replace(/\\s+/g, '-').toLowerCase() }}.json",
        "resource": "file",
        "operation": "edit",
        "repository": {
          "__rl": true,
          "mode": "list",
          "value": "n8n_backup",
          "cachedResultUrl": "https://github.com/YOUR_GITHUB_USERNAME/n8n_backup",
          "cachedResultName": "n8n_backup"
        },
        "fileContent": "={{ $('Encode as Base64').item.json.data }}",
        "commitMessage": "=Sync-{{ $('Build Filename & Commit Date').item.json.commitDate }}"
      },
      "credentials": {
        "githubApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "1c456abc-750e-4892-b025-2cfdfdcbe77b",
      "name": "Create New File",
      "type": "n8n-nodes-base.github",
      "position": [
        688,
        1680
      ],
      "parameters": {
        "owner": {
          "__rl": true,
          "mode": "name",
          "value": "YOUR_GITHUB_USERNAME"
        },
        "filePath": "={{ $('Fetch All n8n Workflows').item.json.name.replace(/\\s+/g, '-').toLowerCase() }}.json",
        "resource": "file",
        "repository": {
          "__rl": true,
          "mode": "list",
          "value": "n8n_backup",
          "cachedResultUrl": "https://github.com/YOUR_GITHUB_USERNAME/n8n_backup",
          "cachedResultName": "n8n_backup"
        },
        "fileContent": "={{ $('Encode as Base64').item.json.data }}",
        "commitMessage": "=Sync-{{ $('Build Filename & Commit Date').item.json.commitDate }}"
      },
      "credentials": {
        "githubApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "67e962f0-e8ec-4c13-be19-e063d4cd756d",
      "name": "Find Orphaned Files to Delete",
      "type": "n8n-nodes-base.code",
      "position": [
        1168,
        1680
      ],
      "parameters": {
        "jsCode": "// Find GitHub files that no longer have a matching n8n workflow.\n// These are workflows that were renamed or deleted in n8n.\n// Safety: if n8n returned 0 workflows, abort to avoid mass deletion.\n\nconst githubFiles = $('List Files in GitHub Repo').all()\n  .map(i => i.json.name)\n  .filter(Boolean);\n\nconst n8nWorkflowFiles = $('Fetch All n8n Workflows').all().map(i => {\n  return i.json.name.replace(/\\s+/g, '-').toLowerCase() + '.json';\n});\n\nif (n8nWorkflowFiles.length === 0) {\n  throw new Error('Safety abort: n8n returned 0 workflows. Refusing to delete any GitHub files.');\n}\n\nif (githubFiles.length === 0) {\n  return [{ json: { skipped: true, reason: 'GitHub repo is empty \u2014 nothing to delete.' } }];\n}\n\nconst filesToDelete = githubFiles.filter(f => !n8nWorkflowFiles.includes(f));\n\nif (filesToDelete.length === 0) {\n  return [{ json: { skipped: true, reason: 'All GitHub files match existing workflows. Nothing to delete.' } }];\n}\n\nreturn filesToDelete.map(f => ({ json: { path: f, skipped: false } }));"
      },
      "typeVersion": 2
    },
    {
      "id": "cd2aaf3d-5af3-4732-8a95-7d9844871ef2",
      "name": "Any Files to Delete?",
      "type": "n8n-nodes-base.if",
      "position": [
        1360,
        1680
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.skipped }}"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "b9a2a6fd-2e83-4bc0-b5c4-bd4ee7713319",
      "name": "Delete Orphaned File",
      "type": "n8n-nodes-base.github",
      "position": [
        1632,
        1680
      ],
      "parameters": {
        "owner": {
          "__rl": true,
          "mode": "name",
          "value": "YOUR_GITHUB_USERNAME"
        },
        "filePath": "={{ $json.path }}",
        "resource": "file",
        "operation": "delete",
        "repository": {
          "__rl": true,
          "mode": "list",
          "value": "n8n_backup",
          "cachedResultUrl": "https://github.com/YOUR_GITHUB_USERNAME/n8n_backup",
          "cachedResultName": "n8n_backup"
        },
        "commitMessage": "=chore: remove deleted workflow {{ $json.path }}"
      },
      "credentials": {
        "githubApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "ecdb18a9-8ec1-44b0-a338-f89e37498bc8",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1872,
        1088
      ],
      "parameters": {
        "width": 320,
        "height": 272,
        "content": "## \ud83d\udd27 Configuration Required \u2014 GitHub Username\n\nSearch for all GitHub nodes in this workflow (List Files in GitHub Repo, Create New File, Update Existing File, Delete Orphaned File) and replace the owner field value YOUR_GITHUB_USERNAME with your actual GitHub username. The same username should also be reflected in the repository URL of each node."
      },
      "typeVersion": 1
    }
  ],
  "active": true,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "40daf65b-515b-459e-8374-f9813d6beb0a",
  "connections": {
    "Encode as Base64": {
      "main": [
        [
          {
            "node": "Build Filename & Commit Date",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "List Files in GitHub Repo",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Any Files to Delete?": {
      "main": [
        [
          {
            "node": "Delete Orphaned File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Existing File": {
      "main": [
        [
          {
            "node": "Find Orphaned Files to Delete",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch All n8n Workflows": {
      "main": [
        [
          {
            "node": "Filter Changed/New Workflows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Collect GitHub Filenames": {
      "main": [
        [
          {
            "node": "Fetch All n8n Workflows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "List Files in GitHub Repo": {
      "main": [
        [
          {
            "node": "Collect GitHub Filenames",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Filename & Commit Date": {
      "main": [
        [
          {
            "node": "File Already Exists in Repo?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "File Already Exists in Repo?": {
      "main": [
        [
          {
            "node": "Update Existing File",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create New File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Changed/New Workflows": {
      "main": [
        [
          {
            "node": "Convert Workflow to JSON File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert Workflow to JSON File": {
      "main": [
        [
          {
            "node": "Encode as Base64",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Orphaned Files to Delete": {
      "main": [
        [
          {
            "node": "Any Files to Delete?",
            "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

Automatically back up and sync your n8n workflows to GitHub with unlimited version control. This workflow ensures your repository always reflects the latest state of your n8n instance by creating, updating, and deleting workflow files as needed.

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

More DevOps workflows → · Browse all categories →

Related workflows

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

DevOps

Who is this for? This template is ideal for n8n administrators, automation engineers, and DevOps teams who want to maintain bidirectional synchronization between their n8n workflows and GitHub reposit

n8n, GitHub
DevOps

Backup workflows to git repository. Uses github, n8n, splitInBatches, scheduleTrigger. Scheduled trigger; 17 nodes.

GitHub, n8n
DevOps

Source code, I maintain this worflow here.

GitHub, n8n
DevOps

[bck-fluxos]. Uses github, itemLists, n8n, moveBinaryData. Scheduled trigger; 16 nodes.

GitHub, Item Lists, n8n +1
DevOps

This workflow provides a robust solution for automatically backing up all your n8n workflows to a designated GitHub repository on a daily basis. By leveraging the n8n API and GitHub API, it ensures yo

n8n, GitHub