https://txtfetch.com/tools/doc-to-text/
Your old .doc file, as text. No code required.
Drop a real Word 97-2003 document below. Watch it become plain text right here, in your browser. No Word install, no conversion service, nothing uploaded.
Drop a legacy Word (.doc), Excel (.xls), or PowerPoint (.ppt) file below to see the actual text it extracts to. These 97-2003 binary formats get read right here in your browser. Nothing is uploaded.
Up to 25 MB, read fully in-browser — larger files still work through the API.
What's not in this text:
curl -X POST https://api.txtfetch.com/v1/extract \
-H "Authorization: Bearer $TXTFETCH_KEY" \
-F file=@__FILENAME__import os
import requests
with open("__FILENAME__", "rb") as f:
r = requests.post(
"https://api.txtfetch.com/v1/extract",
headers={"Authorization": f"Bearer {os.environ['TXTFETCH_KEY']}"},
files={"file": f},
)
print(r.json()["extracted_text"])import { readFile } from "node:fs/promises";
const file = new Blob([await readFile("__FILENAME__")]);
const form = new FormData();
form.append("file", file, "__FILENAME__");
const res = await fetch("https://api.txtfetch.com/v1/extract", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.TXTFETCH_KEY}` },
body: form,
});
const { extracted_text } = await res.json();
console.log(extracted_text);package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
)
type extractResponse struct {
Status string `json:"status"`
ExtractedText string `json:"extracted_text"`
}
func main() {
f, err := os.Open("__FILENAME__")
if err != nil {
panic(err)
}
defer f.Close()
var body bytes.Buffer
writer := multipart.NewWriter(&body)
part, err := writer.CreateFormFile("file", "__FILENAME__")
if err != nil {
panic(err)
}
if _, err := io.Copy(part, f); err != nil {
panic(err)
}
writer.Close()
req, err := http.NewRequest("POST", "https://api.txtfetch.com/v1/extract", &body)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("TXTFETCH_KEY"))
req.Header.Set("Content-Type", writer.FormDataContentType())
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result extractResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
panic(err)
}
fmt.Println(result.ExtractedText)
}whats-hard-about-doc
A .doc isn't a smaller version of .docx. It's a different container entirely, OLE2/Compound File Binary. Even once you're inside it, the document's text doesn't sit in reading order. Word stores it as a piece table: a list of byte ranges, called pieces, in the WordDocument stream. Each piece is either plain 8-bit text or UTF-16, and they have to be stitched together in a specific order to come out readable. Read the stream start to finish and you get a scrambled, out-of-order mess. Not garbage exactly, just wrong.
Fields compound the problem. A page number, a table of contents entry, or a hyperlink is stored as an instruction: what to compute. It's followed by a result: what Word last computed. Both sit inline in the same character stream with no visual separator. Show the instruction and a hyperlink field looks like "HYPERLINK \"http://example.com\"" glued into the middle of a sentence. Skip both and you lose the link text entirely. This reader keeps the result and drops the instruction, the same distinction the file itself makes.
Some content isn't the main body: footnotes, headers and footers, comments, endnotes, text boxes. All of it lives in the SAME character stream as the main text, one after another. The boundaries are recorded only in the file's header, as character counts, not markers you can search for. Treat the whole stream as one document and a footnote ends up dumped mid-paragraph wherever it happens to fall in the byte layout. This tool cuts each of those out using the real counts, and labels them. They show up as their own sections instead of interrupting the body.
what-to-do-next
Got the text out and want the API call for it directly? The panel above already has it, with your file's real name. Extracting many documents, or files bigger than this browser tool's 25 MB cap? See the full legacy-Office extraction guide (.doc, .xls, .ppt) →
faq
- Does this tool upload my .doc file anywhere?
- No. The whole read happens in your browser. The file's bytes never leave your machine. Only the finished text ever leaves, and only if you choose to copy or download it.
- Why does a .doc opened with a plain byte scraper come out scrambled?
- Because the text isn't stored in reading order. Word 97-2003 keeps a piece table, a list of byte ranges scattered through the file. A tool that just reads the stream start to finish gets those ranges out of sequence. This reader reassembles them using the same piece table Word itself relies on.
- What happens to fields like page numbers, a table of contents, or hyperlinks?
- You get the result Word last computed, the link text or the cached page number, not the underlying field instruction. That matches what you'd see reading the document normally, rather than a raw field code sitting mid-sentence.
- Are footnotes, comments, and headers included?
- Yes. Each is pulled out into its own labelled section below the main text. It isn't left wherever it happens to fall in the file's internal byte layout.
- Do tables keep their rows and columns?
- Yes. Cells come out tab-separated and each row on its own line, so a table extracts as a readable grid. That's less obvious than it sounds in this format. Word ends every cell with the same marker byte. It ends the row with one more of them, so a row break is really two cell markers in a row. Miss that and every row separator turns into another tab. The whole table then arrives as one unbroken line. That's exactly the shape that wrecks it downstream in a search index or a RAG chunker.
- My old .doc extracts as ÊîìïàíèÿÀ-style gibberish — what's happening?
- That's a Word 6.0/95 file written in a non-Western codepage. Those older files store raw bytes in whatever Windows codepage the machine that wrote them used: Cyrillic, Greek, Central European, Japanese. The file records that codepage nowhere, so any reader has to guess, and this one assumes Western European. When the result comes back dense in accented characters, this tool says plainly the text is likely garbled. It doesn't present it as the document. txtfetch's API resolves the real codepage.
- What about tracked changes — are deletions removed like the .docx tool does?
- Not in this reader. Telling a tracked deletion apart from kept text means walking each run's character formatting. This tool doesn't do that. So struck-out text can show up in the output. The .docx tool does filter it, since that format marks deletions more directly.
- Does it handle an encrypted or password-protected .doc?
- No. It reports the file as encrypted and stops, rather than attempting to guess a password or return garbled output. txtfetch's API will reject it too, unless it's decrypted first.
- Is there a file size limit?
- This tool reads up to 25 MB entirely in your browser. Larger files, or a batch you want to automate, go through the same extraction via the API, which has no such limit.
That was one file. The API does the queue.
This page read your DOC on your own machine. The API reads a folder of them.
Read the quickstart →