AutomationFlowsDevOps › Save n8n Workflows to GitHub Repo

Save n8n Workflows to GitHub Repo

Original n8n title: Save Your Workflows Into a Github Repository

Save Your Workflows Into A Github Repository. Uses manualTrigger, httpRequest, github, noOp. Event-driven trigger; 16 nodes.

Event trigger★★★★☆ complexity16 nodesHTTP RequestGitHub
DevOps Trigger: Event Nodes: 16 Complexity: ★★★★☆ Added:

This workflow follows the GitHub → HTTP Request 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": [
    {
      "name": "On clicking 'execute'",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        0,
        150
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "name": "dataArray",
      "type": "n8n-nodes-base.function",
      "position": [
        450,
        300
      ],
      "parameters": {
        "functionCode": "const newItems = [];\nfor (item of items[0].json.data) {\n  newItems.push({json: item});\n}\nreturn newItems;"
      },
      "typeVersion": 1
    },
    {
      "name": "N8N Workflows",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        300,
        300
      ],
      "parameters": {
        "url": "http://localhost:8443/rest/workflows",
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "name": "GitHub",
      "type": "n8n-nodes-base.github",
      "position": [
        800,
        130
      ],
      "parameters": {
        "owner": "={{$node[\"Globals\"].json[\"repo\"][\"owner\"]}}",
        "filePath": "={{$node[\"Globals\"].json[\"repo\"][\"path\"]}}{{$json[\"name\"]}}.json",
        "resource": "file",
        "operation": "get",
        "repository": "={{$node[\"Globals\"].json[\"repo\"][\"name\"]}}",
        "asBinaryProperty": false
      },
      "credentials": {
        "githubApi": "<your credential>"
      },
      "typeVersion": 1,
      "continueOnFail": true,
      "alwaysOutputData": true
    },
    {
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "position": [
        1000,
        300
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "name": "N8N Workflow Detail",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        800,
        460
      ],
      "parameters": {
        "url": "=http://localhost:8443/rest/workflows/{{$json[\"id\"]}}",
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "name": "github_status",
      "type": "n8n-nodes-base.switch",
      "position": [
        1300,
        300
      ],
      "parameters": {
        "rules": {
          "rules": [
            {
              "value2": "same"
            },
            {
              "output": 1,
              "value2": "different"
            },
            {
              "output": 2,
              "value2": "new"
            }
          ]
        },
        "value1": "={{$json[\"github_status\"]}}",
        "dataType": "string"
      },
      "typeVersion": 1
    },
    {
      "name": "same",
      "type": "n8n-nodes-base.noOp",
      "position": [
        1500,
        130
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "name": "different",
      "type": "n8n-nodes-base.noOp",
      "position": [
        1500,
        300
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "name": "new",
      "type": "n8n-nodes-base.noOp",
      "position": [
        1500,
        460
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "name": "GitHub Edit",
      "type": "n8n-nodes-base.github",
      "position": [
        1700,
        180
      ],
      "parameters": {
        "owner": "={{$node[\"Globals\"].json[\"repo\"][\"owner\"]}}",
        "filePath": "={{$node[\"Globals\"].json[\"repo\"][\"path\"]}}{{$node[\"N8N Workflow Detail\"].json[\"data\"][\"name\"]}}.json",
        "resource": "file",
        "operation": "edit",
        "repository": "={{$node[\"Globals\"].json[\"repo\"][\"name\"]}}",
        "fileContent": "={{$node[\"isDiffOrNew\"].json[\"n8n_data_stringy\"]}}",
        "commitMessage": "=[N8N Backup] {{$node[\"N8N Workflow Detail\"].json[\"data\"][\"name\"]}}.json ({{$json[\"github_status\"]}})"
      },
      "credentials": {
        "githubApi": "<your credential>"
      },
      "typeVersion": 1
    },
    {
      "name": "GitHub Create",
      "type": "n8n-nodes-base.github",
      "position": [
        1700,
        460
      ],
      "parameters": {
        "owner": "={{$node[\"Globals\"].json[\"repo\"][\"owner\"]}}",
        "filePath": "={{$node[\"Globals\"].json[\"repo\"][\"path\"]}}{{$node[\"N8N Workflow Detail\"].json[\"data\"][\"name\"]}}.json",
        "resource": "file",
        "repository": "={{$node[\"Globals\"].json[\"repo\"][\"name\"]}}",
        "fileContent": "={{$node[\"isDiffOrNew\"].json[\"n8n_data_stringy\"]}}",
        "commitMessage": "=[N8N Backup] {{$node[\"N8N Workflow Detail\"].json[\"data\"][\"name\"]}}.json ({{$json[\"github_status\"]}})"
      },
      "credentials": {
        "githubApi": "<your credential>"
      },
      "typeVersion": 1
    },
    {
      "name": "isDiffOrNew",
      "type": "n8n-nodes-base.function",
      "position": [
        1150,
        300
      ],
      "parameters": {
        "functionCode": "// File Returned with Content\nif (Object.keys(items[0].json).includes(\"content\")) {\n  // Get JSON Objects\n  var origWorkflow = eval(\"(\"+Buffer.from(items[0].json.content, 'base64').toString()+\")\");\n  var n8nWorkflow = (items[1].json.data);\n  \n  // Order JSON Objects\n  var orderedOriginal = {}\n  var orderedActual = {}\n  \n  Object.keys(origWorkflow).sort().forEach(function(key) {\n    orderedOriginal[key] = origWorkflow[key];\n  });\n  \n  Object.keys(n8nWorkflow).sort().forEach(function(key) {\n    orderedActual[key] = n8nWorkflow[key];\n  });\n  \n  // Determine Difference\n  if ( JSON.stringify(orderedOriginal) === JSON.stringify(orderedActual) ) {\n    items[0].json.github_status = \"same\";\n    items[0].json.content_decoded = orderedOriginal;\n  } else {\n    items[0].json.github_status = \"different\";\n    items[0].json.content_decoded = orderedOriginal;\n    items[0].json.n8n_data_stringy = JSON.stringify(orderedActual, null, 2);\n  }\n// No File Returned / New Workflow\n} else {\n  // Order JSON Object\n  var n8nWorkflow = (items[1].json.data);\n  var orderedActual = {}\n  Object.keys(n8nWorkflow).sort().forEach(function(key) {\n    orderedActual[key] = n8nWorkflow[key];\n  });\n  \n  // Proper Formatting\n  items[0].json.github_status = \"new\";\n  items[0].json.n8n_data_stringy = JSON.stringify(orderedActual, null, 2);\n}\n\n// Return Items\nreturn items;"
      },
      "typeVersion": 1
    },
    {
      "name": "Daily @ 20:00",
      "type": "n8n-nodes-base.cron",
      "position": [
        0,
        450
      ],
      "parameters": {
        "triggerTimes": {
          "item": [
            {
              "hour": 20,
              "minute": 11
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "name": "OneAtATime",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        600,
        300
      ],
      "parameters": {
        "options": {},
        "batchSize": 1
      },
      "typeVersion": 1
    },
    {
      "name": "Globals",
      "type": "n8n-nodes-base.set",
      "position": [
        150,
        300
      ],
      "parameters": {
        "values": {
          "string": [
            {
              "name": "repo.owner",
              "value": "octocat"
            },
            {
              "name": "repo.name",
              "value": "Hello-World"
            },
            {
              "name": "repo.path",
              "value": "my-team/n8n/workflows/"
            }
          ]
        },
        "options": {}
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "new": {
      "main": [
        [
          {
            "node": "GitHub Create",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "same": {
      "main": [
        [
          {
            "node": "OneAtATime",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "isDiffOrNew",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GitHub": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Globals": {
      "main": [
        [
          {
            "node": "N8N Workflows",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "dataArray": {
      "main": [
        [
          {
            "node": "OneAtATime",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "different": {
      "main": [
        [
          {
            "node": "GitHub Edit",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OneAtATime": {
      "main": [
        [
          {
            "node": "GitHub",
            "type": "main",
            "index": 0
          },
          {
            "node": "N8N Workflow Detail",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GitHub Edit": {
      "main": [
        [
          {
            "node": "OneAtATime",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "isDiffOrNew": {
      "main": [
        [
          {
            "node": "github_status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Daily @ 20:00": {
      "main": [
        [
          {
            "node": "Globals",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GitHub Create": {
      "main": [
        [
          {
            "node": "OneAtATime",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "N8N Workflows": {
      "main": [
        [
          {
            "node": "dataArray",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "github_status": {
      "main": [
        [
          {
            "node": "same",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "different",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "new",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "N8N Workflow Detail": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "On clicking 'execute'": {
      "main": [
        [
          {
            "node": "Globals",
            "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

How this works

Effortlessly back up your n8n workflows to a GitHub repository, ensuring version control and easy recovery without manual exports. This suits automation enthusiasts and teams managing multiple workflows who need a reliable way to store configurations securely. The key step involves fetching workflow details via HTTP requests from n8n and committing them directly to GitHub, streamlining what could otherwise be a tedious process.

Use this workflow for periodic or event-driven backups when scaling your automations, especially if you're collaborating on GitHub. Avoid it for one-off exports or when workflows contain sensitive credentials that shouldn't be versioned. Common variations include adding filters to exclude certain workflows or integrating notifications for successful commits.

About this workflow

Save Your Workflows Into A Github Repository. Uses manualTrigger, httpRequest, github, noOp. Event-driven trigger; 16 nodes.

Source: https://github.com/Zie619/n8n-workflows — 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

Code Github. Uses manualTrigger, stickyNote, n8n, httpRequest. Event-driven trigger; 25 nodes.

n8n, HTTP Request, GitHub +1
DevOps

Code Github. Uses manualTrigger, stickyNote, httpRequest, noOp. Event-driven trigger; 24 nodes.

HTTP Request, GitHub, Execute Command +1
DevOps

Code Github. Uses manualTrigger, stickyNote, n8n, httpRequest. Event-driven trigger; 23 nodes.

n8n, HTTP Request, GitHub +1
DevOps

Backup Squarespace code Injections to Github. Uses manualTrigger, splitInBatches, scheduleTrigger, stickyNote. Event-driven trigger; 17 nodes.

HTTP Request, GitHub
DevOps

This template lets you selectively import n8n workflows from a GitHub repository, even when your repository uses deeply nested folder structures.

Form Trigger, GitHub, n8n +1