How to Debug html-to-pdfmake Conversion Issues: A Complete Troubleshooting Guide

To debug html-to-pdfmake conversion issues, inspect the parsed DOM before conversion, verify your options object, and trace the recursive walk through parseElement while checking style aggregation in applyStyle and parseStyle.

The html-to-pdfmake library transforms HTML strings into pdfmake document definitions, but when the output doesn't match expectations, debugging requires understanding the three-phase conversion pipeline in index.js. Whether you're troubleshooting missing styles, broken tables, or disappearing images, this guide provides systematic steps to debug html-to-pdfmake conversion issues using the actual source code implementation.


Verify Input HTML and DOM Parsing

The conversion begins by parsing your HTML string using the browser's native DOMParser (lines 21-31 in index.js). Errors here propagate silently or produce unexpected node structures.

Check for Malformed HTML

Stray whitespace, unclosed tags, or HTML entities can cause the parser to insert unexpected nodes. Before calling htmlToPdfMake, test the parsed DOM directly:

const html = '<div style="display:none">Hidden</div><p>  spaced  text </p>';
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
console.log(doc.body.innerHTML); // Inspect the normalized structure

Verify Hidden Elements and Whitespace Handling

Two options control initial filtering:

  • showHidden: Set to true to include elements with display:none (default is false)
  • removeExtraBlanks: Set to false to preserve whitespace in <pre> or <code> blocks
const htmlToPdfMake = require('html-to-pdfmake');
const doc = htmlToPdfMake(html, {
  showHidden: true,
  removeExtraBlanks: false
});

Inspect the Options Object

The options object (lines 13-20) directly influences how parseElement and applyStyle behave. Misconfiguration here is a common source of silent failures.

Default Styles Override

The defaultStyles option merges with the built-in style map (lines 58-79). Setting a key to null removes that style entirely, but forgetting to spread existing defaults can wipe unintended styles:

// Removes bold styling from <b> tags only
const doc = htmlToPdfMake('<b>no bold</b>', {
  defaultStyles: { b: null }
});

Table Auto-Sizing Pitfalls

When tableAutoSize is true, the library calculates column widths from width attributes or percentage-based CSS. If the <table> lacks a width="100%" attribute, the auto-sizing logic may skip percentage calculations (line 194).

const html = `
<table width="100%">
  <tr><td style="width:30%">A</td><td style="width:70%">B</td></tr>
</table>`;
const doc = htmlToPdfMake(html, { tableAutoSize: true });

Image Reference Mode

With imagesByReference: true, the function returns an object {content, images} instead of a direct document definition. Forgetting to access result.images leads to missing images in the final PDF (lines 166-171).

const result = htmlToPdfMake('<img src="logo.png">', {
  imagesByReference: true
});
console.log(result.images); // Check the generated reference keys

Custom Tag Handler Errors

The customTag callback receives {element, parents, ret} but errors inside this function are silently caught. Add defensive logging:

const doc = htmlToPdfMake('<my-box></my-box>', {
  customTag: ({element, parents, ret}) => {
    try {
      if (element.tagName === 'MY-BOX') {
        return {
          canvas: [{ type: 'rect', x: 0, y: 0, w: 200, h: 50 }],
          margin: [0, 10, 0, 10]
        };
      }
    } catch (e) {
      console.error('Custom tag error:', e);
    }
    return ret;
  }
});

Trace the Recursive Element Walker

The core conversion logic resides in parseElement (lines 41-95), which recursively processes each DOM node. Understanding this walk helps diagnose structural issues.

Stack vs Text Detection

The searchForStack function (lines 37-44) determines whether a node should become a stack (container) or text node. Block-level elements like <div> or <p> trigger stack mode, while inline elements remain text.

If elements appear concatenated on a single line when they should stack vertically, check that the parent container is recognized as a block element:

// Debug the node type detection
const html = '<div><p>Line 1</p><p>Line 2</p></div>';
const doc = htmlToPdfMake(html);
console.log(JSON.stringify(doc, null, 2));
// Should show nested stacks, not flat text array

Temporary Debug Logging

Insert a temporary log at the top of parseElement to trace the traversal:

// Inside node_modules/html-to-pdfmake/index.js, temporarily add:
if (element.nodeName) {
  console.log('Parsing', element.nodeName, 
              'parent chain:', parents.map(p => p.nodeName));
}

This reveals the exact path taken through the DOM and helps identify where the walker diverges from expectations.


Debug Style Aggregation

Styles flow through three layers: default styles (lines 58-79), inline style attributes, and CSS classes. The applyStyle (lines 55-90) and parseStyle (lines 68-112) functions handle this transformation.

CSS-to-pdfmake Property Mapping

Not all CSS properties map directly to pdfmake. Key mappings include:

  • background-colorfillColor (for table cells)
  • text-decoration: underlinedecoration: 'underline'
  • font-weight: boldbold: true
  • Margins/paddings → arrays [left, top, right, bottom] in points

Common Style Bugs

RGBA Opacity Dropped: When using rgba() colors with alpha < 1, the opacity is lost unless fillOpacity is explicitly set. Use solid hex colors (#ff0000) for reliable rendering.

Margin Auto Ignored: The parser silently drops margin: auto (line 334 in parseStyle). Always specify concrete units:

// Bad
<div style="margin: auto">

// Good
<div style="margin: 20pt">

Inherited Underlines: Text decoration properties inherit differently than other styles. If a parent <div> has text-decoration: underline, child text nodes may inherit this unexpectedly depending on the removeTagClasses setting.

Inspecting Computed Styles

Always serialize the final document definition to verify style transformation:

const doc = htmlToPdfMake('<p style="margin:2cm;color:#0f0">Test</p>');
console.log(JSON.stringify(doc, null, 2));
// Expected output shows margin converted to points: [56.6929,56.6929,56.6929,56.6929]
// And color normalized to: "#00ff00"

Troubleshoot Table Rendering

Tables undergo complex post-processing for column spans, row spans, and width calculations (lines 95-99, 115-130).

Colspan and Rowspan Handling

The library processes colspan and rowspan attributes during the stack-building phase (lines 135-147). If cells appear misaligned or missing:

  1. Verify that colSpan and rowSpan are lowercase in your HTML (the parser checks these attributes specifically)
  2. Ensure table rows (<tr>) are direct children of <table>, not wrapped in intermediate <tbody> tags that might confuse the walker

Table Auto-Sizing Logic

When tableAutoSize: true, the library extracts widths from:

  • width attributes on <table> and <col> elements
  • Percentage values in style="width:X%"

Critical requirement: The <table> must have width="100%" attribute for percentage-based column widths to calculate correctly (line 194). Without this, the library cannot determine the relative proportions.

Data Attributes for Advanced Control

Use data-pdfmake to inject raw pdfmake properties:

const html = `
<table data-pdfmake='{"layout":"noBorders","dontBreakRows":true}' width="100%">
  <tr><th colspan="2">Header</th></tr>
  <tr><td rowspan="2">A</td><td>B</td></tr>
  <tr><td>C</td></tr>
</table>`;

const docDef = htmlToPdfMake(html, {tableAutoSize:true});
console.log(JSON.stringify(docDef.table.layout)); // "noBorders"

If data-pdfmake JSON is malformed, the library logs to console.error but continues processing, potentially ignoring your custom settings.


Verify Image Handling

Images support two processing modes controlled by the imagesByReference option (lines 166-171).

Embedded vs Reference Mode

Embedded mode (default): Returns image nodes with src containing data URLs or file paths directly in the content tree.

Reference mode (imagesByReference: true): Returns an object with content and images properties. Image URLs are replaced with reference keys (img_ref_<suffix><index>) and the actual data is stored in the images map.

Debugging Image Paths

When images appear missing in the final PDF:

  1. Check the return structure: If using imagesByReference: true, ensure you're accessing result.images, not just result or result.content.
const result = htmlToPdfMake('<img src="logo.png">', {
  imagesByReference: true
});

// Correct way to inspect
console.log('Content:', result.content);
console.log('Images map:', result.images); 
// { img_ref_xxxxxx0: 'data:image/png;base64,...' }
  1. Verify URL accessibility: The library handles both data URLs and relative paths, but the final PDF generation (performed by pdfmake, not html-to-pdfmake) requires that image URLs be resolvable in the target environment (Node.js filesystem or browser blob URLs).

  2. Check for JSON string wrapping: The parser handles cases where src attributes contain JSON-encoded strings, but verify your HTML doesn't contain double-encoded entities.


Summary

  • Start with the input: Verify your HTML parses correctly in a browser's DOMParser and check that showHidden and removeExtraBlanks options match your content needs.
  • Audit the options: Misconfigured defaultStyles, tableAutoSize, or imagesByReference settings are common culprits for missing formatting or content.
  • Trace the recursion: Add temporary logging inside parseElement to follow the DOM traversal and verify that searchForStack correctly identifies block-level containers.
  • Inspect style aggregation: Use JSON.stringify on the output to verify that applyStyle and parseStyle correctly transformed CSS properties like margins, colors, and decorations.
  • Validate tables and images: Ensure tables have proper width attributes for auto-sizing and that image references are correctly extracted when using imagesByReference mode.

Frequently Asked Questions

Why are my CSS styles not appearing in the PDF output?

Styles flow through applyStyle and parseStyle in index.js, which map CSS properties to pdfmake equivalents. If styles are missing, verify that you haven't set removeTagClasses: true (which removes the auto-generated html-tag classes that carry custom CSS), check that ignoreStyles doesn't include your property, and ensure you're using supported units (px, pt, cm, rem) rather than auto or unsupported CSS variables.

How do I fix tables that render with incorrect column widths?

Tables require explicit width handling in html-to-pdfmake. First, ensure tableAutoSize: true is set in your options. Then verify that your <table> tag includes a width="100%" attribute (line 194 in index.js), as percentage-based column widths cannot calculate without this reference. For complex layouts, use <colgroup> with <col width="X%"> elements placed before any <tr> rows, or inject raw pdfmake properties via data-pdfmake='{"widths":["*","auto"]}'.

Why are images missing from my generated PDF?

Images disappear when the reference map isn't properly handled or when URLs are inaccessible. If using imagesByReference: true, remember that the function returns {content, images} rather than a direct document definition—you must merge result.images into your pdfmake definition's images property. For embedded mode, verify that src attributes contain valid data URLs or resolvable paths, and check the browser console for CORS errors or 404s when loading external images.

How can I debug custom tag handlers that aren't working?

The customTag callback receives {element, parents, ret} but errors inside this function are silently swallowed by the library's error handling. To debug, wrap your custom logic in a try/catch block with explicit console.error logging. Also verify that removeTagClasses isn't true, as this removes the html-tag classes that your handler might rely on for element identification. Test with simple HTML first, then gradually add complexity to isolate the failure point.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →