AutomationFlowsWeb Scraping › Jobby - PDF - Dyn

Jobby - PDF - Dyn

Jobby - PDF - dyn. Uses httpRequest, dataTable. Webhook trigger; 11 nodes.

Webhook trigger★★★★☆ complexity11 nodesHTTP RequestData Table
Web Scraping Trigger: Webhook Nodes: 11 Complexity: ★★★★☆ Added:

This workflow follows the Datatable → 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
{
  "//": "Jobby Markdown Editor - jobby-pdf-dyn.json - Dynamic PDF generator workflow",
  "updatedAt": "2026-06-03T11:51:05.014Z",
  "createdAt": "2026-05-29T09:56:13.800Z",
  "id": "dSX8vdBy3PnPdiOU",
  "name": "Jobby - PDF - dyn",
  "description": "retrieves a MD file and returns a printed version in PDF",
  "active": true,
  "isArchived": false,
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "jobby-pdf",
        "authentication": "headerAuth",
        "options": {
          "responseCode": {
            "values": {}
          }
        }
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2.1,
      "position": [
        -656,
        -80
      ],
      "id": "55f844ef-c35d-46d3-92b5-23285e3fd934",
      "name": "Webhook",
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// 1. R\u00e9cup\u00e9ration du CV et des deux nouvelles propri\u00e9t\u00e9s Notion\nconst properties = $('Webhook').item.json.body?.data?.properties || {};\nconst richTextArray = properties.CV?.rich_text || [];\n\n// Extraction propre depuis Get PageID\nconst company = $('Get PageID').first().json.company || 'company';\nconst jobTitle = $('Get PageID').first().json.jobTitle || 'job';\nconst increment = $('Get PageID').first().json.increment || '01';\n\nlet mdText = richTextArray.map(block => block.plain_text || '').join('');\n\n// Fallback: Parser Markdown robuste align\u00e9 sur marked.js\nfunction robustMarkdownToHtml(md) {\n  const lines = md.split(/\\r?\\n/);\n  let htmlOutput = [];\n  let inList = false;\n\n  for (let line of lines) {\n    let trimmed = line.trim();\n    if (!/^[-\\*]\\s+/.test(trimmed) && inList) {\n      htmlOutput.push('</ul>');\n      inList = false;\n    }\n    if (trimmed === '---' || trimmed === '***') {\n      htmlOutput.push('<hr />');\n      continue;\n    }\n    if (trimmed.startsWith('>')) {\n      let content = line.replace(/^>\\s*/, '').trim().replace(/^\"(.*)\"$/, '$1');\n      htmlOutput.push(`<blockquote>${content}</blockquote>`);\n      continue;\n    }\n    if (trimmed.startsWith('# ')) { htmlOutput.push(`<h1>${trimmed.substring(2)}</h1>`); continue; }\n    if (trimmed.startsWith('## ')) { htmlOutput.push(`<h2>${trimmed.substring(3)}</h2>`); continue; }\n    if (trimmed.startsWith('### ')) { htmlOutput.push(`<h3>${trimmed.substring(4)}</h3>`); continue; }\n    if (trimmed.startsWith('#### ')) { htmlOutput.push(`<h4>${trimmed.substring(5)}</h4>`); continue; }\n    if (trimmed.startsWith('##### ')) { htmlOutput.push(`<h5>${trimmed.substring(6)}</h5>`); continue; }\n    if (trimmed.startsWith('###### ')) { htmlOutput.push(`<h6>${trimmed.substring(7)}</h6>`); continue; }\n\n    if (/^[-\\*]\\s+/.test(trimmed)) {\n      if (!inList) { htmlOutput.push('<ul>'); inList = true; }\n      htmlOutput.push(`<li>${trimmed.replace(/^[-\\*]\\s+/, '')}</li>`);\n      continue;\n    }\n    if (trimmed !== '') {\n      if (line.includes('\u2022') || line.includes('\u00b7')) {\n        htmlOutput.push(`<p style=\"text-align: justify; text-justify: inter-word;\">${line}</p>`);\n      } else {\n        htmlOutput.push(`<p>${line}</p>`);\n      }\n    }\n  }\n  if (inList) htmlOutput.push('</ul>');\n\n  let finalBody = htmlOutput.join('\\n');\n  finalBody = finalBody.replace(/\\*\\*(.*?)\\*\\*/g, '<strong>$1</strong>');\n  finalBody = finalBody.replace(/\\*(.*?)\\*/g, '<em>$1</em>');\n  finalBody = finalBody.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href=\"$2\">$1</a>');\n  return finalBody;\n}\n\n// Fonction utilitaire pour nettoyer les caract\u00e8res sp\u00e9ciaux du futur nom de fichier\nfunction slugify(text) {\n  return text\n    .toString()\n    .toLowerCase()\n    .normalize('NFD') // Supprime les accents\n    .replace(/[\\u0300-\\u036f]/g, '')\n    .trim()\n    .replace(/\\s+/g, '-') // Remplace les espaces par des tirets\n    .replace(/[^a-z0-9\\-]/g, ''); // Supprime le reste\n}\n\nconst finalFileName = \"javarre-\" + slugify(company) + \"-\" + slugify(jobTitle);\n\n// 3. RECUPERATION CONFIG & CSS DE DATA TABLE\nconst config = JSON.parse($('Read Config from Table').first().json.value);\nconst templatesCss = $('Read CSS from Table').first().json.value;\n\n// 4. PARSER MARKDOWN AVEC LE MEME COMPILATEUR QUE L'EDITEUR (MARKED.JS VIA CDN)\nlet compiledHtml;\ntry {\n  const cdnUrl = 'https://cdn.jsdelivr.net/npm/marked/marked.min.js';\n  const response = await fetch(cdnUrl);\n  if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);\n  const markedText = await response.text();\n  const evalGlobal = new Function(markedText + '\\nreturn marked;');\n  const marked = evalGlobal();\n  \n  marked.setOptions({\n    gfm: true,\n    breaks: true\n  });\n  \n  let processedMd = mdText;\n  processedMd = processedMd.replace(/:accent\\[([^\\]]+)\\]/g, '<span class=\"resume-accent\">$1</span>');\n  processedMd = processedMd.replace(/:muted\\[([^\\]]+)\\]/g, '<span class=\"resume-muted\">$1</span>');\n  \n  compiledHtml = marked.parse(processedMd);\n  \n  // Post-process styling for paragraph tags with separators/bullets to justify\n  compiledHtml = compiledHtml.replace(/<p>((?:(?!<\\/p>).)*(?:[\u2022\u00b7])(?:(?!<\\/p>).)*)<\\/p>/g, '<p style=\"text-align: justify; text-justify: inter-word;\">$1</p>');\n} catch (error) {\n  console.warn('Fallback: Failed to load marked.js from CDN, using robustMarkdownToHtml', error);\n  compiledHtml = robustMarkdownToHtml(mdText);\n  compiledHtml = compiledHtml.replace(/:accent\\[([^\\]]+)\\]/g, '<span class=\"resume-accent\">$1</span>');\n  compiledHtml = compiledHtml.replace(/:muted\\[([^\\]]+)\\]/g, '<span class=\"resume-muted\">$1</span>');\n}\n\n// Traitement du bloc contact si pr\u00e9sent\ncompiledHtml = compiledHtml.replace(/\\\\\\[CONTACT\\s*:\\s*([^\\]]+)\\\\\\]/gi, (match, contents) => {\n    const parts = contents.split('|').map(p => p.trim());\n    const formattedParts = parts.map(part => {\n        if (part.includes('@') && !part.includes(' ')) {\n            return `<a href=\"mailto:${part}\">&part;</a>`.replace('&part;', part);\n        }\n        if (part.startsWith('http://') || part.startsWith('https://')) {\n            const cleanUrl = part.replace(/^https?:\\/\\/(www\\.)?/, '');\n            return `<a href=\"${part}\" target=\"_blank\">&cleanUrl;</a>`.replace('&cleanUrl;', cleanUrl);\n        }\n        return `<span>${part}</span>`;\n    });\n    return `<div class=\"resume-contact-bar\">&parts;</div>`.replace('&parts;', formattedParts.join(' &nbsp;\u2022&nbsp; '));\n});\n\n// 5. LAYOUT 2 COLUMNS RESTRUCTURING\nlet finalHtml = compiledHtml;\nif (config.layoutMode === '2-column') {\n    const parts = compiledHtml.split(/(?=<h[23]\\b)/i);\n    const headerHtml = parts[0];\n    let mainHtml = '';\n    let sidebarHtml = '';\n    \n    for (let i = 1; i < parts.length; i++) {\n        const part = parts[i];\n        if (part.toLowerCase().startsWith('<h2')) {\n            mainHtml += part;\n        } else if (part.toLowerCase().startsWith('<h3')) {\n            sidebarHtml += part;\n        }\n    }\n    \n    finalHtml = `\n        <div class=\"resume-header\">\n            ${headerHtml}\n        </div>\n        <div class=\"resume-columns ${config.sidebarPosition === 'left' ? 'sidebar-left' : ''}\">\n            <div class=\"resume-main-col\">\n                ${mainHtml}\n            </div>\n            <div class=\"resume-sidebar-col\" style=\"background-color: ${config.sidebarBg}; color: ${config.sidebarText};\">\n                ${sidebarHtml}\n                ${config.showVersion ? `<div class=\"resume-version-sidebar\">v${increment}</div>` : ''}\n            </div>\n        </div>\n    `;\n} else {\n    if (config.showVersion) {\n        finalHtml = finalHtml + `<div class=\"resume-version-footer\">v${increment}</div>`;\n    }\n}\n\n// 6. GENERATE INLINE CSS VARIABLES\nconst inlineVariables = `\n:root {\n    --resume-font-family: ${config.fontFamily};\n    --resume-font-size: ${config.fontSize}px;\n    --resume-line-height: ${config.lineHeight};\n    --resume-heading-scale: ${config.headingScale};\n    --resume-margin-x: ${config.marginX}px;\n    --resume-margin-y: ${config.marginY}px;\n    --resume-section-spacing: ${config.sectionSpacing}px;\n    --resume-color-bg: ${config.colorBg || '#ffffff'};\n    --resume-color-headings: ${config.colorHeadings};\n    --resume-color-body: ${config.colorBody};\n    --resume-color-links: ${config.colorLinks};\n    --resume-color-accent: ${config.colorAccent};\n    --resume-sidebar-bg: ${config.sidebarBg || '#2d3748'};\n    --resume-sidebar-text: ${config.sidebarText || '#ffffff'};\n}`;\n\n// 7. ASSEMBLE STANDALONE DOCUMENT\nconst standaloneHtml = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>${finalFileName}</title>\n  <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n  <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n  <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Playfair+Display:ital,wght@0,600;0,700;1,400&family=Raleway:wght@300;400;500;600;700;800&family=Merriweather:ital,wght@0,300;0,400;0,700;1,300&family=JetBrains+Mono:wght@400;500;700&family=Lora:ital,wght@0,400;0,500;0,600;1,400&display=swap\" rel=\"stylesheet\">\n  <style>\n    ${inlineVariables}\n    ${templatesCss}\n    @media print {\n      body {\n        display: block !important;\n        width: 100% !important;\n        height: auto !important;\n        background: #ffffff !important;\n      }\n      .a4-sheet {\n        width: 100% !important;\n        margin: 0 !important;\n        box-shadow: none !important;\n      }\n    }\n    body {\n        background-color: var(--resume-color-bg, #ffffff);\n        margin: 0;\n        padding: 0;\n        display: flex;\n        justify-content: center;\n    }\n    .a4-sheet {\n        box-shadow: none !important;\n        border-radius: 0 !important;\n        margin: 0 auto;\n    }\n  </style>\n</head>\n<body>\n  <article class=\"a4-sheet\" id=\"resume-output\">\n    ${finalHtml}\n  </article>\n</body>\n</html>`;\n\nreturn [\n  {\n    json: {\n      compiledBody: standaloneHtml,\n      pdfFileName: finalFileName,\n      printBackground: \"true\",\n      marginTop: \"0in\",\n      marginBottom: \"0in\",\n      marginLeft: \"0in\",\n      marginRight: \"0in\"\n    }\n  }\n];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        240,
        -80
      ],
      "id": "ba4574e0-c63b-4d8c-b043-d21b29954aaf",
      "name": "G\u00e9n\u00e9ration du HTML & Style CSS"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $node[\"Notion : Initialiser l'upload\"].json.upload_url }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "notionApi",
        "sendQuery": true,
        "queryParameters": {
          "parameters": []
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Notion-Version",
              "value": "2022-06-28"
            }
          ]
        },
        "sendBody": true,
        "contentType": "multipart-form-data",
        "bodyParameters": {
          "parameters": [
            {
              "parameterType": "formBinaryData",
              "name": "file",
              "inputDataFieldName": "={{ $node[\"(Gotemberg) PDF\"].binary.data }}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        1360,
        -80
      ],
      "id": "425d0195-9b44-4887-ad6c-a86a6af34806",
      "name": "Notion : Envoyer le binaire",
      "credentials": {
        "notionApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.notion.com/v1/file_uploads",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "notionApi",
        "sendQuery": true,
        "queryParameters": {
          "parameters": []
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Notion-Version",
              "value": " 2022-06-28"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"mode\": \"single_part\",\n  \"filename\": \"{{ $('Get PageID').item.json.outputPdf }}\",\n  \"content_type\": \"application/pdf\"\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        1136,
        -80
      ],
      "id": "85ce1de6-67c4-4e7b-be47-b4b4c3a58616",
      "name": "Notion : Initialiser l'upload",
      "credentials": {
        "notionApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// 1. R\u00e9cup\u00e9ration des donn\u00e9es brutes du Webhook\nconst body = $('Webhook').item.json.body?.data || {};\nconst properties = body.properties || {};\n\nconst pageId = body.id;\n\n// 2. Extraction de Company (Type: select)\nlet company = 'company';\nif (properties.Company && properties.Company.select) {\n  company = properties.Company.select.name || 'company';\n}\n\n// 3. Extraction de Job Title / Title (Type: title) - Tol\u00e8re l'ancien et le nouveau nom\nlet jobTitle = 'job';\nconst targetProperty = properties.Title || properties[\"Job Title\"];\n\nif (targetProperty && targetProperty.title && targetProperty.title.length > 0) {\n  jobTitle = targetProperty.title.plain_text || $input.first().json.body.data.properties[\"Job Title\"].title[0].plain_text  || 'job';\n}\n\n// 4. Fonction locale slugify pour nettoyer les cha\u00eenes\nfunction slugify(text) {\n  return text\n    .toString()\n    .toLowerCase()\n    .normalize('NFD') // Supprime les accents\n    .replace(/[\\u0300-\\u036f]/g, '')\n    .trim()\n    .replace(/\\s+/g, '-') // Remplace les espaces par des tirets\n    .replace(/[^a-z0-9\\-]/g, ''); // \u00c9limine le reste\n}\n\n// 5. Forge du nom de fichier final personnalis\u00e9\nconst outputPdf = `javarre-${slugify(company)}_${slugify(jobTitle)}.pdf`;\n\n// 6. Retour des variables propres\nreturn [{ \n  json: { \n    pageId: pageId,\n    company: company,\n    jobTitle: jobTitle,\n    outputPdf: outputPdf\n  } \n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -432,
        -80
      ],
      "id": "3ad811d9-5ff0-4e35-97c3-231e5d5b3553",
      "name": "Get PageID"
    },
    {
      "parameters": {
        "method": "PATCH",
        "url": "=https://api.notion.com/v1/pages/{{ $node[\"Get PageID\"].json.pageId }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "notionApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Notion-Version",
              "value": "2026-03-11"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"properties\": {\n    \"PDF\": {\n      \"type\": \"files\",\n      \"files\": [\n        {\n          \"type\": \"file_upload\",\n          \"file_upload\": { \n            \"id\": \"{{ $('Notion : Initialiser l\\'upload').item.json.id }}\" \n          },\n          \"name\": \"{{ $json.filename }}\"\n        }\n      ]\n    }\n  }\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        1584,
        -80
      ],
      "id": "aee20dc4-18f3-4b2b-a575-1e9066f32baa",
      "name": "Attach Bin to page",
      "credentials": {
        "notionApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "http://gotenberg:3000/forms/chromium/convert/html",
        "sendBody": true,
        "contentType": "multipart-form-data",
        "bodyParameters": {
          "parameters": [
            {
              "parameterType": "formBinaryData",
              "name": "index.html",
              "inputDataFieldName": "indexHtml"
            },
            {
              "name": "printBackground",
              "value": "true"
            },
            {
              "name": "marginTop",
              "value": "0in"
            },
            {
              "name": "marginBottom",
              "value": "0in"
            },
            {
              "name": "marginLeft",
              "value": "0in"
            },
            {
              "name": "marginRight",
              "value": "0in"
            },
            {
              "name": "preferCssPageSize",
              "value": "true"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.4,
      "position": [
        912,
        -80
      ],
      "id": "d28e993d-2353-418d-904b-cd4b6d1ac7d2",
      "name": "(Gotemberg) PDF"
    },
    {
      "parameters": {
        "operation": "toText",
        "sourceProperty": "myRawContent",
        "binaryPropertyName": "indexHtml",
        "options": {
          "addBOM": false,
          "encoding": "utf8",
          "fileName": "=index.html"
        }
      },
      "type": "n8n-nodes-base.convertToFile",
      "typeVersion": 1.1,
      "position": [
        688,
        -80
      ],
      "id": "d43058ca-042b-4786-85ec-7551ff479f79",
      "name": "Convert to File"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "1fb1391e-c916-48ff-ae50-b4da474a5ea1",
              "name": "myRawContent",
              "value": "={{ $json.compiledBody }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        464,
        -80
      ],
      "id": "ba499321-0a26-4a49-b389-3540fa4eed82",
      "name": "Template Design CV"
    },
    {
      "parameters": {
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "value": "OtWoNuYUmoj7knoz",
          "mode": "id"
        },
        "matchType": "allConditions",
        "filters": {
          "conditions": [
            {
              "keyName": "key",
              "keyValue": "config"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1,
      "position": [
        -208,
        -80
      ],
      "id": "a4c9b321-0a26-4a49-b389-3540fa4eed10",
      "name": "Read Config from Table"
    },
    {
      "parameters": {
        "operation": "get",
        "dataTableId": {
          "__rl": true,
          "value": "OtWoNuYUmoj7knoz",
          "mode": "id"
        },
        "matchType": "allConditions",
        "filters": {
          "conditions": [
            {
              "keyName": "key",
              "keyValue": "css"
            }
          ]
        }
      },
      "type": "n8n-nodes-base.dataTable",
      "typeVersion": 1,
      "position": [
        16,
        -80
      ],
      "id": "ba499321-0a26-4a49-b389-3540fa4eed20",
      "name": "Read CSS from Table"
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Get PageID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "G\u00e9n\u00e9ration du HTML & Style CSS": {
      "main": [
        [
          {
            "node": "Template Design CV",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notion : Envoyer le binaire": {
      "main": [
        [
          {
            "node": "Attach Bin to page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notion : Initialiser l'upload": {
      "main": [
        [
          {
            "node": "Notion : Envoyer le binaire",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get PageID": {
      "main": [
        [
          {
            "node": "Read Config from Table",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "(Gotemberg) PDF": {
      "main": [
        [
          {
            "node": "Notion : Initialiser l'upload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Convert to File": {
      "main": [
        [
          {
            "node": "(Gotemberg) PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Template Design CV": {
      "main": [
        [
          {
            "node": "Convert to File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Config from Table": {
      "main": [
        [
          {
            "node": "Read CSS from Table",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read CSS from Table": {
      "main": [
        [
          {
            "node": "G\u00e9n\u00e9ration du HTML & Style CSS",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate",
    "availableInMCP": false
  },
  "staticData": null,
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "versionId": "a9ab848d-0d02-43a2-8728-ca1997482ade",
  "activeVersionId": "a9ab848d-0d02-43a2-8728-ca1997482ade",
  "versionCounter": 65,
  "triggerCount": 1,
  "shared": [
    {
      "updatedAt": "2026-05-29T09:56:13.805Z",
      "createdAt": "2026-05-29T09:56:13.805Z",
      "role": "workflow:owner",
      "workflowId": "dSX8vdBy3PnPdiOU",
      "projectId": "GfFdmZTqGEJQkrXG",
      "project": {
        "updatedAt": "2026-05-11T09:44:44.757Z",
        "createdAt": "2026-05-11T09:34:45.695Z",
        "id": "GfFdmZTqGEJQkrXG",
        "name": "\u00c9ole Wind <megazef@gmail.com>",
        "type": "personal",
        "icon": null,
        "description": null,
        "creatorId": "2792484d-cba3-4156-adba-fbc49134eb55"
      }
    }
  ],
  "tags": [
    {
      "updatedAt": "2026-05-29T11:31:39.972Z",
      "createdAt": "2026-05-29T11:31:39.972Z",
      "id": "5zynHpPHQi9PRMyD",
      "name": "jobby"
    },
    {
      "updatedAt": "2026-05-28T12:28:46.096Z",
      "createdAt": "2026-05-28T12:28:46.096Z",
      "id": "Ft2TXB4BVRBQRwkV",
      "name": "PDF"
    },
    {
      "updatedAt": "2026-05-28T12:28:49.321Z",
      "createdAt": "2026-05-28T12:28:49.321Z",
      "id": "XmFLRaZ2ZU05mmMo",
      "name": "Gotemberg"
    }
  ],
  "activeVersion": {
    "updatedAt": "2026-06-03T11:51:05.017Z",
    "createdAt": "2026-06-03T11:51:05.017Z",
    "versionId": "a9ab848d-0d02-43a2-8728-ca1997482ade",
    "workflowId": "dSX8vdBy3PnPdiOU",
    "nodes": [
      {
        "parameters": {
          "httpMethod": "POST",
          "path": "jobby-pdf",
          "authentication": "headerAuth",
          "options": {
            "responseCode": {
              "values": {}
            }
          }
        },
        "type": "n8n-nodes-base.webhook",
        "typeVersion": 2.1,
        "position": [
          -656,
          -80
        ],
        "id": "55f844ef-c35d-46d3-92b5-23285e3fd934",
        "name": "Webhook",
        "webhookId": "ce00eb54-6844-400c-9aa6-8c1ceb15d940",
        "credentials": {
          "httpHeaderAuth": {
            "id": "QDm4dLNTsiXmLpmM",
            "name": "Header Auth account"
          }
        }
      },
      {
        "parameters": {
          "jsCode": "// 1. R\u00e9cup\u00e9ration du CV et des deux nouvelles propri\u00e9t\u00e9s Notion\nconst properties = $('Webhook').item.json.body?.data?.properties || {};\nconst richTextArray = properties.CV?.rich_text || [];\n\n// Extraction propre depuis Get PageID\nconst company = $('Get PageID').first().json.company || 'company';\nconst jobTitle = $('Get PageID').first().json.jobTitle || 'job';\n\nlet mdText = richTextArray.map(block => block.plain_text || '').join('');\n\n// Fallback: Parser Markdown robuste align\u00e9 sur marked.js\nfunction robustMarkdownToHtml(md) {\n  const lines = md.split(/\\r?\\n/);\n  let htmlOutput = [];\n  let inList = false;\n\n  for (let line of lines) {\n    let trimmed = line.trim();\n    if (!/^[-\\*]\\s+/.test(trimmed) && inList) {\n      htmlOutput.push('</ul>');\n      inList = false;\n    }\n    if (trimmed === '---' || trimmed === '***') {\n      htmlOutput.push('<hr />');\n      continue;\n    }\n    if (trimmed.startsWith('>')) {\n      let content = line.replace(/^>\\s*/, '').trim().replace(/^\"(.*)\"$/, '$1');\n      htmlOutput.push(`<blockquote>${content}</blockquote>`);\n      continue;\n    }\n    if (trimmed.startsWith('# ')) { htmlOutput.push(`<h1>${trimmed.substring(2)}</h1>`); continue; }\n    if (trimmed.startsWith('## ')) { htmlOutput.push(`<h2>${trimmed.substring(3)}</h2>`); continue; }\n    if (trimmed.startsWith('### ')) { htmlOutput.push(`<h3>${trimmed.substring(4)}</h3>`); continue; }\n    if (trimmed.startsWith('#### ')) { htmlOutput.push(`<h4>${trimmed.substring(5)}</h4>`); continue; }\n    if (trimmed.startsWith('##### ')) { htmlOutput.push(`<h5>${trimmed.substring(6)}</h5>`); continue; }\n    if (trimmed.startsWith('###### ')) { htmlOutput.push(`<h6>${trimmed.substring(7)}</h6>`); continue; }\n\n    if (/^[-\\*]\\s+/.test(trimmed)) {\n      if (!inList) { htmlOutput.push('<ul>'); inList = true; }\n      htmlOutput.push(`<li>${trimmed.replace(/^[-\\*]\\s+/, '')}</li>`);\n      continue;\n    }\n    if (trimmed !== '') {\n      if (line.includes('\u2022') || line.includes('\u00b7')) {\n        htmlOutput.push(`<p style=\"text-align: justify; text-justify: inter-word;\">${line}</p>`);\n      } else {\n        htmlOutput.push(`<p>${line}</p>`);\n      }\n    }\n  }\n  if (inList) htmlOutput.push('</ul>');\n\n  let finalBody = htmlOutput.join('\\n');\n  finalBody = finalBody.replace(/\\*\\*(.*?)\\*\\*/g, '<strong>$1</strong>');\n  finalBody = finalBody.replace(/\\*(.*?)\\*/g, '<em>$1</em>');\n  finalBody = finalBody.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href=\"$2\">$1</a>');\n  return finalBody;\n}\n\n// Fonction utilitaire pour nettoyer les caract\u00e8res sp\u00e9ciaux du futur nom de fichier\nfunction slugify(text) {\n  return text\n    .toString()\n    .toLowerCase()\n    .normalize('NFD') // Supprime les accents\n    .replace(/[\\u0300-\\u036f]/g, '')\n    .trim()\n    .replace(/\\s+/g, '-') // Remplace les espaces par des tirets\n    .replace(/[^a-z0-9\\-]/g, ''); // Supprime le reste\n}\n\nconst finalFileName = `javarre-${slugify(company)}-${slugify(jobTitle)}`;\n\n// 3. RECUPERATION CONFIG & CSS DE DATA TABLE\nconst config = JSON.parse($('Read Config from Table').first().json.value);\nconst templatesCss = $('Read CSS from Table').first().json.value;\n\n// 4. PARSER MARKDOWN AVEC LE MEME COMPILATEUR QUE L'EDITEUR (MARKED.JS VIA CDN)\nlet compiledHtml;\ntry {\n  const cdnUrl = 'https://cdn.jsdelivr.net/npm/marked/marked.min.js';\n  const response = await fetch(cdnUrl);\n  if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);\n  const markedText = await response.text();\n  const evalGlobal = new Function(markedText + '\\nreturn marked;');\n  const marked = evalGlobal();\n  \n  marked.setOptions({\n    gfm: true,\n    breaks: true\n  });\n  \n  let processedMd = mdText;\n  processedMd = processedMd.replace(/:accent\\[([^\\]]+)\\]/g, '<span class=\"resume-accent\">$1</span>');\n  processedMd = processedMd.replace(/:muted\\[([^\\]]+)\\]/g, '<span class=\"resume-muted\">$1</span>');\n  \n  compiledHtml = marked.parse(processedMd);\n  \n  // Post-process styling for paragraph tags with separators/bullets to justify\n  compiledHtml = compiledHtml.replace(/<p>((?:(?!<\\/p>).)*(?:[\u2022\u00b7])(?:(?!<\\/p>).)*)<\\/p>/g, '<p style=\"text-align: justify; text-justify: inter-word;\">$1</p>');\n} catch (error) {\n  console.warn('Fallback: Failed to load marked.js from CDN, using robustMarkdownToHtml', error);\n  compiledHtml = robustMarkdownToHtml(mdText);\n  compiledHtml = compiledHtml.replace(/:accent\\[([^\\]]+)\\]/g, '<span class=\"resume-accent\">$1</span>');\n  compiledHtml = compiledHtml.replace(/:muted\\[([^\\]]+)\\]/g, '<span class=\"resume-muted\">$1</span>');\n}\n\n// Traitement du bloc contact si pr\u00e9sent\ncompiledHtml = compiledHtml.replace(/\\[CONTACT\\s*:\\s*([^\\]]+)\\]/gi, (match, contents) => {\n    const parts = contents.split('|').map(p => p.trim());\n    const formattedParts = parts.map(part => {\n        if (part.includes('@') && !part.includes(' ')) {\n            return `<a href=\"mailto:${part}\">${part}</a>`;\n        }\n        if (part.startsWith('http://') || part.startsWith('https://')) {\n            const cleanUrl = part.replace(/^https?:\\/\\/(www\\.)?/, '');\n            return `<a href=\"${part}\" target=\"_blank\">${cleanUrl}</a>`;\n        }\n        return `<span>${part}</span>`;\n    });\n    return `<div class=\"resume-contact-bar\">${formattedParts.join(' &nbsp;\u2022&nbsp; ')}</div>`;\n});\n\n// 5. LAYOUT 2 COLUMNS RESTRUCTURING\nlet finalHtml = compiledHtml;\nif (config.layoutMode === '2-column') {\n    const parts = compiledHtml.split(/(?=<h[23]\\b)/i);\n    const headerHtml = parts[0];\n    let mainHtml = '';\n    let sidebarHtml = '';\n    \n    for (let i = 1; i < parts.length; i++) {\n        const part = parts[i];\n        if (part.toLowerCase().startsWith('<h2')) {\n            mainHtml += part;\n        } else if (part.toLowerCase().startsWith('<h3')) {\n            sidebarHtml += part;\n        }\n    }\n    \n    finalHtml = `\n        <div class=\"resume-header\">\n            ${headerHtml}\n        </div>\n        <div class=\"resume-columns ${config.sidebarPosition === 'left' ? 'sidebar-left' : ''}\">\n            <div class=\"resume-main-col\">\n                ${mainHtml}\n            </div>\n            <div class=\"resume-sidebar-col\" style=\"background-color: ${config.sidebarBg}; color: ${config.sidebarText};\">\n                ${sidebarHtml}\n            </div>\n        </div>\n    `;\n}\n\n// 6. GENERATE INLINE CSS VARIABLES\nconst inlineVariables = `\n:root {\n    --resume-font-family: ${config.fontFamily};\n    --resume-font-size: ${config.fontSize}px;\n    --resume-line-height: ${config.lineHeight};\n    --resume-heading-scale: ${config.headingScale};\n    --resume-margin-x: ${config.marginX}px;\n    --resume-margin-y: ${config.marginY}px;\n    --resume-section-spacing: ${config.sectionSpacing}px;\n    --resume-color-bg: ${config.colorBg || '#ffffff'};\n    --resume-color-headings: ${config.colorHeadings};\n    --resume-color-body: ${config.colorBody};\n    --resume-color-links: ${config.colorLinks};\n    --resume-color-accent: ${config.colorAccent};\n    --resume-sidebar-bg: ${config.sidebarBg || '#2d3748'};\n    --resume-sidebar-text: ${config.sidebarText || '#ffffff'};\n}`;\n\n// 7. ASSEMBLE STANDALONE DOCUMENT\nconst standaloneHtml = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>${finalFileName}</title>\n  <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n  <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n  <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Playfair+Display:ital,wght@0,600;0,700;1,400&family=Raleway:wght@300;400;500;600;700;800&family=Merriweather:ital,wght@0,300;0,400;0,700;1,300&family=JetBrains+Mono:wght@400;500;700&family=Lora:ital,wght@0,400;0,500;0,600;1,400&display=swap\" rel=\"stylesheet\">\n  <style>\n    ${inlineVariables}\n    ${templatesCss}\n    @media print {\n      body {\n        display: block !important;\n        width: 100% !important;\n        height: auto !important;\n        background: #ffffff !important;\n      }\n      .a4-sheet {\n        width: 100% !important;\n        margin: 0 !important;\n        box-shadow: none !important;\n      }\n    }\n    body {\n        background-color: var(--resume-color-bg, #ffffff);\n        margin: 0;\n        padding: 0;\n        display: flex;\n        justify-content: center;\n    }\n    .a4-sheet {\n        box-shadow: none !important;\n        border-radius: 0 !important;\n        margin: 0 auto;\n    }\n  </style>\n</head>\n<body>\n  <article class=\"a4-sheet\" id=\"resume-output\">\n    ${finalHtml}\n  </article>\n</body>\n</html>`;\n\nreturn [\n  {\n    json: {\n      compiledBody: standaloneHtml,\n      pdfFileName: finalFileName,\n      printBackground: \"true\",\n      marginTop: \"0in\",\n      marginBottom: \"0in\",\n      marginLeft: \"0in\",\n      marginRight: \"0in\"\n    }\n  }\n];"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          240,
          -80
        ],
        "id": "ba4574e0-c63b-4d8c-b043-d21b29954aaf",
        "name": "G\u00e9n\u00e9ration du HTML & Style CSS"
      },
      {
        "parameters": {
          "method": "POST",
          "url": "={{ $node[\"Notion : Initialiser l'upload\"].json.upload_url }}",
          "authentication": "predefinedCredentialType",
          "nodeCredentialType": "notionApi",
          "sendQuery": true,
          "queryParameters": {
            "parameters": []
          },
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Notion-Version",
                "value": "2022-06-28"
              }
            ]
          },
          "sendBody": true,
          "contentType": "multipart-form-data",
          "bodyParameters": {
            "parameters": [
              {
                "parameterType": "formBinaryData",
                "name": "file",
                "inputDataFieldName": "={{ $node[\"(Gotemberg) PDF\"].binary.data }}"
              }
            ]
          },
          "options": {}
        },
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.4,
        "position": [
          1360,
          -80
        ],
        "id": "425d0195-9b44-4887-ad6c-a86a6af34806",
        "name": "Notion : Envoyer le binaire",
        "credentials": {
          "notionApi": {
            "id": "KYrsxF0plfLkPpqW",
            "name": "Notion LinkedIn Auto"
          }
        }
      },
      {
        "parameters": {
          "method": "POST",
          "url": "https://api.notion.com/v1/file_uploads",
          "authentication": "predefinedCredentialType",
          "nodeCredentialType": "notionApi",
          "sendQuery": true,
          "queryParameters": {
            "parameters": []
          },
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Notion-Version",
                "value": " 2022-06-28"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={\n  \"mode\": \"single_part\",\n  \"filename\": \"{{ $('Get PageID').item.json.outputPdf }}\",\n  \"content_type\": \"application/pdf\"\n}",
          "options": {}
        },
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.4,
        "position": [
          1136,
          -80
        ],
        "id": "85ce1de6-67c4-4e7b-be47-b4b4c3a58616",
        "name": "Notion : Initialiser l'upload",
        "credentials": {
          "notionApi": {
            "id": "KYrsxF0plfLkPpqW",
            "name": "Notion LinkedIn Auto"
          }
        }
      },
      {
        "parameters": {
          "jsCode": "// 1. R\u00e9cup\u00e9ration des donn\u00e9es brutes du Webhook\nconst body = $('Webhook').item.json.body?.data || {};\nconst properties = body.properties || {};\n\nconst pageId = body.id;\n\n// 2. Extraction du texte MD de CV\nconst richTextArray = properties.CV?.rich_text || [];\nconst mdText = richTextArray.map(block => block.plain_text || '').join('');\n\n// 3. Extraction du nom ($name est la ligne apr\u00e8s le '#' ou sur la ligne du '#')\nlet name = 'resume';\nconst lines = mdText.split(/\\r?\\n/);\nfor (let i = 0; i < lines.length; i++) {\n  const trimmed = lines[i].trim();\n  if (trimmed.startsWith('# ')) {\n    const headingText = trimmed.substring(2).trim();\n    if (headingText) {\n      name = headingText;\n    } else if (i + 1 < lines.length) {\n      name = lines[i + 1].trim();\n    }\n    break;\n  }\n}\n\n// 4. Fonction locale slugify pour nettoyer les cha\u00eenes\nfunction slugify(text) {\n  return text\n    .toString()\n    .toLowerCase()\n    .normalize('NFD')\n    .replace(/[\\u0300-\\u036f]/g, '')\n    .trim()\n    .replace(/\\s+/g, '-')\n    .replace(/[^a-z0-9\\-]/g, '');\n}\n\nconst nameSlug = slugify(name);\nconst today = new Date().toISOString().split('T')[0];\n\n// 5. Calcul de l'incr\u00e9ment quotidien en inspectant les PDF existants\nlet maxIncrement = 0;\nconst pdfFiles = properties.PDF?.files || [];\nconst prefixPattern = new RegExp(`^${nameSlug}-resume-${today}-(\\d+)\\.pdf$`, 'i');\n\nfor (const fileObj of pdfFiles) {\n  const fileName = fileObj.name || '';\n  const match = fileName.match(prefixPattern);\n  if (match) {\n    const inc = parseInt(match[1], 10);\n    if (inc > maxIncrement) {\n      maxIncrement = inc;\n    }\n  }\n}\n\nconst nextIncrement = String(maxIncrement + 1).padStart(2, '0');\nconst finalFileName = `${nameSlug}-resume-${today}-${nextIncrement}`;\nconst outputPdf = `${finalFileName}.pdf`;\n\n// 6. Extraction de Company et JobTitle pour r\u00e9trocompatibilit\u00e9 n8n\nlet company = 'company';\nif (properties.Company && properties.Company.select) {\n  company = properties.Company.select.name || 'company';\n}\nlet jobTitle = 'job';\nconst targetProperty = properties.Title || properties[\"Job Title\"];\nif (targetProperty && targetProperty.title && targetProperty.title.length > 0) {\n  jobTitle = targetProperty.title.plain_text || 'job';\n}\n\n// 7. Retour des variables\nreturn [{ \n  json: {\n    pageId: pageId,\n    company: company,\n    jobTitle: jobTitle,\n    outputPdf: outputPdf,\n    increment: nextIncrement\n  }\n}];"
        },
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          -432,
          -80
        ],
        "id": "3ad811d9-5ff0-4e35-97c3-231e5d5b3553",
        "name": "Get PageID"
      },
      {
        "parameters": {
          "method": "PATCH",
          "url": "=https://api.notion.com/v1/pages/{{ $node[\"Get PageID\"].json.pageId }}",
          "authentication": "predefinedCredentialType",
          "nodeCredentialType": "notionApi",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Notion-Version",
                "value": "2026-03-11"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={\n  \"properties\": {\n    \"PDF\": {\n      \"type\": \"files\",\n      \"files\": [\n        {\n          \"type\": \"file_upload\",\n          \"file_upload\": { \n            \"id\": \"{{ $('Notion : Initialiser l\\'upload').item.json.id }}\" \n          },\n          \"name\": \"{{ $json.filename }}\"\n        }\n      ]\n    }\n  }\n}",
          "options": {}
        },
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.4,
        "position": [
          1584,
          -80
        ],
        "id": "aee20dc4-18f3-4b2b-a575-1e9066f32baa",
        "name": "Attach Bin to page",
        "credentials": {
          "notionApi": {
            "id": "KYrsxF0plfLkPpqW",
            "name": "Notion LinkedIn Auto"
          }
        }
      },
      {
        "parameters": {
          "method": "POST",
          "url": "http://gotenberg:3000/forms/chromium/convert/html",
          "sendBody": true,
          "contentType": "multipart-form-data",
          "bodyParameters": {
            "parameters": [
              {
                "parameterType": "formBinaryData",
                "name": "index.html",
                "inputDataFieldName": "indexHtml"
              },
              {
                "name": "printBackground",
                "value": "true"
              },
              {
                "name": "marginTop",
                "value": "0in"
              },
              {
                "name": "marginBottom",
                "value": "0in"
              },
              {
                "name": "marginLeft",
                "value": "0in"
              },
              {
                "name": "marginRight",
                "value": "0in"
              },
              {
                "name": "preferCssPageSize",
                "value": "true"
              }
            ]
          },
          "options": {}
        },
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.4,
        "position": [
          912,
          -80
        ],
        "id": "d28e993d-2353-418d-904b-cd4b6d1ac7d2",
        "name": "(Gotemberg) PDF"
      },
      {
        "parameters": {
          "operation": "toText",
          "sourceProperty": "myRawContent",
          "binaryPropertyName": "indexHtml",
          "options": {
            "addBOM": false,
            "encoding": "utf8",
            "fileName": "=index.html"
          }
        },
        "type": "n8n-nodes-base.convertToFile",
        "typeVersion": 1.1,
        "position": [
          688,
          -80
        ],
        "id": "d43058ca-042b-4786-85ec-7551ff479f79",
        "name": "Convert to File"
      },
      {
        "parameters": {
          "assignments": {
            "assignments": [
              {
                "id": "1fb1391e-c916-48ff-ae50-b4da474a5ea1",
                "name": "myRawContent",
                "value": "={{ $json.compiledBody }}",
                "type": "string"
              }
            ]
          },
          "options": {}
        },
        "type": "n8n-nodes-base.set",
        "typeVersion": 3.4,
        "position": [
          464,
          -80
        ],
        "id": "ba499321-0a26-4a49-b389-3540fa4eed82",
        "name": "Template Design CV"
      },
      {
        "parameters": {
          "operation": "get",
          "dataTableId": {
            "__rl": true,
            "value": "OtWoNuYUmoj7knoz",
            "mode": "id"
          },
          "matchType": "allConditions",
          "filters": {
            "conditions": [
              {
                "keyName": "key",
                "keyValue": "config"
              }
            ]
          }
        },
        "type": "n8n-nodes-base.dataTable",
        "typeVersion": 1,
        "position": [
          -208,
          -80
        ],
        "id": "a4c9b321-0a26-4a49-b389-3540fa4eed10",
        "name": "Read Config from Table"
      },
      {
        "parameters": {
          "operation": "get",
          "dataTableId": {
            "__rl": true,
            "value": "OtWoNuYUmoj7knoz",
            "mode": "id"
          },
          "matchType": "allConditions",
          "filters": {
            "conditions": [
              {
                "keyName": "key",
                "keyValue": "css"
              }
            ]
          }
        },
        "type": "n8n-nodes-base.dataTable",
        "typeVersion": 1,
        "position": [
          16,
          -80
        ],
        "id": "ba499321-0a26-4a49-b389-3540fa4eed20",
        "name": "Read CSS from Table"
      }
    ],
    "connections": {
      "Webhook": {
        "main": [
          [
            {
              "node": "Get PageID",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "G\u00e9n\u00e9ration du HTML & Style CSS": {
        "main": [
          [
            {
              "node": "Template Design CV",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Notion : Envoyer le binaire": {
        "main": [
          [
            {
              "node": "Attach Bin to page",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Notion : Initialiser l'upload": {
        "main": [
          [
            {
              "node": "Notion : Envoyer le binaire",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Get PageID": {
        "main": [
          [
            {
              "node": "Read Config from Table",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "(Gotemberg) PDF": {
        "main": [
          [
            {
              "node": "Notion : Initialiser l'upload",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Convert to File": {
        "main": [
          [
            {
              "node": "(Gotemberg) PDF",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Template Design CV": {
        "main": [
          [
            {
              "node": "Convert to File",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Read Config from Table": {
        "main": [
          [
            {
              "node": "Read CSS from Table",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Read CSS from Table": {
        "main": [
          [
            {
              "node": "G\u00e9n\u00e9ration du HTML & Style CSS",
              "type": "main",
              "index": 0
            }
          ]
        ]
      }
    },
    "authors": "\u00c9ole Wind",
    "name": null,
    "description": null,
    "autosaved": false,
    "workflowPublishHistory": [
      {
        "createdAt": "2026-06-03T11:51:05.164Z",
        "id": 210,
        "workflowId": "dSX8vdBy3PnPdiOU",
        "versionId": "a9ab848d-0d02-43a2-8728-ca1997482ade",
        "event": "activated",
        "userId": "2792484d-cba3-4156-adba-fbc49134eb55"
      },
      {
        "createdAt": "2026-06-03T11:51:05.476Z",
        "id": 212,
        "workflowId": "dSX8vdBy3PnPdiOU",
        "versionId": "a9ab848d-0d02-43a2-8728-ca1997482ade",
        "event": "activated",
        "userId": "2792484d-cba3-4156-adba-fbc49134eb55"
      },
      {
        "createdAt": "2026-06-03T11:51:05.094Z",
        "id": 209,
        "workflowId": "dSX8vdBy3PnPdiOU",
        "versionId": "a9ab848d-0d02-43a2-8728-ca1997482ade",
        "event": "deactivated",
        "userId": "2792484d-cba3-4156-adba-fbc49134eb55"
      },
      {
        "createdAt": "2026-06-03T11:51:05.453Z",
        "id": 211,
        "workflowId": "dSX8vdBy3PnPdiOU",
        "versionId": "a9ab848d-0d02-43a2-8728-ca1997482ade",
        "event": "deactivated",
        "userId": "2792484d-cba3-4156-adba-fbc49134eb55"
      }
    ]
  }
}

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

About this workflow

Jobby - PDF - dyn. Uses httpRequest, dataTable. Webhook trigger; 11 nodes.

Source: https://github.com/gnueole/jobby-md2html/blob/main/n8n/jobby-pdf-dyn.json — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Web Scraping

Response_Handler. Uses httpRequest, dataTable, n8n-nodes-evolution-api, redis. Webhook trigger; 32 nodes.

HTTP Request, Data Table, N8N Nodes Evolution Api +1
Web Scraping

Receive instant push notifications on your phone and voice announcements on your Google Home every time someone orders from your intranet menu — with cumulative BAC tracking per person.

Data Table, HTTP Request, Home Assistant
Web Scraping

Jobby - PDF - static. Uses httpRequest, dataTable. Webhook trigger; 10 nodes.

HTTP Request, Data Table
Web Scraping

This n8n template provides enterprise-level version control for your workflows using GitHub integration. Stop losing hours to broken workflows and manual exports – get proper commit history, visual di

n8n, Execute Workflow Trigger, HTTP Request +1
Web Scraping

This flow creates dummy files for every item added in your *Arrs (Radarr/Sonarr) with the tag .

HTTP Request, Ssh