# PDF to Markdown – Word, PowerPoint, Excel, Tables (`plainwren/doc-to-markdown`) Actor

Turns PDF, Word (DOCX), PowerPoint (PPTX), Excel (XLSX), HTML, CSV and TXT files into clean Markdown your LLM can use: headings, lists and real Markdown tables, plus optional heading-aware chunks for RAG. Upload files or paste file URLs, up to 1,000 per run. Pay per page; failed files are free.

- **URL**: https://apify.com/plainwren/doc-to-markdown.md
- **Developed by:** [Plainwren Studio](https://apify.com/plainwren) (community)
- **Categories:** AI, Developer tools, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 page converteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

### What is Document to Markdown?

**Document to Markdown** converts **PDF, Word, PowerPoint and Excel** files into **clean Markdown** an LLM or RAG pipeline can use: headings, paragraphs, lists and **real Markdown tables**. It can also split each document into **heading-aware chunks** ready for embedding.

- **Formats:** PDF (with a text layer), DOCX, PPTX, XLSX, HTML, CSV/TSV and TXT/MD. The format is detected from the file itself, so a wrong file extension is fine.
- **Batch:** upload files or paste direct file URLs, **up to 1,000 files per run**. Each file becomes one dataset row.
- **No crawling:** one URL is one file you point to. Private and internal network addresses are blocked.

Try it: leave the input empty and click **Start**. The Actor converts a built-in sample PDF so you can see the output. The demo run charges no pages (only the $0.001 start fee).

Bot info (user agent and behaviour): [tools.plainwrenstudio.com/bots/doc-to-markdown/](https://tools.plainwrenstudio.com/bots/doc-to-markdown/). Support: <support@plainwrenstudio.com>.

### What do you get for each file?

- `markdown`: headings, paragraphs, lists and GFM tables.
  - **Word, PowerPoint and Excel:** tables are read from the file's own structure, so they keep their rows and columns. Excel shows raw numbers (0.5, not 50%) and merged cells once.
  - **PDF:** tables are rebuilt from the text layout (see Limits).
  - **PowerPoint:** one section per slide (`## Slide 3: Pricing`), plus speaker notes if you want them.
  - **Excel:** one section and table per sheet, dates as ISO dates (`2026-01-15`), and formula results rather than formulas (the formula text is shown if the file has no saved result). Other number formats come out raw (0.5, not 50%), and merged cells show the value once.
- `chunks` (optional): heading-aware chunks. You set the size and overlap. Tables are never cut mid-row; a big table is split by rows with the header repeated. Each chunk has its heading path, page range, character count and a token estimate.
- `title`, `pages`, `billablePages`, `stats` (characters, words, tables, chunks), `warnings`, and on failure a plain `error` with an `errorCode`.

### Who is it for?

- 🧑‍💻 **Developers building RAG or AI-agent pipelines** who need tables and headings kept, through the Apify API, MCP or the REST API.
- 🔗 **No-code automators** (Make, n8n, Zapier through Apify's integrations) turning uploaded files into text for an LLM step.

### How to convert documents to Markdown

1. Open the **Input** tab.
2. Add files under **Upload files**, or paste direct links under **File URLs** (one file per URL).
3. Optional: change **Chunk size** and **Chunk overlap**, turn **Include RAG chunks** or **Include PowerPoint speaker notes** off, or add **Page markers**.
4. Click **Start**.
5. Get results from the **Output** tab, or download them as JSON, CSV or Excel. You can also call the Actor through the Apify API, schedule it, or connect it to other tools with Apify integrations.

#### Input example

```json
{
  "urls": ["https://example.com/report.pdf"],
  "includeChunks": true,
  "chunkSize": 2000,
  "chunkOverlap": 200,
  "pageMarkers": false,
  "includeSpeakerNotes": true,
  "maxPagesPerFile": 500,
  "maxFileSizeMb": 50,
  "maxRowsPerSheet": 10000
}
```

| Field | Type | What it does |
|---|---|---|
| `files` | array | Files you upload. Each becomes one row. |
| `urls` | array of strings | Direct links to files, one file per URL. Leave both `files` and `urls` empty for a demo run (no pages charged, only the $0.001 start fee). |
| `includeChunks` | boolean (default `true`) | Also return heading-aware chunks for RAG. |
| `chunkSize` | integer (default `2000`) | Target maximum characters per chunk (about 4 characters per token). |
| `chunkOverlap` | integer (default `200`) | Characters repeated from the previous chunk (not applied to tables). |
| `pageMarkers` | boolean (default `false`) | Add `<!-- page N -->` comments at each PDF page, slide or sheet. |
| `includeSpeakerNotes` | boolean (default `true`) | Include PowerPoint speaker notes. |
| `maxPagesPerFile` | integer (default `500`, max `5000`) | Stop after this many PDF pages, slides or sheets per file. You only pay for pages converted. |
| `maxFileSizeMb` | integer (default `50`, max `200`) | Largest file accepted. |
| `maxRowsPerSheet` | integer (default `10000`) | Rows kept per Excel sheet or CSV file. Extra rows are cut and a warning is added. |
| `pdfPassword` | secret string | Only for password-protected PDFs. |

#### Output example

This is the real output for our 2-page sample PDF (the `markdown` field, shown as Markdown):

```markdown
## Quarterly Report

## Summary

Revenue grew in every region this quarter. The table below lists units and prices for each product line.

| Product | Units | Price |
| --- | --- | --- |
| Widget | 120 | 4.50 |
| Gadget | 75 | 12.00 |
| Doohickey | 9 | 99.99 |
```

The same dataset row also has (shortened):

```json
{"index":0,"filename":"report.pdf","ok":true,"format":"pdf","title":"Quarterly Report","pages":2,"billablePages":2,
 "chunks":[{"index":0,"headings":["Summary"],"pageStart":1,"pageEnd":1,"chars":290,"tokensEstimate":73}],
 "stats":{"chars":503,"words":66,"tables":2,"chunks":2},"warnings":[],"error":null,"errorCode":null}
```

Word tables keep special characters safe. A cell containing a pipe comes out escaped:
`| Design \| icons | Design | $120 |`

Excel sheets keep computed values, including the totals row:
`| North | 120 | 540.5 | 2026-01-15 |` … `| Total | 204 | 2340.41 |  |`

A scanned PDF comes back as a free failure row:
`{"ok":false,"errorCode":"NO_TEXT","error":"No extractable text: the PDF looks scanned/image-only (OCR is not supported in this version)","billablePages":0}`

| Output field | Meaning |
|---|---|
| `ok` | `true` if the file was converted. |
| `format` | Detected format (`pdf`, `docx`, `pptx`, `xlsx`, `html`, `csv`, `txt`). |
| `markdown` | The converted document. |
| `chunks` | RAG chunks, if turned on. |
| `title` | Document title, when the file has one. |
| `pages` | PDF pages, slides or sheets converted (empty for Word, HTML, CSV and text). |
| `billablePages` | Pages you're charged for (see pricing). |
| `stats` | Characters, words, tables and chunks. |
| `warnings` | Notes such as rows cut by `maxRowsPerSheet`. |
| `error` / `errorCode` | Plain reason and code when `ok` is `false`. |

A run summary is saved in the key-value store under `OUTPUT`: `{ files, ok, failed, pagesCharged, stoppedAtLimit }`.

### How much does it cost to convert documents to Markdown?

Document to Markdown uses **pay-per-event** pricing, so you pay for pages converted:

- **$0.002 per page** (`page-converted`): **$2.00 per 1,000 pages**.
  - A **page** is one PDF page or one PowerPoint slide.
  - For Word, HTML and text files, a page is **every 3,000 characters** of Markdown output (minimum 1 per file).
  - **Excel, CSV and TSV files** are also counted per 3,000 characters, but **never more than 25 pages per file** ($0.05), however many rows they have.
- **Failed files are free.** The demo run charges no pages (only the $0.001 start fee).
- A small **Actor start** fee of **$0.001 per run at the default 1 GB memory**. Apify charges it per GB, so a 4 GB run pays $0.004.

Examples:

- A 10-page PDF: 10 × $0.002 + $0.001 = **$0.021**.
- A 100-slide deck: **$0.201**.
- A 50,000-row, 8-column spreadsheet: 1,162 pages by size, capped at 25 = **$0.051** (by default only the first 10,000 rows are converted; set Max rows per sheet to 50,000 to convert every row; the price is still $0.051).

Apify's free plan includes monthly platform credit you can use to try it.

### Limits

- **No OCR.** Scanned or image-only PDFs come back as "no extractable text" (`NO_TEXT`) and aren't charged. It works on PDFs that have a text layer.
- **PDF tables are rebuilt from text alignment.** Very complex or borderless tables with multi-line cells can come out imperfect. Word and PowerPoint tables are exact. Excel shows raw numbers (0.5, not 50%) and merged cells once.
- **PDF details:** headers, footers and page numbers stay in the text; bold and italic aren't marked; a paragraph that crosses a page break is split in two.
- **Not supported:** legacy .doc/.xls/.ppt, RTF, OpenDocument, EPUB and image files.
- **Size limits:** 50 MB per file by default (up to 200 MB), 500 pages per file by default (up to 5,000), 10,000 rows per sheet by default, 1,000 files per run.

### FAQ

#### Can it read scanned PDFs?

No. It has no OCR. A scanned or image-only PDF returns "no extractable text" and you aren't charged for it.

#### Are tables kept?

Yes. Word, PowerPoint and Excel tables are read from the file's structure, so rows and columns are kept. Excel shows raw numbers (0.5, not 50%) and merged cells once. PDF tables are rebuilt from the text layout, which works well on simple tables but can be imperfect on complex or borderless ones.

#### Am I charged for files that fail?

No. Failed files are free. The demo run charges no pages (only the $0.001 start fee).

#### How are Word, Excel, HTML, CSV and text files counted?

They have no fixed pages, so every 3,000 characters of Markdown output counts as one page, with a minimum of 1 per file. Excel, CSV and TSV files are capped at 25 pages each.

#### Does it crawl websites?

No. Each URL must point to one file, and the Actor downloads only that file. Private and internal network addresses are blocked.

#### What do I get from PowerPoint and Excel files?

PowerPoint: one section per slide with its title, plus speaker notes. Excel: one section and table per sheet, with dates as ISO dates and formula results (the formula text is shown if the file has no saved result). Other number formats come out raw (0.5, not 50%), and merged cells show the value once.

#### Can I use it from Make, n8n, Zapier or an AI agent?

Yes, through Apify's integrations, API and MCP, the same way as any other Apify Actor.

#### Something looks wrong. What should I do?

Open an issue on the Actor's **Issues** tab (remove anything private) or email <support@plainwrenstudio.com>.

# Actor input Schema

## `files` (type: `array`):

Upload PDF, DOCX, PPTX, XLSX, HTML, CSV or TSV files (or paste their URLs). Each file becomes one dataset row. You pay per page converted; spreadsheets are charged for at most 25 pages each, and failed files are free.

## `urls` (type: `array`):

Direct links to documents (one file per URL, e.g. https://example.com/report.pdf). The Actor downloads exactly these files; it does not crawl pages. Leave both fields empty to run a free demo on a built-in sample PDF.

## `includeChunks` (type: `boolean`):

Also return heading-aware chunks (with heading path and page range) ready for embedding.

## `chunkSize` (type: `integer`):

Target maximum characters per chunk (about 4 characters per token). Tables are kept whole when they fit; larger tables are split by rows with the header repeated.

## `chunkOverlap` (type: `integer`):

Characters of the previous text chunk repeated at the start of the next one (not applied to tables).

## `pageMarkers` (type: `boolean`):

Insert  comments at each PDF page, slide or sheet.

## `includeSpeakerNotes` (type: `boolean`):

Include speaker notes from PowerPoint slides in the Markdown output.

## `maxPagesPerFile` (type: `integer`):

Stop after this many PDF pages / slides / sheets per file (you are only charged for pages converted; spreadsheets are charged for at most 25 pages each).

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

Reject any single uploaded or downloaded file larger than this many megabytes.

## `maxRowsPerSheet` (type: `integer`):

Stop after this many rows per Excel sheet or CSV/TSV file. Spreadsheets are charged for at most 25 pages each, however many rows are converted.

## `pdfPassword` (type: `string`):

Only for password-protected PDFs. Stored encrypted.

## Actor input object example

```json
{
  "includeChunks": true,
  "chunkSize": 2000,
  "chunkOverlap": 200,
  "pageMarkers": false,
  "includeSpeakerNotes": true,
  "maxPagesPerFile": 500,
  "maxFileSizeMb": 50,
  "maxRowsPerSheet": 10000
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset of converted (or failed) documents.

## `OUTPUT` (type: `string`):

Files processed, pages charged, spending-limit stop.

## `markdownFiles` (type: `string`):

Full Markdown for oversized outputs (doc-{index}.md).

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("plainwren/doc-to-markdown").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("plainwren/doc-to-markdown").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{}' |
apify call plainwren/doc-to-markdown --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,plainwren/doc-to-markdown"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Hf2zshn4mayGn3jxl/builds/95xFDXqV3eFBEYq43/openapi.json
