runlocally

runlocally engineering notes

Strip XLSX Metadata

How Strip XLSX Metadata is built

By Geppetto · · Open Strip XLSX Metadata →

Strip XLSX Metadata removes author names, company names, and other identifying properties from an Excel workbook before it’s shared. This post covers where that information actually lives inside the OOXML package — there are more places than the one everyone remembers to check — and what it takes to remove one of them cleanly.

Tech used

OOXML metadata parts, and the browser’s own XML APIs

As covered in Extract Images from Excel, an .xlsx/.xlsm file is a ZIP archive of XML parts. Metadata specifically lives in a handful of those parts: docProps/core.xml (author, last-modified-by, created/modified timestamps), docProps/app.xml (company, manager), and optionally docProps/custom.xml (arbitrary custom properties someone added). The tool reads and rewrites only these targeted parts with DOMParser/XMLSerializer — both built into every browser — and copies every other ZIP entry through unmodified via @zip.js/zip.js. No XML or XLSX library is added for this; text nodes are targeted by local name and cleared in place:

function clearTextNodes(node: Node): boolean {
  let changed = false;
  for (const child of Array.from(node.childNodes)) {
    if (child.nodeType === 3 || child.nodeType === 4) {
      if (child.nodeValue !== '') {
        child.nodeValue = '';
        changed = true;
      }
    } else {
      changed = clearTextNodes(child) || changed;
    }
  }
  return changed;
}

Matching by element.localName rather than a qualified tag string sidesteps a real annoyance in OOXML: the same element can appear with different namespace prefixes (dc:creator vs. a default-namespaced creator) depending on which application last saved the file.

Implementation & operational notes

Comment authors and threaded-comment people are metadata too, and they’re stored differently from each other. Classic cell comments (xl/comments1.xml, one file per sheet that has any) record the author’s name directly as an <author> element’s text — cleared the same way as docProps/core.xml. Modern threaded comments are different: the comment XML only holds a personId reference, and the actual display name lives in a separate xl/persons/person.xml part, as a displayName attribute rather than element text:

for (const person of elementsByLocalName(document, 'person')) {
  if (!person.hasAttribute('displayName')) continue;
  const rawValue = person.getAttribute('displayName') ?? '';
  ...
  if (rawValue !== '') {
    person.setAttribute('displayName', '');
    changed = true;
  }
}

Clearing only docProps and missing xl/persons/person.xml would leave every threaded-comment participant’s real name intact in an otherwise “cleaned” file — the two comment systems needed two different pieces of code.

Removing docProps/custom.xml means unregistering it from two other files, not just deleting the part. A ZIP entry can’t simply be dropped if other parts of the package still reference it: [Content_Types].xml declares an Override for /docProps/custom.xml’s content type, and _rels/.rels declares a Relationship pointing to it. Leaving either reference dangling produces a package that some Excel versions may flag as damaged on open. So removing custom properties is a three-part operation — omit the custom.xml entry itself, then strip the matching Override element out of [Content_Types].xml and the matching Relationship out of _rels/.rels — and the tool only performs any of the three when docProps/custom.xml is actually present, since untouched files should stay byte-identical wherever nothing needs to change.

The custom-properties values are inspected before the part is dropped, so the result can report what was found. inspectCustomPropertiesXml reads out each <property> element’s name attribute before that entry is omitted from the rewritten archive — the result the user sees lists which custom property names were removed, rather than a silent “something was cleaned.”

One deliberate scope cut: xl/revisions/*. Shared-workbook change-history parts can also carry author names, but they’re a rarer feature (shared workbooks are a legacy Excel collaboration mode) and parsing their revision-log structure is a meaningfully larger job than the fixed set of metadata parts above. This is left out of the current scope rather than half-handled, and the tool doesn’t claim to cover it.

The output keeps the input’s container format. A macro-enabled .xlsm produces a *-cleaned.xlsm, not a .xlsx — the MIME type and extension are chosen from the uploaded file’s own extension, since silently changing a macro-enabled workbook to a plain one would be a bigger, unrequested change than the metadata removal itself.

Try it / source

Strip XLSX Metadata

Open the tool → All posts →