This workflow corresponds to n8n.io template #12873 — we link there as the canonical source.
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 →
{
"id": "16SvkzB4jgrePTnC",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Environment Config Diff & Propagate for Android Builds",
"tags": [],
"nodes": [
{
"id": "71767832-90ad-4269-97ab-c23f32e2c317",
"name": "Combine Env & Gradle",
"type": "n8n-nodes-base.merge",
"position": [
-736,
-2112
],
"parameters": {},
"typeVersion": 3.2
},
{
"id": "69c3ae90-b09c-4cc1-affd-d09078712e5a",
"name": "Decode gradle.properties",
"type": "n8n-nodes-base.code",
"position": [
800,
-1920
],
"parameters": {
"jsCode": "const base64 = $json.content;\nconst decoded = Buffer.from(base64, 'base64').toString('utf8');\n\n// convert KEY=VALUE to object\nconst lines = decoded.split('\\n');\nconst env = {};\n\nfor (const line of lines) {\n if (!line || line.startsWith('#')) continue;\n const [key, ...rest] = line.split('=');\n env[key] = rest.join('=');\n}\n\nreturn [{\n raw: decoded,\n parsed: env\n}];\n"
},
"typeVersion": 2
},
{
"id": "6e18e5ea-bcab-4590-a177-6340b7d091a5",
"name": "Receive GitHub Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
-1888,
-2112
],
"parameters": {
"path": "env-config-diff",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 2.1
},
{
"id": "d939f1c6-ea57-4267-83df-f9169f618287",
"name": "Check if .env.staging Changed",
"type": "n8n-nodes-base.code",
"position": [
-1664,
-2112
],
"parameters": {
"jsCode": "// Works for Postman AND real GitHub webhooks\nconst payload = $input.first().json.body ?? $input.first().json;\n\nconst commits = payload.commits || [];\nconst changedFiles = [];\n\nfor (const commit of commits) {\n if (commit.added) changedFiles.push(...commit.added);\n if (commit.modified) changedFiles.push(...commit.modified);\n if (commit.removed) changedFiles.push(...commit.removed);\n}\n\nconst envFileChanged = changedFiles.includes('.env.staging');\n\nreturn [{\n json: {\n repository: payload.repository?.full_name,\n branch: payload.ref,\n envFileChanged,\n changedFiles\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "9c6cdf35-2024-43b5-b90f-73161dc05a59",
"name": "Proceed Only if ENV Changed",
"type": "n8n-nodes-base.if",
"position": [
-1440,
-2112
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "007202b7-7abf-4b2d-b940-c6382dc25f6d",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.envFileChanged }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.2
},
{
"id": "e3c6602c-e533-4cd4-841a-0c233433a8ed",
"name": "Fetch .env.staging from Repo",
"type": "n8n-nodes-base.github",
"position": [
-1216,
-2208
],
"parameters": {
"owner": {
"__rl": true,
"mode": "list",
"value": "tanvidg2025",
"cachedResultUrl": "https://github.com/tanvidg2025",
"cachedResultName": "tanvidg2025"
},
"filePath": ".env.staging",
"resource": "file",
"operation": "get",
"repository": {
"__rl": true,
"mode": "name",
"value": "=android-config-sync-n8nDemo"
},
"asBinaryProperty": false,
"additionalParameters": {
"reference": "main"
}
},
"credentials": {
"githubApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.1
},
{
"id": "8668805d-adb4-4609-894a-429306cccc5b",
"name": "Parse .env.staging into Key-Value",
"type": "n8n-nodes-base.code",
"position": [
-992,
-2208
],
"parameters": {
"jsCode": "// 1. Get Base64 content from GitHub node\nconst base64Content = $input.first().json.content;\n\n// 2. Decode Base64 to text\nconst decodedText = Buffer.from(base64Content, 'base64').toString('utf-8');\n\n// 3. Split lines and parse KEY=VALUE\nconst envLines = decodedText.split('\\n');\n\nconst envObject = {};\n\nfor (const line of envLines) {\n const trimmed = line.trim();\n\n // Ignore empty lines and comments\n if (!trimmed || trimmed.startsWith('#')) continue;\n\n const [key, ...valueParts] = trimmed.split('=');\n envObject[key] = valueParts.join('=');\n}\n\nreturn [{\n json: {\n rawEnv: decodedText,\n envVars: envObject\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "cafeceb7-63c6-4257-8cca-7596ac14e352",
"name": "Fetch gradle.properties from Repo",
"type": "n8n-nodes-base.github",
"position": [
-1216,
-2016
],
"parameters": {
"owner": {
"__rl": true,
"mode": "list",
"value": "tanvidg2025",
"cachedResultUrl": "https://github.com/tanvidg2025",
"cachedResultName": "tanvidg2025"
},
"filePath": "gradle.properties",
"resource": "file",
"operation": "get",
"repository": {
"__rl": true,
"mode": "list",
"value": "android-config-sync-n8nDemo",
"cachedResultUrl": "https://github.com/tanvidg2025/android-config-sync-n8nDemo",
"cachedResultName": "android-config-sync-n8nDemo"
},
"asBinaryProperty": false,
"additionalParameters": {
"reference": "main"
}
},
"credentials": {
"githubApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.1
},
{
"id": "b8df0df1-3cad-462b-ba86-c0db95a61ddf",
"name": "Parse gradle.properties into Key-Value",
"type": "n8n-nodes-base.code",
"position": [
-992,
-2016
],
"parameters": {
"jsCode": "// 1. Get Base64 content\nconst base64Content = $input.first().json.content;\n\n// 2. Decode Base64\nconst decodedText = Buffer.from(base64Content, 'base64').toString('utf-8');\n\n// 3. Split lines\nconst lines = decodedText.split('\\n');\n\nconst gradleVars = {};\n\nfor (const line of lines) {\n const trimmed = line.trim();\n\n // Ignore comments and empty lines\n if (!trimmed || trimmed.startsWith('#')) continue;\n\n const [key, ...valueParts] = trimmed.split('=');\n gradleVars[key] = valueParts.join('=');\n}\n\nreturn [{\n json: {\n rawGradle: decodedText,\n gradleVars\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "25f75f8a-68cd-4c77-85cf-1bbcf90e2e9d",
"name": "Compare ENV and Gradle Variables",
"type": "n8n-nodes-base.code",
"position": [
-528,
-2112
],
"parameters": {
"jsCode": "const inputArray = $input.all().map(item => item.json);\n\nconst envVars = inputArray[0]?.envVars || {};\nconst gradleVars = inputArray[1]?.gradleVars || {};\n\nconst changes = [];\nconst needsCacheInvalidation = [];\n\nfor (const key of Object.keys(envVars)) {\n if (!gradleVars.hasOwnProperty(key)) {\n changes.push({\n key,\n envValue: envVars[key],\n gradleValue: null,\n reason: 'missing_in_gradle'\n });\n } else if (envVars[key] !== gradleVars[key]) {\n changes.push({\n key,\n envValue: envVars[key],\n gradleValue: gradleVars[key],\n reason: 'value_changed'\n });\n }\n\n if (['API_KEY', 'BUILD_VERSION', 'ENVIRONMENT'].includes(key)) {\n needsCacheInvalidation.push(key);\n }\n}\n\nreturn [{\n json: {\n changes,\n hasChanges: changes.length > 0,\n needsCacheInvalidation,\n envVars,\n gradleVars\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "c6c4c5f5-e009-4dc6-a715-f0248939cbb9",
"name": "Generate New Branch Name",
"type": "n8n-nodes-base.code",
"position": [
-320,
-2112
],
"parameters": {
"jsCode": "const data = $input.first().json;\nconst timestamp = new Date().toISOString().replace(/[:.]/g, '-');\nconst branchName = `config-sync/${timestamp}`;\n\nreturn [{\n json: {\n ...data,\n branchName\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "05481330-eb64-4fd4-a7bd-b3582c1e5477",
"name": "Get Latest SHA of main Branch",
"type": "n8n-nodes-base.httpRequest",
"position": [
-112,
-2112
],
"parameters": {
"url": "{{Your_Github_URL_Here}}",
"options": {},
"sendHeaders": true,
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearar ghp_YOUR_GITHUB_TOKEN_HERE"
},
{
"name": "Accept",
"value": "application/vnd.github+json"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"nodeCredentialType": "githubApi"
},
"credentials": {
"githubApi": {
"name": "<your credential>"
}
},
"typeVersion": 4.3
},
{
"id": "1abcdfca-a4c4-4c17-a9bd-cacd7924daff",
"name": "Prepare SHA for Branch Creation",
"type": "n8n-nodes-base.set",
"position": [
96,
-2112
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "8629de6b-d00b-4cea-b5aa-4b41145c5e17",
"name": "=commitSHA",
"type": "string",
"value": "={{$json[\"object\"][\"sha\"]}}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "00d3a128-9f51-45a1-a4b0-d4d517cc2fdd",
"name": "Create New Git Branch",
"type": "n8n-nodes-base.httpRequest",
"position": [
304,
-2112
],
"parameters": {
"url": "{{Your_Github_URL_Here}}",
"method": "POST",
"options": {},
"jsonBody": "={\n \"ref\": \"refs/heads/{{ $('Generate New Branch Name').item.json.branchName }}\",\n \"sha\": \"{{$node[\"Prepare SHA for Branch Creation\"].json[\"commitSHA\"]}}\"\n}\n",
"sendBody": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "githubApi"
},
"credentials": {
"githubApi": {
"name": "<your credential>"
}
},
"typeVersion": 4.3
},
{
"id": "0e1bd81a-eadf-4199-9453-16c319ca108d",
"name": "Fetch .env.staging from New Branch",
"type": "n8n-nodes-base.github",
"position": [
576,
-2304
],
"parameters": {
"owner": {
"__rl": true,
"mode": "list",
"value": "tanvidg2025",
"cachedResultUrl": "https://github.com/tanvidg2025",
"cachedResultName": "tanvidg2025"
},
"filePath": ".env.staging",
"resource": "file",
"operation": "get",
"repository": {
"__rl": true,
"mode": "list",
"value": "android-config-sync-n8nDemo",
"cachedResultUrl": "https://github.com/tanvidg2025/android-config-sync-n8nDemo",
"cachedResultName": "android-config-sync-n8nDemo"
},
"asBinaryProperty": false,
"additionalParameters": {
"reference": "={{ $json.ref }}"
}
},
"credentials": {
"githubApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.1
},
{
"id": "6462df63-65a2-499f-8210-1c313b36a157",
"name": "Decode .env.staging",
"type": "n8n-nodes-base.code",
"position": [
800,
-2304
],
"parameters": {
"jsCode": "const base64 = $json.content;\nconst decoded = Buffer.from(base64, 'base64').toString('utf8');\n\n// convert KEY=VALUE to object\nconst lines = decoded.split('\\n');\nconst env = {};\n\nfor (const line of lines) {\n if (!line || line.startsWith('#')) continue;\n const [key, ...rest] = line.split('=');\n env[key] = rest.join('=');\n}\n\nreturn [{\n raw: decoded,\n parsed: env\n}];\n"
},
"typeVersion": 2
},
{
"id": "20a21eac-5e23-4bd1-bee3-a5f306959702",
"name": "Fetch build.gradle File",
"type": "n8n-nodes-base.github",
"position": [
576,
-2112
],
"parameters": {
"owner": {
"__rl": true,
"mode": "list",
"value": "tanvidg2025",
"cachedResultUrl": "https://github.com/tanvidg2025",
"cachedResultName": "tanvidg2025"
},
"filePath": "app/build.gradle",
"resource": "file",
"operation": "get",
"repository": {
"__rl": true,
"mode": "list",
"value": "android-config-sync-n8nDemo",
"cachedResultUrl": "https://github.com/tanvidg2025/android-config-sync-n8nDemo",
"cachedResultName": "android-config-sync-n8nDemo"
},
"asBinaryProperty": false,
"additionalParameters": {}
},
"credentials": {
"githubApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.1
},
{
"id": "f5ab4891-cb9a-461d-bcfc-3dd36953d02c",
"name": "Fetch gradle.properties File",
"type": "n8n-nodes-base.github",
"position": [
576,
-1920
],
"parameters": {
"owner": {
"__rl": true,
"mode": "list",
"value": "tanvidg2025",
"cachedResultUrl": "https://github.com/tanvidg2025",
"cachedResultName": "tanvidg2025"
},
"filePath": "gradle.properties",
"resource": "file",
"operation": "get",
"repository": {
"__rl": true,
"mode": "list",
"value": "android-config-sync-n8nDemo",
"cachedResultUrl": "https://github.com/tanvidg2025/android-config-sync-n8nDemo",
"cachedResultName": "android-config-sync-n8nDemo"
},
"asBinaryProperty": false,
"additionalParameters": {}
},
"credentials": {
"githubApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.1
},
{
"id": "f8d6ef7d-04ec-4a80-99fd-a273047d7574",
"name": "Decode build.gradle",
"type": "n8n-nodes-base.code",
"position": [
800,
-2112
],
"parameters": {
"jsCode": "const base64 = $json.content;\nconst decoded = Buffer.from(base64, 'base64').toString('utf8');\n\n// convert KEY=VALUE to object\nconst lines = decoded.split('\\n');\nconst env = {};\n\nfor (const line of lines) {\n if (!line || line.startsWith('#')) continue;\n const [key, ...rest] = line.split('=');\n env[key] = rest.join('=');\n}\n\nreturn [{\n raw: decoded,\n parsed: env\n}];\n"
},
"typeVersion": 2
},
{
"id": "a23eccd8-e2c2-4fb2-b7ec-6a8e444044a3",
"name": "Combine Decoded Files for Diff",
"type": "n8n-nodes-base.merge",
"position": [
1008,
-2128
],
"parameters": {
"numberInputs": 3
},
"typeVersion": 3.2
},
{
"id": "6e10be61-22a7-422c-99f6-c93d03f07225",
"name": "Identify Config Changes",
"type": "n8n-nodes-base.code",
"position": [
1216,
-2112
],
"parameters": {
"jsCode": "const items = $input.all();\n\nif (items.length !== 3) {\n throw new Error(`Expected 3 inputs, got ${items.length}`);\n}\n\n// Env file (source of truth)\nconst env = items[0].json.parsed;\n\n// build.gradle text (already decoded)\nconst buildGradleText = items[1].json.raw;\n\n// gradle.properties text (already decoded)\nconst gradlePropsText = items[2].json.raw;\n\nconst updates = {\n buildGradle: {},\n gradleProperties: {}\n};\n\nfor (const [key, value] of Object.entries(env)) {\n\n // build.gradle \u2192 buildConfigField\n if (buildGradleText.includes(`\"${key}\"`)) {\n updates.buildGradle[key] = value;\n }\n\n // gradle.properties \u2192 KEY=value\n if (gradlePropsText.includes(`${key}=`)) {\n updates.gradleProperties[key] = value;\n }\n}\n\nreturn [\n {\n json: {\n updates,\n hasChanges:\n Object.keys(updates.buildGradle).length > 0 ||\n Object.keys(updates.gradleProperties).length > 0\n }\n }\n];\n"
},
"typeVersion": 2
},
{
"id": "b1850639-ae21-44ad-ad94-67ca0ea20d7c",
"name": "Apply ENV Changes to build.gradle",
"type": "n8n-nodes-base.code",
"position": [
1472,
-2208
],
"parameters": {
"jsCode": "const updates = $items(\"Identify Config Changes\")[0].json.updates.buildGradle;\nlet raw = $items(\"Decode build.gradle\")[0].json.raw;\n\nfor (const [key, value] of Object.entries(updates)) {\n const regex = new RegExp(\n `(buildConfigField\\\\s+\"String\",\\\\s+\"${key}\",\\\\s+\"\\\\\\\\\")(.*?)(\")`,\n \"g\"\n );\n raw = raw.replace(regex, `$1${value}$3`);\n}\n\nreturn [{\n json: {\n path: \"app/build.gradle\",\n content: Buffer.from(raw).toString(\"base64\"),\n message: \"Sync env vars to build.gradle\"\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "8e0710a8-f595-4904-88d2-5edbf5893087",
"name": "Apply ENV Changes to gradle.properties",
"type": "n8n-nodes-base.code",
"position": [
1472,
-2016
],
"parameters": {
"jsCode": "const updates = $items(\"Identify Config Changes\")[0].json.updates.gradleProperties;\nlet raw = $items(\"Decode gradle.properties\")[0].json.raw;\n\nfor (const [key, value] of Object.entries(updates)) {\n const regex = new RegExp(`^${key}=.*$`, \"gm\");\n if (raw.match(regex)) {\n raw = raw.replace(regex, `${key}=${value}`);\n } else {\n raw += `\\n${key}=${value}`;\n }\n}\n\nreturn [{\n json: {\n path: \"gradle.properties\",\n content: Buffer.from(raw).toString(\"base64\"),\n message: \"Sync env vars to gradle.properties\"\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "b4daf1e6-d646-4e5e-a941-e2680216638f",
"name": "Commit build.gradle Updates to Branch",
"type": "n8n-nodes-base.github",
"position": [
1696,
-2208
],
"parameters": {
"owner": {
"__rl": true,
"mode": "list",
"value": "tanvidg2025",
"cachedResultUrl": "https://github.com/tanvidg2025",
"cachedResultName": "tanvidg2025"
},
"filePath": "={{ $json.path }}",
"resource": "file",
"operation": "edit",
"repository": {
"__rl": true,
"mode": "list",
"value": "android-config-sync-n8nDemo",
"cachedResultUrl": "https://github.com/tanvidg2025/android-config-sync-n8nDemo",
"cachedResultName": "android-config-sync-n8nDemo"
},
"fileContent": "={{ $json.content }}",
"commitMessage": "={{ $json.message }}",
"additionalParameters": {
"branch": {
"branch": "={{ $('Generate New Branch Name').first().json.branchName }}"
}
}
},
"credentials": {
"githubApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.1
},
{
"id": "fbc4f688-af39-4d8e-816d-407b28a52913",
"name": "Commit gradle.properties Updates to Branch",
"type": "n8n-nodes-base.github",
"position": [
1696,
-2016
],
"parameters": {
"owner": {
"__rl": true,
"mode": "list",
"value": "tanvidg2025",
"cachedResultUrl": "https://github.com/tanvidg2025",
"cachedResultName": "tanvidg2025"
},
"filePath": "={{ $json.path }}",
"resource": "file",
"operation": "edit",
"repository": {
"__rl": true,
"mode": "list",
"value": "android-config-sync-n8nDemo",
"cachedResultUrl": "https://github.com/tanvidg2025/android-config-sync-n8nDemo",
"cachedResultName": "android-config-sync-n8nDemo"
},
"fileContent": "={{ $json.content }}",
"commitMessage": "={{ $json.message }}",
"additionalParameters": {
"branch": {
"branch": "={{ $('Generate New Branch Name').first().json.branchName }}"
}
}
},
"credentials": {
"githubApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.1
},
{
"id": "a2e326b5-1f1b-41c3-a4d7-c2bfda307158",
"name": "Prepare Pull Request Data",
"type": "n8n-nodes-base.set",
"position": [
1952,
-2112
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "14d22051-7cf7-4cc7-9c80-4a3a95e3ba3a",
"name": "branchName",
"type": "string",
"value": "={{ $('Generate New Branch Name').first().json.branchName }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "a60cba8b-7fe9-451c-aa5d-b5798093d24d",
"name": "Create Pull Request",
"type": "n8n-nodes-base.httpRequest",
"position": [
2160,
-2112
],
"parameters": {
"url": "{{Your_Github_URL_Here}}",
"method": "POST",
"options": {},
"jsonBody": "={\n \"title\": \"Sync Android config files\",\n \"body\": \"Auto-generated PR from n8n\",\n \"head\": \"{{ $json.branchName }}\",\n \"base\": \"main\"\n}\n",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/vnd.github+json"
}
]
},
"nodeCredentialType": "githubApi"
},
"credentials": {
"githubApi": {
"name": "<your credential>"
}
},
"typeVersion": 4.3
},
{
"id": "ef65909b-64bd-4cde-ad28-f73d2d6fbdf6",
"name": "Notify Team on Slack",
"type": "n8n-nodes-base.slack",
"position": [
2368,
-2112
],
"parameters": {
"text": ":rocket: A new pull request has been created!\n*Title:* Sync Android config files\n*Branch:* {{ $json.branchName }}\n*PR Link:* {{ $json.pullRequest.html_url }}\n",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "list",
"value": "C09S57E2JQ2",
"cachedResultName": "n8n"
},
"otherOptions": {}
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.3
},
{
"id": "8b662d1a-5f2e-4b8d-b939-bda32ad98a1c",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1968,
-2208
],
"parameters": {
"color": 7,
"height": 256,
"content": "Listens for changes in the repository, like when someone updates files. This starts the workflow automatically."
},
"typeVersion": 1
},
{
"id": "62225cd0-4919-429a-b9b3-8240d9ddbfbd",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1712,
-2272
],
"parameters": {
"color": 7,
"width": 416,
"height": 336,
"content": "## Check if .env.staging Changed + Proceed Only if ENV Changed \nChecks whether the .env.staging file was modified in the recent commit. The workflow continues only if this file has changed."
},
"typeVersion": 1
},
{
"id": "3b93d060-e5dd-4bbf-ac31-f3ca1bc5589e",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1280,
-2336
],
"parameters": {
"color": 7,
"width": 496,
"height": 496,
"content": "## Fetch & Parse Files \nFetches the .env.staging and gradle.properties files from the repository and converts them into a simple key-value format so we can easily compare and use the values."
},
"typeVersion": 1
},
{
"id": "27576e57-e18e-4293-8116-abaf3bda3949",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-768,
-2240
],
"parameters": {
"color": 7,
"width": 1216,
"height": 352,
"content": "## Combine & Prepare Branch\nCompares the values in .env.staging and gradle.properties to see what needs updating. Then, creates a new branch from the main branch to apply these updates safely."
},
"typeVersion": 1
},
{
"id": "af5d8308-0f67-4799-a08f-24be27d0f3bd",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
464,
-2464
],
"parameters": {
"color": 7,
"width": 912,
"height": 720,
"content": "## Fetch & Decode Files for Diff\nFetches the latest versions of config files from the new branch, decodes them into readable text and prepares a comparison to see exactly which values need to be updated."
},
"typeVersion": 1
},
{
"id": "6ac689dd-91c3-48f3-99c7-0410b0ff1552",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1392,
-2336
],
"parameters": {
"color": 7,
"width": 480,
"height": 496,
"content": "## Update Files\n\nUpdates the build.gradle and gradle.properties files with the new values from .env.staging and commits these changes to the new branch.\n"
},
"typeVersion": 1
},
{
"id": "c6c32328-88fd-4cfa-ab81-31e18a7434dc",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
1904,
-2256
],
"parameters": {
"color": 7,
"width": 656,
"height": 352,
"content": "## Create Pull Request & Notify\n\nPrepares and creates a pull request for the updated files and sends a message to the team on Slack to let them know a new PR is ready for review.\n"
},
"typeVersion": 1
},
{
"id": "8d8186d8-9195-41f9-9719-b00e6c8fafdc",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2752,
-3072
],
"parameters": {
"width": 416,
"height": 672,
"content": "## How it works\n\nThis workflow starts automatically when a change is pushed to GitHub. It first checks whether the .env.staging file was modified and if not, the workflow stops immediately. When .env.staging has changed, the workflow reads the environment values and compares them with the existing values in build.gradle and gradle.properties. If differences are found, it creates a new branch from the main branch, applies only the required updates to the Android configuration files and commits those changes to the new branch. After committing, it creates a pull request so the changes can be reviewed safely before merging. Finally, a Slack notification is sent to inform the team that a configuration update pull request is ready.\n\n\n## Setup steps\n\n**1.** Import this workflow into n8n.\n\n**2.** Connect your GitHub credentials.\n\n**3.** Connect your Slack credentials and select a channel.\n\n**4.** Make sure .env.staging, build.gradle and gradle.properties exist in the repo.\n\n**5.** Activate the workflow.\n\n**6.** Push a change to .env.staging to test."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "c09ae9e6-8fe1-4fa1-9da4-c0608f1a0852",
"connections": {
"Create Pull Request": {
"main": [
[
{
"node": "Notify Team on Slack",
"type": "main",
"index": 0
}
]
]
},
"Decode .env.staging": {
"main": [
[
{
"node": "Combine Decoded Files for Diff",
"type": "main",
"index": 0
}
]
]
},
"Decode build.gradle": {
"main": [
[
{
"node": "Combine Decoded Files for Diff",
"type": "main",
"index": 1
}
]
]
},
"Combine Env & Gradle": {
"main": [
[
{
"node": "Compare ENV and Gradle Variables",
"type": "main",
"index": 0
}
]
]
},
"Create New Git Branch": {
"main": [
[
{
"node": "Fetch .env.staging from New Branch",
"type": "main",
"index": 0
},
{
"node": "Fetch build.gradle File",
"type": "main",
"index": 0
},
{
"node": "Fetch gradle.properties File",
"type": "main",
"index": 0
}
]
]
},
"Receive GitHub Webhook": {
"main": [
[
{
"node": "Check if .env.staging Changed",
"type": "main",
"index": 0
}
]
]
},
"Fetch build.gradle File": {
"main": [
[
{
"node": "Decode build.gradle",
"type": "main",
"index": 0
}
]
]
},
"Identify Config Changes": {
"main": [
[
{
"node": "Apply ENV Changes to build.gradle",
"type": "main",
"index": 0
},
{
"node": "Apply ENV Changes to gradle.properties",
"type": "main",
"index": 0
}
]
]
},
"Decode gradle.properties": {
"main": [
[
{
"node": "Combine Decoded Files for Diff",
"type": "main",
"index": 2
}
]
]
},
"Generate New Branch Name": {
"main": [
[
{
"node": "Get Latest SHA of main Branch",
"type": "main",
"index": 0
}
]
]
},
"Prepare Pull Request Data": {
"main": [
[
{
"node": "Create Pull Request",
"type": "main",
"index": 0
}
]
]
},
"Proceed Only if ENV Changed": {
"main": [
[
{
"node": "Fetch .env.staging from Repo",
"type": "main",
"index": 0
},
{
"node": "Fetch gradle.properties from Repo",
"type": "main",
"index": 0
}
]
]
},
"Fetch .env.staging from Repo": {
"main": [
[
{
"node": "Parse .env.staging into Key-Value",
"type": "main",
"index": 0
}
]
]
},
"Fetch gradle.properties File": {
"main": [
[
{
"node": "Decode gradle.properties",
"type": "main",
"index": 0
}
]
]
},
"Check if .env.staging Changed": {
"main": [
[
{
"node": "Proceed Only if ENV Changed",
"type": "main",
"index": 0
}
]
]
},
"Get Latest SHA of main Branch": {
"main": [
[
{
"node": "Prepare SHA for Branch Creation",
"type": "main",
"index": 0
}
]
]
},
"Combine Decoded Files for Diff": {
"main": [
[
{
"node": "Identify Config Changes",
"type": "main",
"index": 0
}
]
]
},
"Prepare SHA for Branch Creation": {
"main": [
[
{
"node": "Create New Git Branch",
"type": "main",
"index": 0
}
]
]
},
"Compare ENV and Gradle Variables": {
"main": [
[
{
"node": "Generate New Branch Name",
"type": "main",
"index": 0
}
]
]
},
"Apply ENV Changes to build.gradle": {
"main": [
[
{
"node": "Commit build.gradle Updates to Branch",
"type": "main",
"index": 0
}
]
]
},
"Fetch gradle.properties from Repo": {
"main": [
[
{
"node": "Parse gradle.properties into Key-Value",
"type": "main",
"index": 0
}
]
]
},
"Parse .env.staging into Key-Value": {
"main": [
[
{
"node": "Combine Env & Gradle",
"type": "main",
"index": 0
}
]
]
},
"Fetch .env.staging from New Branch": {
"main": [
[
{
"node": "Decode .env.staging",
"type": "main",
"index": 0
}
]
]
},
"Commit build.gradle Updates to Branch": {
"main": [
[
{
"node": "Prepare Pull Request Data",
"type": "main",
"index": 0
}
]
]
},
"Apply ENV Changes to gradle.properties": {
"main": [
[
{
"node": "Commit gradle.properties Updates to Branch",
"type": "main",
"index": 0
}
]
]
},
"Parse gradle.properties into Key-Value": {
"main": [
[
{
"node": "Combine Env & Gradle",
"type": "main",
"index": 1
}
]
]
},
"Commit gradle.properties Updates to Branch": {
"main": [
[
{
"node": "Prepare Pull Request Data",
"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.
githubApislackApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow automatically detects changes in the .env.staging file in a GitHub repository and keeps Android configuration files (build.gradle and gradle.properties) in sync.It creates a new Git branch, applies updates safely, opens a pull request and notifies the team on Slack…
Source: https://n8n.io/workflows/12873/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
HR teams, IT Operations, and System Administrators managing employee onboarding at scale. It’s perfect if you use Odoo 18 to trigger account requests and need Redmine + GitLab accounts created instant
This workflow is a complete, production-ready solution for recovering abandoned carts in Shopify stores using a multi-channel, multi-touch approach. It automates personalized follow-ups via Email, SMS
This workflow automates end-to-end research analysis by coordinating multiple AI models—including NVIDIA NIM (Llama), OpenAI GPT-4, and Claude to analyze uploaded documents, extract insights, and gene
Are you tired of the repetitive dance between git push, creating a pull request in GitHub, updating the corresponding task in JIRA, and then manually notifying your team in Slack, or Notion?
This workflow is an AI-assisted clean plate and object removal pipeline built for modern VFX production environments. It transforms a single plate image and removal brief into multiple high-quality cl