A modern Excel cell can show a single word — Microsoft, Berlin, a stock ticker — while holding an entire structured record behind it. That record is a rich value: a linked data type whose dozens of fields live in the xl/richData/ parts and are wired to the grid through xl/metadata.xml. The cell displays one field; the file caches all of them — the price, the market cap, the population, the CEO, the headquarters, the data provider, and the moment the snapshot was taken — whether or not the author ever pulled those fields into a visible column. This post walks through where the rich-data layer lives, what a single linked cell leaks, how images placed in cells keep their original media and alt text, why dynamic-array and rich-value metadata survives converting the cell to plain text, why Document Inspector ignores it entirely, and how to actually strip cached rich data before a workbook leaves the perimeter.
When a user converts text to a linked data type — Stocks, Geography, Currencies, or any of the organisation or third-party data types now available through the ribbon — the cell stops being a string and becomes a reference to a rich value. On screen it still looks like a single value with a small icon, and the formula bar shows one display string. Underneath, Excel has fetched and frozen an entire record: a stock cell carries price, change, volume, market cap, P/E ratio, 52-week high and low, currency, exchange, the company’s full legal name, headquarters, employee count, industry, and more; a geography cell carries population, area, capital, currency, time zone, and a chain of administrative parents. The grid shows whichever one field the author chose. The file stores all of them.
That record does not live in the worksheet cell. The cell holds a tiny pointer; the data lives in a dedicated family of parts under xl/richData/ — principally rdrichvalue.xml for the values, rdrichvaluestructure.xml for the field schema, and rdRichValueTypes.xml for the type definitions — and the connection between a cell and its record is made through a separate part, xl/metadata.xml. This is the same frozen-copy pattern as the pivot cache and the chart cache, applied to a layer that looks like an ordinary cell value rather than a data store.
A linked data type caches every field of its record, not just the one displayed in the cell. A column that shows only company names still carries each company’s full profile — revenue, headcount, headquarters, executives — sitting in xl/richData/rdrichvalue.xml in plain text, ready to read.
The rich-data layer separates the values from their shape. rdrichvaluestructure.xml declares the structure — the ordered list of field names and their types — and rdrichvalue.xml stores one <rv> element per record, with a <v> child for each field in the order the structure defines. A single stock record looks like this:
// xl/richData/rdrichvaluestructure.xml — the field schema
<s>
<k n="_DisplayString" t="s"/>
<k n="Price" t="r"/>
<k n="Market cap" t="r"/>
<k n="Employees" t="r"/>
<k n="Headquarters" t="s"/>
</s>
// xl/richData/rdrichvalue.xml — one record, every field cached
<rv s="0">
<v>Contoso Components Inc.</v>
<v>148.27</v>
<v>92340000000</v>
<v>41200</v>
<v>Redmond, WA, United States</v>
</rv>
The cell on the sheet might be formatted to show only _DisplayString — the company name. But the record carries the price, the market cap, the headcount, and the headquarters, each in plain UTF-8. If the author built a watchlist of acquisition targets and only ever displayed their names, the rich value still holds every financial field Excel pulled for each one — a full profile per row, recoverable without opening the file in Excel at all.
Each linked cell stores a complete entity record — every field the data provider returned, not the single one rendered in the grid. Read rdrichvalue.xml and you recover the entire profile of every company, place, or instrument in the column, including the fields the author deliberately left out of view.
The worksheet cell itself does not contain the record — it contains a small vm (value metadata) attribute, an index into xl/metadata.xml. That part defines a metadata type for rich values and a list of metadata records, each of which points, in turn, at an <rv> index in rdrichvalue.xml. The chain is: cell → vm → metadata.xml record → richData record. A cell carrying a linked type looks like this in the sheet:
// xl/worksheets/sheet1.xml — the cell points out, not in
<c r="B2" t="e" vm="1">
<v>#VALUE!</v>
</c>
// the literal cell value is a fallback; the record lives elsewhere
Two things follow from this design. First, the visible cell value is almost a decoy — the real content is one indirection away, which is exactly why a casual look at the worksheet XML understates what the file holds. Second, the same metadata.xml part also records the metadata for dynamic-array formulas — the spill ranges produced by functions like FILTER, SORT, and UNIQUE — so a workbook with no linked data types at all can still carry a populated metadata layer describing which cells are array roots and how far they spilled. The presence of xl/metadata.xml is itself a fingerprint of a modern Excel build and the features the author used.
A rich value is not just data — it is sourced data, and the source travels with it. The record schema typically carries provider fields alongside the visible ones: a _Provider or sub-label naming the data partner that returned the record, identifiers tying the entity back to the provider’s catalogue, and supporting property bags that describe how the value was resolved from the text the author originally typed. A stock record names the market-data partner; a geography record names the mapping source; an organisation data type names the internal service it was pulled from.
Two disclosures fall out of this. The provider identity reveals which data services the organisation subscribes to and routes its analysis through — a piece of competitive and operational intelligence in its own right. And because a rich value is a snapshot, not a live feed, the cached numbers are frozen as of the moment they were fetched: a price, a market cap, or a population that was current on the day the author pressed the button and has been carried, unchanged, ever since. That makes the rich value a quiet timestamp — the same kind of frozen-as-of provenance that timestamp analysis reconstructs from other corners of the package.
Every rich value carries provider provenance and a frozen snapshot of the data as of when it was fetched. The file therefore discloses which data services you use and preserves figures that were accurate on one specific day — long after the live values have moved on.
The same rich-data machinery powers the modern “place an image in a cell” feature and the IMAGE function. When a picture is inserted into a cell rather than floated over the grid, it becomes a rich value whose media is stored in xl/media/ and bound to the record through xl/richData/richValueRel.xml and its relationships file. The cell shows a thumbnail; the package holds the original image at full resolution, along with any alternative-text description the author or the source attached to it.
That is the same exposure covered for floated pictures in embedded images and OLE objects, with an extra wrinkle: because the image is a rich value, it is even less visible as a separable object. The cell looks like data. But the original file — with whatever EXIF, GPS coordinates, or camera detail it arrived with — sits in the media folder, and the alt text travels in plain words in the rich-data part. Cropping or shrinking the in-cell thumbnail changes the display, not the stored original.
The dangerous property of the rich-data layer, exactly as with pivot and chart caches, is that it is decoupled from the visible state of the cell. Several routine “clean-up” actions leave the cached records intact while creating the impression the data is gone:
rdrichvalue.xml — the display choice never touched the stored record.In each case the author performs an action that feels like removing data and concludes the workbook is clean. The visible grid agrees with that belief, which is exactly why the cached record — one indirection away, in a part nobody opens — is so easy to ship by accident.
Extracting the full record set needs only a ZIP reader and an XML parser. Pairing the field names from rdrichvaluestructure.xml with the values in rdrichvalue.xml reconstructs every entity profile directly from the package:
# dump every rich value: field schema + cached records
import zipfile
from lxml import etree
def local(tag): return tag.rsplit("}", 1)[-1]
with zipfile.ZipFile("workbook.xlsx") as z:
struct = etree.fromstring(z.read("xl/richData/rdrichvaluestructure.xml"))
keys = [k.get("n") for k in struct.iter() if local(k.tag) == "k"]
data = etree.fromstring(z.read("xl/richData/rdrichvalue.xml"))
for rv in data.iter():
if local(rv.tag) != "rv": continue
vals = [v.text for v in rv if local(v.tag) == "v"]
print(dict(zip(keys, vals)))
The script prints, for every linked cell, the complete field-to-value mapping — the entire profile, not the one field the grid showed. For ad-hoc inspection, renaming the file to .zip, expanding it, and grepping the xl/richData/ folder for <v> surfaces every cached value in seconds. That is the same trivial unzip-and-read move that also exposes the shared-strings table and the chart caches.
From a single sweep through the rich-data and metadata parts of a workbook, a reader typically extracts:
metadata.xml even with no linked types present.The first four categories are the data and its sourcing; the last two are structure and provenance. Together they routinely reconstruct an entire dataset the author believed they had reduced to a tidy column of names — the full profile of every entity, with its origin attached.
Microsoft’s Document Inspector checks document properties, hidden sheets, comments, and a handful of other layers — but it has no concept of rich-value or linked-data-type content. From the inspector’s point of view a linked cell is legitimate data the user deliberately created and wants to keep; stripping the record behind it would break the cell, so the inspector leaves it entirely alone. There is no “remove cached rich data” option, because the cache is not treated as removable metadata at all.
That places rich data in the same blind spot as chart caches, slicer caches, and table schemas: layers that carry real disclosure but that the inspector classifies as ordinary content. A workbook that has been “inspected and cleaned” ships every rich value untouched. Showing one field does not help, converting cells you happen to notice does not catch the orphans, and the one tool most users trust to find hidden data does not look here.
Document Inspector treats a linked cell as content to keep, not metadata to remove, so it never reads the record behind it. A workbook can pass every inspector check and still carry the full profile of every entity, the data providers behind them, and the original media of every in-cell image.
There is no single Excel UI action that empties the rich-data layer while keeping the linked cells live, because the record is what makes a linked cell work. The realistic options trade off how much of the data-type behaviour you need to preserve:
xl/richData/ part and the rich-value entries in xl/metadata.xml, clear the vm attributes from the worksheet cells, prune the orphaned relationships and [Content_Types].xml entries, and replace each former linked cell with its display string — a complete pass, not just a UI conversion of the cells you happened to see.Whichever path you take, the orphaned record is the easy thing to miss: collapsing a linked cell to text feels like removing the data, but the richData record can remain in the package as an unreferenced part, still holding the full profile. A complete pass enumerates the rich-data parts directly rather than trusting that converting the cell removed the record.
xl/richData/ part and the rich-value records in xl/metadata.xml directly — do not assume converting the visible cells removed them.<rv> record against its schema; confirm the full field set is safe to disclose, including the fields never shown in the grid.xl/media/ and richValueRel.xml for in-cell images; verify each carries no EXIF, GPS, or other media metadata.metadata.xml for dynamic-array footprints if the spill structure of the workbook is itself revealing.The rich-data layer rarely travels alone. The same entities it profiles are often also frozen in a pivot cache if a pivot summarised them, plotted into a chart cache if a chart used their fields, preserved in the shared-strings table wherever their names were typed into cells, and reachable through a Power Query connection if the data was pulled from a source. Stripping the rich-data records while leaving those siblings populated removes one copy of the dataset and ships several others. A workbook is only rich-data-clean when the records, the metadata wiring, the in-cell media, and every parallel store of the same values are handled in one pass.
The broader lesson is the one this series keeps returning to: a workbook is a package of cooperating parts, and the same data is frequently stored in several of them at once. A linked cell looks like the most compact object in the file — one word, one icon, the opposite of a dataset. In the format it is a complete, sourced, time-stamped record, sitting in plain XML one indirection away from the cell that hides it.
A linked data type is meant to be the convenient face of external data — type a company name, get a live, structured entity you can pull fields from on demand. The rich-data layer quietly inverts that convenience for anyone sharing the file. Behind every one-word cell is the full record Excel fetched; behind the displayed field are all the fields the author never showed; behind the data is the provider that supplied it and the day it was frozen; behind the in-cell thumbnail is the original image at full resolution. None of it shows in the rendered grid, and none of it is removed by displaying one field, hiding a sheet, or running Document Inspector. All of it is plain UTF-8 in xl/richData/ and xl/metadata.xml, recoverable with a ZIP reader and a ten-line script.
For workbooks that stay inside an organisation, the cache is exactly what it was built to be: the thing that makes a linked cell instant and structured. For workbooks crossing the perimeter, it is a complete, sourced, dated dataset the author thought they had reduced to a column of names. The only durable defences are to flatten linked cells to static text or values, or to strip the rich-data and metadata parts in code — and to remember that in this format, the word in the cell was never the whole story. The record behind it is.
Use MetaData Analyzer to enumerate every linked data type and rich value across every worksheet, read the full record behind each one, recover the fields never displayed in the grid, surface the data providers and frozen snapshots behind each record, flag the original media behind in-cell images, and confirm no cell is shipping a dataset you never meant to disclose before your workbooks leave the organisation.
The same cache-survives-the-source story in the chart layer — a frozen data table the picture renders from and Document Inspector never touches.
Where in-cell images keep their EXIF, GPS, and full-resolution originals — the media layer that a thumbnail never reveals.
Another layer that names external sources and caches their data — connections and rich values both record where the numbers came from.