Describe the problem/error/question
I am building a multi-step form workflow that receives a PDF document via a Form Trigger, extracts the text using an OCR API (returning Markdown), and then displays the parsed results for human validation using an in-workflow Form node (Action). After the user’s approval, an email containing the formatted data should be sent.
Unfortunately, after adding the in-workflow Form node, the execution enters an infinite loading state upon submitting the initial trigger form. The workflow never stops loading, the front-end never redirects to the second form step, and the canvas nodes continuously show “loading data”, preventing me from inspecting the outputs or proceeding with the workflow.
What is the error message (if any)?
There is no explicit crash or error log. The UI simply hangs indefinitely with the nodes showing “loading data”, and the workflow execution never reaches a resolved state.
Please share your workflow
Here is my workflow:
{
“nodes”: [
{
“parameters”: {
“formTitle”: “Portal de Contratos - Conteco”,
“formDescription”: “Faça o upload do PDF do contrato de experiência para agendar os avisos automáticos.”,
“formFields”: {
“values”: [
{
“fieldLabel”: “Arquivo PDF”,
“fieldType”: “file”,
“requiredField”: true
},
{
“fieldLabel”: “Informe o seu email”,
“fieldType”: “email”,
“requiredField”: true
},
{
“fieldLabel”: “Para qual email deve ser encaminhado o aviso?”,
“fieldType”: “email”,
“requiredField”: true
}
]
},
“options”: {
“respondWithOptions”: {
“values”: {
“formSubmittedText”: “Seu PDF fo submetido.”
}
}
}
},
“type”: “n8n-nodes-base.formTrigger”,
“typeVersion”: 2.6,
“position”: [
-80,
-16
],
“id”: “d865f634-c349-4f0e-9293-c307bb536310”,
“name”: “Formaulário de envio de Contratos”,
“webhookId”: “REMOVIDO_POR_SEGURANCA”
},
{
“parameters”: {
“method”: “POST”,
“url”: “https://api.mistral.ai/v1/files”,
“authentication”: “predefinedCredentialType”,
“nodeCredentialType”: “mistralCloudApi”,
“sendHeaders”: true,
“headerParameters”: {
“parameters”: [
{
“name”: “Authorization”,
“value”: “Bearer $MISTRAL_API_KEY”
}
]
},
“sendBody”: true,
“contentType”: “multipart-form-data”,
“bodyParameters”: {
“parameters”: [
{
“name”: “purpose”,
“value”: “ocr”
},
{
“parameterType”: “formBinaryData”,
“name”: “file”,
“inputDataFieldName”: “Arquivo_PDF”
}
]
},
“options”: {}
},
“type”: “n8n-nodes-base.httpRequest”,
“typeVersion”: 4.4,
“position”: [
384,
-16
],
“id”: “f86ce096-e582-430a-9578-09a4a13881d8”,
“name”: “Envia PDF p/ Mistral”,
“credentials”: {
“mistralCloudApi”: {
“id”: “REMOVIDO_POR_SEGURANCA”,
“name”: “Mistral Cloud account”
}
}
},
{
“parameters”: {
“url”: “=https://api.mistral.ai/v1/files/{{ $json.id }}/url”,
“authentication”: “predefinedCredentialType”,
“nodeCredentialType”: “mistralCloudApi”,
“sendQuery”: true,
“queryParameters”: {
“parameters”: [
{
“name”: “expiry”,
“value”: “24”
}
]
},
“sendHeaders”: true,
“headerParameters”: {
“parameters”: [
{
“name”: “Accept”,
“value”: “application/json”
},
{
“name”: “Authorization”,
“value”: “Bearer $MISTRAL_API_KEY”
}
]
},
“options”: {}
},
“type”: “n8n-nodes-base.httpRequest”,
“typeVersion”: 4.4,
“position”: [
592,
-16
],
“id”: “dfa197de-3eb2-4238-acdd-0c6d4983a6c6”,
“name”: “OCR Mistral”,
“credentials”: {
“mistralCloudApi”: {
“id”: “REMOVIDO_POR_SEGURANCA”,
“name”: “Mistral Cloud account”
}
}
},
{
“parameters”: {
“method”: “POST”,
“url”: “https://api.mistral.ai/v1/ocr”,
“authentication”: “predefinedCredentialType”,
“nodeCredentialType”: “mistralCloudApi”,
“sendHeaders”: true,
“headerParameters”: {
“parameters”: [
{
“name”: “Authorization”,
“value”: “Bearer ${MISTRAL_API_KEY}”
}
]
},
“sendBody”: true,
“specifyBody”: “json”,
“jsonBody”: "={\n “model”: “mistral-ocr-latest”,\n “document”: {\n “type”: “document_url”,\n “document_url”: “{{ $json.url }}”\n },\n “table_format”: “html”,\n “include_image_base64”: true\n} ",
“options”: {
“response”: {
“response”: {
“responseFormat”: “file”,
“outputPropertyName”: “ocr_output.json”
}
}
}
},
“type”: “n8n-nodes-base.httpRequest”,
“typeVersion”: 4.4,
“position”: [
800,
-16
],
“id”: “d0f90d0d-bc89-4464-8098-aad75b4ff0af”,
“name”: “Recebe OCR”,
“credentials”: {
“mistralCloudApi”: {
“id”: “REMOVIDO_POR_SEGURANCA”,
“name”: “Mistral Cloud account”
}
}
},
{
“parameters”: {
“operation”: “fromJson”,
“binaryPropertyName”: “ocr_output.json”,
“options”: {
“encoding”: “utf8”
}
},
“type”: “n8n-nodes-base.extractFromFile”,
“typeVersion”: 1.1,
“position”: [
976,
-16
],
“id”: “18869f1d-fde6-488d-923d-1758fe863886”,
“name”: “Extract from File1”
},
{
“parameters”: {
“jsCode”: “const data = $input.first().json.data.pages;\n\nreturn data.map(entry => {\n // 1. Criamos uma variável mutável para o texto do markdown\n let textoCompleto = entry.markdown;\n\n // 2. Verificamos se existem tabelas nesta página\n if (entry.tables && entry.tables.length > 0) {\n \n // 3. Percorremos cada tabela encontrada\n entry.tables.forEach(table => {\n // Montamos a string exata que o OCR deixou como referência\n const referencia = [${table.id}](${table.id});\n \n // 4. Substituímos a referência pelo conteúdo HTML da tabela\n // Usamos split e join para garantir que substitua exatamente o texto, sem problemas com caracteres especiais\n textoCompleto = textoCompleto.split(referencia).join(table.content);\n });\n }\n\n // Retornamos o objeto com o markdown já unificado com as tabelas\n return {\n json: {\n markdown: textoCompleto\n }\n };\n});”
},
“type”: “n8n-nodes-base.code”,
“typeVersion”: 2,
“position”: [
1152,
-16
],
“id”: “9f106006-6e4b-4df1-99b5-bf2ff873188e”,
“name”: “Code in JavaScript”
},
{
“parameters”: {
“assignments”: {
“assignments”: [
{
“id”: “cd38ab23-9da2-427c-bd7a-84fdadffe163”,
“name”: “text”,
“value”: “={{ $json.markdown }}”,
“type”: “string”
}
]
},
“options”: {}
},
“type”: “n8n-nodes-base.set”,
“typeVersion”: 3.4,
“position”: [
1408,
-16
],
“id”: “083060b0-068a-425c-858c-3c873cb2ea78”,
“name”: “Edit Fields”
},
{
“parameters”: {
“jsCode”: “const text = $input.first().json.text;\n\n// 1. Extrair o nome da empresa\nconst empresaMatch = text.match(/Empresa:\s*(.)/i);\nlet empresa = empresaMatch ? empresaMatch[1].trim() : “Empresa não identificada”;\n\n// Remove o código numérico e o traço inicial (Ex: "431 - ")\n// ^ → Início da string\n// \d+ → Um ou mais números\n// \s → Zero ou mais espaços\n// - → O caractere de traço\nempresa = empresa.replace(/^\d+\s*-\s*/, ‘’).trim();\n\n// 2. Extrair apenas as linhas da tabela (entre e )\nconst rowRegex = /(.?)</tr>/gs;\nconst cellRegex = /(.?)</td>/g;\nconst rows = […text.matchAll(rowRegex)];\n\nlet tabelaHtml = \n<div style="font-family: sans-serif; border: 1px solid #ddd; padding: 15px; border-radius: 8px;">\n <h3 style="color: #2c3e50;">Empresa: ${empresa}</h3>\n <table style="width: 100%; border-collapse: collapse;">\n <thead>\n <tr style="background-color: #f8f9fa; border-bottom: 2px solid #dee2e6;">\n <th style="text-align: left; padding: 10px; border: 1px solid #dee2e6;">Funcionário</th>\n <th style="text-align: center; padding: 10px; border: 1px solid #dee2e6;">Início</th>\n <th style="text-align: center; padding: 10px; border: 1px solid #dee2e6;">Fim Experiência</th>\n <th style="text-align: center; padding: 10px; border: 1px solid #dee2e6;">Período</th>\n </tr>\n </thead>\n <tbody>;\n\n// 3. Processar cada linha\nrows.forEach((row, index) => {\n const cells = […row[1].matchAll(cellRegex)].map(c => c[1]);\n\n // Validamos se a linha tem pelo menos 8 colunas\n if (cells.length >= 8) {\n const nome = cells[1]; // Coluna: Nome do empregado\n const inicio = cells[4]; // Coluna: Data início contr.\n const fimContrato = cells[5]; // Coluna: Data fim contr.\n const diasContrato = cells[6]; // Coluna: Dias contr.\n const fimProrrog = cells[7]; // Coluna: Data fim prorrog.\n \n // Como a coluna 8 pode estar vazia (sem final), fazemos uma verificação segura\n const diasProrrog = cells[8] ? cells[8].trim() : ""; \n\n // Lógica da Data Final\n const temProrrogacao = (fimProrrog && fimProrrog.trim() !== "" && !fimProrrog.includes(" "));\n const dataFinal = temProrrogacao ? fimProrrog : fimContrato;\n\n // Lógica do Período (Transforma em número inteiro para poder somar)\n const numDiasIniciais = parseInt(diasContrato) || 0;\n const numDiasProrrog = parseInt(diasProrrog) || 0;\n const totalDias = numDiasIniciais + numDiasProrrog;\n\n // Formata o texto final do período (ex: "90 dias (45+45)" ou "45 dias")\n let textoPeriodo = ${totalDias} dias;\n if (numDiasProrrog > 0) {\n textoPeriodo += (${numDiasIniciais}+${numDiasProrrog});\n }\n\n tabelaHtml += \n <tr>\n <td style="padding: 10px; border: 1px solid #dee2e6;\">${nome}</td>\n <td style="padding: 10px; text-align: center; border: 1px solid #dee2e6;\">${inicio}</td>\n <td style="padding: 10px; text-align: center; border: 1px solid #dee2e6; font-weight: bold; color: #d9534f;\">${dataFinal}</td>\n <td style="padding: 10px; text-align: center; border: 1px solid #dee2e6; color: #555;\">${textoPeriodo}</td>\n </tr>;\n }\n});\n\ntabelaHtml += </tbody></table></div>;\n\nreturn {\n html_final: tabelaHtml,\n empresa: empresa\n};”
},
“type”: “n8n-nodes-base.code”,
“typeVersion”: 2,
“position”: [
1920,
-192
],
“id”: “5876b2ff-4624-4669-babe-88bb0515606d”,
“name”: “Code in JavaScript1”
},
{
“parameters”: {
“toEmail”: “={{ $(‘Formaulário de envio de Contratos’).item.json[‘Para qual email deve ser encaminhado o aviso?’] }}”,
“subject”: “=Aviso de Fim de Contrato de Experiência - {{ $json.empresa }}”,
“html”: “=<div style="font-family: Arial, sans-serif; color: #333;">\n\n\nOlá, equipe da Conteco,\n\n\n\nUm novo documento de contrato foi processado pelo sistema. Confira os dados extraídos abaixo:\n\n\n \n <div style="margin: 20px 0;">\n {{ $json.html_final}}\n \n\n <p style="font-size: 12px; color: #777;">\n Este é um envio automático do Assistente de IA interno.\n\n\n\n”,
“options”: {
“appendAttribution”: false,
“ccEmail”: “={{ $(‘Formaulário de envio de Contratos’).item.json[‘Informe o seu email’] }}”
}
},
“type”: “n8n-nodes-base.emailSend”,
“typeVersion”: 2.1,
“position”: [
2832,
-192
],
“id”: “3c7ee9b9-395f-4a0f-b861-3e41d220da1c”,
“name”: “Send an Email”,
“webhookId”: “REMOVIDO_POR_SEGURANCA”,
“credentials”: {
“smtp”: {
“id”: “REMOVIDO_POR_SEGURANCA”,
“name”: “SMTP account”
}
}
},
{
“parameters”: {
“formFields”: {
“values”: [
{
“fieldType”: “html”,
“html”: “<div style="font-family: Arial, sans-serif; color: #333;">\n\n\nOlá, equipe da Conteco,\n\n\n\nUm novo documento de contrato foi processado pelo sistema. Confira os dados extraídos abaixo:\n\n\n \n <div style="margin: 20px 0;">\n {{ $json.html_final }}\n \n\n <p style="font-size: 12px; color: #777;">\n Este é um envio automático do Assistente de IA interno.\n\n\n\n”
},
{
“fieldLabel”: “Após revisar os dados, você confirma o envio desse email?”,
“fieldType”: “checkbox”,
“fieldOptions”: {
“values”: [
{
“option”: “Sim, confirmo o envio do email.”
},
{
“option”: “Não, o email contém divergências.”
}
]
}
}
]
},
“options”: {}
},
“type”: “n8n-nodes-base.form”,
“typeVersion”: 2.5,
“position”: [
2144,
-192
],
“id”: “06ba2fe9-bf23-48b7-a89f-99905eb19246”,
“name”: “Pré-visualização do E-mail”,
“webhookId”: “REMOVIDO_POR_SEGURANCA”,
“alwaysOutputData”: true
},
{
“parameters”: {
“conditions”: {
“options”: {
“caseSensitive”: true,
“leftValue”: “”,
“typeValidation”: “strict”,
“version”: 3
},
“conditions”: [
{
“id”: “9bf5112e-4c63-416c-94f9-d73524758fa3”,
“leftValue”: “={{ $json[‘Após revisar os dados, você confirma o envio desse email?’][0] }}”,
“rightValue”: “=Sim, confirmo o envio do email.”,
“operator”: {
“type”: “string”,
“operation”: “equals”
}
}
],
“combinator”: “and”
},
“options”: {}
},
“type”: “n8n-nodes-base.if”,
“typeVersion”: 2.3,
“position”: [
2576,
-192
],
“id”: “50458d2c-6db9-419c-a8f9-a5d930912bf7”,
“name”: “If”
},
{
“parameters”: {
“operation”: “completion”,
“completionTitle”: “Obrigado!”,
“completionMessage”: “O email foi enviado ao devido destinatário.”,
“limitWaitTime”: true,
“resumeAmount”: 2,
“resumeUnit”: “minutes”,
“options”: {}
},
“type”: “n8n-nodes-base.form”,
“typeVersion”: 2.5,
“position”: [
2352,
-192
],
“id”: “9ae6ea32-f7ab-47ac-85d5-6649f061b805”,
“name”: “Form”,
“webhookId”: “REMOVIDO_POR_SEGURANCA”,
“alwaysOutputData”: true
},
{
“parameters”: {},
“type”: “n8n-nodes-base.noOp”,
“typeVersion”: 1,
“position”: [
2832,
-16
],
“id”: “570c6dd6-6b6f-4c7a-b3a5-49e2d46d2d63”,
“name”: “No Operation, do nothing”
}
],
“connections”: {
“Formaulário de envio de Contratos”: {
“main”: [
[
{
“node”: “Envia PDF p/ Mistral”,
“type”: “main”,
“index”: 0
}
]
]
},
“Envia PDF p/ Mistral”: {
“main”: [
[
{
“node”: “OCR Mistral”,
“type”: “main”,
“index”: 0
}
]
]
},
“OCR Mistral”: {
“main”: [
[
{
“node”: “Recebe OCR”,
“type”: “main”,
“index”: 0
}
]
]
},
“Recebe OCR”: {
“main”: [
[
{
“node”: “Extract from File1”,
“type”: “main”,
“index”: 0
}
]
]
},
“Extract from File1”: {
“main”: [
[
{
“node”: “Code in JavaScript”,
“type”: “main”,
“index”: 0
}
]
]
},
“Code in JavaScript”: {
“main”: [
[
{
“node”: “Edit Fields”,
“type”: “main”,
“index”: 0
}
]
]
},
“Edit Fields”: {
“main”: [
[
{
“node”: “Code in JavaScript1”,
“type”: “main”,
“index”: 0
}
]
]
},
“Code in JavaScript1”: {
“main”: [
[
{
“node”: “Pré-visualização do E-mail”,
“type”: “main”,
“index”: 0
}
]
]
},
“Send an Email”: {
“main”: [
]
},
“Pré-visualização do E-mail”: {
“main”: [
[
{
“node”: “Form”,
“type”: “main”,
“index”: 0
}
]
]
},
“If”: {
“main”: [
[
{
“node”: “Send an Email”,
“type”: “main”,
“index”: 0
}
],
[
{
“node”: “No Operation, do nothing”,
“type”: “main”,
“index”: 0
}
]
]
},
“Form”: {
“main”: [
[
{
“node”: “If”,
“type”: “main”,
“index”: 0
}
]
]
}
},
“pinData”: {},
“meta”: {
“templateCredsSetupCompleted”: true,
“instanceId”: “REMOVIDO_POR_SEGURANCA”
}
}
Share the output returned by the last node
Information on your n8n setup
-
Debug info
core
- n8nVersion: 2.32.5
- platform: docker (self-hosted)
- nodeJsVersion: 24.18.0
- nodeEnv: production
- database: postgres
- executionMode: regular
- concurrency: -1
- license: enterprise (production)
storage
- success: none
- error: none
- progress: false
- manual: false
- binaryMode: filesystem
pruning
- enabled: true
- maxAge: 168 hours
- maxCount: 50000 executions
client
- userAgent: mozilla/5.0 (windows nt 10.0; win64; x64) applewebkit/537.36 (khtml, like gecko) chrome/150.0.0.0 safari/537.36
- isTouchDevice: false

