SUBMIT A FILE TYPE CONTACT US SUPPORT ABOUT US

What Is a DOCX File? Unzip One and the Text Is Right There

DOCX files are Word documents saved as a ZIP package of XML parts. Jump to your question below, or work through it in order.

51 min read Updated October 2026 Checked against sources and tests

What Is a DOCX File? Word's default format since 2007, packed as a ZIP file

Short answer

DOCX files are Word documents saved as a ZIP package of XML parts. Word has made DOCX the default for new documents since Word 2007. The text is in one part, and the pictures, styles and settings are in others.

The signature is shared. DOCX, XLSX, PPTX and ODT files all start with 50 4b 03 04, so the proof is two parts inside the package. In our tests of 28 damaged DOCX files, the best readers returned all the text from 12, and a text pass found all of it in 20.

To check your DOCX file, read the signature test. To open one, read how to open DOCX files. If yours is damaged, start with the damage tests.

DOCX files are Word documents saved as a ZIP package of XML parts. Word has made DOCX the default for new documents since Word 2007, and you can unzip one to read its text.

Picture a contract named offer.docx that Word refuses to open. The file is 80 KB, so something is inside. Rename it to offer.zip and open it, and a folder of XML files appears. That is the idea behind the format, and it is why a broken DOCX file is often easier to save than a broken DOC file.

FileInfo describes DOCX files as documents made by Word or exported from other word processors. They can contain text, images, tables and charts. The Library of Congress says DOCX has been the default in Word for new documents and Save since Word 2007.

The name belongs to a family. The Library of Congress lists .dotx for templates, .docm for documents with macros and .dotm for templates with macros. The kinds section shows how they differ.

Ecma approved the standard, ECMA-376, in December 2006, and ISO/IEC 29500 followed in November 2008, says Wikipedia. Ecma now lists the fifth edition as the current one.

DOCX file facts at a glance

Quick facts about DOCX files
Property Value
Meaning Word documents saved as Office Open XML, a ZIP package of XML parts
Ending .docx for documents. Related endings are .dotx, .docm and .dotm, says the Library of Congress.
Made by Microsoft Word. Other word processors can export DOCX files too, says FileInfo.
MIME type application/vnd.openxmlformats-officedocument.wordprocessingml.document
Starts with 50 4b 03 04, the ZIP signature that XLSX, PPTX and ODT files share
Container A ZIP package with the folders _rels, docProps and word
Text part word/document.xml, says the Library of Congress
Opens in Word, LibreOffice, Apple Pages, Google Docs and ONLYOFFICE, says FileInfo
Safe to open Reading the text is low risk. Macros are the risk, and they belong in DOCM files.

Wrong page? Older Word files that end in .doc are in the DOC file guide. Open-source documents are in the ODT file guide. Read-only documents are in the PDF file guide. Any ZIP archive is in the ZIP file guide.

How Do You Know Your File Is a DOCX File? The first 4 bytes are shared, so read the parts

The first 4 bytes do not prove a DOCX file. DOCX, XLSX, PPTX and ODT files all start with 50 4b 03 04. The proof is inside the package: the content types part names the Word type, and the root relationships point to word/document.xml.

Many pages call 50 4b 03 04 the signature of DOCX files. It is the signature of ZIP. Our own earlier page on this site said the same. We built DOCX files with three writers and saved an Excel file, a PowerPoint file and an ODT file under the ending .docx. All of them start with those 4 bytes.

The file command named our good DOCX files Microsoft Word 2007+. It gave the same name to 23 of our 28 damaged DOCX files, and 14 of those returned nothing from all five readers. The commands unzip -t and 7z t passed only 7 of the 28. So neither tool shows whether the text can be read.

A table of 14 files and the first 8 bytes of every one. Five files that are ZIP packages start with 50 4b 03 04: two DOCX files, a template, a macro-enabled copy, and an Excel, a PowerPoint and an ODT file saved as .docx. A password-protected DOCX and an old DOC file both start with d0 cf 11 e0 a1 b1 1a e1. An RTF file starts with the text {\rtf1, a web page with the text <!DOCTYP, a text file with a byte order mark and a PDF file with the text %PDF-1.7. An empty file has no bytes
ZIP-based files start alike. The parts inside show a DOCX from an XLSX, PPTX or ODT.

The PRONOM entry identifies DOCX files by more than the first bytes. Its container signature looks inside the ZIP package, as the box below shows. The entries for DOTX templates and DOTM files look for their own content types in the same part.

What the PRONOM signature for DOCX files looks for
Look in the ZIP part  [Content_Types].xml
Within the first 32,768 bytes of that part
For the text (ASCII, UTF-16LE or UTF-16BE):
application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml

The script below checks DOCX files with the Python standard library. It reads the end record and the central directory of the ZIP, tests the CRC of every part and reads the content types. Then it follows the root relationships to the main part and prints the problems it finds. Save it as check_docx.py and give it the name of your DOCX file.

Check DOCX files with Python
#!/usr/bin/env python3
"""check_docx.py: look inside a .docx file without opening Word.
Usage: python3 check_docx.py file.docx [more files]
Prints the first 8 bytes, what kind of file it is, the size, the end record and
central directory of the ZIP package, every part with a CRC test, the main
content type (DOCX, template or macro-enabled) and any problems found.
Reads only. It never changes the file."""
import struct, sys, zlib, re

CFB = bytes.fromhex('d0cf11e0a1b11ae1')
TYPES = {
    'application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml': 'DOCX document',
    'application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml': 'DOTX template',
    'application/vnd.ms-word.document.macroEnabled.main+xml': 'DOCM macro-enabled document',
    'application/vnd.ms-word.template.macroEnabledTemplate.main+xml': 'DOTM macro-enabled template',
}

def read_part(b, off, csize, method):
    nl, el = struct.unpack_from('<HH', b, off + 26)
    s = off + 30 + nl + el
    raw = b[s:s + csize]
    if method == 0: return raw
    if method == 8: return zlib.decompress(raw, -15)
    raise ValueError(f'method {method}')

def central(b):
    e = b.rfind(b'PK\x05\x06')
    if e < 0: return None, 'no end record (PK 05 06)'
    n, size, start = struct.unpack_from('<HII', b, e + 10)
    ents, p = [], start
    if start + size > e + 1 or b[start:start + 4] != b'PK\x01\x02':
        return None, f'end record points to {start:,}, no central directory there'
    for _ in range(n):
        if b[p:p + 4] != b'PK\x01\x02': return ents, 'central directory is cut short'
        meth, = struct.unpack_from('<H', b, p + 10)
        crc, cs, us = struct.unpack_from('<III', b, p + 16)
        nl, el, cl = struct.unpack_from('<HHH', b, p + 28)
        off, = struct.unpack_from('<I', b, p + 42)
        ents.append((b[p + 46:p + 46 + nl].decode('utf-8', 'replace'), meth, crc, cs, us, off))
        p += 46 + nl + el + cl
    return ents, None

def check(path):
    b = open(path, 'rb').read()
    print(f'{path}: {len(b):,} bytes, starts {b[:8].hex(" ")}')
    problems = []
    if not b:
        print('  empty file'); return ['empty file']
    if b[:8] == CFB:
        print('  kind: compound file (CFB). A password-protected DOCX looks like this; so does an old DOC.')
        try:
            import olefile, io
            names = ['/'.join(x) for x in olefile.OleFileIO(io.BytesIO(b)).listdir()]
            print('  streams:', ', '.join(names))
            if 'EncryptedPackage' in names:
                print('  this is an encrypted DOCX: it needs its password before it is a ZIP'); return []
            if 'WordDocument' in names:
                print('  this is an old DOC, not a DOCX'); return ['not a DOCX: old DOC']
            return ['not a DOCX: compound file without EncryptedPackage']
        except Exception as ex: print('  could not list streams:', ex)
        return []
    if b[:5] == b'{\\rtf':
        print('  kind: RTF text, not a DOCX'); return ['not a ZIP: RTF']
    if b[:5] == b'%PDF-':
        print('  kind: PDF, not a DOCX'); return ['not a ZIP: PDF']
    if b[:4] != b'PK\x03\x04':
        at = b[:65536].find(b'PK\x03\x04')
        if at > 0:
            if b[:4] == bytes(4) and any(b[:at]):
                print(f'  the first 4 bytes are zeros (the ZIP signature is wiped); the next signature is at offset {at:,}')
                problems.append('the first 4 bytes are zeros (signature wiped)')
            elif not any(b[:at]):
                print(f'  the first {at:,} bytes are zeros; the next ZIP signature is at offset {at:,}')
                problems.append(f'the first {at:,} bytes are zeros (start of the file wiped)')
            else:
                print(f'  ZIP signature found at offset {at:,}: extra bytes come before the file')
                problems.append(f'junk before the file ({at:,} bytes)')
        else:
            head = b[:512].lstrip().lower()
            printable = sum(32 <= x < 127 or x in (9, 10, 13) for x in b[:2000]) / max(1, len(b[:2000]))
            if head.startswith((b'<?xml', b'<html', b'<!doctype')): print('  kind: XML or HTML text, not a DOCX')
            elif printable > .95: print('  kind: plain text, not a DOCX')
            else: print('  kind: no ZIP signature (PK 03 04)')
            return problems or ['no ZIP signature']
    if b.count(b'\r\n') > 20 and b'\n' not in b.replace(b'\r\n', b''):
        problems.append('every 0A is paired with 0D: a text-mode transfer')
    ents, err = central(b)
    if err: problems.append(err)
    else:
        e = b.rfind(b'PK\x05\x06'); clen, = struct.unpack_from('<H', b, e + 20)
        if e + 22 + clen < len(b): problems.append(f'{len(b) - (e + 22 + clen):,} extra bytes after the end record')
    parts = {}
    if ents:
        print(f'  central directory: {len(ents)} entries')
        for name, meth, crc, cs, us, off in ents:
            st = 'ok'
            try:
                if b[off:off + 4] != b'PK\x03\x04': st = 'no local header at the stored offset'
                else:
                    d = read_part(b, off, cs, meth)
                    if zlib.crc32(d) & 0xffffffff != crc: st = 'CRC does not match'
                    else: parts[name] = d
            except Exception as ex: st = 'data is damaged (inflate failed)'
            if st != 'ok': problems.append(f'{name}: {st}')
            print(f'    {name}  {us:,} bytes  {st}')
    else:
        # fall back: walk local headers
        p, found = 0, 0
        while True:
            p = b.find(b'PK\x03\x04', p)
            if p < 0: break
            nl, el = struct.unpack_from('<HH', b, p + 26); meth, = struct.unpack_from('<H', b, p + 8)
            cs, = struct.unpack_from('<I', b, p + 18)
            name = b[p + 30:p + 30 + nl].decode('utf-8', 'replace'); found += 1
            try:
                d = zlib.decompressobj(-15).decompress(b[p + 30 + nl + el:]) if meth == 8 else b[p + 30 + nl + el:p + 30 + nl + el + cs]
                parts[name] = d; print(f'    local header: {name}  {len(d):,} bytes')
            except Exception as ex: print(f'    local header: {name}  cannot be read')
            p += 4
        print(f'  no usable central directory; {found} local headers found by scanning')
    names = set(parts) | {e[0] for e in (ents or [])}
    other = None
    if 'xl/workbook.xml' in names: other = 'Excel workbook (XLSX)'
    elif 'ppt/presentation.xml' in names: other = 'PowerPoint presentation (PPTX)'
    elif 'mimetype' in names:
        mt = parts.get('mimetype', b'').decode('utf-8', 'replace').strip()
        other = 'OpenDocument file (' + (mt.rsplit('.', 1)[-1] if mt else 'ODF') + ')'
    elif names and not any(n.startswith('word/') for n in names): other = 'ZIP archive with no word/ folder'
    if other and 'word/document.xml' not in names:
        print(f'  kind: {other}, not a DOCX')
        print('  problems: not a DOCX')
        return ['not a DOCX: ' + other]
    for need in ('[Content_Types].xml', '_rels/.rels'):
        if need not in parts and not any(x.startswith(need + ':') for x in problems): problems.append(f'{need} is missing or unreadable')
    ct = parts.get('[Content_Types].xml', b'').decode('utf-8', 'replace')
    main = None
    for t, label in TYPES.items():
        if t in ct: main = label; break
    if ct and not main: problems.append('no Word main-part content type in [Content_Types].xml')
    if main: print(f'  main part content type: {main}')
    rels = parts.get('_rels/.rels', b'').decode('utf-8', 'replace')
    m = re.search(r'Type="[^"]*officeDocument"[^>]*Target="([^"]+)"|Target="([^"]+)"[^>]*Type="[^"]*officeDocument"', rels)
    target = (m.group(1) or m.group(2)).lstrip('/') if m else None
    if rels and not target: problems.append('_rels/.rels has no officeDocument relationship')
    if target:
        print(f'  main part: {target}')
        if target not in parts:
            if not any(x.startswith(target + ':') for x in problems): problems.append(f'{target} is missing or unreadable')
        else:
            x = parts[target].decode('utf-8', 'replace')
            if not x.strip(): problems.append(f'{target} is empty')
            elif not re.search(r'</w:document>\s*$', x): problems.append(f'{target} does not end with </w:document>')
            else:
                try:
                    import xml.dom.minidom as md; md.parseString(parts[target])
                except Exception as ex: problems.append(f'{target} is not well-formed XML')
            print(f'  text elements (w:t): {len(re.findall(r"<w:t[ >]", x))}')
        relp = target.rsplit('/', 1)[0] + '/_rels/' + target.rsplit('/', 1)[-1] + '.rels'
        if relp not in parts: problems.append(f'{relp} is missing')
    print('  problems:', '; '.join(problems) if problems else 'none found')
    return problems

if __name__ == '__main__':
    for p in sys.argv[1:]:
        try: check(p)
        except Exception as ex: print(f'  could not check: {ex}')
The answer for our good DOCX file
full.docx: 76,357 bytes, starts 50 4b 03 04 14 00 00 00
  central directory: 18 entries
    [Content_Types].xml  1,788 bytes  ok
    _rels/.rels  734 bytes  ok
    docProps/core.xml  816 bytes  ok
    docProps/app.xml  1,132 bytes  ok
    word/document.xml  8,193 bytes  ok
    word/_rels/document.xml.rels  1,359 bytes  ok
    word/styles.xml  349,458 bytes  ok
    word/stylesWithEffects.xml  438,131 bytes  ok
    word/settings.xml  2,535 bytes  ok
    word/webSettings.xml  438 bytes  ok
    word/fontTable.xml  2,811 bytes  ok
    word/theme/theme1.xml  10,939 bytes  ok
    customXml/item1.xml  262 bytes  ok
    customXml/_rels/item1.xml.rels  295 bytes  ok
    customXml/itemProps1.xml  354 bytes  ok
    word/numbering.xml  5,513 bytes  ok
    word/media/image1.png  38,520 bytes  ok
    docProps/thumbnail.jpeg  8,324 bytes  ok
  main part content type: DOCX document
  main part: word/document.xml
  text elements (w:t): 53
  problems: none found

We tried the script on all 61 test files. It flagged none of the 25 good DOCX files and all 28 damaged DOCX files, and it named the kind of all 8 files that were not DOCX files. A flipped bit shows up too, because ZIP stores a CRC for every part. The script cannot say which damage matters, and the damage section lists all 28 cases.

The answer for our DOCX file with 512 bytes of junk in front
broken.docx: 76,869 bytes, starts ed 6f ac 06 1d 49 87 e2
  ZIP signature found at offset 512: extra bytes come before the file
    local header: [Content_Types].xml  1,788 bytes
    local header: _rels/.rels  734 bytes
    local header: docProps/core.xml  816 bytes
    local header: docProps/app.xml  1,132 bytes
    local header: word/document.xml  8,193 bytes
    local header: word/_rels/document.xml.rels  1,359 bytes
    local header: word/styles.xml  349,458 bytes
    local header: word/stylesWithEffects.xml  438,131 bytes
    local header: word/settings.xml  2,535 bytes
    local header: word/webSettings.xml  438 bytes
    local header: word/fontTable.xml  2,811 bytes
    local header: word/theme/theme1.xml  10,939 bytes
    local header: customXml/item1.xml  262 bytes
    local header: customXml/_rels/item1.xml.rels  295 bytes
    local header: customXml/itemProps1.xml  354 bytes
    local header: word/numbering.xml  5,513 bytes
    local header: word/media/image1.png  38,520 bytes
    local header: docProps/thumbnail.jpeg  8,324 bytes
  no usable central directory; 18 local headers found by scanning
  main part content type: DOCX document
  main part: word/document.xml
  text elements (w:t): 53
  problems: junk before the file (512 bytes); end record points to 75,147, no central directory there
What files that are not DOCX files show
Kind Where to look What you see
XLSX file The part names xl/workbook.xml, not word/document.xml
PPTX file The part names ppt/presentation.xml
ODT file The part names A part named mimetype
Password-protected DOCX Bytes 0 to 7 d0 cf 11 e0 a1 b1 1a e1 and a stream named EncryptedPackage
Old DOC file Bytes 0 to 7 The same 8 bytes and a stream named WordDocument
RTF file The first bytes The text {\rtf1
Web page The first bytes The text <!DOCTYPE or <html
PDF file The first bytes The text %PDF-
Plain text file The entire file Readable text from byte 0
Empty file The size 0 bytes

Four more clues about your DOCX file

The unzip -l command It lists the parts of any ZIP-based file. For a DOCX file you see content types, root relationships and word/document.xml.
The content type The content types part names the kind of DOCX file: document, template or macro-enabled. The script prints it.
The root relationships They point to the main part. In every good DOCX file we built, they pointed to word/document.xml.
The ending Eight files in our set were saved under the ending .docx without being DOCX files. Read the bytes before you trust the ending.

What Is Inside a DOCX File? Parts, relationships and one XML file of text

A DOCX file is a ZIP package of parts. One part lists the content types, small parts link the others together, and the text is in word/document.xml. In our file, the style parts were 90 percent of the unpacked bytes.

Think of the shipping box and its packing slip. The slip, [Content_Types].xml, says what every item is. Smaller slips, called relationships, say which item belongs with which. The text is one item in the box, and the box is a ZIP file.

A diagram of one DOCX file of 76,357 bytes. Stored in the ZIP, the picture takes 38,535 bytes, the two style parts 25,772 bytes, document.xml 1,562 bytes, other parts 8,378 bytes and the ZIP headers and directory 2,110 bytes. Unpacked, the file is 871,602 bytes, and the two style parts are 787,589 of them. Three steps lead from the file to the text. First find the end record and the central directory. Second read the content types and the root relationships, which name word/document.xml. Third read the w:t elements inside w:p paragraphs
One DOCX file of 76,357 bytes, drawn from the parts that unzip listed.

The sizes in the figure come from one DOCX file we built with python-docx, with 40 paragraphs, a list, a table and a picture. It has 18 parts and 76,357 bytes. Unpacked, the parts add up to 871,602 bytes, and two style parts are 787,589 of them. ZIP shrinks them about 31 to 1. The text part is 8,193 bytes unpacked and 1,562 bytes stored.

The Library of Congress format record says a minimal DOCX package has the folders _rels, docProps and word, plus a content types part, and that the main content is word/document.xml. Microsoft Learn adds that the simplest valid document needs only a main document part.

The parts of one DOCX file

Parts of the DOCX file we unpacked
Part What it does Bytes
[Content_Types].xml Names the kind of every part 1,788
_rels/.rels Points to the main part 734
word/document.xml The text, the table and the list 8,193
word/_rels/document.xml.rels Links the main part to styles, the picture and settings 1,359
word/styles.xml Style definitions 349,458
word/stylesWithEffects.xml More style definitions from the python-docx template 438,131
word/media/image1.png The picture, stored as it came 38,520
docProps/core.xml Title, author, last editor and times 816

The other 10 parts contain fonts, a theme, settings, numbering, a thumbnail and small custom XML files. Style parts decide the size of a small DOCX file. LibreOffice wrote one styles part of 347,389 bytes, pandoc wrote one of 21,011 bytes, and python-docx wrote the two above. The samples section shows what that did to file sizes.

The text in word/document.xml

Microsoft Learn names the nesting: a document contains a body, and the body contains paragraphs (w:p). Inside a paragraph are pieces of text that share one set of properties (w:r). Inside every piece is the text itself (w:t). Any ZIP tool can show you this part.

Two paragraphs from word/document.xml in our full DOCX file, with line breaks added
<w:p>
  <w:pPr><w:pStyle w:val="Heading1"/></w:pPr>
  <w:r><w:t>Harbor Notes</w:t></w:r>
</w:p>
<w:p>
  <w:r><w:t>Paragraph 01: The harbor report for week 1
    lists 52 boats and 255 crates.</w:t></w:r>
</w:p>

Every picture in your DOCX file is a separate part. We pulled word/media/image1.png out with unzip, and its SHA-256 hash matched the PNG we put in. We did not test any DOCX file made by Word, which may store pictures in other ways.

Which part does a reader need?

We removed one part at a time from copies of our full DOCX file and tried five readers. The readers use different shortcuts. That is why one reader can open a file that another refuses, and why a script that writes the missing slips can save a file that no reader would open.

What happened when we removed one part
Part removed Still read it Failed
Content types pandoc, docx2txt and mammoth LibreOffice and python-docx
Root relationships docx2txt and mammoth LibreOffice, pandoc and python-docx
Main part links All except mammoth mammoth
Main part None All five

Which Kind of DOCX File Do You Have? Documents, templates, macro-enabled files and passwords

DOCX files come in a few kinds: documents, templates, macro-enabled files and password-protected files. The first three differ by one line of text in the content types part. A password wraps the package in a different kind of file.

The ending .docx covers documents. The Library of Congress lists .dotx for templates, .docm for macro-enabled documents and .dotm for macro-enabled templates. Inside any DOCX file, the kind is the content type of its main part, and our check script prints it.

Kinds of DOCX file and what we made
Kind How to see it Our files
Document The ending .docx and the document content type Made 20 files
Template The ending .dotx and the template content type Made 2 files
Macro-enabled document The ending .docm and the macro-enabled content type Made 2 files
Macro-enabled template The ending .dotm and its own content type Not made
Password-protected A compound file with an EncryptedPackage stream Made 1 file
Strict Other content types, listed by PRONOM as fmt/1827 Not made
The content type of the main part, by kind
DOCX  application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml
DOTX  application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml
DOCM  application/vnd.ms-word.document.macroEnabled.main+xml
DOTM  application/vnd.ms-word.template.macroEnabledTemplate.main+xml

PRONOM lists the template and macro-enabled template strings on its DOTX page and its DOTM page. Strict files, which PRONOM lists as fmt/1827, use other content types, and we made none.

A macro-enabled copy is still a ZIP file

We made one DOCM file and one DOCM copy under the ending .docx by editing the content type of a DOCX file. Neither contains a macro, so they show only how readers treat the label. Both stopped python-docx with a ValueError that names the content type, and both templates did the same. LibreOffice, pandoc, docx2txt, mammoth and the text pass read all 52 items from all four.

We did not test any DOCX file with a macro. Our macro-enabled files are labels only, so we have no result for code inside a package.

A password-protected DOCX file is not a ZIP file

LibreOffice encrypted the entire package of our test DOCX file. The result is a compound file, as in old DOC files, with two streams and nothing else. Didier Stevens describes the layout, and Microsoft's encryption specification lists the Standard encryption versions as 2.2, 3.2 and 4.2.

The two streams in our password-protected file
Stream Size and first bytes
EncryptedPackage 59,480 bytes. Starts with 42 e8 00 00, which is 59,458 as a little endian number
EncryptionInfo 224 bytes. Starts with 03 00 02 00, the version numbers 3 and 2

The number 59,458 is the size of the package inside. With the right password, msoffcrypto-tool 6.0.0 wrote one DOCX file of exactly 59,458 bytes, and all five readers then returned all 52 items. With a wrong password it stopped with an InvalidKeyError. The file command named the locked file CDFV2 Encrypted, and unzip said it could not find a zipfile directory.

Decrypt a DOCX file with the password
import io, msoffcrypto
f = msoffcrypto.OfficeFile(open('locked.docx', 'rb'))
f.load_key(password='your-password')
out = io.BytesIO()
f.decrypt(out)
open('unlocked.docx', 'wb').write(out.getvalue())

We do not cover password guessing. We did not test any tool that finds a lost password. If you forgot the password, ask the person who sent the file, or look for an older copy without one.

What Do 61 Test DOCX Files Show? 25 good files, 8 other kinds, 28 damaged copies and five readers

LibreOffice, pandoc, docx2txt, mammoth and a text pass returned all the text from all 19 good DOCX files that have text. Damage is where they differ: a text pass found all the text in 20 of 28 damaged DOCX files, and the best single reader found it in 10.

We found no public set of DOCX files with known content, and files from offices have text we cannot compare. So we built our own in October 2026. Every full DOCX file got the same 52 test items: 40 paragraphs, 9 table cells and 3 list items. A reader counted as returning all the text only when all 52 items came back.

We built 25 good DOCX files with three writers and made 28 damaged DOCX files in 28 ways. We also saved 8 other kinds of file under the ending .docx. We tried five readers on all 61 files: LibreOffice 24.2.7.2, pandoc 3.1.3, python-docx 1.2.0, docx2txt 0.9 and mammoth 1.13.0. A text pass script, GNU strings 2.42, unzip 6.00, 7-Zip 23.01, the file command 5.45 and zip 3.0 helped with the rest.

We did not test any DOCX file made by Word. Our files came from python-docx, LibreOffice and pandoc. We did not use Word, Pages, Google Docs or WPS Office. The DOCM files and one of the two DOTX files came from editing the content type of a DOCX file, so none of them contains a macro. We also left out Strict files, files with embedded objects and the SysTools and Freeviewer tools. Word may write the parts in another order and with other content.

61 files tested
25 good DOCX files we built
28 damaged DOCX files
5 readers used

The 25 good DOCX files we built

The 25 good DOCX files and what they test
Writer Files What they test
python-docx 1.2.0 7 files text, full, multi, big, empty, pic and tableonly. The full DOCX file is 76,357 bytes.
LibreOffice 24.2.7.2 9 files The same 7, a template and a password-protected file. The full DOCX file is 59,697 bytes.
pandoc 3.1.3 6 files The same set without pic. The full DOCX file is 49,783 bytes.
Our edits 3 files The full DOCX file with its content type edited to DOCM or DOTX, and a DOCM kept under .docx.

Five of the 25 files have no text: three empty files and two with only a picture. One has a password. Every reader returned nothing from those, which is right for the first five. So the counts below use the 19 files that have text and no password.

Which readers returned all the text from 19 good DOCX files
Reader Files read Note
LibreOffice 24.2.7.2 19 of 19 Failed only on the password-protected file. It also read the ODT, RTF, HTML, text and old DOC files that we had saved as .docx.
pandoc 3.1.3 19 of 19 Read every good DOCX file. It read none of the 8 other kinds, even the ones that LibreOffice read.
python-docx 1.2.0 15 of 19 Refused both templates and both macro-enabled copies because of their content types.
docx2txt 0.9 19 of 19 Read every good DOCX file, with tables.
mammoth 1.13.0 19 of 19 Read every good DOCX file, with tables.
Text pass 19 of 19 Our short script. It needs no word processor.
strings 2.42 0 of 19 Found nothing, because ZIP packs the text. It stood in for Notepad.
A grid of seven readers against nine DOCX files. LibreOffice, pandoc, docx2txt, mammoth and a text pass script returned all the text from the 8 files without a password. python-docx refused the template and the macro-enabled copy because their content types are not the usual DOCX type. strings returned nothing from any file, because ZIP packs the text. Every reader returned nothing from the password-protected file
Four readers and a text pass returned all the text. The reader python-docx refused two kinds, and strings found none.

The same text in three sizes

Identical content gave DOCX files of widely different sizes. An empty document was 9,700 bytes from pandoc, 19,502 from LibreOffice and 36,563 from python-docx. The writer, not the text, sets the size of a small DOCX file, and style parts are the reason. With 4,000 lines the gap closes, because text starts to count.

Size in bytes of the same content from three writers
Input python-docx LibreOffice pandoc
empty 36,563 19,502 9,700
text 37,135 20,110 10,347
multi 36,846 19,808 10,044
table only 36,784 19,948 10,000
full 76,357 59,697 49,783
big 80,054 66,025 55,970

The DOC guide compares sizes of the older format with the same content.

The 28 damaged DOCX files

We damaged DOCX files of our full DOCX file in 28 ways, with one kind of damage in every case. The list of 28 cases gives a fix for every one. The table counts cases where at least one reader returned all 52 items, and cases where the text pass did.

Damaged DOCX files that gave back all 52 items
Damage (cases) Before After Text pass
Start of the file wiped (2) 2 2 2
End record or directory wiped or wrong (4) 1 4 4
Cut short (5) 0 5 5
Extra bytes or a bad transfer (4) 2 4 2
Flipped bits (3) 2 2 2
A part removed (4) 3 3 3
A part damaged or empty (5) 2 2 2
Empty file (1) 0 0 0
All 28 cases 12 22 20

Before repair means at least one of five readers returned all 52 items from a damaged copy. After repair means the same check on output from our repair script. The text pass is a short script with no repair step. The repair script added 10 cases to what readers managed alone. The text pass beat every single reader, and docx2txt came closest with 10 cases.

A text pass beats the readers It found all 52 items in 20 of the 28 damaged DOCX files. LibreOffice found them in 4 and docx2txt in 10. When the ZIP directory is damaged, the parts are often still in place.
Readers disagree With the first 4 bytes zeroed, only docx2txt read the file. With 1,024 junk bytes at the end, LibreOffice refused it and the other four read it. No reader was the best on every file.
ZIP makes damage loud Flipped bits in document.xml made every reader refuse the file, because ZIP checks every part. In our DOC tests, one flipped bit changed a letter and no reader complained.
A cut file keeps its early parts The text pass found all 52 items even after a cut at 10 percent, because python-docx stores document.xml early. A file from Word may order its parts in another way.

Read the counts with care. We damaged small DOCX files with simple breaks. Failing drives, bad downloads and half-finished copies do messier harm. The numbers show how these readers behave with these files, and they are not rates for all DOCX files.

What Does Our Expert Say About DOCX Files? A note from Neeraj Singh on getting the text out first

Pull the text out of a damaged DOCX file before you try to repair it. In our tests, a text pass found all the text in 20 of 28 damaged DOCX files, and the best single reader found it in 10.

Neeraj Singh

Expert insight

“A DOCX file is a box of small files, so damage rarely takes everything. In our tests, a text pass found all the text in 20 of 28 damaged DOCX files, and the best single reader found it in 10. Work on a copy, pull the text out first and only then try to repair the package.”

Neeraj Singh Manager, Tech Support & Operations 19+ years fixing Windows and system errors

Why the text survives

Every part of any DOCX file has its own header and its own CRC, and the ZIP directory at the end is only an index. When your DOCX file loses its start, its end record or its directory, the parts are often still in place. A text pass that reads local headers finds them. Most readers we tried need the index, so they refused those files.

A text pass is not a repair. Formatting, tables and pictures are gone. It also ignores the CRC, so it reads parts that a strict reader refuses. It found all 52 items even in the copy with a wrong CRC for document.xml, and that is one reason to compare the result with a good copy.

Microsoft's page on damaged documents points the same way. It lists Recover Text from Any File, which keeps the text and loses formatting, graphics and fields. We did not test Word, so we cannot say how it would do on our DOCX files.

Our repair script gave all 52 items back in 22 of 28 cases and part of them in one. It failed in 5: flipped bits in document.xml, zeroed bytes in it, a removed or empty document.xml and an empty file. In all five DOCX copies, the text part itself was gone or broken, and only a good copy helps.

Keep the original. Work on copies of DOCX files and keep the first one. If a repair goes wrong, you can start again. Our own repair script never touches the original and wrote 23 new files.

How Do You Open DOCX Files? Word, LibreOffice, command line readers and passwords

Double-click the DOCX file to open it in Word. Without Word, open it in LibreOffice, or read it with pandoc, docx2txt, mammoth or one unzip command. All of them returned the text from all 19 good DOCX files that have text. A file with a password needs the password first.

A good DOCX file is easy to open. Most trouble comes from a file that is not what its ending says, a locked file or a damaged file. The sections below cover those three cases in order.

Ways to open DOCX files
Program What it does What we saw
Microsoft Word The program that made DOCX the default format in Word 2007 Not tested
LibreOffice Free office suite for Windows, Mac and Linux 19 of 19 files
Apple Pages and Preview Listed by FileInfo for Mac Not tested
Google Docs Opens DOCX files in a browser, says FileInfo Not tested
ONLYOFFICE Listed by FileInfo as a DOCX editor Not tested
pandoc Command line converter that writes text, Markdown and more 19 of 19 files
docx2txt Python module that returns the text 19 of 19 files
mammoth Python module that returns the text or HTML 19 of 19 files
python-docx Python module for reading and writing DOCX files 15 of 19 files
unzip and sed Two standard commands, no word processor 19 of 19 files

Open a DOCX file in Word

  1. Double-click the DOCX file, or open Word and choose File, then Open.
  2. If Word says the file is corrupt, go to the damaged DOCX files section before you try anything else.
  3. To get a copy in another format, choose File, then Save As. FileInfo lists DOC, PDF, ODT, RTF, TXT, HTML, XML, MHTML and XPS. We did not test these steps.

Older versions of Word need a free compatibility pack to read and write DOCX files. the Wikipedia page adds that some items, like equations, become pictures in the older program. We did not test it.

Open a DOCX file without Word

LibreOffice returned all the text from all 19 good DOCX files that have text. It also read the ODT, RTF, HTML, text and old DOC files that we had saved as .docx, by content. The first command below writes its text to a file. The others do the same job with other readers.

Read DOCX files without Word
# one line for every reader we tested
soffice --headless --convert-to "txt:Text (encoded):UTF8" report.docx
pandoc report.docx -o report.txt
python3 -c "import docx2txt; print(docx2txt.process('report.docx'))"
python3 -c "import mammoth; print(mammoth.extract_raw_text(open('report.docx','rb')).value)"
# no tools at all: unzip the text part and strip the tags
unzip -p report.docx word/document.xml | sed 's/<[^>]*>/ /g'

Every reader has a weak spot. The reader python-docx refuses templates and macro-enabled files because of their content types. Neither pandoc, docx2txt nor mammoth read any of the 8 files that were not DOCX files. LibreOffice read 5 of them. The unzip command needs no program at all. It printed every test item from all 19 good DOCX files, including the Hindi, Arabic, Chinese and Russian lines, because document.xml is UTF-8 text. It puts all paragraphs on one long line.

Before you try any of them, list the parts with unzip -l. If the list has no word/document.xml, you do not have a DOCX file, and the parts test says what you do have.

Open a password-protected DOCX file

A password-protected DOCX file is a compound file and not a ZIP file, so zip tools fail on it. In our test, LibreOffice, pandoc, python-docx, docx2txt, mammoth and the text pass all returned nothing from it. You need the password.

With the right password, msoffcrypto-tool wrote a normal DOCX file that all five readers could read. The password part of the kinds section has the code and the numbers.

Free viewers and reference pages

These cards link to six outside pages. Two are Windows or Mac viewers for DOCX files and one is a tool for damaged Word files. The other three are the Library of Congress record, Microsoft's page on damaged documents and the Wikipedia article on the format.

SysTools DOCX Viewer

Opens DOCX files on Windows and Mac

Free and paid options

Freeviewer DOCX Viewer

Opens DOCX files on Windows

Demo and paid options

SysTools Word Recovery

Repairs damaged Word files

Trial previews only

Microsoft: damaged documents

Steps to try in Word

Microsoft page

Office Open XML on Wikipedia

History and editions

Wikipedia page

DOCX at the Library of Congress

Format description

Library page

SysTools describes a free desktop viewer, version 3.0, for Windows and Mac. It opens DOCX files only and shows a preview or the text. A paid Pro version exists, and the page says the free version only previews the PDF conversion. The page is not consistent about whether Word must be installed, so read its notes. We did not start it.

Freeviewer describes a Windows viewer, version 4.0, that opens and previews DOCX files made with Office 2007 to 2019 and has a repair option. Its demo only previews recovered files, and its page is not consistent about whether Word is needed. We did not start it.

The SysTools viewer page itself points to the recovery tool for corrupt files. That tool's page says the free trial previews and cannot save. We name no prices, because the pages change.

Tip. You do not need a paid tool to read the words in a damaged DOCX file. The free text pass found all the text in 20 of our 28 damaged DOCX files.

Are DOCX Files Safe to Open? Macros, the internet and what the file says about you

Reading the text of any DOCX file is low risk. Macros are the main danger, and the Library of Congress ties them to .docm and .dotm files. DOCX files also store the author name and save times, so check them before you send a file out.

A DOCX file is a package. It can contain text, pictures, tables, styles and settings. The text and pictures do nothing until you read them. A macro starts working when you choose to enable it, so the safe habit is to leave macros off unless you know the sender.

The ending is a name anyone can change. The content type inside a DOCX file is the better guide, and our check script prints it. We built our test DOCX files with python-docx, LibreOffice and pandoc, so none had a macro. We did not test any DOCX file with a macro, and our DOCM files are labels only.

What to check before you open a DOCX file
Check What to do What we saw
Macros Read the content type, not the ending Our script named the DOCX, DOTX and DOCM types
Where it came from Windows Word blocks macros in files from the internet Microsoft page, not tested
Author and times Read docProps/core.xml Printed our test author
Program name Do not treat it as proof Said Word for Mac, but Word never touched the file
Hidden text Remove it with Document Inspector Microsoft page, not tested
The file kind Use the signature test Named all 8 files that were not DOCX files

What Microsoft says about macros and the internet

Microsoft's page on blocked macros says Windows marks files from the internet with a Mark of the Web. Office on Windows then blocks the macros in those files by default, and users see a SECURITY RISK banner with no Enable content button. The change began in Current Channel version 2206 on July 27, 2022.

The same page says Office on Mac, on the web, on Android and on iOS is not affected, and neither are perpetual versions like Office 2019 and LTSC 2021. It talks about Office files in general.

Microsoft's file block page lists the DOCX, DOTX, DOCM and DOTM endings and Word Flat Open XML as Word types. An administrator can block them from opening or send them to Protected View. If you manage computers, that setting is the one to look at.

What a DOCX file says about you

The part docProps/core.xml stores title, author, last editor, keywords, description and creation and save times. Our full DOCX file had an 816-byte core part. Three lines of Python printed the names and the date, and the unzip command prints the entire part.

Read the author and times of a DOCX file
import re, zipfile
x = zipfile.ZipFile('report.docx').read('docProps/core.xml').decode()
for tag in ('dc:creator', 'cp:lastModifiedBy', 'dcterms:created'):
    print(tag, re.findall('<%s[^>]*>([^<]*)' % tag, x))
The answer for our full DOCX file
dc:creator ['Test Author']
cp:lastModifiedBy ['Test Editor']
dcterms:created ['2026-10-19T08:00:00Z']

The part docProps/app.xml names a program. Ours said Microsoft Macintosh Word, version 14.0000. Word never touched that file. The library python-docx copied the line from its starting template. So the program name in a DOCX file is a label, and anyone can copy it.

Microsoft's page on Document Inspector says the inspector can remove comments, revision marks, document properties, personal information, headers, footers, watermarks and hidden text. It adds that it cannot find hidden text made in other ways, like white text on a white background. We did not test it.

Do not enable content for strangers. If a DOCX file from someone you do not know asks you to enable editing or enable content, close it. Text you can read without clicking anything is safe to read.

Why Will Your DOCX File Not Open? Twenty-eight damage tests, and the repairs that worked

DOCX files that will not open usually have a damaged ZIP index or a missing part, and the text is usually still inside. In our 28 damaged cases, a reader returned all the text from 12 as they were and from 22 after our repair script. A text pass found all of it in 20.

We broke copies of the full DOCX file in 28 ways and tried the readers before and after repairs. In the chart, green means all 52 items came back, amber means some did and red means none did.

A grid of 28 damage tests. Before any repair, at least one of five readers returned all 52 test items from 12 damaged copies, some from 0 and none from 16. After our repair script, all 52 items came back from 22, some from 1 and none from 5. A text pass found all 52 items in 20 copies, some in 2 and none in 6. The fixes range from cutting junk bytes to downloading the file again.
28 tests. Green is all 52 items back, amber is some and red is none.
Damage we met and what fixed it
Damage (cases) What you see and the fix
Start of the file wiped (2) Only docx2txt read the copies. The text pass found all 52 items in both. Fix: Write the ZIP signature back, or rebuild from the local headers.
End record or directory wiped or wrong (4) unzip and 7-Zip failed on all four. All five readers refused three of them, and only pandoc read the copy with the wrong offset. The text pass found all 52 items in every copy. Fix: Rebuild the index from the local headers.
Cut short (5) All five readers refused every cut file, even the one cut at 90 percent. The text pass found all 52 items in all five. Fix: Download again, or rebuild and lose the picture.
Extra bytes or a bad transfer (4) Junk in front: three readers read it. Junk at the end: four read it, and LibreOffice refused it. A text-mode transfer and a UTF-8 resave: all five refused them. Fix: Cut the junk and change 0D 0A back to 0A, or convert the bytes back.
Flipped bits (3) Bits in document.xml: all five readers refused it. Bits in the picture: python-docx refused it and four read it. Bits in styles.xml: three read it. Fix: Use a good copy. The script keeps the text when the bits are in the picture or styles.
A part removed (4) Without the content types or the root relationships, some readers still read the file (see the table in the inside section). Without document.xml, all five refused it. Both unzip and 7-Zip passed all four. Fix: Write the missing small part again, or use a good copy.
A part damaged or empty (5) Zeroed bytes in document.xml: all five refused it. A wrong CRC: only pandoc read it. 4,096 zeroed bytes mid-file: four read it. A cut document.xml or an empty one: all five refused them. Fix: Keep the full paragraphs of a cut part and ignore a wrong CRC, or use a good copy.
Empty file (1) Nothing to read. Fix: Download again.

The repairs, in small patches

Always work on a copy. The dd option conv=notrunc matters: without it, dd cuts the file after the bytes it writes and destroys the rest. The four patches below fixed our signature, junk in front, junk at the end and text-mode cases. In all four, the fixed copy was byte for byte the same as the file we started from.

Four small repairs we tested, on copies of the DOCX files
# work on copies, never on the original
# 1. first 4 bytes zeroed: write the ZIP signature back
cp broken.docx fixed-1.docx
printf '\x50\x4b\x03\x04' | dd of=fixed-1.docx bs=1 seek=0 conv=notrunc
# 2. junk in front: check_docx.py said the ZIP signature is at offset 512
tail -c +513 broken.docx > fixed-2.docx
# 3. junk at the end: cut the last 1,024 bytes
head -c -1024 broken.docx > fixed-3.docx
# 4. text-mode transfer: change every 0D 0A back to 0A
python3 -c "d=open('broken.docx','rb').read().replace(b'\r\n',b'\n'); open('fixed-4.docx','wb').write(d)"

The script below does the rest. It also undoes a UTF-8 resave and rebuilds the ZIP index from the local headers. When a small part is missing, it writes the part again from standard rules. When document.xml is cut short, it keeps the full paragraphs. It prints every fix it makes, writes a new file and never touches the original.

Repair DOCX files with Python
#!/usr/bin/env python3
"""repair_docx.py: small, safe fixes for a damaged .docx. Writes a new file, never touches the original.
Usage: python3 repair_docx.py damaged.docx fixed.docx
Steps, in order (every one is printed):
  1. undo a text-mode transfer (0D 0A back to 0A) when every 0A has a 0D in front of it
  2. cut junk in front of the first ZIP signature, and bytes after the end record
  3. if the ZIP still does not open: scan for local headers, inflate every part, keep the parts that pass
     the CRC test, and write a fresh ZIP with a new central directory
  4. put back a missing [Content_Types].xml, _rels/.rels or word/_rels/document.xml.rels from standard rules
  5. if word/document.xml is cut short or damaged, keep every full paragraph before the damage
It cannot bring back text that was never stored, a deflate stream after a flipped bit, or a password."""
import struct, sys, zlib, re, io, zipfile
from xml.sax.saxutils import escape

NS_W = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'
MAIN = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml'
OV = {
    'word/styles.xml': ('wordprocessingml.styles+xml', 'styles'),
    'word/numbering.xml': ('wordprocessingml.numbering+xml', 'numbering'),
    'word/settings.xml': ('wordprocessingml.settings+xml', 'settings'),
    'word/fontTable.xml': ('wordprocessingml.fontTable+xml', 'fontTable'),
    'word/webSettings.xml': ('wordprocessingml.webSettings+xml', 'webSettings'),
    'word/footnotes.xml': ('wordprocessingml.footnotes+xml', 'footnotes'),
    'word/endnotes.xml': ('wordprocessingml.endnotes+xml', 'endnotes'),
    'word/comments.xml': ('wordprocessingml.comments+xml', 'comments'),
}
IMG = {'png': 'image/png', 'jpeg': 'image/jpeg', 'jpg': 'image/jpeg', 'gif': 'image/gif', 'emf': 'image/x-emf', 'wmf': 'image/x-wmf', 'bmp': 'image/bmp'}

def inflate_partial(raw):
    """Inflate as far as the data allows. Returns (bytes, finished_cleanly)."""
    d, out = zlib.decompressobj(-15), b''
    try:
        for i in range(0, len(raw), 64):
            out += d.decompress(raw[i:i + 64])
            if d.eof: return out, True
    except zlib.error:
        return out, False
    return out, d.eof

def scan_parts(b):
    """Walk the local headers. Returns {name: (data, status)} with status ok, bad-crc or partial."""
    parts, p = {}, 0
    while True:
        p = b.find(b'PK\x03\x04', p)
        if p < 0 or p + 30 > len(b): break
        flag, meth = struct.unpack_from('<HH', b, p + 6)
        crc, cs, us = struct.unpack_from('<III', b, p + 14)
        nl, el = struct.unpack_from('<HH', b, p + 26)
        name = b[p + 30:p + 30 + nl].decode('utf-8', 'replace')
        s = p + 30 + nl + el
        if not re.fullmatch(r'[\w\[\]\-. /]+', name) or nl == 0:
            p += 4; continue
        if meth == 0:
            data = b[s:s + cs] if not flag & 8 else b[s:s + us]
            st = 'ok' if len(data) == us and (zlib.crc32(data) & 0xffffffff) == crc else 'partial'
            if flag & 8 and st == 'partial': st = 'ok'
        elif meth == 8:
            data, fin = inflate_partial(b[s:s + cs] if cs and not flag & 8 else b[s:])
            st = ('ok' if (zlib.crc32(data) & 0xffffffff) == crc or flag & 8 else 'bad-crc') if fin else 'partial'
        else:
            p += 4; continue
        if name not in parts or st == 'ok': parts[name] = (data, st)
        p = s
    return parts

def paragraphs_before_damage(xml):
    t = xml.decode('utf-8', 'replace')
    out = []
    for m in re.finditer(r'<w:p[ >].*?</w:p>', t, re.S):
        txt = ''.join(re.findall(r'<w:t[^>]*>([^<]*)</w:t>', m.group(0)))
        out.append(txt)
    return out

def plain_document(paras):
    body = ''.join(f'<w:p><w:r><w:t xml:space="preserve">{p}</w:t></w:r></w:p>' for p in paras)
    return (f'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<w:document xmlns:w="{NS_W}"><w:body>{body}'
            '<w:sectPr><w:pgSz w:w="12240" w:h="15840"/></w:sectPr></w:body></w:document>').encode()

def content_types(names):
    x = ['<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">',
         '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>',
         '<Default Extension="xml" ContentType="application/xml"/>']
    exts = {n.rsplit('.', 1)[-1].lower() for n in names if '.' in n.rsplit('/', 1)[-1]}
    for e in sorted(exts & set(IMG)): x.append(f'<Default Extension="{e}" ContentType="{IMG[e]}"/>')
    x.append(f'<Override PartName="/word/document.xml" ContentType="{MAIN}"/>')
    for n, (t, _) in OV.items():
        if n in names: x.append(f'<Override PartName="/{n}" ContentType="application/vnd.openxmlformats-officedocument.{t}"/>')
    if 'word/theme/theme1.xml' in names: x.append('<Override PartName="/word/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/>')
    if 'word/stylesWithEffects.xml' in names: x.append('<Override PartName="/word/stylesWithEffects.xml" ContentType="application/vnd.ms-word.stylesWithEffects+xml"/>')
    for n in sorted(names):
        if re.fullmatch(r'customXml/itemProps\d+\.xml', n): x.append(f'<Override PartName="/{n}" ContentType="application/vnd.openxmlformats-officedocument.customXmlProperties+xml"/>')
    if 'docProps/core.xml' in names: x.append('<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>')
    if 'docProps/app.xml' in names: x.append('<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>')
    x.append('</Types>')
    return ''.join(x).encode()

def root_rels(names):
    R = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'
    x = ['<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">',
         f'<Relationship Id="rId1" Type="{R}/officeDocument" Target="word/document.xml"/>']
    if 'docProps/core.xml' in names: x.append('<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>')
    if 'docProps/app.xml' in names: x.append(f'<Relationship Id="rId3" Type="{R}/extended-properties" Target="docProps/app.xml"/>')
    x.append('</Relationships>')
    return ''.join(x).encode()

def doc_rels(names, docxml):
    R = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'
    x = ['<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">']
    i = 1
    for n, (_, rt) in OV.items():
        if n in names: x.append(f'<Relationship Id="rId{i}" Type="{R}/{rt}" Target="{n[5:]}"/>'); i += 1
    if 'word/theme/theme1.xml' in names: x.append(f'<Relationship Id="rId{i}" Type="{R}/theme" Target="theme/theme1.xml"/>'); i += 1
    ids = list(dict.fromkeys(re.findall(r'r:embed="([^"]+)"', docxml.decode('utf-8', 'replace'))))
    media = sorted(n for n in names if n.startswith('word/media/'))
    for rid, m in zip(ids, media):
        x.append(f'<Relationship Id="{rid}" Type="{R}/image" Target="{m[5:]}"/>')
    x.append('</Relationships>')
    return ''.join(x).encode(), (len(ids), len(media))

def prune(keep, log):
    """Drop relationships and content-type overrides that point at parts which are gone."""
    import posixpath
    for rp in [n for n in keep if n.endswith('.rels')]:
        base = posixpath.dirname(posixpath.dirname(rp))
        x = keep[rp].decode('utf-8', 'replace'); gone = []
        def chk(m):
            tag = m.group(0)
            if 'TargetMode="External"' in tag: return tag
            t = re.search(r'Target="([^"]+)"', tag)
            if not t: return tag
            tgt = t.group(1).lstrip('/') if t.group(1).startswith('/') else posixpath.normpath(posixpath.join(base, t.group(1)))
            if tgt in keep: return tag
            gone.append(tgt); return ''
        y = re.sub(r'<Relationship\b[^>]*/>', chk, x)
        if gone:
            keep[rp] = y.encode(); log.append(f'{rp}: removed {len(gone)} link(s) to missing parts ({", ".join(gone)})')
    ct = keep['[Content_Types].xml'].decode('utf-8', 'replace')
    ct2 = re.sub(r'<Override PartName="/([^"]+)"[^>]*/>', lambda m: m.group(0) if m.group(1) in keep else '', ct)
    if ct2 != ct: keep['[Content_Types].xml'] = ct2.encode(); log.append('[Content_Types].xml: removed overrides for missing parts')

def zip_opens(b):
    try:
        z = zipfile.ZipFile(io.BytesIO(b)); return z.testzip() is None
    except Exception:
        return False

def xml_ok(d):
    try:
        import xml.dom.minidom as md; md.parseString(d); return True
    except Exception:
        return False

def fix(b):
    log = []
    if b[:4] == b'PK\x03\x04' and not zip_opens(b):
        try:
            u = b.decode('utf-8').encode('latin-1')
            if zip_opens(u): b = u; log.append('converted the bytes back from UTF-8 to single bytes (undo a UTF-8 resave)')
        except Exception:
            pass
    if b[:4] == b'PK\x03\x04' and b.count(b'\r\n') > 20 and b'\n' not in b.replace(b'\r\n', b''):
        b = b.replace(b'\r\n', b'\n'); log.append('replaced 0D 0A with 0A (undo a text-mode transfer)')
    at = b[:65536].find(b'PK\x03\x04')
    if at > 0 and b[:4] != b'PK\x03\x04' and any(b[:at]) and b[:4] != bytes(4):
        b = b[at:]; log.append(f'removed {at} bytes in front of the first ZIP signature')
    e = b.rfind(b'PK\x05\x06')
    if e >= 0 and e + 22 <= len(b):
        clen, = struct.unpack_from('<H', b, e + 20)
        if e + 22 + clen < len(b):
            n = len(b) - (e + 22 + clen); b = b[:e + 22 + clen]; log.append(f'removed {n} bytes after the end record')
    parts = scan_parts(b)
    keep, dropped = {}, []
    for n, (d, st) in parts.items():
        if st == 'ok': keep[n] = d
        elif st == 'bad-crc' and (n.startswith('word/media/') or xml_ok(d)):
            keep[n] = d; log.append(f'{n}: CRC does not match but the data inflates{"" if n.startswith("word/media/") else " and parses as XML"}; kept it, check the result')
        elif st == 'partial' and n.startswith('word/media/'):
            keep[n] = d; log.append(f'{n}: cut short; kept the part so the link still points somewhere (the picture will not show)')
        else: dropped.append(f'{n} ({st})')
    if dropped: log.append('dropped damaged parts: ' + ', '.join(dropped))
    names = set(keep)
    doc = keep.get('word/document.xml')
    if not doc and 'word/document.xml' in parts:
        data, st = parts['word/document.xml']
        paras = paragraphs_before_damage(data) if data else []
        if paras:
            doc = plain_document(paras); log.append(f'word/document.xml was {st}: kept {len(paras)} complete paragraphs as plain text (formatting and tables are lost)')
            keep['word/document.xml'] = doc; names.add('word/document.xml')
    if not doc:
        log.append('word/document.xml could not be recovered: there is no text left to rebuild from')
        return None, log, 'no document part'
    if not re.search(rb'</w:document>\s*$', doc) or not xml_ok(doc):
        paras = paragraphs_before_damage(doc)
        if not paras:
            log.append('word/document.xml is damaged and has no full paragraph'); return None, log, 'no document text'
        keep['word/document.xml'] = doc = plain_document(paras); log.append(f'word/document.xml did not end cleanly: rebuilt from {len(paras)} complete paragraphs (formatting and tables are lost)')
    rebuilt = False
    if '[Content_Types].xml' not in keep:
        keep['[Content_Types].xml'] = content_types(names); log.append('wrote a new [Content_Types].xml from standard rules'); rebuilt = True
    if '_rels/.rels' not in keep:
        keep['_rels/.rels'] = root_rels(names); log.append('wrote a new _rels/.rels from standard rules'); rebuilt = True
    if 'word/_rels/document.xml.rels' not in keep:
        r, (ni, nm) = doc_rels(names, doc); keep['word/_rels/document.xml.rels'] = r
        log.append(f'wrote a new word/_rels/document.xml.rels (picture links are a guess: {ni} used, {nm} stored)'); rebuilt = True
    prune(keep, log)
    heavy = bool(dropped or rebuilt or any('CRC does not match' in l or 'cut short' in l or 'did not end' in l or 'complete paragraphs' in l or 'missing parts' in l for l in log))
    if not heavy and zip_opens(b):
        return b, log, ('fixed without rewriting any part' if log else 'nothing to fix: the ZIP opens and every required part is there')
    bio = io.BytesIO(); zo = zipfile.ZipFile(bio, 'w', zipfile.ZIP_DEFLATED)
    order = ['[Content_Types].xml', '_rels/.rels'] + [n for n in keep if n not in ('[Content_Types].xml', '_rels/.rels')]
    for n in order: zo.writestr(n, keep[n])
    zo.close()
    log.insert(0, f'scanned local headers: {len(parts)} parts found, {sum(1 for d, st in parts.values() if st == "ok")} pass the CRC test')
    return bio.getvalue(), log, 'new ZIP written'

if __name__ == '__main__':
    b = open(sys.argv[1], 'rb').read()
    if not b: print('empty file: nothing to repair'); sys.exit(1)
    out, log, note = fix(b)
    for l in log: print('fix:', l)
    print(note)
    if out is None: sys.exit(1)
    open(sys.argv[2], 'wb').write(out)
    print(f'wrote {sys.argv[2]} ({len(out):,} bytes)')
The answer for our DOCX file with 512 bytes of junk in front
fix: removed 512 bytes in front of the first ZIP signature
fixed without rewriting any part
wrote fixed.docx (76,357 bytes)

The script wrote 23 files from our 28 copies. In 4 cases the fixed DOCX file matched the original byte for byte. In the others it wrote a new ZIP with the same text. It wrote no file when the text part was gone. When it writes picture links again, it has to guess which part is the picture, so check the result.

What the tools could not fix

Flipped bits or zeroed bytes in document.xml Every reader refused both copies, the text pass found nothing and the script wrote no file. The unzip command printed the first 11 or 12 items with garbled words at the break. The fix is a good copy.
document.xml removed or stored empty No text is left in the package. A good copy is the only fix.
document.xml cut in half The text pass returned 26 of 52 items and the repair script 25. The rest is gone.
Cut files and picture damage The cut files lost the picture. Flipped bits in the picture left the text readable, but python-docx refused the file.
Empty file Nothing to read. Download the file again.
Junk bytes, a transfer and a wrong CRC Junk in front, junk at the end, a text-mode transfer and a UTF-8 resave were all undone byte for byte. A wrong CRC for document.xml was ignored by the script, and the text came back.

Check the result. After a repair, compare the text with the original if you have it. An open file is not always the right file, so count the pages, the table cells and the pictures.

How Do You Get the Text Out of a Broken DOCX File? Two commands and a short script

Unzip a DOCX file and strip the tags, and you have its text. When the ZIP index is damaged, a short script that reads local headers returned all the text from 20 of our 28 damaged DOCX files. The best single reader managed 10.

A DOCX file keeps its text in one XML part, so you can skip the word processor. The one-line command below needs a ZIP index that works. On our 28 damaged DOCX files it printed all 52 items from 11, and part of them from 3 more.

Pull the text out with two standard commands
unzip -p broken.docx word/document.xml | sed 's/<[^>]*>/ /g' > text.txt

When unzip fails, use a text pass. The script below ignores the index. It looks for every local header, inflates what it can of every part and prints text from every paragraph. In our tests it returned all 52 items from 20 DOCX copies, part of them from 2 and none from 6. It reads text in stored order, so paragraphs come out in order.

Pull the text out of a damaged DOCX file
#!/usr/bin/env python3
"""text_pass.py: print the text of a DOCX file, even when the ZIP directory is damaged.
Usage: python3 text_pass.py damaged.docx > text.txt
It ignores the central directory. It scans for local headers, inflates what it
can of every part and prints the w:t text of every paragraph it finds."""
import re, struct, sys, zlib

def parts(b):
    p = 0
    while True:
        p = b.find(b'PK\x03\x04', p)
        if p < 0 or p + 30 > len(b): return
        meth, = struct.unpack_from('<H', b, p + 8)
        nl, el = struct.unpack_from('<HH', b, p + 26)
        name = b[p + 30:p + 30 + nl].decode('utf-8', 'replace')
        start = p + 30 + nl + el
        if meth == 8 and name.endswith('.xml'):
            d, out = zlib.decompressobj(-15), b''
            try:
                for i in range(start, len(b), 64):
                    out += d.decompress(b[i:i + 64])
                    if d.eof: break
            except zlib.error:
                pass
            yield name, out
        p += 4

for path in sys.argv[1:]:
    b = open(path, 'rb').read()
    for name, xml in parts(b):
        t = xml.decode('utf-8', 'replace')
        if '<w:t' not in t: continue
        for para in re.findall(r'<w:p[ >].*?(?:</w:p>|$)', t, re.S):
            line = ''.join(re.findall(r'<w:t[^>]*>([^<]*)', para))
            if line: print(line)
The answer for our DOCX file with its central directory zeroed
$ python3 text_pass.py broken.docx | wc -l
53
$ python3 text_pass.py broken.docx | head -2
Harbor Notes
Paragraph 01: The harbor report for week 1 lists 52 boats and 255 crates.

The 53 lines are the title and our 52 test items. In that copy, unzip and 7-Zip failed and every reader refused the file.

The limits of a text pass

The text pass drops formatting and pictures, and it prints table cells as separate lines. It reads only what it can inflate. Where a flipped bit or zeroed bytes are early in document.xml, it printed nothing, though the unzip command printed some garbled items from the same copies. It ignores the CRC, so a part with a wrong CRC can give wrong words. Compare the result with a good copy when you have one.

A free ZIP repair: zip -FF

Info-ZIP's zip 3.0 has a repair mode that scans for local headers and writes a new index. It asks whether the archive is a single disk, so answer y.

Rebuild the ZIP index with zip
zip -FF broken.docx --out fixed.docx
# answer y when it asks about a single-disk archive
unzip -t fixed.docx

On our 28 copies, at least one reader returned all 52 items from 20 of the repaired DOCX files. That is 8 more than before repair, and none fewer. All five readers did so for only 7 files, because zip -FF does not write back a missing content types part or relationships part. It did nothing for a text-mode transfer, a UTF-8 resave, bit flips in document.xml, zeroed bytes, a cut or missing document.xml or an empty file. Our own script reached 22 files.

The Register's 2014 guide describes a manual route: rename your DOCX file to .zip, find the unclosed tag in document.xml, fix it and zip the file again. That route works when one tag is wrong and the rest of the XML is fine. It does not help with a broken index or a cut file.

Work on a copy. Every command on this page that writes a file writes a new one. Keep the first copy until you have checked the output.

How Do You Convert a DOCX File to Another Format? PDF, ODT, DOC, RTF, HTML, Markdown and EPUB, checked by count

Convert DOCX files with Save As in Word, or from the command line with LibreOffice or pandoc. In our tests, both kept all 52 items in every format we tried, and ODT, DOC and EPUB kept the picture byte for byte.

Converting any DOCX file reads its document and writes it again in another format. A good conversion keeps words, tables and pictures. We converted our 76,357-byte full DOCX file with LibreOffice 24.2.7.2 and pandoc 3.1.3, and counted the 52 items in every output.

LibreOffice conversions

What the LibreOffice conversions gave
Format Bytes What we saw
DOC 66,048 All 52 items. The picture was byte for byte the same.
ODT 69,873 All 52 items. The picture was byte for byte the same.
DOTX 59,701 All 52 items. A template with the same picture.
PDF 40,100 All 52 items, read back with pdftotext. The picture was stored as a 9,677-byte JPEG.
RTF 112,396 All 52 items. Bigger than the DOCX file.
HTML 8,416 All 52 items. The picture came out as a separate 38,520-byte PNG.
Text 3,114 All 52 items. Formatting and the picture are gone.

pandoc conversions

What the pandoc conversions gave
Format Bytes What we saw
ODT 46,741 All 52 items. The picture was byte for byte the same.
EPUB 44,220 All 52 items. The picture was byte for byte the same.
RTF 82,854 All 52 items.
HTML 3,843 All 52 items. No picture came out.
Markdown 3,258 All 52 items. No picture came out.
Text 3,492 All 52 items. No picture came out.
Convert DOCX files from the command line
soffice --headless --convert-to pdf report.docx
soffice --headless --convert-to odt report.docx
soffice --headless --convert-to doc report.docx
soffice --headless --convert-to rtf report.docx
soffice --headless --convert-to html report.docx
soffice --headless --convert-to "txt:Text (encoded):UTF8" report.docx
pandoc report.docx -o report.md
pandoc report.docx -o report.epub

PDF was the smallest output with a picture and a fixed layout, and it cannot be edited as a document. Text and Markdown were smaller still, and they lose the formatting. We did not test Word, so we cannot say how its Save As differs.

Count the output

A count shows whether a conversion did its job. We searched every output for all 52 items. Do the same with your DOCX files by comparing page counts, table cells and pictures with the original.

Does the picture survive?

In ODT, DOC, DOTX and EPUB outputs, the picture was stored as the same 38,520 bytes as the PNG we put in. In PDF it was packed again as a JPEG. In LibreOffice HTML it came out as a separate PNG file next to the page, so move both files together. We did not check the picture in RTF files.

Where every format is covered

FileInfo lists these Save As targets in Word: DOC, PDF, ODT, RTF, TXT, HTML, XML, MHTML and XPS, with XPS on Windows only. Read about the PDF format, OpenDocument text, MHTML web archives, XPS documents and EPUB books before you select a target.

Keep the DOCX file. Convert a copy. If the output looks wrong, the DOCX file is the only place the original layout lives.

What Else Looks Like a DOCX File? .dotx, .docm, .zip and endings that mislead

Not every file named .docx is a DOCX file. We saved eight other kinds under the ending .docx. LibreOffice read five of them by their content, and the check script named all eight. Of the other readers, pandoc, python-docx, docx2txt and mammoth read none.

The ending is a name anyone can change. The Wikipedia entry shows the standard, and the PRONOM record shows what a DOCX file is. This table lists endings that you may meet near .docx.

Endings near the DOCX file
Ending What it is Our test
.docx Word 2007 and later documents, the format in this guide Tested
.dotx Word templates, listed by PRONOM Tested
.docm Documents with macros, says the Library of Congress Label only
.dotm Templates with macros Not tested
.doc Word 97 to 2003 documents, a compound file Tested
.odt OpenDocument text, also a ZIP package Tested
.xlsx Excel workbooks, also a ZIP package Tested
.pptx PowerPoint slides, also a ZIP package Tested
.rtf Rich Text Format, readable text with markup Tested
.html A web page that Word can open Tested
.zip Any ZIP archive, and the container of every DOCX file Tested

Endings that mislead

We gave eight other files the ending .docx: an ODT, an RTF, a web page, a text file, an old DOC, a PDF, an Excel workbook and a PowerPoint file. LibreOffice read the first five by content and returned all 52 items from every one. It read nothing from the PDF, workbook or slides. The file command named all eight correctly.

The first-bytes table shows what every kind looks like. A program that trusts the ending would try to read all eight as Word documents. A program that looks inside would not.

Other Word and Office formats

For older Word files, see the guide to the older DOC format and its DOC versus DOCX comparison. The guide to OpenDocument text covers the open-source format. Older spreadsheets and slides are in the guide to XLS workbooks and the guide to PPT slides. For any ZIP archive, see the guide to ZIP archives.

Word packages DOCX, DOTX, DOCM and DOTM files. They are ZIP packages with word/document.xml, and the content type names the kind.
Other ZIP-based files ODT, XLSX and PPTX files. They start with 50 4b 03 04 and have other parts inside.
Compound files Password-protected DOCX files and old DOC files. They start with the same 8 bytes and are not ZIP files.
Text-based files RTF, HTML and text files. Readable text from the first bytes.

What Is the DOCX File Format Made Of? The facts we could check, and what we left out

The DOCX format is an open standard, and its main facts are easy to check. These are the facts we could confirm against Ecma, the Library of Congress, PRONOM, Microsoft Learn, Wikipedia, FileInfo and our own 61 test files, plus the points we left out.

Facts about the DOCX file format
Part What we could check
Standard Office Open XML, as ECMA-376 and ISO/IEC 29500. Ecma lists the fifth edition as the current one, with Part 2 dated December 2021.
Editions Ecma lists archived editions from December 2006, December 2008, June 2011 and December 2012. Wikipedia still names an older edition as the latest, so we used Ecma.
Variants Transitional and Strict. Office 2010 saves only Transitional, and Office 2013 and later fully support Strict but do not default to it, says Wikipedia.
Container A ZIP package with the folders _rels, docProps and word, says the Library of Congress.
Main part word/document.xml, with document, body, paragraph and text elements, says Microsoft Learn.
MIME type application/vnd.openxmlformats-officedocument.wordprocessingml.document, says the Library of Congress.
PRONOM fmt/412 for DOCX, fmt/1827 for Strict, fmt/597 for DOTX and fmt/599 for DOTM. The DOCX rule looks in the content types part.
Encryption A compound file with EncryptionInfo and EncryptedPackage streams. Microsoft Learn lists Standard encryption versions 2.2, 3.2 and 4.2.
Text UTF-8 XML. The Hindi, Arabic, Chinese and Russian lines in our multi files came back through the unzip command.
Pictures In word/media, stored as they came. Our 38,520-byte PNG matched byte for byte.
Properties Title, author and times in docProps/core.xml. A program name in docProps/app.xml.

About the values we list

The sizes and part lists come from our own files and from the sources named. All of our DOCX files came from python-docx 1.2.0, LibreOffice 24.2.7.2 and pandoc 3.1.3 on Linux in October 2026. Word may write other parts and other values, so treat our numbers as guides, not promises.

The Library of Congress record covers the Transitional form, in editions 1 to 5 of the standard. Wikipedia's history section dates Ecma approval to December 2006 and ISO publication to November 2008. Microsoft Learn says the simplest valid document needs only the main document part, which fits what we saw: some readers still read a file that lacks the small parts.

What we left out

We did not test any DOCX file made by Word. We also left out Strict files, files with macros, files with embedded objects and files with comments or tracked changes. We did not use Word, Pages, Google Docs or WPS Office. Everything about them comes from FileInfo, Wikipedia and Microsoft. The DOCM files and one DOTX file are content type edits.

The SysTools and Freeviewer tools come from their own pages, and we did not start either one. We name no prices. Their pages are not always consistent about free and paid features, so read the limits there.

Our damage tests are simple breaks in small files. We tried five readers, and others may behave differently. Python writes the parts of a small file in a fixed order, so a cut file kept its text. Test our check script, text pass and repair script on copies before you trust them with anything that matters.

Sources checked

For the format and its history, see the Wikipedia article on Office Open XML, the FileInfo page for the DOCX ending and the Ecma page for ECMA-376. The Library of Congress record fdd000397 describes the Transitional form. For identification, see the PRONOM record fmt/412. For layout, see the Microsoft Learn page on document structure. For encryption, see the Microsoft Learn page on encryption and the Didier Stevens post on encrypted documents. For damage and safety, see Microsoft on damaged documents, on blocked macros, on file block and on Document Inspector.

Every test count came from the 61 files and the scripts in our research folder.

Frequently Asked Questions About DOCX Files

What is a DOCX file?

DOCX files are Word documents saved as a ZIP package of XML parts. Word has made DOCX the default for new documents since Word 2007, says the Library of Congress. The text is in word/document.xml, and the pictures, styles and settings are in other parts. FileInfo says a DOCX file can contain text, images, tables and charts.

How do I open a DOCX file?

Double-click it to open it in Word. Without Word, use LibreOffice, Apple Pages, Google Docs or ONLYOFFICE, which FileInfo lists for DOCX files. In our tests, LibreOffice, pandoc, docx2txt, mammoth and an unzip command all returned the text from all 19 good DOCX files that have text.

How do I know if a file is a DOCX file?

The first 4 bytes, 50 4b 03 04, do not prove it, because XLSX, PPTX and ODT files start the same way. Look inside the ZIP for a content types part that names the Word type and for word/document.xml. A short Python script on this page lists the parts, tests every CRC and prints the kind.

Is a DOCX file safe to open?

Reading the text is low risk. Macros are the danger, because they can start code when you enable them, and the Library of Congress ties them to .docm and .dotm files. Microsoft says Office on Windows blocks macros in files from the internet by default. Do not enable content for a file from someone you do not know.

How do I open a password-protected DOCX file?

You need the password. A locked DOCX file is a compound file and not a ZIP file, and all our readers returned nothing from it. With the right password, msoffcrypto-tool decrypted our test file into a normal DOCX file, and all five readers then returned the text. We did not test any way to find a lost password.

Why will my DOCX file not open?

The usual cause is a damaged ZIP index or a missing part. In our 28 damaged DOCX files, a reader returned all the text from 12 as they were and from 22 after our repair script. A text pass found all of it in 20, so the words are often still inside.

How do I get the text out of a corrupt DOCX file?

Work on a copy. If unzip still lists the file, use unzip -p broken.docx word/document.xml and strip the tags. If the index is gone, a short script that reads the local headers found all the text in 20 of our 28 damaged DOCX files. Microsoft also lists Recover Text from Any File in Word, which keeps text and loses formatting.

Can I repair a DOCX file with a script?

For some damage, yes. Our script cut junk bytes, undid a text-mode transfer and a UTF-8 resave, and rebuilt the ZIP index from the local headers. It also wrote back a missing content types part or relationships part. It gave all 52 test items back in 22 of 28 cases, and it cannot rebuild a flipped or zeroed document.xml.

How do I convert a DOCX file to PDF or ODT?

In Word, use Save As. With LibreOffice, use the command soffice --headless --convert-to pdf report.docx, and change pdf to odt for an ODT file. In our tests, LibreOffice and pandoc kept all 52 test items in every format we tried. The picture came out byte for byte the same in ODT, DOC and EPUB.

Can I open a DOCX file by renaming it to ZIP?

Yes, for most files. The Library of Congress says a DOCX file can be opened with ZIP software after you rename it to .zip, and our unzip command opened DOCX files with no rename. Only a ZIP-based file opens this way. A password-protected DOCX file is a compound file, and a file saved under the wrong ending is still the wrong kind.

What is the difference between DOCX, DOTX and DOCM files?

They differ by the content type of the main part. DOCX is a document, DOTX is a template, DOCM is a document with macros and DOTM is a template with macros, says the Library of Congress. All four are ZIP packages. Our templates and macro-enabled copies stopped python-docx, and the other readers we tried read them.

Is there a free DOCX viewer?

SysTools and Freeviewer both describe viewers for DOCX files. The SysTools page lists a free version for Windows and Mac, and the Freeviewer page lists a Windows demo. We did not start either one. LibreOffice is free and read all 19 of our good DOCX files that have text.

Still Stuck With a DOCX File? Eight routes out, depending on what went wrong

Your DOCX file will not open

Start with the fixes by damage. A wiped signature, a bad index or a missing small part explains many cases, and small byte edits fix them.

Your DOCX file asks for a password

Go to the password steps. You need the password, and the readers return nothing without it.

Does it end in .doc? The page on the older DOC format covers Word 97 to 2003 files.

Is it an open-source document? See the page on OpenDocument ODT files.

Do you want a fixed layout? The page on PDF documents explains the format for documents that must look the same everywhere.

Is it a ZIP archive? The page on ZIP files explains the container that every DOCX file uses.

Is it a spreadsheet or slides? The Excel XLS page and the PowerPoint PPT page cover the older formats.

Is it a file type you do not know? Browse the file extension resource library, or send it to us on the submit a file type page.