A workbook can present three clean tabs and carry a dozen more you will never see by clicking around. Excel decides whether a sheet appears in the tab strip with a single word — the state attribute on each <sheet> element in xl/workbook.xml. Set it to hidden and the tab disappears but the right-click Unhide list still offers it back. Set it to veryHidden and the tab disappears and the Unhide list stays empty — a sheet that, by ordinary means, the recipient cannot even discover exists. Either way the sheet itself, with every value, formula, source range, and old draft on it, sits in the package fully intact. This post walks through where the flag lives, the difference between the two hidden states, what routinely ends up on concealed sheets, why hiding removes nothing from the file, why Document Inspector is an unreliable guard, and how to find and strip the sheets before a workbook leaves the building.
Every worksheet in a workbook is registered once in xl/workbook.xml, inside a <sheets> list. Each entry is a <sheet> element with a display name, an internal sheetId, a relationship r:id pointing at the part that actually holds the cells, and — only when the sheet is not visible — a state attribute. That single attribute is the entire mechanism. It does not move, encrypt, or shrink the sheet; it only tells the application whether to draw a tab for it.
There are three legal values. The default, written as visible or simply left off, draws the tab normally. hidden removes the tab but keeps the sheet in the user-facing Unhide dialog, so anyone can bring it back in two clicks. veryHidden removes the tab and withholds it from that dialog — a state that can only be set or cleared through VBA or by editing the XML directly. The data on the sheet is identical in all three cases. The only thing that changes is how hard the format works to keep the reader from noticing it is there.
Marking a sheet hidden or veryHidden changes one attribute in xl/workbook.xml. The worksheet part — every value, formula, and source range on it — remains in the package, byte for byte, and ships with the file.
The sheet registry is small and readable. A workbook with three visible tabs and two concealed ones looks like this — the concealment is the one extra attribute, nothing more:
// xl/workbook.xml — the sheets list, two sheets the reader will never see
<sheets>
<sheet name="Summary" sheetId="1" r:id="rId1"/>
<sheet name="Dashboard" sheetId="2" r:id="rId2"/>
<sheet name="Client View" sheetId="3" r:id="rId3"/>
// these two have a state attribute — the reader never sees a tab
<sheet name="Cost_Model" sheetId="4" state="hidden" r:id="rId4"/>
<sheet name="Margin_Floor_DONOTSEND" sheetId="5" state="veryHidden" r:id="rId5"/>
</sheets>
Read what the registry hands over before anyone opens a single cell. There are five sheets, not three. One is named Cost_Model and one Margin_Floor_DONOTSEND — names the author chose for their own eyes, now legible to anyone who unzips the file. The r:id on each points through xl/_rels/workbook.xml.rels to the exact worksheet part holding the data, so finding the concealed content is a single hop away. The sheet name alone — like a defined name or a connection label — is often enough to tell a reader what they were not meant to find.
The gap between the two concealed states is the whole reason veryHidden matters. A hidden sheet is a courtesy hide: right-click any tab, choose Unhide, and it is listed there for the taking. Most recipients who suspect something is missing will find a hidden sheet in seconds. It protects against clutter, not against a curious reader.
A veryHidden sheet is different in kind. It does not appear in the Unhide dialog at all. There is no menu, no checkbox, no ribbon command in standard Excel that will reveal it. To an ordinary user it simply does not exist — which is precisely why developers use it to stash configuration, license logic, and scratch calculations behind a polished front end. The catch is that “cannot be revealed through the UI” is not “cannot be read.” The sheet is in the ZIP exactly like any other, and a text editor or a three-line script walks straight past the concealment the interface so carefully maintains.
veryHidden only instructs the Excel interface not to offer the sheet for unhiding. It does not remove, encrypt, or restrict the worksheet part. Anyone who opens the file as a ZIP — or flips the attribute back in a few seconds — reads it in full.
Concealed sheets are not exotic; they are where the working machinery of a workbook is kept once the front tabs are made presentable. The recurring offenders are exactly the things an author would least like to forward:
VLOOKUP formulas pull from — the same internal picklists that also surface through data validation lists.The visible tabs are the report; the concealed ones are how it was made. Forwarding the file ships both. And because the formulas on the visible sheets frequently reference the hidden ones by name — =Cost_Model!B12 — the front of the workbook openly cites the back, telling a reader a hidden sheet exists and exactly what it is called even before they go looking for it.
Concealment fails most completely when the rest of the workbook keeps referring to what is concealed. A hidden sheet that nothing references is at least quiet; a hidden sheet wired into the live model announces itself from every direction. The trails are everywhere:
=Margin_Floor_DONOTSEND!A1 on a visible sheet names the hidden sheet in plain text and shows what it is used for.refersTo, fully legible regardless of the sheet’s state.The intuition that makes hidden sheets dangerous is the belief that hiding is a kind of deletion — that an out-of-sight sheet is, for practical purposes, gone. It is not. The worksheet lives in its own part, typically xl/worksheets/sheet5.xml, and that file is present in the ZIP whether the sheet is visible, hidden, or very hidden. Setting the state attribute touches xl/workbook.xml and nothing else.
That means every defence people imagine they have applied is illusory. Hiding a sheet does not strip its formulas. It does not flatten its values. It does not break the references the rest of the workbook makes to it. It does not remove it from the shared-strings table that pools every text value in the file, so even the words on a very-hidden sheet sit in the same plain-text vocabulary as everything else. The sheet is exactly as readable as the front page; it simply has no tab.
The tab strip is a rendering of the visible sheets, not an inventory of the file. Count the <sheet> elements in xl/workbook.xml and the xl/worksheets/ folder, not the tabs, to know how many worksheets you are actually sending.
Surfacing the layer needs only a ZIP reader and an XML parser. Listing every sheet with its visibility state takes a few lines and never opens Excel — and crucially, it lists the veryHidden sheets the application itself refuses to show:
# list every sheet and its visibility state, very-hidden included
import zipfile
from lxml import etree
def local(t): return t.rsplit("}", 1)[-1]
with zipfile.ZipFile("workbook.xlsx") as z:
root = etree.fromstring(z.read("xl/workbook.xml"))
for s in root.iter():
if local(s.tag) != "sheet": continue
state = s.get("state", "visible")
print(f"{state:<10} {s.get('name')}")
For a quick manual check, renaming the file to .zip, expanding it, and opening xl/workbook.xml shows the full sheet list with every state in seconds — the same trivial unzip-and-read move that also exposes the data-connection store and the hyperlink relationship records. Anyone who can rename a file can enumerate every sheet you thought you had concealed.
From a single sweep through a workbook’s sheet registry and the parts behind it, a reader typically extracts:
DONOTSEND to old_ to a client’s name left on a reused template.Taken together, the concealed sheets often hold more than the visible ones — they are the inputs, the workings, and the history that the presentable front tabs are designed to summarise and smooth over. For anyone reviewing a workbook adversarially, the hidden sheets are the first place worth looking.
Hidden sheets are one of the areas Document Inspector does address — its “Hidden Worksheets” check can find and remove sheets in the hidden state. That is genuinely useful, but it comes with two large caveats that turn it into false confidence if relied on alone.
First, the inspector historically does not remove veryHidden sheets — the very state most likely to hold the things an author most wants concealed. A sheet hidden the ordinary way may be flagged and stripped while the very-hidden sheet beside it is passed over entirely. Second, the check is opt-in and inconsistent across versions and platforms; a workbook can be saved, sent, and never inspected at all. The result is the familiar blind spot: a file can clear the checks a user did run and still ship a very-hidden sheet full of cost models — the same gap that leaves custom XML parts and defined names in place.
Document Inspector can remove ordinary hidden sheets but typically leaves veryHidden ones in place. Passing the inspector is not evidence that every concealed sheet is gone — it is evidence that the easy ones are.
The goal is a delivered workbook whose xl/workbook.xml lists only the sheets you intend the recipient to have, with no hidden or veryHidden survivors and no orphaned worksheet parts. The realistic options, in increasing order of reliability:
#REF! errors the deletion surfaces on the visible sheets — those errors are the visible model telling you what it was quietly pulling from the hidden one.Whichever path you take, verify by re-opening the delivered file as a ZIP and confirming that xl/workbook.xml lists exactly the sheets you meant to send and that the xl/worksheets/ folder holds no more parts than that. A workbook that merely shows the right tabs is not a workbook that contains only the right sheets.
<sheet> elements in xl/workbook.xml and compare to the tabs you can see — a mismatch means concealed sheets.state attribute on every sheet and treat any veryHidden value as a deliberate concealment to investigate.xl/workbook.xml and xl/worksheets/ contain only the intended sheets.A concealed sheet rarely travels alone. The lookup table on it usually backs data-validation dropdowns on the visible sheets; its values are pooled into the same shared-strings table as everything else; its formula cells are catalogued in the calculation chain; and a defined name may point straight at it. Removing the sheet while leaving those siblings populated can shed the obvious copy and keep several echoes. A workbook is only sheet-clean when the concealed sheets, the names and validations that reference them, and the caches built from their data 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 what the interface chooses to draw is not the same as what the file contains. The tab strip is a curated view; the sheet registry is the truth. Three clean tabs can sit in front of a cost model, a rate card, and last quarter’s draft — each a full worksheet in the package, each one attribute away from visible, each readable by anyone who unzips the file.
Hiding a sheet was meant to be housekeeping — a way to keep helper tabs out of sight so a report reads cleanly. The state attribute does exactly that and nothing more: it asks the interface not to draw a tab. It does not ask the format to forget the sheet, and the format does not. Behind every very-hidden tab is a complete worksheet, with its values, its formulas, and the references that wire it into the visible model — all in plain XML, all one unzip away.
For workbooks that stay inside the team, hidden and very-hidden sheets are exactly the convenience they were built to be: the working parts kept tidily out of the way. For workbooks crossing the perimeter, they are the inputs, the workings, and the history the author assumed had been left behind. The only durable defences are to enumerate every sheet regardless of state, delete the ones that should not ship rather than re-hiding them, and verify against the sheet registry — and to remember that in this format, the tabs you can see were never the same thing as the sheets you are sending.
Use MetaData Analyzer to enumerate every worksheet in your files regardless of visibility state, surface hidden and very-hidden sheets the Excel interface refuses to list, read the names and contents of concealed lookup tables, cost models, and leftover drafts, flag sheets the visible model still references, and confirm no workbook is shipping a sheet you meant to keep before it leaves the organisation.
Named ranges scoped to a hidden sheet carry its name in plain text, pointing straight at the sheet you tried to conceal.
The dropdowns on a visible sheet often draw their values straight from the hidden lookup tables beside them.
Even the text on a very-hidden sheet is pooled into the same plain-text vocabulary as everything else in the file.