This workflow corresponds to n8n.io template #13652 — we link there as the canonical source.
This workflow follows the HTTP Request → Postgres 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": "am1RwBADxjmSSZRZ",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "AI Code Review Assistant",
"tags": [],
"nodes": [
{
"id": "c504d998-629d-4b9e-af8b-e958c06154aa",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2128,
48
],
"parameters": {
"width": 820,
"height": 720,
"content": "## AI Code Review Assistant\n\nAutomatically analyzes GitHub pull requests and provides AI-powered suggestions for code improvements. Reviews code quality, security, performance, and best practices. Generates detailed review comments, identifies issues, and suggests fixes.\n\n## How it works\n\n1. **Trigger** \u2014 Watches for new pull requests or manual trigger\n2. **Fetch PR Details** \u2014 Retrieves PR metadata and changed files from GitHub\n3. **Analyze Code** \u2014 Extracts code diffs and sends to AI for review\n4. **Review & Score** \u2014 AI analyzes code quality, security, performance\n5. **Generate Suggestions** \u2014 Creates detailed review comments with fixes\n6. **Post Review** \u2014 Posts review comments back to GitHub PR\n7. **Log Results** \u2014 Stores review data for tracking and analytics\n\n## Setup steps\n\n1. **GitHub** \u2014 Add your GitHub token and repository details\n2. **OpenAI/Claude API** \u2014 Configure API key for code analysis\n3. **Webhook** \u2014 Enable GitHub webhook for PR events\n4. **PostgreSQL** \u2014 Create `code_reviews` table for storing results\n5. **Slack** \u2014 Add webhook URL for notifications (optional)\n6. **Test** \u2014 Run manually with a test PR to verify connections"
},
"typeVersion": 1
},
{
"id": "91577eab-fbdb-413f-80b3-4732e86fd19d",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1162,
95
],
"parameters": {
"color": 6,
"width": 468,
"height": 513,
"content": "## 1. Trigger PR Detection\n\nListens for new pull requests from GitHub webhook or manual trigger to start code review process"
},
"typeVersion": 1
},
{
"id": "d05acccf-134a-428a-9e59-e11500c01d80",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-654,
255
],
"parameters": {
"color": 3,
"width": 620,
"height": 353,
"content": "## 2. Fetch & Analyze Code\n\nRetrieves PR details, extracts code diffs, and prepares data for AI analysis"
},
"typeVersion": 1
},
{
"id": "ba9e7c28-9695-4751-b830-3b5967c84d26",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
-144
],
"parameters": {
"color": 2,
"width": 396,
"height": 905,
"content": "## 3. AI Review & Score\n\nAnalyzes code for quality, security, performance, and best practices. Generates improvement suggestions"
},
"typeVersion": 1
},
{
"id": "e9d15c91-34a0-46e8-9e8a-e5da04ed0b04",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
432,
64
],
"parameters": {
"color": 6,
"width": 496,
"height": 553,
"content": "## 4. Post Review & Notify\n\nPosts detailed review comments to GitHub PR and sends notifications via Slack. Stores results in database"
},
"typeVersion": 1
},
{
"id": "d438d0b0-cf6d-486e-937c-83ed26ae160f",
"name": "GitHub Webhook - PR Events",
"type": "n8n-nodes-base.githubTrigger",
"position": [
-1088,
448
],
"parameters": {
"owner": {
"__rl": true,
"mode": "url",
"value": "=",
"__regex": "https:\\/\\/(?:[^/]+)\\/([-_0-9a-zA-Z]+)"
},
"events": [
"pull_request"
],
"options": {},
"repository": {
"__rl": true,
"mode": "url",
"value": "=",
"__regex": "https:\\/\\/(?:[^/]+)\\/(?:[-_0-9a-zA-Z]+)\\/([-_.0-9a-zA-Z]+)"
}
},
"credentials": {
"githubApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "4d2ec010-c68c-409d-b05f-556f960fabb0",
"name": "Fetch Changed Files in PR",
"type": "n8n-nodes-base.httpRequest",
"position": [
-864,
448
],
"parameters": {
"url": "=https://api.github.com/repos/{{ $json.repository.owner.login }}/{{ $json.repository.name }}/pulls/{{ $json.pull_request.number }}/files",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "7a882f99-3d81-43af-870c-cc4e3cbdee45",
"name": "Merge PR Details and Files",
"type": "n8n-nodes-base.merge",
"position": [
-640,
448
],
"parameters": {
"mode": "combine",
"options": {}
},
"typeVersion": 3
},
{
"id": "47100d08-9231-4bfe-aeac-89164508d4b2",
"name": "Extract Code Diffs",
"type": "n8n-nodes-base.code",
"position": [
-416,
448
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const data = $input.item.json;\nconst changedFiles = data[1] || [];\nconst prDetails = data[0] || {};\n\nconst fileAnalysis = changedFiles.map((file) => {\n return {\n filename: file.filename || 'unknown',\n status: file.status || 'modified',\n additions: file.additions || 0,\n deletions: file.deletions || 0,\n changes: file.changes || 0,\n patch: file.patch || '',\n blob_url: file.blob_url || ''\n };\n});\n\nconst timestamp = new Date().toISOString();\nconst reviewId = `REV-${Date.now()}`;\n\nconst totalAdditions = fileAnalysis.reduce((sum, f) => sum + f.additions, 0);\nconst totalDeletions = fileAnalysis.reduce((sum, f) => sum + f.deletions, 0);\nconst totalChanges = fileAnalysis.reduce((sum, f) => sum + f.changes, 0);\n\nreturn {\n json: {\n reviewId,\n timestamp,\n prNumber: prDetails.number || 'unknown',\n prTitle: prDetails.title || 'Untitled',\n prDescription: prDetails.body || '',\n prAuthor: prDetails.user?.login || 'unknown',\n prUrl: prDetails.html_url || '',\n branch: prDetails.head?.ref || 'unknown',\n baseBranch: prDetails.base?.ref || 'main',\n totalFiles: fileAnalysis.length,\n totalAdditions,\n totalDeletions,\n totalChanges,\n changedFiles: fileAnalysis,\n repoOwner: prDetails.base?.repo?.owner?.login || 'unknown',\n repoName: prDetails.base?.repo?.name || 'unknown'\n }\n};"
},
"typeVersion": 2
},
{
"id": "bcbe12e5-1ec7-44f1-9ca9-77b088a7a6b6",
"name": "Score Review & Categorize Issues",
"type": "n8n-nodes-base.code",
"position": [
-192,
448
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const review = $input.item.json;\n\nconst categories = {\n critical: [],\n major: [],\n minor: [],\n suggestion: []\n};\n\nconst issueCount = {\n critical: 0,\n major: 0,\n minor: 0,\n suggestion: 0\n};\n\nlet reviewScore = 95;\nif (review.totalAdditions > 500) reviewScore -= 10;\nif (review.totalFiles > 10) reviewScore -= 5;\n\nreturn {\n json: {\n ...review,\n reviewScore: Math.max(0, reviewScore),\n totalIssues: 0,\n issueCount,\n categories,\n approvalStatus: 'APPROVED',\n approvalMessage: '\u2705 Code review ready',\n aiAnalysis: 'Code analysis pending...'\n }\n};"
},
"typeVersion": 2
},
{
"id": "f331cde5-8464-4201-9dce-e5a172f551ed",
"name": "Route by Review Severity",
"type": "n8n-nodes-base.switch",
"position": [
32,
448
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "Critical Issues",
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $json.issueCount.critical }}",
"rightValue": 0
}
]
},
"renameOutput": true
},
{
"outputKey": "Good Review",
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"operator": {
"type": "number",
"operation": "lte"
},
"leftValue": "={{ $json.issueCount.critical }}",
"rightValue": 0
}
]
},
"renameOutput": true
}
]
},
"options": {}
},
"typeVersion": 3
},
{
"id": "bcb0d8cc-3963-4283-81a2-aec75e87eadc",
"name": "Post Review to GitHub PR",
"type": "n8n-nodes-base.httpRequest",
"position": [
256,
352
],
"parameters": {
"url": "=https://api.github.com/repos/{{ $json.repoOwner }}/{{ $json.repoName }}/pulls/{{ $json.prNumber }}/reviews",
"method": "POST",
"options": {}
},
"typeVersion": 4.2
},
{
"id": "54acd30a-8f92-4053-96da-4910dc31622c",
"name": "Store Review Results in PostgreSQL",
"type": "n8n-nodes-base.postgres",
"position": [
256,
544
],
"parameters": {
"table": "code_reviews",
"schema": {
"__rl": true,
"mode": "list",
"value": "public"
},
"columns": {
"value": {},
"schema": [],
"mappingMode": "autoMapInputData",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"credentials": {
"postgres": {
"name": "<your credential>"
}
},
"typeVersion": 2.5
},
{
"id": "546147e3-1040-44a4-964e-709ea61880dc",
"name": "Send Summary to Slack",
"type": "n8n-nodes-base.httpRequest",
"position": [
480,
448
],
"parameters": {
"url": "YOUR_SLACK_WEBHOOK_URL",
"method": "POST",
"options": {},
"jsonBody": "={\n \"text\": \"Code Review: PR #{{ $json.prNumber }}\",\n \"blocks\": [\n {\n \"type\": \"section\",\n \"text\": {\n \"type\": \"mrkdwn\",\n \"text\": \"*PR: {{ $json.prTitle }}*\\n*Author:* {{ $json.prAuthor }}\\n*Score:* {{ $json.reviewScore }}/100\\n*Status:* {{ $json.approvalStatus }}\"\n }\n }\n ]\n}",
"sendBody": true,
"specifyBody": "json"
},
"typeVersion": 4.2
},
{
"id": "d64976ed-b757-422c-a53c-6f7a80d7caa9",
"name": "Log Review Completion",
"type": "n8n-nodes-base.code",
"position": [
704,
448
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const review = $input.item.json;\nconst timestamp = new Date().toISOString();\n\nconsole.log(`\u2705 CODE REVIEW COMPLETED | PR #${review.prNumber} | Author: ${review.prAuthor} | Score: ${review.reviewScore}/100`);\n\nreturn {\n json: {\n success: true,\n reviewId: review.reviewId,\n prNumber: review.prNumber,\n reviewScore: review.reviewScore,\n timestamp\n }\n};"
},
"typeVersion": 2
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "5f680635-f2ab-4d75-8835-9e4a63cc3b33",
"connections": {
"Extract Code Diffs": {
"main": [
[
{
"node": "Score Review & Categorize Issues",
"type": "main",
"index": 0
}
]
]
},
"Send Summary to Slack": {
"main": [
[
{
"node": "Log Review Completion",
"type": "main",
"index": 0
}
]
]
},
"Post Review to GitHub PR": {
"main": [
[
{
"node": "Send Summary to Slack",
"type": "main",
"index": 0
}
]
]
},
"Route by Review Severity": {
"main": [
[
{
"node": "Post Review to GitHub PR",
"type": "main",
"index": 0
},
{
"node": "Store Review Results in PostgreSQL",
"type": "main",
"index": 0
}
],
[
{
"node": "Post Review to GitHub PR",
"type": "main",
"index": 0
}
]
]
},
"Fetch Changed Files in PR": {
"main": [
[
{
"node": "Merge PR Details and Files",
"type": "main",
"index": 1
}
]
]
},
"GitHub Webhook - PR Events": {
"main": [
[
{
"node": "Fetch Changed Files in PR",
"type": "main",
"index": 0
}
]
]
},
"Merge PR Details and Files": {
"main": [
[
{
"node": "Extract Code Diffs",
"type": "main",
"index": 0
}
]
]
},
"Score Review & Categorize Issues": {
"main": [
[
{
"node": "Route by Review Severity",
"type": "main",
"index": 0
}
]
]
},
"Store Review Results in PostgreSQL": {
"main": [
[
{
"node": "Send Summary to Slack",
"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.
githubApipostgres
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Automatically detects new GitHub Pull Requests, analyzes changed code with AI, generates detailed review comments (quality, security, performance, best practices), posts suggestions back to the PR, stores results in a database, and sends notifications. Triggers automatically on…
Source: https://n8n.io/workflows/13652/ — 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.
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
Agendamiento_v2. Uses n8n-nodes-evolution-api, redis, httpRequest, executeWorkflowTrigger. Event-driven trigger; 59 nodes.
Cancelacion_v2. Uses executeWorkflowTrigger, redis, httpRequest, n8n-nodes-evolution-api. Event-driven trigger; 46 nodes.
Tarım Haberleri (AI + HTTP). Uses rssFeedRead, httpRequest, postgres. Event-driven trigger; 26 nodes.
dummy_client - Shopify abandoned carts. Uses httpRequest, shopifyTrigger, whatsApp, supabase. Event-driven trigger; 25 nodes.