{
  "id": "7ebD2fYHEFz7Qzku",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Custom Interactive Forms - Submission Handler",
  "tags": [],
  "nodes": [
    {
      "id": "06da9d06-b087-4cfd-a35f-ccaf59c9ca5b",
      "name": "Read Form Config",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        -208,
        128
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1eAwCFWAICx3m9_ZCka0i0orrtMwcyRXbcunpgHFZc_k/edit#gid=0",
          "cachedResultName": "Form_Config"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1eAwCFWAICx3m9_ZCka0i0orrtMwcyRXbcunpgHFZc_k",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1eAwCFWAICx3m9_ZCka0i0orrtMwcyRXbcunpgHFZc_k/edit?usp=drivesdk",
          "cachedResultName": "Feedback Form"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4
    },
    {
      "id": "1beea621-4227-42e8-beaf-8955a4d37c03",
      "name": "Format Config",
      "type": "n8n-nodes-base.code",
      "position": [
        16,
        128
      ],
      "parameters": {
        "jsCode": "const items = $input.all();\nlet config = { title: '', subtitle: '', fields: [] };\nfor (const item of items) {\n  const data = item.json;\n  if (data.Setting_Type === 'Meta') {\n    config[data.Key] = data.Value;\n  }\n  if (data.Setting_Type === 'Field') {\n    config.fields.push({\n      name: data.Key,\n      type: data.Value,\n      label: data.Label,\n      options: data.Options ? data.Options.split(',').map(s => s.trim()) : [],\n      required: data.Required === 'TRUE' || data.Required === 'true'\n    });\n  }\n}\nreturn [{ json: config }];"
      },
      "typeVersion": 2
    },
    {
      "id": "990ee665-d30d-4717-a36a-8787a54f10ca",
      "name": "Generate HTML",
      "type": "n8n-nodes-base.code",
      "position": [
        288,
        128
      ],
      "parameters": {
        "jsCode": "const inputData = $input.all()[0].json;\n\n// Handle both array and object inputs\nconst config = Array.isArray(inputData) ? inputData[0] : inputData;\n\n// Escape closing script tags to avoid breaking HTML\nconst safeConfig = JSON.stringify(config).replace(/<\\/script>/gi, '<\\\\/script>');\n\n// 1. Get your n8n instance's base URL from its environment variables\nconst n8nBaseUrl = $env.WEBHOOK_URL || \"http:localhost:5678\";\n// 2. Check if you clicked 'Execute workflow' (manual/test) or if it's running live\nconst isTestMode = n8nBaseUrl === \"http:localhost:5678\";\nconst webhookPrefix = isTestMode ? \"webhook-test\" : \"webhook\";\n// 3. Dynamically build the final URL\nconst URL = `${n8nBaseUrl}/${webhookPrefix}/dynamic-form-submit`;\n\nconst html = `<!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\n    <script src=\"https://cdn.tailwindcss.com\"></script>\n    <script defer src=\"https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js\"></script>\n\n    <link\n        href=\"https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap\"\n        rel=\"stylesheet\"\n    />\n\n    <style>\n        body {\n            font-family: 'Plus Jakarta Sans', sans-serif;\n        }\n\n        [x-cloak] {\n            display: none !important;\n        }\n\n        .aurora-bg {\n            background: #070b14;\n            position: fixed;\n            inset: 0;\n            z-index: 0;\n            overflow: hidden;\n        }\n\n        .aurora-bg::before {\n            content: '';\n            position: absolute;\n            top: -30%;\n            left: -20%;\n            width: 70%;\n            height: 70%;\n            background: radial-gradient(ellipse at center, rgba(99,102,241,0.25) 0%, transparent 70%);\n            animation: drift1 18s ease-in-out infinite alternate;\n        }\n\n        .aurora-bg::after {\n            content: '';\n            position: absolute;\n            bottom: -20%;\n            right: -20%;\n            width: 65%;\n            height: 65%;\n            background: radial-gradient(ellipse at center, rgba(168,85,247,0.2) 0%, transparent 70%);\n            animation: drift2 22s ease-in-out infinite alternate;\n        }\n\n        .aurora-mid {\n            position: absolute;\n            top: 40%;\n            left: 30%;\n            width: 50%;\n            height: 50%;\n            background: radial-gradient(ellipse at center, rgba(56,189,248,0.1) 0%, transparent 65%);\n            animation: drift3 26s ease-in-out infinite alternate;\n            pointer-events: none;\n        }\n\n        @keyframes drift1 {\n            from { transform: translate(0, 0) scale(1); }\n            to   { transform: translate(6%, 10%) scale(1.08); }\n        }\n        @keyframes drift2 {\n            from { transform: translate(0, 0) scale(1); }\n            to   { transform: translate(-8%, -6%) scale(1.1); }\n        }\n        @keyframes drift3 {\n            from { transform: translate(0, 0) scale(1); }\n            to   { transform: translate(5%, -8%) scale(0.95); }\n        }\n\n        .glass-card {\n            background: rgba(255, 255, 255, 0.04);\n            backdrop-filter: blur(28px);\n            -webkit-backdrop-filter: blur(28px);\n            border: 1px solid rgba(255, 255, 255, 0.1);\n        }\n\n        .field-input {\n            background: rgba(255, 255, 255, 0.06);\n            border: 1px solid rgba(255, 255, 255, 0.12);\n            color: #f1f5f9;\n            transition: all 0.2s ease;\n        }\n\n        .field-input::placeholder {\n            color: rgba(148, 163, 184, 0.5);\n        }\n\n        .field-input:focus {\n            background: rgba(255, 255, 255, 0.09);\n            border-color: rgba(99, 102, 241, 0.7);\n            box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.15);\n            outline: none;\n        }\n\n        .field-input option {\n            background: #0f172a;\n            color: #f1f5f9;\n        }\n\n        .submit-btn {\n            background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);\n            box-shadow: 0 8px 32px rgba(99, 102, 241, 0.35);\n            transition: all 0.25s ease;\n        }\n\n        .submit-btn:hover:not(:disabled) {\n            background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);\n            box-shadow: 0 12px 40px rgba(99, 102, 241, 0.5);\n            transform: translateY(-1px);\n        }\n\n        .submit-btn:active:not(:disabled) {\n            transform: scale(0.98) translateY(0);\n        }\n\n        .label-text {\n            color: rgba(148, 163, 184, 0.9);\n            letter-spacing: 0.06em;\n            font-size: 0.7rem;\n            font-weight: 600;\n            text-transform: uppercase;\n        }\n\n        .success-ring {\n            box-shadow: 0 0 0 8px rgba(34, 197, 94, 0.1);\n        }\n\n        .icon-glow {\n            box-shadow: 0 8px 32px rgba(99, 102, 241, 0.5);\n        }\n\n        .divider {\n            border-color: rgba(255,255,255,0.08);\n        }\n\n        .select-arrow {\n            color: rgba(148, 163, 184, 0.5);\n        }\n    </style>\n\n    <title>${config.title || 'Dynamic Form Portal'}</title>\n</head>\n\n<body class=\"min-h-screen flex items-center justify-center p-4 md:p-8 overflow-x-hidden relative\" style=\"background:#070b14;\">\n\n    <!-- Aurora Background -->\n    <div class=\"aurora-bg\">\n        <div class=\"aurora-mid\"></div>\n    </div>\n\n    <!-- Subtle grid overlay -->\n    <div class=\"fixed inset-0 z-0 opacity-[0.03]\"\n        style=\"background-image: linear-gradient(rgba(255,255,255,0.4) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,0.4) 1px, transparent 1px); background-size: 40px 40px;\">\n    </div>\n\n    <div\n        x-data=\"formHandler()\"\n        x-cloak\n        class=\"max-w-lg w-full glass-card rounded-3xl p-8 md:p-10 z-10 relative\"\n    >\n\n        <!-- Header -->\n        <div class=\"text-center mb-10\">\n\n            <div class=\"inline-flex p-3.5 bg-indigo-600 rounded-2xl icon-glow mb-6\">\n                <svg class=\"w-7 h-7 text-white\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n                    <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"\n                        d=\"M13 10V3L4 14h7v7l9-11h-7z\"></path>\n                </svg>\n            </div>\n\n            <h1 class=\"text-3xl md:text-[2.1rem] font-bold text-white tracking-tight mb-3 leading-tight\"\n                x-text=\"config.title\"></h1>\n\n            <p class=\"text-slate-400 text-base leading-relaxed\"\n                x-text=\"config.description || config.subtitle\"></p>\n\n            <hr class=\"divider border-t mt-8 mb-0\" />\n\n        </div>\n\n        <!-- Main Form -->\n        <div x-show=\"!submitted\">\n\n            <form @submit.prevent=\"submitForm\" class=\"space-y-5\">\n\n                <template x-for=\"field in config.fields\" :key=\"field.name\">\n\n                    <div class=\"text-left\">\n\n                        <label class=\"block label-text mb-2 ml-0.5\">\n                            <span x-text=\"field.label\"></span>\n                            <span x-show=\"field.required\" class=\"text-indigo-400 ml-0.5\">*</span>\n                        </label>\n\n                        <!-- Select -->\n                        <template x-if=\"field.type === 'select'\">\n                            <div class=\"relative\">\n                                <select\n                                    x-model=\"formData[field.name]\"\n                                    :required=\"field.required\"\n                                    class=\"field-input w-full px-5 py-3.5 rounded-xl appearance-none cursor-pointer pr-11\"\n                                >\n                                    <option value=\"\" disabled>Choose an option\u2026</option>\n                                    <template x-for=\"option in field.options\" :key=\"option\">\n                                        <option :value=\"option\" x-text=\"option\"></option>\n                                    </template>\n                                </select>\n                                <div class=\"select-arrow absolute right-4 top-1/2 -translate-y-1/2 pointer-events-none\">\n                                    <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n                                        <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\"></path>\n                                    </svg>\n                                </div>\n                            </div>\n                        </template>\n\n                        <!-- Textarea -->\n                        <template x-if=\"field.type === 'textarea'\">\n                            <textarea\n                                x-model=\"formData[field.name]\"\n                                :required=\"field.required\"\n                                rows=\"3\"\n                                placeholder=\"Enter details\u2026\"\n                                class=\"field-input w-full px-5 py-3.5 rounded-xl resize-none\"\n                            ></textarea>\n                        </template>\n\n                        <!-- Standard Input -->\n                        <template x-if=\"!['select', 'textarea'].includes(field.type)\">\n                            <input\n                                :type=\"field.type\"\n                                x-model=\"formData[field.name]\"\n                                :required=\"field.required\"\n                                placeholder=\"Enter value\u2026\"\n                                class=\"field-input w-full px-5 py-3.5 rounded-xl\"\n                            />\n                        </template>\n\n                    </div>\n\n                </template>\n\n                <!-- Submit Button -->\n                <div class=\"pt-2\">\n                    <button\n                        type=\"submit\"\n                        :disabled=\"submitting\"\n                        class=\"submit-btn w-full disabled:opacity-50 disabled:cursor-not-allowed text-white font-semibold py-4 rounded-xl flex items-center justify-center gap-3\"\n                    >\n\n                        <span x-show=\"!submitting\" class=\"flex items-center gap-2\">\n                            <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n                                <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 19l9 2-9-18-9 18 9-2zm0 0v-8\"></path>\n                            </svg>\n                            Submit Form\n                        </span>\n\n                        <span x-show=\"submitting\" class=\"flex items-center gap-2\">\n                            <svg class=\"animate-spin h-5 w-5 text-white\" fill=\"none\" viewBox=\"0 0 24 24\">\n                                <circle class=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" stroke-width=\"4\"></circle>\n                                <path class=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"></path>\n                            </svg>\n                            Processing\u2026\n                        </span>\n\n                    </button>\n                </div>\n\n            </form>\n\n        </div>\n\n        <!-- Success State -->\n        <div x-show=\"submitted\" class=\"text-center py-8\">\n\n            <div class=\"w-20 h-20 bg-green-500/10 success-ring rounded-full flex items-center justify-center mx-auto mb-7\">\n                <svg class=\"w-10 h-10 text-green-400\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n                    <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2.5\" d=\"M5 13l4 4L19 7\"></path>\n                </svg>\n            </div>\n\n            <h2 class=\"text-2xl font-bold text-white mb-3\">All done!</h2>\n\n            <p class=\"text-slate-400 text-base mb-8\">Your response has been submitted successfully.</p>\n\n            <button\n                @click=\"resetForm\"\n                class=\"text-indigo-400 hover:text-indigo-300 font-semibold transition-colors flex items-center justify-center gap-2 mx-auto text-sm\"\n            >\n                <svg class=\"w-4 h-4\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n                    <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10 19l-7-7m0 0l7-7m-7 7h18\"></path>\n                </svg>\n                Submit another response\n            </button>\n\n        </div>\n\n        <!-- Error -->\n        <div\n            x-show=\"error\"\n            class=\"mt-5 p-4 rounded-xl text-red-300 text-center text-sm font-medium\"\n            style=\"background: rgba(239,68,68,0.1); border: 1px solid rgba(239,68,68,0.2);\"\n            x-text=\"error\"\n        ></div>\n\n    </div>\n\n    <script>\n\n        function formHandler() {\n\n            const INJECTED_CONFIG = ${safeConfig};\n\n            return {\n\n                submitting: false,\n                submitted: false,\n                error: null,\n\n                config: INJECTED_CONFIG,\n\n                formData: {},\n\n                init() {\n                    if (this.config.fields) {\n                        this.config.fields.forEach(field => {\n                            this.formData[field.name] = '';\n                        });\n                    }\n                },\n\n                async submitForm() {\n\n                    this.submitting = true;\n                    this.error = null;\n\n                    try {\n\n                        const response = await fetch('${URL}', {\n                            method: 'POST',\n                            headers: { 'Content-Type': 'application/json' },\n                            body: JSON.stringify(this.formData)\n                        });\n\n                        if (!response.ok) throw new Error('Submission failed');\n\n                        this.submitted = true;\n\n                    } catch (err) {\n\n                        console.error(err);\n                        this.error = 'Submission failed. Please try again later.';\n\n                    } finally {\n\n                        this.submitting = false;\n\n                    }\n\n                },\n\n                resetForm() {\n\n                    this.submitted = false;\n\n                    Object.keys(this.formData).forEach(key => {\n                        this.formData[key] = '';\n                    });\n\n                }\n\n            };\n\n        }\n\n    </script>\n\n</body>\n</html>`;\n\nreturn [\n    {\n        json: {\n            html\n        }\n    }\n];"
      },
      "typeVersion": 2
    },
    {
      "id": "bb37a34e-3b27-408c-8237-2c1973bf77cb",
      "name": "When clicking \u2018Execute workflow\u2019",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -480,
        0
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "c6117291-f528-4e97-95a9-d30a11775055",
      "name": "Upsert HTML Page",
      "type": "@custom-js/n8n-nodes-pdf-toolkit-v2.pdfToolkit",
      "position": [
        448,
        128
      ],
      "parameters": {
        "pageName": "={{ $('Format Config').item.json.title }}",
        "resource": "page",
        "operation": "upsert",
        "htmlContent": "={{ $json.html }}"
      },
      "credentials": {
        "customJsApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "0e1da581-715a-4b2b-8437-9e3c7bd0872e",
      "name": "Webhook (POST)",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -368,
        480
      ],
      "parameters": {
        "path": "dynamic-form-submit",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "lastNode"
      },
      "typeVersion": 1
    },
    {
      "id": "7f44215b-4353-48c3-9f42-69ae936bbd28",
      "name": "Add Timestamp",
      "type": "n8n-nodes-base.code",
      "position": [
        96,
        480
      ],
      "parameters": {
        "jsCode": "const body = $input.all()[0].json.body;\nbody.timestamp = new Date().toISOString();\nreturn [{ json: body }];"
      },
      "typeVersion": 2
    },
    {
      "id": "c8467345-3c27-4e38-b587-34c138b83531",
      "name": "Save Response",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        576,
        480
      ],
      "parameters": {
        "columns": {
          "value": {
            "name": "={{ $json.name }}",
            "comments": "={{ $json.comments }}",
            "timestamp": "={{ $json.timestamp }}",
            "department": "={{ $json.department }}",
            "feedback_date": "={{ $json.feedback_date }}"
          },
          "schema": [
            {
              "id": "name",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "department",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "department",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "feedback_date",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "feedback_date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "comments",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "comments",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "timestamp",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "timestamp",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 479587747,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1eAwCFWAICx3m9_ZCka0i0orrtMwcyRXbcunpgHFZc_k/edit#gid=479587747",
          "cachedResultName": "Form_Responses"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1eAwCFWAICx3m9_ZCka0i0orrtMwcyRXbcunpgHFZc_k",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1eAwCFWAICx3m9_ZCka0i0orrtMwcyRXbcunpgHFZc_k/edit?usp=drivesdk",
          "cachedResultName": "Feedback Form"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4
    },
    {
      "id": "df5352fc-1a8a-45a3-9aba-8376ac1a912e",
      "name": "Convert HTML to PDF",
      "type": "@custom-js/n8n-nodes-pdf-toolkit-v2.pdfToolkit",
      "position": [
        736,
        128
      ],
      "parameters": {
        "html": "=<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"UTF-8\">\n<title>KPI Dashbaord</title>\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js\"></script>\n<style>\nbody {\n  font-family: system-ui, -apple-system, sans-serif;\n  background: #f6f7fb;\n  margin: 0;\n  padding: 24px;\n  color: #111;\n}\n.container {\n  max-width: 400px;\n  margin: auto;\n  background: #fff;\n  padding: 24px;\n  border-radius: 12px;\n  box-shadow: 0 2px 8px rgba(0,0,0,0.1);\n}\nh1 {\n  margin-bottom: 24px;\n}\n#qrcode {\n  margin-top: 24px;\n}\n</style>\n</head>\n<body>\n\n<div class=\"container\">\n  <h1>{{$('Format Config').item.json.title}}</h1>\n  <div id=\"qrcode\"></div>\n</div>\n\n<script>\nconst url = \"{{ $json.htmlFileUrl }}\";\n\nnew QRCode(document.getElementById(\"qrcode\"), {\n  text: url,\n  width: 200,\n  height: 200,\n  colorDark: \"#4f46e5\",\n  colorLight: \"#ffffff\",\n  correctLevel: QRCode.CorrectLevel.H\n});\n</script>\n\n</body>\n</html> ",
        "operation": "htmlToPdf",
        "pdfHeightMm": 120
      },
      "credentials": {
        "customJsApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "5100eb03-e81a-4c73-b1e0-d75aa2cbcb59",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -544,
        336
      ],
      "parameters": {
        "color": 5,
        "width": 1504,
        "height": 320,
        "content": "## Handle Form responses\nReceives incoming form submissions via Webhook, adds a timestamp, and securely logs the data back into Google Sheets."
      },
      "typeVersion": 1
    },
    {
      "id": "a4c762af-414f-4a5c-ba8d-2f26acd6cf98",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -544,
        -128
      ],
      "parameters": {
        "color": 5,
        "width": 720,
        "height": 432,
        "content": "## Feedback Form Configuration\nRetrieves the form field definitions from Google Sheets and parses them into a structured JSON format."
      },
      "typeVersion": 1
    },
    {
      "id": "69f54556-3f4c-4352-8917-0c3489dad32f",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        208,
        -128
      ],
      "parameters": {
        "color": 5,
        "width": 400,
        "height": 432,
        "content": "## Generate & Host Form\nDynamically embeds the configuration into the HTML template and publishes the live page to CustomJs server."
      },
      "typeVersion": 1
    },
    {
      "id": "773c2f78-50f6-481a-92c1-b27458298ef5",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        640,
        -128
      ],
      "parameters": {
        "color": 5,
        "width": 320,
        "height": 432,
        "content": "## Generate QR Code\nConverts the HTML or form link into a PDF/QR code to make sharing and access effortless for your end users."
      },
      "typeVersion": 1
    },
    {
      "id": "a7a3a097-fd49-4d27-9bb3-8916c512e67d",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -480,
        160
      ],
      "parameters": {
        "rule": {
          "interval": [
            {}
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "70797e42-6bcb-4bcc-b56a-3259fb0acece",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -960,
        -128
      ],
      "parameters": {
        "color": 5,
        "width": 400,
        "height": 784,
        "content": "## Custom Interactive Forms\n\n### \ud83c\udf1f Overview\nThis workflow powers a fully dynamic, data-driven feedback system. \n\n### \ud83d\udcca Data Source & Integration\nIt automatically pulls form configuration settings (such as field types, dropdown options, and labels) directly from a Google Sheet and seamlessly injects them into a beautiful Tailwind CSS HTML template. \n\n### \u2699\ufe0f Core Processing\nThe workflow handles both generating the live form and listening for user submissions. \n\n### \ud83d\udd04 Triggers & Automation\nYou can trigger it manually whenever you update your form settings, or leave it on a schedule to ensure the live form is always perfectly synced with your spreadsheet.\n"
      },
      "typeVersion": 1
    }
  ],
  "active": true,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "f3557e7d-734a-4a1e-ae31-e29768d40bad",
  "connections": {
    "Add Timestamp": {
      "main": [
        [
          {
            "node": "Save Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format Config": {
      "main": [
        [
          {
            "node": "Generate HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate HTML": {
      "main": [
        [
          {
            "node": "Upsert HTML Page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Response": {
      "main": [
        []
      ]
    },
    "Webhook (POST)": {
      "main": [
        [
          {
            "node": "Add Timestamp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Form Config": {
      "main": [
        [
          {
            "node": "Format Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Read Form Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Upsert HTML Page": {
      "main": [
        [
          {
            "node": "Convert HTML to PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking \u2018Execute workflow\u2019": {
      "main": [
        [
          {
            "node": "Read Form Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}