This workflow follows the Agent → 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 →
{
"name": "Image Generator - Nano Banana Pro + GitHub Storage",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "gerar-imagem",
"responseMode": "responseNode",
"options": {}
},
"id": "webhook-trigger",
"name": "Webhook Trigger",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1.1,
"position": [
250,
300
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={{ { \"status\": \"received\", \"post_id\": $json.post_id } }}"
},
"id": "webhook-response",
"name": "Responder Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [
450,
300
]
},
{
"parameters": {
"authentication": "serviceAccount",
"resource": "row",
"operation": "get",
"tableId": "posts",
"returnAll": false,
"limit": 1,
"filterType": "string",
"filterString": "=id=eq.{{ $json.post_id }}"
},
"id": "get-post",
"name": "Buscar Post Completo",
"type": "n8n-nodes-base.supabase",
"typeVersion": 1,
"position": [
650,
300
],
"credentials": {
"supabaseApi": {
"name": "<your credential>"
}
},
"continueOnFail": true
},
{
"parameters": {
"jsCode": "// Extrair tema principal e contexto\nconst post = $json;\n\n// Extrair primeiras palavras-chave (mais relevantes)\nconst palavrasChavePrincipais = (post.palavras_chave || []).slice(0, 3).join(', ');\n\n// Extrair primeiro par\u00e1grafo do conte\u00fado (contexto)\nconst conteudoLimpo = (post.conteudo || '')\n .replace(/#+\\s/g, '') // Remove markdown headers\n .replace(/\\*\\*/g, '') // Remove bold\n .replace(/\\n+/g, ' ') // Remove quebras de linha\n .trim();\n\nconst primeiroParagrafo = conteudoLimpo.split('.').slice(0, 2).join('.') + '.';\n\nreturn [{\n json: {\n post_id: post.id,\n titulo: post.titulo || '',\n slug: post.slug || '',\n plataforma: post.plataforma || 'blog',\n palavras_chave_principais: palavrasChavePrincipais || 'tecnologia, inova\u00e7\u00e3o',\n contexto: primeiroParagrafo.substring(0, 300) || post.titulo || 'Artigo sobre tecnologia e inova\u00e7\u00e3o',\n conteudo_completo: post.conteudo || ''\n }\n}];"
},
"id": "extract-context",
"name": "Extrair Tema e Contexto",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
850,
300
]
},
{
"parameters": {
"promptType": "define",
"text": "=Voc\u00ea \u00e9 um especialista em cria\u00e7\u00e3o de prompts para gera\u00e7\u00e3o de imagens com IA.\n\nBaseado no t\u00edtulo e contexto do artigo abaixo, crie um prompt detalhado e visual para gerar uma imagem de destaque profissional.\n\n**T\u00edtulo do Artigo:**\n{{ $json.titulo }}\n\n**Contexto:**\n{{ $json.contexto }}\n\n**Palavras-chave:**\n{{ $json.palavras_chave_principais }}\n\n**Requisitos do Prompt:**\n- Descrever uma cena visual espec\u00edfica e profissional\n- Incluir elementos relacionados ao tema (tecnologia, neg\u00f3cios, etc)\n- Estilo: moderno, clean, profissional, high-quality\n- Cores: vibrantes mas harmoniosas\n- Composi\u00e7\u00e3o: adequada para imagem de destaque de blog (16:9)\n- Evitar texto na imagem\n- Foco em conceitos visuais abstratos ou metaf\u00f3ricos\n\n**Formato da resposta:**\nRetorne APENAS o prompt em ingl\u00eas, sem introdu\u00e7\u00f5es ou explica\u00e7\u00f5es. O prompt deve ter entre 50-100 palavras.\n\n**Exemplo de bom prompt:**\n\"A modern digital illustration of a bustling Indian marketplace merging with futuristic technology, holographic shopping carts, vibrant colors of orange and blue, professional business atmosphere, high-quality 3D render, clean composition, 16:9 aspect ratio, studio lighting\"",
"options": {
"systemMessage": "Voc\u00ea \u00e9 um especialista em cria\u00e7\u00e3o de prompts para gera\u00e7\u00e3o de imagens. Responda apenas com o prompt solicitado, sem explica\u00e7\u00f5es."
}
},
"id": "generate-prompt",
"name": "IA: Gerar Prompt para Imagem",
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 1.3,
"position": [
1050,
200
]
},
{
"parameters": {
"options": {
"temperature": 0.7,
"maxOutputTokens": 300
}
},
"id": "gemini-prompt-model",
"name": "Gemini - Prompt",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"typeVersion": 1,
"position": [
1050,
400
],
"credentials": {
"googlePalmApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Traduzir e otimizar prompt\nconst promptNode = $('IA: Gerar Prompt para Imagem').item.json;\nconst contextData = $('Extrair Tema e Contexto').item.json;\n\nlet promptBase = promptNode.output || promptNode.text || promptNode.content || '';\npromptBase = promptBase.trim();\n\n// Garantir que est\u00e1 em ingl\u00eas e adicionar par\u00e2metros de qualidade\nconst promptOtimizado = `${promptBase}, professional photography, high resolution, 4K quality, detailed, sharp focus, vibrant colors, modern aesthetic, no text, no watermark, clean composition, 16:9 aspect ratio`;\n\n// Limitar tamanho (Nano Banana Pro aceita at\u00e9 ~1000 caracteres)\nconst promptFinal = promptOtimizado.substring(0, 900);\n\nconsole.log('\ud83d\udcdd Prompt gerado:', promptFinal);\nconsole.log('\ud83d\udcca Tamanho do prompt:', promptFinal.length, 'caracteres');\n\nreturn [{\n json: {\n prompt_imagem: promptFinal,\n post_id: contextData.post_id,\n slug: contextData.slug,\n titulo: contextData.titulo\n }\n}];"
},
"id": "optimize-prompt",
"name": "Otimizar Prompt",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1250,
200
]
},
{
"parameters": {
"method": "POST",
"url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-3-pro-image:generateContent",
"authentication": "genericCredentialType",
"genericAuthType": "queryAuth",
"queryParameters": {
"parameters": [
{
"name": "key",
"value": "={{ $credentials.googlePalmApi.apiKey }}"
}
]
},
"options": {
"headers": {
"entries": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"bodyParameters": {
"parameters": [
{
"name": "contents",
"value": "={{ [{ parts: [{ text: $json.prompt_imagem }] }] }}"
},
{
"name": "generationConfig",
"value": "={{ { temperature: 0.4, candidateCount: 1, maxOutputTokens: 8192, responseMimeType: \"image/png\" } }}"
}
]
},
"timeout": 60000
},
"sendBody": true,
"bodyContentType": "json"
},
"id": "generate-image",
"name": "Nano Banana Pro - Gerar Imagem",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1450,
200
],
"continueOnFail": true
},
{
"parameters": {
"jsCode": "// Extrair imagem Base64 da resposta\nconst resposta = $json;\nconst contextData = $('Otimizar Prompt').item.json;\n\nlet imagemBase64;\n\ntry {\n if (resposta.candidates && resposta.candidates[0]) {\n const candidate = resposta.candidates[0];\n const parts = candidate.content?.parts || [];\n \n // Procurar pela parte que cont\u00e9m a imagem\n for (const part of parts) {\n if (part.inlineData && part.inlineData.data) {\n imagemBase64 = part.inlineData.data;\n break;\n }\n }\n }\n \n if (!imagemBase64) {\n throw new Error('Imagem n\u00e3o encontrada na resposta da API');\n }\n \n const tamanhoMB = (imagemBase64.length * 0.75 / 1024 / 1024).toFixed(2);\n \n console.log('\u2705 Imagem gerada com sucesso (base64)');\n console.log(`\ud83d\udcca Tamanho aproximado: ${tamanhoMB} MB`);\n \n return [{\n json: {\n imagem_base64: imagemBase64,\n post_id: contextData.post_id,\n slug: contextData.slug,\n prompt_usado: contextData.prompt_imagem,\n tamanho_bytes: Math.floor(imagemBase64.length * 0.75)\n }\n }];\n \n} catch (error) {\n console.error('\u274c Erro ao extrair imagem:', error.message);\n console.error('Resposta recebida:', JSON.stringify(resposta, null, 2));\n throw error;\n}"
},
"id": "extract-image",
"name": "Extrair Imagem Base64",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1650,
200
]
},
{
"parameters": {
"jsCode": "// Preparar nome do arquivo e path GitHub\nconst slug = $json.slug || 'imagem-blog';\nconst timestamp = Date.now();\n\n// Sanitizar slug para nome de arquivo\nconst nomeArquivo = `${slug}-${timestamp}.png`\n .toLowerCase()\n .replace(/[^a-z0-9-.]/g, '-')\n .replace(/-+/g, '-')\n .substring(0, 100);\n\n// Configura\u00e7\u00f5es do GitHub (do .env ou hardcoded)\nconst githubConfig = {\n owner: 'attivamente',\n repo: 'imagensBlogELinkedin',\n path: 'uploads',\n branch: 'main',\n token: '<redacted-credential>'\n};\n\n// Path completo no GitHub\nconst pathCompleto = `${githubConfig.path}/${nomeArquivo}`;\n\nconsole.log(`\ud83d\udcc1 Nome do arquivo: ${nomeArquivo}`);\nconsole.log(`\ud83d\udcc2 Path no GitHub: ${pathCompleto}`);\n\nreturn [{\n json: {\n imagem_base64: $json.imagem_base64,\n post_id: $json.post_id,\n slug: $json.slug,\n prompt_usado: $json.prompt_usado,\n nome_arquivo: nomeArquivo,\n path_completo: pathCompleto,\n github_config: githubConfig,\n tamanho_bytes: $json.tamanho_bytes\n }\n}];"
},
"id": "prepare-github",
"name": "Preparar Nome e Path GitHub",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1850,
200
]
},
{
"parameters": {
"method": "GET",
"url": "=https://api.github.com/repos/attivamente/imagensBlogELinkedin/contents/uploads/{{ $json.nome_arquivo }}",
"authentication": "genericCredentialType",
"genericAuthType": "headerAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=token {{ $json.github_config.token }}"
},
{
"name": "Accept",
"value": "application/vnd.github.v3+json"
}
]
},
"options": {
"timeout": 10000
}
},
"id": "check-file",
"name": "Verificar se Arquivo Existe",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2050,
200
],
"continueOnFail": true
},
{
"parameters": {
"jsCode": "// Extrair SHA se arquivo existir\nconst verificacao = $json;\nconst githubData = $('Preparar Nome e Path GitHub').item.json;\n\nlet sha = null;\n\n// Se o arquivo j\u00e1 existe, a API retorna um SHA\nif (verificacao.sha) {\n sha = verificacao.sha;\n console.log(`\u26a0\ufe0f Arquivo j\u00e1 existe no GitHub. SHA: ${sha}`);\n} else {\n console.log('\u2705 Arquivo n\u00e3o existe, ser\u00e1 criado novo');\n}\n\nreturn [{\n json: {\n ...githubData,\n sha_existente: sha\n }\n}];"
},
"id": "extract-sha",
"name": "Extrair SHA se Existir",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2250,
200
]
},
{
"parameters": {
"jsCode": "// Construir body com SHA se existir\nconst data = $('Extrair SHA se Existir').item.json;\nconst sha = data.sha_existente;\n\nconst body = {\n message: `Add blog image: ${data.nome_arquivo}`,\n content: data.imagem_base64,\n branch: 'main'\n};\n\nif (sha) {\n body.sha = sha;\n}\n\nreturn [{\n json: {\n ...data,\n upload_body: body\n }\n}];"
},
"id": "prepare-upload-body",
"name": "Preparar Body Upload",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2350,
200
]
},
{
"parameters": {
"method": "PUT",
"url": "=https://api.github.com/repos/attivamente/imagensBlogELinkedin/contents/uploads/{{ $json.nome_arquivo }}",
"authentication": "genericCredentialType",
"genericAuthType": "headerAuth",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=token {{ $json.github_config.token }}"
},
{
"name": "Accept",
"value": "application/vnd.github.v3+json"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"options": {
"timeout": 120000
},
"sendBody": true,
"bodyContentType": "json",
"specifyBody": "json",
"jsonBody": "={{ $json.upload_body }}"
},
"id": "upload-github",
"name": "Upload para GitHub",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
2450,
200
],
"continueOnFail": true
},
{
"parameters": {
"jsCode": "// Extrair URL p\u00fablica da imagem do GitHub\nconst respostaGithub = $json;\nconst githubData = $('Preparar Nome e Path GitHub').item.json;\n\n// Construir URL p\u00fablica raw.githubusercontent.com (recomendado)\nconst owner = githubData.github_config.owner;\nconst repo = githubData.github_config.repo;\nconst branch = githubData.github_config.branch;\nconst path = githubData.github_config.path;\nconst nomeArquivo = githubData.nome_arquivo;\n\nconst urlGithub = `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${path}/${nomeArquivo}`;\n\n// URL alternativa da API (pode ter cache)\nconst downloadUrl = respostaGithub.content?.download_url || urlGithub;\n\nconsole.log('\ud83d\udd17 URL p\u00fablica da imagem:', urlGithub);\nconsole.log('\ud83d\udce5 Download URL (API):', downloadUrl);\n\nreturn [{\n json: {\n url_imagem: urlGithub,\n url_imagem_alternativa: downloadUrl,\n post_id: githubData.post_id,\n nome_arquivo: nomeArquivo,\n prompt_usado: githubData.prompt_usado,\n github_sha: respostaGithub.content?.sha || respostaGithub.commit?.sha,\n tamanho_bytes: githubData.tamanho_bytes\n }\n}];"
},
"id": "extract-url",
"name": "Extrair URL P\u00fablica",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2650,
200
]
},
{
"parameters": {
"authentication": "serviceAccount",
"resource": "row",
"operation": "update",
"tableId": "posts",
"filterType": "string",
"filterString": "=id=eq.{{ $json.post_id }}",
"fieldsUi": {
"fieldValues": [
{
"fieldName": "url_imagem",
"fieldValue": "={{ $json.url_imagem }}"
}
]
}
},
"id": "update-post",
"name": "Atualizar Post no Banco",
"type": "n8n-nodes-base.supabase",
"typeVersion": 1,
"position": [
2850,
200
],
"credentials": {
"supabaseApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "// Log de sucesso\nconst resultado = $('Extrair URL P\u00fablica').item.json;\n\nconsole.log('\u2705 IMAGEM GERADA E SALVA NO GITHUB COM SUCESSO:');\nconsole.log(`\ud83d\udcdd Post ID: ${resultado.post_id}`);\nconsole.log(`\ud83d\uddbc\ufe0f URL da Imagem: ${resultado.url_imagem}`);\nconsole.log(`\ud83d\udcc1 Nome do Arquivo: ${resultado.nome_arquivo}`);\nconsole.log(`\ud83d\udd11 GitHub SHA: ${resultado.github_sha}`);\nconsole.log(`\ud83c\udfa8 Prompt Usado: ${resultado.prompt_usado}`);\nconsole.log(`\ud83d\udcca Tamanho: ${(resultado.tamanho_bytes / 1024 / 1024).toFixed(2)} MB`);\nconsole.log(`\u23f1\ufe0f Timestamp: ${new Date().toISOString()}`);\nconsole.log(`\ud83d\udce6 Reposit\u00f3rio: attivamente/imagensBlogELinkedin`);\n\nreturn [{\n json: {\n status: 'sucesso',\n post_id: resultado.post_id,\n url_imagem: resultado.url_imagem,\n github_sha: resultado.github_sha,\n nome_arquivo: resultado.nome_arquivo,\n timestamp: new Date().toISOString()\n }\n}];"
},
"id": "log-success",
"name": "Log de Sucesso",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
3050,
200
]
},
{
"parameters": {
"jsCode": "// Error Handler\nconst erro = $json.error || {};\nlet contexto = {};\n\ntry {\n contexto = $('Webhook Trigger').item.json || {};\n} catch (e) {\n // Fallback\n contexto = { post_id: 'N/A' };\n}\n\nconsole.error('\u274c ERRO NO WORKFLOW DE GERA\u00c7\u00c3O DE IMAGEM:');\nconsole.error(`Mensagem: ${erro.message || 'Erro desconhecido'}`);\nconsole.error(`Post ID: ${contexto.post_id || 'N/A'}`);\nconsole.error(`Stack: ${erro.stack || 'N/A'}`);\n\n// Usar imagem placeholder do pr\u00f3prio GitHub\nconst imagemPlaceholder = 'https://raw.githubusercontent.com/attivamente/imagensBlogELinkedin/main/uploads/placeholder-blog.png';\n\nreturn [{\n json: {\n status: 'erro',\n post_id: contexto.post_id,\n url_imagem_fallback: imagemPlaceholder,\n erro_mensagem: erro.message || 'Erro desconhecido',\n timestamp: new Date().toISOString()\n }\n}];"
},
"id": "error-handler",
"name": "Error Handler",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
3250,
400
]
}
],
"connections": {
"Webhook Trigger": {
"main": [
[
{
"node": "Responder Webhook",
"type": "main",
"index": 0
},
{
"node": "Buscar Post Completo",
"type": "main",
"index": 0
}
]
]
},
"Buscar Post Completo": {
"main": [
[
{
"node": "Extrair Tema e Contexto",
"type": "main",
"index": 0
}
]
]
},
"Extrair Tema e Contexto": {
"main": [
[
{
"node": "IA: Gerar Prompt para Imagem",
"type": "main",
"index": 0
}
]
]
},
"IA: Gerar Prompt para Imagem": {
"main": [
[
{
"node": "Otimizar Prompt",
"type": "main",
"index": 0
}
]
]
},
"Gemini - Prompt": {
"ai_languageModel": [
[
{
"node": "IA: Gerar Prompt para Imagem",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Otimizar Prompt": {
"main": [
[
{
"node": "Nano Banana Pro - Gerar Imagem",
"type": "main",
"index": 0
}
]
]
},
"Nano Banana Pro - Gerar Imagem": {
"main": [
[
{
"node": "Extrair Imagem Base64",
"type": "main",
"index": 0
}
]
]
},
"Extrair Imagem Base64": {
"main": [
[
{
"node": "Preparar Nome e Path GitHub",
"type": "main",
"index": 0
}
]
]
},
"Preparar Nome e Path GitHub": {
"main": [
[
{
"node": "Verificar se Arquivo Existe",
"type": "main",
"index": 0
}
]
]
},
"Verificar se Arquivo Existe": {
"main": [
[
{
"node": "Extrair SHA se Existir",
"type": "main",
"index": 0
}
]
]
},
"Extrair SHA se Existir": {
"main": [
[
{
"node": "Preparar Body Upload",
"type": "main",
"index": 0
}
]
]
},
"Preparar Body Upload": {
"main": [
[
{
"node": "Upload para GitHub",
"type": "main",
"index": 0
}
]
]
},
"Upload para GitHub": {
"main": [
[
{
"node": "Extrair URL P\u00fablica",
"type": "main",
"index": 0
}
]
]
},
"Extrair URL P\u00fablica": {
"main": [
[
{
"node": "Atualizar Post no Banco",
"type": "main",
"index": 0
}
]
]
},
"Atualizar Post no Banco": {
"main": [
[
{
"node": "Log de Sucesso",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"saveManualExecutions": true,
"callerPolicy": "workflowsFromSameOwner"
},
"staticData": null,
"tags": [
{
"name": "automation",
"id": "1"
},
{
"name": "image",
"id": "2"
},
{
"name": "github",
"id": "3"
},
{
"name": "gemini",
"id": "4"
}
],
"triggerCount": 1,
"updatedAt": "2025-01-15T14:00:00.000Z",
"versionId": "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.
googlePalmApisupabaseApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Image Generator - Nano Banana Pro + GitHub Storage. Uses supabase, agent, lmChatGoogleGemini, httpRequest. Webhook trigger; 18 nodes.
Source: https://github.com/artubss/contentflow-automation/blob/92598bf6726e2866bef62d4ded75da913fb23b57/n8n-workflow-image-generator.json — 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.
⏺ 🚀 How it works
leads. Uses supabase, gmail, formTrigger, httpRequest. Webhook trigger; 62 nodes.
Agent: IPTV (instance_e2165d22_1762376395079). Uses openAi, redis, supabase, httpRequest. Webhook trigger; 56 nodes.
This workflow transforms any PDF legal contract into a detailed AI-powered risk report — in under 5 minutes. Upload a contract, and the system automatically splits it into clauses, analyses each one u
I2A2 - AI Minds V2. Uses chatTrigger, httpRequest, lmChatGoogleGemini, compression. Webhook trigger; 23 nodes.