# PDF to HTML Converter — Tables, Text & Layout (`junipr/pdf-to-html`) Actor

Convert PDFs to clean HTML with page-level output, metadata, table extraction, images, and formatting controls for automation workflows.

- **URL**: https://apify.com/junipr/pdf-to-html.md
- **Developed by:** [junipr](https://apify.com/junipr) (community)
- **Categories:** Automation, Developer tools
- **Stats:** 10 total users, 1 monthly users, 71.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $6.50 / 1,000 page converteds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## PDF to HTML Converter — Tables, Text & Layout

### Introduction

Convert PDFs to clean HTML with page-level output, metadata, table extraction, images, and formatting controls for automation workflows. Unlike most PDF-to-HTML tools that produce visual HTML with absolutely-positioned divs and spans (mimicking PDF layout pixel-by-pixel), this actor generates real semantic elements: `<h1>`-`<h6>` headings, `<table>` with `<thead>`/`<tbody>`, `<ul>`/`<ol>` lists, and `<p>` paragraphs. The output is valid HTML5, screen-reader accessible, and ready for web publishing, CMS import, content migration, or further processing in any pipeline. Batch processing is supported with configurable styling options.

### Why Use This Actor

Most PDF-to-HTML converters produce "visual HTML" — absolute-positioned divs that look like the PDF but have no semantic meaning. This means tables are rendered as scattered text spans, headings are just bigger fonts, and lists become disconnected bullet characters. Our actor produces **semantic HTML** that browsers, search engines, screen readers, and CMS platforms can actually understand.

| Feature | This Actor | pdf2htmlEX | Adobe API | Online Tools |
|---------|-----------|-----------|-----------|-------------|
| Semantic HTML | Headings, tables, lists | Absolute positioning | Partial | Rarely |
| Table detection | Proper `<table>` | Positioned text | Yes | Poor |
| List detection | `<ul>` + `<ol>` | None | Partial | None |
| CSS options | Inline / class / none | Inline only | Class-based | Inline |
| Batch processing | Yes (up to 5,000 PDFs) | CLI only | Yes | Single file |
| Cost | $6.50/1K pages | Free (self-hosted) | $0.05/page | Freemium |
| Setup | Zero config | CLI install + Docker | API key required | Upload UI |

The output is WCAG-friendly: screen readers can navigate headings, read table headers, and traverse list items — something impossible with visual HTML output.

### How to Use

**Zero-config example — just provide a PDF URL:**

```json
{
  "sources": [
    { "url": "https://example.com/report.pdf" }
  ]
}
```

**Node.js (Apify Client):**

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_TOKEN' });

const run = await client.actor('junipr/pdf-to-html').call({
    sources: [{ url: 'https://example.com/document.pdf' }],
    stylingMode: 'class',
    wrapInDocument: true,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items[0].html);
```

**Python:**

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_TOKEN")
run = client.actor("junipr/pdf-to-html").call(run_input={
    "sources": [{"url": "https://example.com/document.pdf"}],
    "stylingMode": "class",
})

dataset = client.dataset(run["defaultDatasetId"]).list_items().items
print(dataset[0]["html"])
```

**Load from Apify Key-Value Store:**

```json
{
  "sources": [
    { "kvStoreKey": "my-document.pdf", "kvStoreId": "abc123" }
  ]
}
```

### Input Configuration

All parameters except `sources` are optional. Common recipes:

**Quick conversion** — URL only, all defaults:

```json
{ "sources": [{ "url": "https://example.com/doc.pdf" }] }
```

**Web publishing** — styled HTML document with WebP images:

```json
{
  "sources": [{ "url": "https://example.com/doc.pdf" }],
  "stylingMode": "class",
  "wrapInDocument": true,
  "imageFormat": "webp",
  "includeDefaultStyles": true
}
```

**CMS import** — pure semantic HTML, no styling, no page breaks:

```json
{
  "sources": [{ "url": "https://example.com/doc.pdf" }],
  "stylingMode": "none",
  "pageBreakMode": "none",
  "wrapInDocument": false
}
```

**Print archive** — preserve all formatting with inline CSS:

```json
{
  "sources": [{ "url": "https://example.com/doc.pdf" }],
  "stylingMode": "inline",
  "preserveFontSizes": true,
  "preserveColors": true,
  "preserveFontStyles": true
}
```

See the Input tab for the full list of parameters with descriptions and defaults.

### Output Format

Each converted PDF produces one dataset item with the full HTML, per-page breakdown, image references, metadata, and conversion stats. Example output (fragment mode):

```html
<h1 class="heading-1">Annual Report 2024</h1>
<p class="paragraph">Revenue grew 23% year-over-year...</p>

<h2 class="heading-2">Financial Summary</h2>
<table class="pdf-table">
  <thead>
    <tr><th>Quarter</th><th>Revenue</th><th>Growth</th></tr>
  </thead>
  <tbody>
    <tr><td>Q1</td><td>$2.1M</td><td>18%</td></tr>
    <tr><td>Q2</td><td>$2.5M</td><td>25%</td></tr>
  </tbody>
</table>

<ul class="pdf-list">
  <li>Expanded into 3 new markets</li>
  <li>Launched enterprise tier</li>
</ul>
```

When `wrapInDocument` is enabled, the output includes `<!DOCTYPE html>`, `<html>`, `<head>` with meta tags from PDF metadata, and a `<style>` block with the default or custom stylesheet.

Extracted images are stored in the run's Key-Value Store and referenced in the `images` array with dimensions, format, and originating page number.

### Tips and Advanced Usage

- **Multi-column PDFs:** Enable `detectColumns` (on by default) to merge multi-column text in natural reading order rather than interleaving columns.
- **Custom CSS:** Use `customCss` with `stylingMode: "class"` to inject your own styles. Class names like `.heading-1`, `.paragraph`, `.pdf-table`, `.pdf-list` are consistent across all documents.
- **Scanned PDFs:** This actor works with text-based PDFs only. For scanned documents, run them through an OCR actor first, then convert the output.
- **Page selection:** Use `pageRange` to convert only specific pages (e.g., `"1-3,7"`) — you only pay for pages actually converted.
- **Batch optimization:** Process up to 5,000 PDFs per run. Each PDF is processed sequentially to manage memory. For very large batches, increase the memory allocation to 4096 MB or higher.
- **CMS integration:** Use `stylingMode: "none"` and `wrapInDocument: false` for WordPress, Contentful, or Strapi imports — these platforms apply their own styling.

### Pricing

This actor uses **Pay-Per-Event (PPE)** pricing at **$6.50 per 1,000 pages converted** ($0.0065 per page) in the queued monetization update.

| Scenario | Pages | Cost |
|----------|-------|------|
| Single 10-page document | 10 | $0.07 |
| Product catalog (100 pages) | 100 | $0.65 |
| Legal contract batch (50 docs x 20 pages) | 1,000 | $6.50 |
| Website migration (500 PDFs x 5 pages) | 2,500 | $16.25 |
| Document archive (10K pages) | 10,000 | $65.00 |

**Not billed:** pages that fail to convert, scanned pages with no text, pages skipped by `pageRange`, empty pages, and duplicate PDFs. You only pay for successfully converted pages.

Compared to Adobe Document Services API ($0.05/page = $50/1K pages), this actor is **87% cheaper** at any scale.

### FAQ

#### What makes this different from pdf2htmlEX?

pdf2htmlEX produces visual HTML — every text element is absolutely positioned with pixel coordinates, mimicking the PDF layout. While it looks identical to the original, the HTML has no semantic meaning. Our actor produces real `<h1>`, `<table>`, `<ul>`, and `<p>` elements that browsers, search engines, and screen readers understand. The tradeoff is that our output may not be pixel-perfect, but it is actually useful as HTML.

#### Can it handle tables with merged cells?

Yes. The actor detects table structures and attempts to identify colspan/rowspan relationships. For very complex tables (deeply nested or irregularly merged), it falls back to a `<pre>` block with formatted text to preserve readability.

#### What happens with scanned/image-only PDF pages?

Scanned pages are detected automatically and produce a `SCANNED_PAGE_DETECTED` warning. These pages are skipped (not billed) because there is no text to convert. For scanned documents, use an OCR actor first to extract text, then run this actor on the result.

#### Does it support password-protected PDFs?

Yes. Provide the password via the `password` input field (applies to all sources) or per-source via `sources[].password`. If a PDF is encrypted and no password is provided, you get an `ENCRYPTED_NO_PASSWORD` error. If the password is wrong, you get an `INVALID_PASSWORD` error.

#### Can I customize the CSS output?

Yes. Choose between three styling modes: `"class"` (CSS classes + `<style>` block), `"inline"` (style attributes on each element), or `"none"` (pure semantic HTML). With class-based styling, you can inject custom CSS via the `customCss` field and toggle the built-in default styles with `includeDefaultStyles`.

#### How are images handled in the output?

When `extractImages` is enabled, embedded images are extracted from the PDF, converted to your chosen format (PNG, JPEG, or WebP), and stored in the run's Key-Value Store. The HTML output references images via `<img>` tags, and the `images` array in the dataset provides each image's KV store key, dimensions, format, and originating page number.

#### What's the maximum PDF file size?

Configurable via `maxFileSizeMb`, default is 100 MB, maximum is 500 MB. For very large PDFs, increase the actor's memory allocation proportionally. A 200-page PDF with many images may need 4096 MB of memory.

#### Can I convert only specific pages?

Yes. Use the `pageRange` field with ranges like `"1-5"`, `"1,3,5"`, or `"1-3,7,9-12"`. Pages outside the range are skipped and not billed. The output includes only the requested pages.

# Actor input Schema

## `sources` (type: `array`):

List of PDF sources to convert. Each source is an object with either a 'url' field (HTTP/HTTPS URL to a PDF) or 'kvStoreKey' + 'kvStoreId' fields (to load from an Apify Key-Value Store).

## `maxPdfs` (type: `integer`):

Maximum number of PDFs to process per run. Use this to limit costs on large batches.

## `detectHeadings` (type: `boolean`):

Analyze font sizes and weights to infer heading levels (H1-H6). Larger, bolder text becomes higher-level headings.

## `detectLists` (type: `boolean`):

Detect bullet points and numbered sequences, converting them to proper <ul> and <ol> HTML elements.

## `detectTables` (type: `boolean`):

Detect tabular data layouts and convert them to semantic <table> elements with <thead> and <tbody>.

## `extractImages` (type: `boolean`):

Extract embedded images from the PDF and store them in the Key-Value Store. Images are referenced via <img> tags in the HTML output.

## `imageFormat` (type: `string`):

Output format for extracted images. PNG for lossless quality, JPEG/WebP for smaller file sizes.

## `imageQuality` (type: `integer`):

Quality level for JPEG and WebP image extraction (1-100). Higher values produce larger but sharper images. Ignored for PNG.

## `preserveLinks` (type: `boolean`):

Preserve hyperlinks from the PDF as clickable <a> elements in the HTML output.

## `detectColumns` (type: `boolean`):

Detect multi-column layouts and merge content in natural reading order (left-to-right, top-to-bottom).

## `stylingMode` (type: `string`):

CSS strategy for the HTML output. 'class' adds class names with a <style> block, 'inline' adds style attributes directly, 'none' produces pure semantic HTML with no styling.

## `includeDefaultStyles` (type: `boolean`):

Include a built-in stylesheet that makes the HTML output look presentable. Only applies when stylingMode is 'class'.

## `customCss` (type: `string`):

Custom CSS to inject into the output. Appended after default styles. Only applies when stylingMode is 'class'.

## `preserveFontStyles` (type: `boolean`):

Preserve bold, italic, and underline formatting from the PDF. When disabled, output is plain semantic HTML.

## `preserveFontSizes` (type: `boolean`):

Include font-size information in the HTML output. When disabled, headings are determined by relative size only.

## `preserveColors` (type: `boolean`):

Preserve text and background colors from the PDF in the HTML output.

## `pageRange` (type: `string`):

Convert only specific pages. Supports ranges and comma-separated values: '1-5', '1,3,5', '1-3,7,9-12'. Leave empty to convert all pages.

## `pageBreakMode` (type: `string`):

How to mark page boundaries in the HTML output. 'hr' inserts <hr> elements, 'div' wraps each page in a <div class="page">, 'none' produces continuous HTML.

## `wrapInDocument` (type: `boolean`):

Wrap the output in a complete HTML5 document with <!DOCTYPE html>, <html>, <head>, and <body> tags. When disabled, output is an HTML fragment.

## `includePageNumbers` (type: `boolean`):

Add page number annotations (e.g., <span class="page-number">Page 1</span>) to the HTML output.

## `password` (type: `string`):

Password for opening encrypted PDFs. Applied to all sources unless overridden per-source.

## `requestTimeout` (type: `integer`):

Timeout in milliseconds for downloading each PDF from a URL.

## `maxFileSizeMb` (type: `integer`):

Maximum PDF file size in megabytes. PDFs larger than this are skipped with a FILE\_TOO\_LARGE error.

## `proxyConfiguration` (type: `object`):

Proxy settings for downloading PDFs from URLs. Defaults to Apify datacenter proxies.

## `httpHeaders` (type: `object`):

Custom HTTP headers sent with PDF download requests.

## `maxRetries` (type: `integer`):

Maximum number of retry attempts for failed PDF downloads, with exponential backoff.

## `extractMetadata` (type: `boolean`):

Extract PDF metadata (title, author, creation date, etc.) and include in the output.

## `includeMetaTags` (type: `boolean`):

When wrapInDocument is enabled, add <meta> tags from PDF metadata (title, author, subject) to the HTML <head>.

## Actor input object example

```json
{
  "sources": [
    {
      "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
    }
  ],
  "maxPdfs": 1,
  "detectHeadings": true,
  "detectLists": true,
  "detectTables": true,
  "extractImages": false,
  "imageFormat": "png",
  "imageQuality": 85,
  "preserveLinks": true,
  "detectColumns": true,
  "stylingMode": "class",
  "includeDefaultStyles": true,
  "preserveFontStyles": true,
  "preserveFontSizes": false,
  "preserveColors": false,
  "pageBreakMode": "hr",
  "wrapInDocument": false,
  "includePageNumbers": false,
  "requestTimeout": 60000,
  "maxFileSizeMb": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "httpHeaders": {},
  "maxRetries": 3,
  "extractMetadata": true,
  "includeMetaTags": true
}
```

# Actor output Schema

## `results` (type: `string`):

HTML conversion results for each processed PDF page or document with metadata and styling.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "sources": [
        {
            "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
        }
    ],
    "maxPdfs": 1,
    "imageFormat": "png",
    "imageQuality": 85,
    "stylingMode": "class",
    "pageBreakMode": "hr",
    "requestTimeout": 60000,
    "maxFileSizeMb": 100,
    "proxyConfiguration": {
        "useApifyProxy": true
    },
    "maxRetries": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("junipr/pdf-to-html").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "sources": [{ "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf" }],
    "maxPdfs": 1,
    "imageFormat": "png",
    "imageQuality": 85,
    "stylingMode": "class",
    "pageBreakMode": "hr",
    "requestTimeout": 60000,
    "maxFileSizeMb": 100,
    "proxyConfiguration": { "useApifyProxy": True },
    "maxRetries": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("junipr/pdf-to-html").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "sources": [
    {
      "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
    }
  ],
  "maxPdfs": 1,
  "imageFormat": "png",
  "imageQuality": 85,
  "stylingMode": "class",
  "pageBreakMode": "hr",
  "requestTimeout": 60000,
  "maxFileSizeMb": 100,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxRetries": 3
}' |
apify call junipr/pdf-to-html --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=junipr/pdf-to-html",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/xKoM8nLdrLSh6zt5t/builds/bcsNouI2iyQ8GFZuj/openapi.json
