# How to make PDF from HTML

**URL:** <https://discourse.nodered.org/t/how-to-make-pdf-from-html/67688>\
**Category:** General\
**Created:** [14 September 2022 08:51 UTC](https://discourse.nodered.org/t/how-to-make-pdf-from-html/67688 "2022-09-14T08:51:21Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![smitterer](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/smitterer/32/59179_2.png) [@smitterer](https://discourse.nodered.org/u/smitterer)\
**Post date:** [14 September 2022 08:51 UTC](https://discourse.nodered.org/t/how-to-make-pdf-from-html/67688/1 "2022-09-14T08:51:21Z")

</div>

Hi,

Since I came across some limitations in node-red-contrib-pdf (for example not being able to change the paper size) and seeing that the node seems to be deprecated, I'm trying to switch over to [html-pdf-node - npm](https://www.npmjs.com/package/html-pdf-node).

I'm loading the module and it's dependencies in the module settings of the function code:  
 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/d/8/d89a8c5d5bd1f5caa58b082320ea727a48e811d8.png)

The function currently is:

```auto
const html_to_pdf = new htmlPdfNode();
//let generated_html = msg.generated_html;

let options = { format: 'A4', path: '/media/qsl/test.pdf' };
//let options = { width: '140m', height: '90mm' };
// Example of options with args //
// let options = { format: 'A4', args: ['--no-sandbox', '--disable-setuid-sandbox'] };

let file = { content: "<h1>Welcome to html-pdf-node</h1>" };

html_to_pdf.generatePdf(file, options).then(pdfBuffer => {
  console.log("PDF Buffer:-", pdfBuffer);
});
return msg

```

Unfortunately I get:

`TypeError: htmlPdfNode is not a constructor`

How can I address this issue?

Besides that, I'd like to use a msg value (like msg.id) to define the path. Example: `let options = { format: 'A4', path: '/media/qsl/HEREGOESTHEMSGID.pdf' };`  
Also, I need to load html which is generated in the template node before the function node `into let file = { content: "<h1>Welcome to html-pdf-node</h1>" }` instead of

# Welcome to html-pdf-node

I'd really appreciate your help on this.

Thank you very much

---

<div class="post-metadata">

**Author:** ![Steve-Mcl](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/steve-mcl/32/4826_2.png) [@Steve-Mcl](https://discourse.nodered.org/u/Steve-Mcl)\
**Post date:** [14 September 2022 08:58 UTC](https://discourse.nodered.org/t/how-to-make-pdf-from-html/67688/2 "2022-09-14T08:58:29Z")

</div>

> [@smitterer](#):
>
> node-red-contrib-pdf (for example not being able to change the paper size

Sure you can. Just set it in the `docDefinition`

[https://pdfmake.github.io/docs/0.1/document-definition-object/page/](https://pdfmake.github.io/docs/0.1/document-definition-object/page/)

EDIT:

I assume you mean the [node-red-contrib-pdfmake](https://flows.nodered.org/node/node-red-contrib-pdfmake) node?

---

<div class="post-metadata">

**Author:** ![smitterer](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/smitterer/32/59179_2.png) [@smitterer](https://discourse.nodered.org/u/smitterer)\
**Post date:** [14 September 2022 09:11 UTC](https://discourse.nodered.org/t/how-to-make-pdf-from-html/67688/3 "2022-09-14T09:11:34Z")

</div>

> [@smitterer](#):
>
> node-red-contrib-pdf

Currently I'm using [node-red-contrib-pdf (node) - Node-RED](https://flows.nodered.org/node/node-red-contrib-pdf)

I also thought about using the node use sent but it does not support html code. There is an "extension" to it ([GitHub - Aymkdn/html-to-pdfmake: This module permits to convert HTML to the PDFMake format](https://github.com/Aymkdn/html-to-pdfmake)) but I don't know if it'd be hard to combine a node-red module with a non-node-red node.

---

<div class="post-metadata">

**Author:** ![Steve-Mcl](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/steve-mcl/32/4826_2.png) [@Steve-Mcl](https://discourse.nodered.org/u/Steve-Mcl)\
**Post date:** [14 September 2022 09:46 UTC](https://discourse.nodered.org/t/how-to-make-pdf-from-html/67688/4 "2022-09-14T09:46:24Z")

</div>

> [@smitterer](#):
>
> I don't know if it'd be hard to combine a node-red module with a non-node-red node.

Not really. It is a client side node (needs a window and DOM) but that can be solved with other packages (jsdom).

Here is a demo...  
 ![Code_nV71JDWy9C](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/e/0/e0a4511d6374e364cea24fd49613d3ad40a7a110.gif)

The output...

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/a/5/a58e48a0d104171eddb82ecf4d71807b7088841d.png)

The function...

```javascript
const { JSDOM } = jsdom;
const { window } = new JSDOM("");

const html = htmlToPdfmake(msg.payload, { window: window });

const docDefinition = {
    // a string or { width: number, height: number }
    pageSize: msg.pageSize || 'A4',

    // by default we use portrait, you can change it to landscape if you wish
    pageOrientation: msg.pageOrientation || 'portrait',

    // [left, top, right, bottom] or [horizontal, vertical] or just a number for equal margins
    pageMargins: [40, 60, 40, 60],

    content: [
        html
    ]
};
msg.payload = docDefinition
return msg

```

The Demo flow...

```auto
[{"id":"075ae101fed922d1","type":"pdfmake","z":"4c5ad8c7caa80822","name":"","outputType":"Buffer","inputProperty":"payload","options":"{}","outputProperty":"payload","x":1300,"y":980,"wires":[["4e692a74d7a1230c"]]},{"id":"11a2106c4a5c8d06","type":"function","z":"4c5ad8c7caa80822","name":"HTML to docDef","func":"const { JSDOM } = jsdom;\nconst { window } = new JSDOM(\"\");\n\nconst html = htmlToPdfmake(msg.payload, { window: window });\n\nconst docDefinition = {\n // a string or { width: number, height: number }\n pageSize: msg.pageSize || 'A4',\n\n // by default we use portrait, you can change it to landscape if you wish\n pageOrientation: msg.pageOrientation || 'portrait',\n\n // [left, top, right, bottom] or [horizontal, vertical] or just a number for equal margins\n pageMargins: [40, 60, 40, 60],\n\n content: [\n html\n]\n};\n\n\nmsg.payload = docDefinition\n\nreturn msg","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"htmlToPdfmake","module":"html-to-pdfmake"},{"var":"jsdom","module":"jsdom"}],"x":1300,"y":920,"wires":[["075ae101fed922d1"]]},{"id":"d75fa487a7253c78","type":"inject","z":"4c5ad8c7caa80822","name":"HTML, A6, Landscape","props":[{"p":"payload"},{"p":"topic","vt":"str"},{"p":"filename","v":"c:/temp/b1.pdf","vt":"str"},{"p":"pageOrientation","v":"landscape","vt":"str"},{"p":"pageSize","v":"A6","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"<div> <h1>Different HTML</h1> <table> <tr> <td>col 1</td> <td>col 2</td> </tr> <tr> <td>A</td> <td>1</td> </tr> <tr> <td>B</td> <td>2</td> </tr> <tr> <td>C</td> <td>3</td> </tr> </table> </div>","payloadType":"str","x":1080,"y":940,"wires":[["11a2106c4a5c8d06"]]},{"id":"4e692a74d7a1230c","type":"file","z":"4c5ad8c7caa80822","name":"","filename":"filename","filenameType":"msg","appendNewline":false,"createDir":true,"overwriteFile":"true","encoding":"none","x":1320,"y":1040,"wires":[["9ba7ebda55b1d722"]]},{"id":"18e8442ae7da9bbc","type":"inject","z":"4c5ad8c7caa80822","name":"HTML, A5, Portait","props":[{"p":"payload"},{"p":"topic","vt":"str"},{"p":"filename","v":"c:/temp/a1.pdf","vt":"str"},{"p":"pageSize","v":"A5","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"<div> <h1>My title</h1> <p> This is a sentence with a <strong>bold word</strong>, <em>one in italic</em>, and <u>one with underline</u>. And finally <a href=\"https://www.google.com\">a link to google</a>. </p> </div>","payloadType":"str","x":1100,"y":900,"wires":[["11a2106c4a5c8d06"]]},{"id":"9ba7ebda55b1d722","type":"debug","z":"4c5ad8c7caa80822","name":"","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"filename","targetType":"msg","statusVal":"filename","statusType":"auto","x":1310,"y":1100,"wires":[]}]

```

---

<div class="post-metadata">

**Author:** ![smitterer](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/smitterer/32/59179_2.png) [@smitterer](https://discourse.nodered.org/u/smitterer)\
**Post date:** [14 September 2022 10:09 UTC](https://discourse.nodered.org/t/how-to-make-pdf-from-html/67688/5 "2022-09-14T10:09:01Z")

</div>

Thank you very much,

Unfortunately I get `TypeError: JSDOM is not a constructor`

with the following flow:

```auto
[
    {
        "id": "ee71cb46e6084549",
        "type": "template",
        "z": "9a9055680f904d80",
        "name": "",
        "field": "generated_html",
        "fieldType": "msg",
        "format": "handlebars",
        "syntax": "mustache",
        "template": "\"<!doctype html>\n<html>\n<head>\n<style>\n body {\n background: url('https://cloudlog.online360.at/qslprinter/radio_qsl_sql_bg_withouttext.jpg');\n background-repeat: no-repeat;\n background-size: 140mm 90mm;\n font-family: Arial, Helvetica, sans-serif;\n }\n html {\n height: 100%;\n }\n #sql {\n margin-left: 5mm;\n margin-top: 15mm;\n font-size: 12px;\n }\n #address {\n margin-left: 95mm;\n margin-top: -10mm;\n font-size: 14px;\n font-weight: bold;\n }\n table {\n width: 120mm;\n margin-top: 20mm;\n margin-left: 5mm;\n }\n table, th, td {\n font-size: 13px;\n border: 1px solid;\n text-align: center;\n border-collapse: collapse;\n }\n th, td {\n padding: 5px;\n }\n</style>\n</head>\n<body>\n <div id=\"sql\">SELECT *<br>FROM logbook<br>WHERE callsign='OE3BIO' AND partner='{{COL_CALL}}'<br>ORDER BY date DESC </div>\n <div id=\"address\">To radio: {{COL_CALL}}\n <br>\n {{#COL_QSL_VIA}}\n <b>via {{COL_QSL_VIA}}</b>\n {{/COL_QSL_VIA}}\n </div>\n <table>\n <tr>\n <th>YEAR</th>\n <th>MONTH</th>\n <th>DAY</th>\n <th>TIME (UTC)</th>\n <th>BAND</th>\n <th>MODE</th>\n <th>RST</th>\n </tr>\n <tr>\n <td>{{year}}</td>\n <td>{{month}}</td>\n <td>{{day}}</td>\n <td>{{time}}</td>\n <td>{{COL_BAND}}</td>\n <td>{{COL_MODE}}</td>\n <td>{{COL_RST_SENT}}</td>\n </tr>\n </table>\n</body>\n</html>\"",
        "output": "str",
        "x": 900,
        "y": 480,
        "wires": [
            [
                "834be93ce354c306"
            ]
        ]
    },
    {
        "id": "b80b61d89ced5bf4",
        "type": "inject",
        "z": "9a9055680f904d80",
        "name": "inject demo",
        "props": [
            {
                "p": "partner",
                "v": "ABC123",
                "vt": "str"
            },
            {
                "p": "filename",
                "v": "/media/qsl.pdf",
                "vt": "str"
            },
            {
                "p": "manager",
                "v": "CBA321",
                "vt": "str"
            },
            {
                "p": "COL_TIME_OFF",
                "v": "2022-09-05 20:33:00",
                "vt": "str"
            },
            {
                "p": "COL_MODE",
                "v": "FT8",
                "vt": "str"
            },
            {
                "p": "COL_BAND",
                "v": "20m",
                "vt": "str"
            },
            {
                "p": "COL_RST_SENT",
                "v": "-16",
                "vt": "str"
            },
            {
                "p": "COL_QSL_SENT",
                "v": "R",
                "vt": "str"
            },
            {
                "p": "COL_QSL_SENT",
                "v": "R",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "x": 390,
        "y": 480,
        "wires": [
            [
                "ee71cb46e6084549"
            ]
        ]
    },
    {
        "id": "1f5a7fe3013edd67",
        "type": "mysql",
        "z": "9a9055680f904d80",
        "mydb": "67a5a532e5304528",
        "name": "",
        "x": 730,
        "y": 160,
        "wires": [
            [
                "9f0f9ab18c1b3975",
                "0c360a6d178ffda4"
            ]
        ]
    },
    {
        "id": "0340d4c330c4e0f6",
        "type": "function",
        "z": "9a9055680f904d80",
        "name": "Get QSOs",
        "func": "msg.payload = \"\"\nmsg.topic = 'SELECT COL_PRIMARY_KEY,COL_CALL,COL_QSL_VIA,COL_TIME_ON,COL_MODE,COL_BAND,COL_RST_SENT,COL_QSL_SENT,COL_QSL_SENT FROM TABLE_HRD_CONTACTS_V01 WHERE COL_QSL_SENT=\"R\" ';\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 530,
        "y": 160,
        "wires": [
            [
                "1f5a7fe3013edd67"
            ]
        ]
    },
    {
        "id": "8b3132695fdda741",
        "type": "inject",
        "z": "9a9055680f904d80",
        "name": "Trigger QSL card generation",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "filename",
                "v": "/media/qsl.pdf",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payload": "",
        "payloadType": "date",
        "x": 300,
        "y": 160,
        "wires": [
            [
                "e60da31cd797efdf"
            ]
        ]
    },
    {
        "id": "ea741ffad455ff94",
        "type": "change",
        "z": "9a9055680f904d80",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "COL_CALL",
                "pt": "msg",
                "to": "payload.COL_CALL",
                "tot": "msg"
            },
            {
                "t": "set",
                "p": "COL_QSL_VIA",
                "pt": "msg",
                "to": "payload.COL_QSL_VIA",
                "tot": "msg"
            },
            {
                "t": "set",
                "p": "COL_MODE",
                "pt": "msg",
                "to": "payload.COL_MODE",
                "tot": "msg"
            },
            {
                "t": "set",
                "p": "COL_BAND",
                "pt": "msg",
                "to": "payload.COL_BAND",
                "tot": "msg"
            },
            {
                "t": "set",
                "p": "COL_RST_SENT",
                "pt": "msg",
                "to": "payload.COL_RST_SENT",
                "tot": "msg"
            },
            {
                "t": "set",
                "p": "COL_QSL_SENT",
                "pt": "msg",
                "to": "payload.COL_QSL_SENT",
                "tot": "msg"
            },
            {
                "t": "set",
                "p": "filename",
                "pt": "msg",
                "to": "\"/media/qsl/\" & $.payload.COL_PRIMARY_KEY & \".pdf\"",
                "tot": "jsonata"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 400,
        "y": 300,
        "wires": [
            [
                "a7bc6ca94c8649cb"
            ]
        ]
    },
    {
        "id": "a7bc6ca94c8649cb",
        "type": "moment",
        "z": "9a9055680f904d80",
        "name": "get year",
        "topic": "",
        "input": "COL_TIME_ON_formatted",
        "inputType": "msg",
        "inTz": "ETC/UTC",
        "adjAmount": 0,
        "adjType": "days",
        "adjDir": "add",
        "format": "YYYY",
        "locale": "C",
        "output": "year",
        "outputType": "msg",
        "outTz": "ETC/UTC",
        "x": 640,
        "y": 300,
        "wires": [
            [
                "6eb1f5ba2fddb727"
            ]
        ]
    },
    {
        "id": "6eb1f5ba2fddb727",
        "type": "moment",
        "z": "9a9055680f904d80",
        "name": "get month",
        "topic": "",
        "input": "COL_TIME_ON_formatted",
        "inputType": "msg",
        "inTz": "ETC/UTC",
        "adjAmount": 0,
        "adjType": "days",
        "adjDir": "add",
        "format": "MM",
        "locale": "C",
        "output": "month",
        "outputType": "msg",
        "outTz": "ETC/UTC",
        "x": 640,
        "y": 340,
        "wires": [
            [
                "7adeee6b4f9eebf3"
            ]
        ]
    },
    {
        "id": "7adeee6b4f9eebf3",
        "type": "moment",
        "z": "9a9055680f904d80",
        "name": "get day",
        "topic": "",
        "input": "COL_TIME_ON_formatted",
        "inputType": "msg",
        "inTz": "ETC/UTC",
        "adjAmount": 0,
        "adjType": "days",
        "adjDir": "add",
        "format": "DD",
        "locale": "C",
        "output": "day",
        "outputType": "msg",
        "outTz": "ETC/UTC",
        "x": 640,
        "y": 380,
        "wires": [
            [
                "58994e7e0483c7ac"
            ]
        ]
    },
    {
        "id": "58994e7e0483c7ac",
        "type": "moment",
        "z": "9a9055680f904d80",
        "name": "get time",
        "topic": "",
        "input": "COL_TIME_ON_formatted",
        "inputType": "msg",
        "inTz": "ETC/UTC",
        "adjAmount": 0,
        "adjType": "days",
        "adjDir": "add",
        "format": "HH:mm",
        "locale": "C",
        "output": "time",
        "outputType": "msg",
        "outTz": "ETC/UTC",
        "x": 640,
        "y": 420,
        "wires": [
            [
                "ee71cb46e6084549"
            ]
        ]
    },
    {
        "id": "6c1cd0a9d0a25243",
        "type": "string",
        "z": "9a9055680f904d80",
        "name": "",
        "methods": [
            {
                "name": "toString",
                "params": []
            },
            {
                "name": "replaceAll",
                "params": [
                    {
                        "type": "str",
                        "value": " GMT+0200 (Central European Summer Time)"
                    },
                    {
                        "type": "str",
                        "value": " UTC"
                    }
                ]
            }
        ],
        "prop": "payload.COL_TIME_ON",
        "propout": "COL_TIME_ON_formatted",
        "object": "msg",
        "objectout": "msg",
        "x": 370,
        "y": 240,
        "wires": [
            [
                "ea741ffad455ff94"
            ]
        ]
    },
    {
        "id": "9f0f9ab18c1b3975",
        "type": "split",
        "z": "9a9055680f904d80",
        "name": "",
        "splt": "\\n",
        "spltType": "str",
        "arraySplt": 1,
        "arraySpltType": "len",
        "stream": false,
        "addname": "",
        "x": 890,
        "y": 180,
        "wires": [
            [
                "6c1cd0a9d0a25243"
            ]
        ]
    },
    {
        "id": "7ab6f15f763da16a",
        "type": "function",
        "z": "9a9055680f904d80",
        "name": "function 52",
        "func": "const html_to_pdf = new htmlPdfNode();\n//let generated_html = msg.generated_html;\n\nlet options = { format: 'A4', path: '/media/qsl/test.pdf' };\n//let options = { width: '140m', height: '90mm' };\n// Example of options with args //\n// let options = { format: 'A4', args: ['--no-sandbox', '--disable-setuid-sandbox'] };\n\nlet file = { content: \"<h1>Welcome to html-pdf-node</h1>\" };\n\nhtml_to_pdf.generatePdf(file, options).then(pdfBuffer => {\n console.log(\"PDF Buffer:-\", pdfBuffer);\n});\nreturn msg",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [
            {
                "var": "htmlPdfNode",
                "module": "html-pdf-node"
            },
            {
                "var": "bluebird",
                "module": "bluebird"
            },
            {
                "var": "handlebars",
                "module": "handlebars"
            },
            {
                "var": "inlineCss",
                "module": "inline-css"
            },
            {
                "var": "puppeteer",
                "module": "puppeteer"
            }
        ],
        "x": 1090,
        "y": 420,
        "wires": [
            [
                "041f05f7276be52e",
                "cd8e27f9212632c3"
            ]
        ]
    },
    {
        "id": "834be93ce354c306",
        "type": "change",
        "z": "9a9055680f904d80",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "width",
                "pt": "msg",
                "to": "140",
                "tot": "num"
            },
            {
                "t": "set",
                "p": "height",
                "pt": "msg",
                "to": "90",
                "tot": "num"
            },
            {
                "t": "set",
                "p": "pageOrientation",
                "pt": "msg",
                "to": "landscape",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 1060,
        "y": 480,
        "wires": [
            [
                "11a2106c4a5c8d06"
            ]
        ]
    },
    {
        "id": "0c360a6d178ffda4",
        "type": "debug",
        "z": "9a9055680f904d80",
        "name": "debug 47",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "false",
        "statusVal": "",
        "statusType": "auto",
        "x": 980,
        "y": 120,
        "wires": []
    },
    {
        "id": "e60da31cd797efdf",
        "type": "function",
        "z": "9a9055680f904d80",
        "name": "Get QSOs",
        "func": "msg.payload = \"\"\nmsg.topic = 'SELECT COL_PRIMARY_KEY,COL_CALL,COL_QSL_VIA,COL_TIME_ON,COL_MODE,COL_BAND,COL_RST_SENT,COL_QSL_SENT,COL_QSL_SENT FROM TABLE_HRD_CONTACTS_V01 WHERE COL_CALL=\"DK1PU\" ';\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 530,
        "y": 120,
        "wires": [
            [
                "1f5a7fe3013edd67"
            ]
        ]
    },
    {
        "id": "075ae101fed922d1",
        "type": "pdfmake",
        "z": "9a9055680f904d80",
        "name": "",
        "outputType": "Buffer",
        "inputProperty": "payload",
        "options": "{}",
        "outputProperty": "payload",
        "x": 1340,
        "y": 660,
        "wires": [
            [
                "4e692a74d7a1230c"
            ]
        ]
    },
    {
        "id": "11a2106c4a5c8d06",
        "type": "function",
        "z": "9a9055680f904d80",
        "name": "HTML to docDef",
        "func": "const { JSDOM } = jsdom;\nconst { window } = new JSDOM(\"\");\n\nconst html = htmlToPdfmake(msg.payload, { window: window });\n\nconst docDefinition = {\n // a string or { width: number, height: number }\n pageSize: {\n width: msg.width ,\n height: msg.height\n },\n\n // by default we use portrait, you can change it to landscape if you wish\n pageOrientation: msg.pageOrientation || 'landscape',\n\n // [left, top, right, bottom] or [horizontal, vertical] or just a number for equal margins\n //pageMargins: [40, 60, 40, 60],\n\n content: [\n html\n]\n};\n\n\nmsg.payload = docDefinition\n\nreturn msg",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [
            {
                "var": "htmlToPdfmake",
                "module": "html-to-pdfmake"
            },
            {
                "var": "jsdom",
                "module": "jsdom"
            }
        ],
        "x": 1340,
        "y": 600,
        "wires": [
            [
                "075ae101fed922d1"
            ]
        ]
    },
    {
        "id": "4e692a74d7a1230c",
        "type": "file",
        "z": "9a9055680f904d80",
        "name": "",
        "filename": "filename",
        "filenameType": "msg",
        "appendNewline": false,
        "createDir": true,
        "overwriteFile": "true",
        "encoding": "none",
        "x": 1360,
        "y": 720,
        "wires": [
            [
                "9ba7ebda55b1d722"
            ]
        ]
    },
    {
        "id": "9ba7ebda55b1d722",
        "type": "debug",
        "z": "9a9055680f904d80",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": true,
        "complete": "filename",
        "targetType": "msg",
        "statusVal": "filename",
        "statusType": "auto",
        "x": 1350,
        "y": 780,
        "wires": []
    },
    {
        "id": "67a5a532e5304528",
        "type": "MySQLdatabase",
        "name": "cloudlog db",
        "host": "0.0.0.0",
        "port": "3306",
        "db": "cloudlog",
        "tz": "",
        "charset": "UTF8"
    }
]

```

jsdom is set under modules as I copied the node from your side.

---

<div class="post-metadata">

**Author:** ![Steve-Mcl](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/steve-mcl/32/4826_2.png) [@Steve-Mcl](https://discourse.nodered.org/u/Steve-Mcl)\
**Post date:** [14 September 2022 10:26 UTC](https://discourse.nodered.org/t/how-to-make-pdf-from-html/67688/6 "2022-09-14T10:26:15Z")

</div>

> [@smitterer](#):
>
> Unfortunately I get `TypeError: JSDOM is not a constructor`

As you can see from my demo, it works for me.

1. Does the server running node-red have internet access?
2. Have you tried restarting node-red?
3. What versions of NODE and node-red are you using? (I am using Node 16 and Node-red 3.0.2)
4. Are you running node-red directly or in a container (e.g. docker/HA)?

---

<div class="post-metadata">

**Author:** ![smitterer](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/smitterer/32/59179_2.png) [@smitterer](https://discourse.nodered.org/u/smitterer)\
**Post date:** [14 September 2022 11:00 UTC](https://discourse.nodered.org/t/how-to-make-pdf-from-html/67688/7 "2022-09-14T11:00:12Z")

</div>

Regarding each question:

1. 

yes, I'm actually accessing it via the Internet  
2.  
yes, also the server itself  
3.  
Node-Red 3.0.2  
Node 16.17  
4.  
It's directly installed without a container

I can see jsdom under /.node-red/node\_modules.

---

<div class="post-metadata">

**Author:** ![smitterer](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/smitterer/32/59179_2.png) [@smitterer](https://discourse.nodered.org/u/smitterer)\
**Post date:** [14 September 2022 11:46 UTC](https://discourse.nodered.org/t/how-to-make-pdf-from-html/67688/8 "2022-09-14T11:46:32Z")

</div>

Ok so after running `n latest` and `n prune`, I'm now getting an output.  
Node version is now v18.9.0

The pdf is currently only several empty pages some tables at the end (without content).

Do I have to put some " or ' at the beginning and end of the following template?:

```auto
<!doctype html>
<html>
<head>
<style>
    body {
        background: url('/radio_qsl_sql_bg_withouttext.jpg');
        background-repeat: no-repeat;
        background-size: 140mm 90mm;
        font-family: Arial, Helvetica, sans-serif;
    }
    html {
        height: 100%;
    }
    #sql {
        margin-left: 5mm;
        margin-top: 15mm;
        font-size: 12px;
    }
    #address {
        margin-left: 95mm;
        margin-top: -10mm;
        font-size: 14px;
        font-weight: bold;
    }
    table {
        width: 120mm;
        margin-top: 20mm;
        margin-left: 5mm;
    }
    table, th, td {
        font-size: 13px;
        border: 1px solid;
        text-align: center;
        border-collapse: collapse;
    }
    th, td {
        padding: 5px;
    }
</style>
</head>
<body>
    <div id="sql">SELECT *<br>FROM logbook<br>WHERE callsign='OE3BIO' AND partner='{{COL_CALL}}'<br>ORDER BY date DESC </div>
    <div id="address">To radio: {{COL_CALL}}
        <br>
        {{#COL_QSL_VIA}}
        <b>via {{COL_QSL_VIA}}</b>
        {{/COL_QSL_VIA}}
    </div>
    <table>
        <tr>
            <th>YEAR</th>
            <th>MONTH</th>
            <th>DAY</th>
            <th>TIME (UTC)</th>
            <th>BAND</th>
            <th>MODE</th>
            <th>RST</th>
        </tr>
        <tr>
            <td>{{year}}</td>
            <td>{{month}}</td>
            <td>{{day}}</td>
            <td>{{time}}</td>
            <td>{{COL_BAND}}</td>
            <td>{{COL_MODE}}</td>
            <td>{{COL_RST_SENT}}</td>
        </tr>
    </table>
</body>
</html>

```

---

<div class="post-metadata">

**Author:** ![smitterer](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/smitterer/32/59179_2.png) [@smitterer](https://discourse.nodered.org/u/smitterer)\
**Post date:** [14 September 2022 12:08 UTC](https://discourse.nodered.org/t/how-to-make-pdf-from-html/67688/9 "2022-09-14T12:08:48Z")

</div>

Ok it seems to be some problem with page width and height as well as the node not accepting all css.

I will play a bit with them and post a solution, if I find one.

---

<div class="post-metadata">

**Author:** ![smitterer](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/smitterer/32/59179_2.png) [@smitterer](https://discourse.nodered.org/u/smitterer)\
**Post date:** [15 September 2022 08:13 UTC](https://discourse.nodered.org/t/how-to-make-pdf-from-html/67688/10 "2022-09-15T08:13:21Z")

</div>

After hundreds of pdf generations, I'm now somehow happy with the result:

```auto
[
    {
        "id": "d3a42c6fb6abe1dc",
        "type": "template",
        "z": "9a9055680f904d80",
        "name": "",
        "field": "payload",
        "fieldType": "msg",
        "format": "handlebars",
        "syntax": "mustache",
        "template": "<!doctype html>\n<html>\n<head>\n</head>\n<body>\n <div class=\"sql\">SELECT *<br>FROM logbook<br>WHERE callsign='OE3BIO' AND partner='{{COL_CALL}}'<br>ORDER BY date DESC </div>\n <div class=\"address\">To radio: {{COL_CALL}}\n <br>\n {{#COL_QSL_VIA}}\n <b>via {{COL_QSL_VIA}}</b>\n {{/COL_QSL_VIA}}\n </div>\n <div class=\"qso\" style=\"font-size:13px; text-align:center;\">\n <table style=\"border-collapse:collapse; border:1px solid;\">\n <tr style=\"font-weight:bold;\">\n <th width=\"50px\">YEAR</th>\n <th width=\"50px\">MONTH</th>\n <th width=\"50px\">DAY</th>\n <th width=\"80px\">TIME (UTC)</th>\n <th width=\"50px\">BAND</th>\n <th width=\"50px\">MODE</th>\n <th width=\"50px\">RST</th>\n </tr>\n <tr style=\"padding:5px\">\n <td>{{year}}</td>\n <td>{{month}}</td>\n <td>{{day}}</td>\n <td>{{time}}</td>\n <td>{{COL_BAND}}</td>\n <td>{{COL_MODE}}</td>\n <td>{{COL_RST_SENT}}</td>\n </tr>\n </table>\n </div>\n</body>\n</html>",
        "output": "str",
        "x": 880,
        "y": 480,
        "wires": [
            [
                "834be93ce354c306"
            ]
        ]
    },
    {
        "id": "834be93ce354c306",
        "type": "change",
        "z": "9a9055680f904d80",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "pageOrientation",
                "pt": "msg",
                "to": "landscape",
                "tot": "str"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 1090,
        "y": 480,
        "wires": [
            [
                "11a2106c4a5c8d06"
            ]
        ]
    },
    {
        "id": "11a2106c4a5c8d06",
        "type": "function",
        "z": "9a9055680f904d80",
        "name": "HTML to docDef",
        "func": "const { JSDOM } = jsdom;\nconst { window } = new JSDOM(\"\");\n\nconst html = htmlToPdfmake(msg.payload, { window: window, tableAutoSize:true });\n\nconst docDefinition = {\n // a string or { width: number, height: number }\npageSize: {\n width: 396.85,\n height: 255.118\n },\n\n//pageSize: msg.pageSize || 'A4',\n\n // by default we use portrait, you can change it to landscape if you wish\n pageOrientation: msg.pageOrientation || 'landscape',\n pageMargins: [20, 0, 0, 0],\n\n // [left, top, right, bottom] or [horizontal, vertical] or just a number for equal margins\n //pageMargins: [40, 60, 40, 60],\n\n content: [\n html\n],\n styles:{\n sql:{\n //marginLeft: 14.1732,\n marginTop: 40,\n fontSize: 9,\n marginBottom: 25\n },\n address:{\n marginLeft: 270,\n marginTop: -80,\n fontSize: 10.5,\n bold: true,\n marginBottom: 40\n },\n //qso:{\n //alignment: 'center'\n //width: 100,\n //width: 200,\n //marginTop: 56.6929,\n //paddingLeft: 14.1732,\n //fontSize: 13\n //}\n}};\n\n\nmsg.payload = docDefinition\n\nreturn msg",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [
            {
                "var": "htmlToPdfmake",
                "module": "html-to-pdfmake"
            },
            {
                "var": "jsdom",
                "module": "jsdom"
            }
        ],
        "x": 1340,
        "y": 600,
        "wires": [
            [
                "075ae101fed922d1"
            ]
        ]
    },
    {
        "id": "075ae101fed922d1",
        "type": "pdfmake",
        "z": "9a9055680f904d80",
        "name": "",
        "outputType": "Buffer",
        "inputProperty": "payload",
        "options": "{}",
        "outputProperty": "payload",
        "x": 1340,
        "y": 660,
        "wires": [
            [
                "4e692a74d7a1230c"
            ]
        ]
    },
    {
        "id": "4e692a74d7a1230c",
        "type": "file",
        "z": "9a9055680f904d80",
        "name": "",
        "filename": "filename",
        "filenameType": "msg",
        "appendNewline": false,
        "createDir": true,
        "overwriteFile": "true",
        "encoding": "none",
        "x": 1360,
        "y": 720,
        "wires": [
            [
                "9ba7ebda55b1d722"
            ]
        ]
    },
    {
        "id": "9ba7ebda55b1d722",
        "type": "debug",
        "z": "9a9055680f904d80",
        "name": "",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": true,
        "complete": "filename",
        "targetType": "msg",
        "statusVal": "filename",
        "statusType": "auto",
        "x": 1350,
        "y": 780,
        "wires": []
    }
]

```

Template:

```auto
<!doctype html>
<html>
<head>
</head>
<body>
    <div class="sql">SELECT *<br>FROM logbook<br>WHERE callsign='OE3BIO' AND partner='{{COL_CALL}}'<br>ORDER BY date DESC </div>
    <div class="address">To radio: {{COL_CALL}}
        <br>
        {{#COL_QSL_VIA}}
        <b>via {{COL_QSL_VIA}}</b>
        {{/COL_QSL_VIA}}
    </div>
    <div class="qso" style="font-size:13px; text-align:center;">
    <table style="border-collapse:collapse; border:1px solid;">
        <tr style="font-weight:bold;">
            <th width="50px">YEAR</th>
            <th width="50px">MONTH</th>
            <th width="50px">DAY</th>
            <th width="80px">TIME (UTC)</th>
            <th width="50px">BAND</th>
            <th width="50px">MODE</th>
            <th width="50px">RST</th>
        </tr>
        <tr style="padding:5px">
            <td>{{year}}</td>
            <td>{{month}}</td>
            <td>{{day}}</td>
            <td>{{time}}</td>
            <td>{{COL_BAND}}</td>
            <td>{{COL_MODE}}</td>
            <td>{{COL_RST_SENT}}</td>
        </tr>
    </table>
    </div>
</body>
</html>

```

Function:

```auto
const { JSDOM } = jsdom;
const { window } = new JSDOM("");

const html = htmlToPdfmake(msg.payload, { window: window, tableAutoSize:true });

const docDefinition = {
    // a string or { width: number, height: number }
pageSize: {
    width: 396.85,
    height: 255.118
  },

//pageSize: msg.pageSize || 'A4',

    // by default we use portrait, you can change it to landscape if you wish
    pageOrientation: msg.pageOrientation || 'landscape',
    pageMargins: [20, 0, 0, 0],

    // [left, top, right, bottom] or [horizontal, vertical] or just a number for equal margins
    //pageMargins: [40, 60, 40, 60],

    content: [
        html
    ],
    styles:{
        sql:{
            //marginLeft: 14.1732,
            marginTop: 40,
            fontSize: 9,
            marginBottom: 25
        },
        address:{
            marginLeft: 270,
            marginTop: -80,
            fontSize: 10.5,
            bold: true,
            marginBottom: 40
        },
        //qso:{
            //alignment: 'center'
            //width: 100,
            //width: 200,
            //marginTop: 56.6929,
            //paddingLeft: 14.1732,
            //fontSize: 13
        //}
}};

msg.payload = docDefinition

return msg

```

The thing is, html-to-pdfmake make and pdfmake both do not allow all css functionalities in html or in the function.  
Especially tables are very hard to format and need inline css and so on.

There are definitely people who are able to clean this up but for me it's working now.

Thanks again to @Steve-Mcl

I'll now try to add another row to the table for other payloads with the same identifier (callsign).

---

<div class="post-metadata">

**Author:** ![system](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/1X/d073cd938eafa2e558d7c2cd59003b3ef4963033.png) [@system](https://discourse.nodered.org/u/system)\
**Post date:** [14 November 2022 08:14 UTC](https://discourse.nodered.org/t/how-to-make-pdf-from-html/67688/11 "2022-11-14T08:14:13Z")

</div>

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.
