DOCX to Text & Markdown Converter avatar

DOCX to Text & Markdown Converter

Pricing

$3.00 / 1,000 results

Go to Apify Store
DOCX to Text & Markdown Converter

DOCX to Text & Markdown Converter

Convert Word .docx files to Markdown, plain text, HTML or JSON, with tables as rows, images, properties and RAG chunks. Use when you need Word content as text.

Pricing

$3.00 / 1,000 results

Rating

0.0

(0)

Developer

Plain Pipe

Plain Pipe

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

3 days ago

Last modified

Security

Limited permissions

Share

Convert Word .docx files to Markdown, plain text, HTML or a JSON block structure, with tables as structured rows, embedded images, document properties, headers and footers, footnotes, review comments, and optional page, section or RAG-chunk splitting.

Use this when: you need the contents of Word .docx files as clean text or Markdown, for example to feed an LLM, a RAG pipeline, a search index or a wiki.

Why this one

  • $3.00 per 1,000 results, one result per document (or per page, section or chunk if you turn splitting on). Files that fail, are not .docx or hold no text are not charged.
  • More than text: tables come out as rows of cells (tables), footnotes become real Markdown footnotes ([^1]), pictures are saved and linked, and you can add document properties, headers/footers and review comments, each with one checkbox.
  • Clear answers for bad files: the file type is checked from its bytes, so an old .doc, an encrypted/protected .docx, an Excel or PowerPoint file, a PDF, an RTF or a web page each gets its own message telling you what to do, and the run fails with 0 results if nothing could be converted.
  • Fast and light: pure Node.js (mammoth plus a small ZIP reader), no browser, no proxy, no Word install. Four files are fetched and converted in parallel; each test file in this README converts in under 2 seconds per run. The prefilled input is test-run daily.

What it does

For every Word file you give it, the Actor:

  1. Fetches the file (links, an uploaded file, links listed in an Apify dataset, or files stored in a key-value store), up to 50 MB each.
  2. Checks what the file really is. Only real .docx/.docm/.dotx files are converted; everything else is skipped with a reason.
  3. Converts it with the open-source mammoth library and builds its own block list: headings (from Word heading styles), paragraphs, bulleted and numbered lists (also nested), tables, footnotes and endnotes.
  4. Writes one dataset row per document, or per page, section or chunk, in the formats you picked: Markdown, plain text, HTML and/or a JSON structure of blocks, plus word, character, heading and table counts.

Use cases

  • Turn Word policies, specs and manuals into Markdown chunks with heading paths for a RAG pipeline or vector database (chunkWords).
  • Pull every table out of a batch of Word reports as rows you can load into a spreadsheet or database (tables).
  • Move .docx documentation into a wiki, static site or Git repository as Markdown with its images (extractImages).
  • Index Word files for search with their title, subject, keywords and dates (includeMetadata).
  • Convert the .docx links found by another Actor in one step by pointing at its dataset (datasetId).

How to use it

  1. Paste direct .docx links into DOCX URLs, upload a file, or fill Dataset ID or Key-value store ID to convert files in bulk.
  2. Pick the Output formats and turn on the extras you need (tables, images, properties, headers/footers, comments).
  3. Optional: choose Split each document (by page or by heading) and/or a Chunk size (words).
  4. Click Start. Export the rows as JSON, CSV or Excel, or read them through the Apify API.

Input

FieldWhat it isDefaultExample
docxUrlsDirect links to .docx files, one per line. Dropbox share links with ?dl=0 are fixed automatically.none["https://raw.githubusercontent.com/python-openxml/python-docx/master/tests/test_files/test.docx"]
docxFileOne uploaded .docx file, instead of or in addition to links.nonea file picked in the input form
datasetIdBatch input: ID or name of an Apify dataset whose items hold .docx links.none"aBcD1234efGh5678"
datasetUrlFieldField of each dataset item that holds the link. Dot paths and lists of links work.url"file.url"
keyValueStoreIdBatch input: ID or name of a key-value store with .docx files as records. Keys ending in .docx are used; if none do, every record is tried.none"my-word-files"
keyValueStoreKeysOnly these record keys from that store.all .docx keys["contract.docx"]
outputFormatsAny of markdown, text, html (whole documents only), structure (JSON blocks).["markdown","text"]["markdown","structure"]
extractTablesAdd tables: every table as rows of cell text, merged cells padded so columns line up.truefalse
extractImagesSave pictures to the run's key-value store and link them from the Markdown.falsetrue
includeMetadataAdd metadata from the file's own properties (title, subject, keywords, description, author, language, dates, revision, saved page/word counts, application). Off by default because author is a document property.falsetrue
includeHeadersFootersAdd headers and footers (distinct text of each).falsetrue
includeFootnotesKeep footnotes and endnotes. Off drops them and their reference marks.truefalse
includeCommentsAdd comments: text of each review comment and the passage it marks. Comment authors are never output.falsetrue
splitBydocument (one row per file), heading (one row per Heading 1-3 section) or page (one row per page).document"page"
chunkWords0 = off. Otherwise cut each document, section or page into chunks of about this many words, between paragraphs and never right after a heading.0300
maxItemsStop after this many rows in total.10010

Output

One row per document by default. Real output of the prefilled input:

{
"source": "https://raw.githubusercontent.com/python-openxml/python-docx/master/tests/test_files/test.docx",
"fileName": "test.docx",
"markdown": "# python-docx was here!\n\npython-docx was here too!",
"text": "python-docx was here!\n\npython-docx was here too!",
"words": 7,
"characters": 48,
"headings": 1,
"tableCount": 0,
"tables": [],
"fileSizeBytes": 31773
}

Below are real results for each option, from the test files in tests/inputs (public sample files of the mammoth, python-docx and Apache POI projects). Long values are shortened with ….

Tables as rows (extractTables, on by default), a small file with one table:

{
"markdown": "Above\n\n| Top left | Top right |\n| --- | --- |\n| Bottom left | Bottom right |\n\nBelow",
"text": "Above\n\nTop left\tTop right\nBottom left\tBottom right\n\nBelow",
"tableCount": 1,
"tables": [{ "index": 0, "rowCount": 2, "columnCount": 2, "rows": [["Top left", "Top right"], ["Bottom left", "Bottom right"]] }]
}

Footnotes and the JSON structure ("outputFormats": ["markdown", "html", "structure"]):

{
"markdown": "Ouch[^1].[^2]\n\n[^1]: A tachyon walks into a bar.\n\n[^2]: Fin.",
"html": "<p>Ouch<sup><a href=\"#footnote-1\" id=\"footnote-ref-1\">[1]</a></sup>.…</p><ol><li id=\"footnote-1\">…</li></ol>",
"blocks": [
{ "type": "paragraph", "text": "Ouch[1].[2]" },
{ "type": "footnote", "label": "1", "text": "[1] A tachyon walks into a bar." },
{ "type": "footnote", "label": "2", "text": "[2] Fin." }
]
}

With "includeFootnotes": false the same file gives "markdown": "Ouch.".

Split by page plus headers and footers ("splitBy": "page", "includeHeadersFooters": true), a two-page file. The first row also carries the document-level fields:

[
{ "page": 1, "partIndex": 0, "parts": 2, "markdown": "This is a fairly simple word document, over two pages, with headers and footers.\n\n…\n\n# Molière\n\n…\n\nThat’s it for page one", "words": 65,
"headers": ["This is a simple header, with a € euro symbol in it."],
"footers": ["The footer, with Molière, has Unicode in it."] },
{ "page": 2, "partIndex": 1, "parts": 2, "markdown": "This is page two. *Les Précieuses ridicules.* The end.", "words": 9 }
]

Document properties ("includeMetadata": true; the author value is left out here):

{
"metadata": {
"title": "Test Doc",
"subject": "Testing is about all",
"keywords": "Test word",
"description": "These are our document's comments",
"created": "2010-09-14T16:04:00Z",
"modified": "2010-09-14T16:04:00Z",
"revision": 2,
"pagesSaved": 1,
"wordsSaved": 32,
"application": "Microsoft Office Word"
}
}

Review comments ("includeComments": true):

{ "markdown": "Ouch.", "comments": [{ "id": "0", "text": "A tachyon walks into a bar.", "quotedText": "Ouch" }, { "id": "2", "text": "Fin." }] }

Images ("extractImages": true). On Apify the link points at the record in the run's key-value store, https://api.apify.com/v2/key-value-stores/<storeId>/records/doc0001-image-1.png:

{
"markdown": "![](…/records/doc0001-image-1.png)",
"text": "[image]",
"images": [{ "key": "doc0001-image-1.png", "url": "…/records/doc0001-image-1.png", "contentType": "image/png", "bytes": 110 }]
}

Split by heading ("splitBy": "heading") on a 1.3 MB public Word demo file gave 16 rows; one of them:

{ "partIndex": 4, "parts": 16, "section": "Tables", "headingPath": ["Tables"],
"markdown": "# Tables\n\n| ITEM | NEEDED |\n| --- | --- |\n| Books | 1 |\n| Pens | 3 |\n…", "words": 367, "tableCount": 5 }

Chunks for RAG ("chunkWords": 150) on the same file gave 13 rows of 37 to 150 words; one of them:

{ "chunkIndex": 1, "chunks": 13, "headingPath": ["Text Formatting", "Inline formatting"],
"text": "Here is some bold, italic, bold-italic, underlined and struck out text. Then, we have a superscript and a subscript. …", "words": 138 }
FieldMeaning
source / fileNameThe link or key-value-store:<id>/<key> the document came from, and its file name.
markdown / text / html / blocksThe content in the formats you picked.
page, section, headingPath, chunkIndex, chunks, partIndex, partsWhere this row sits in the document when splitting or chunking.
words, characters, headings, tableCountCounts for this row.
tablesTables in this row as rows of cell text.
metadata, headers, footers, comments, images, fileSizeBytesDocument-level fields, on the first row of each document only.
truncated, markdownUrl, textUrl, htmlUrlOnly for very large documents: a row near the dataset size limit keeps the first 100,000 characters and links the whole text in the key-value store.

Pricing

Pay per result: $3.00 per 1,000 results ($0.003 per result). You only pay for rows the Actor returns. Set a maximum total charge on the run to cap your spend.

One result is one dataset row: a whole document by default, or one page, section or chunk when you split. Skipped files are not charged.

Limits

  • Only .docx (Word 2007 and newer, also .docm and .dotx). Old .doc, .odt, .rtf, PDF and encrypted/protected files are detected and skipped with a message, not charged.
  • Headings come from Word heading styles; text that only looks like a heading (big bold font) stays a paragraph.
  • Page split uses the page breaks stored in the file: manual page breaks and the layout Word recorded at the last save. Files written by other tools often store no layout, so they come out as one page.
  • Tables lose colours and layout; merged cells are padded with empty cells. Text boxes and shapes may be missed.
  • HTML output is only given for whole documents (not with splitting or chunking).
  • Files up to 50 MB. Links must return the file itself; redirects are followed and failing servers are retried once.

FAQ

How much does it cost to convert 1,000 documents? $3.00 with the default one row per document. With page, section or chunk splitting you pay per row, so check maxItems.

Why was my file skipped with "legacy Word 97-2003 .doc"? The file uses the old binary Word format. Open it in Word or LibreOffice, choose Save as > Word Document (.docx) and run again.

Why "encrypted/protected"? The document can only be opened with a secret in Word. Remove the protection (File > Info > Protect Document) and save a normal copy.

Can I get tables as data rather than text? Yes. tables holds each table as an array of rows, each row an array of cell strings, next to the Markdown table.

Is personal data in the output? Only what is in your documents. Document properties (including author) are off by default, the last-modified-by property is never output, and comment authors are never output.

Can I convert the results of another Actor? Yes. Put its dataset ID into datasetId and the name of the link field into datasetUrlField.

Can I run it on a schedule or from my own code? Yes. Use Apify schedules, the API, the JavaScript or Python client, or integrations such as Make, Zapier and webhooks.

This Actor processes only what you give it. It does not log in anywhere, does not collect personal data, and does not bypass paywalls, CAPTCHAs or access controls. Document properties such as author are only read when you turn on includeMetadata. You are responsible for having the right to process the inputs you provide.

Support

Open an issue on the Issues tab. Issues are answered within 14 days. Please include the run ID and, if you can, a sample .docx that gave the wrong result.

Other Plain Pipe document tools: PDF to Markdown Converter, EPUB to Markdown & Text Converter, PDF Merge & Split Tool, PDF Watermark & Page Number Tool, Markdown to HTML Converter, Markdown Table Converter (CSV, JSON, HTML).

Built and maintained by Plain Pipe.