xlsx pivot parts: pivotTableParts, pivotSource, and fmtId¶
Searches for OOXML pivot internals usually start from a part name and a question: why a worksheet carries <pivotTableParts>, what <c:pivotSource> binds inside a chart, and what the <c:fmtId> value in it refers to. This page maps those relationships inside the package, names the parts involved, and lists the failures Excel repairs silently.
Read and write pivot tables in Python with WolfXL¶
You can build every part in the graph below from Python with WolfXL: the pivot cache definition and its records, the pivot table part, the worksheet relationship, and a pivot chart's <c:pivotSource> with its <c:fmtId>. Install the Community release from PyPI:
import wolfxl
from wolfxl.chart import BarChart, Reference
from wolfxl.pivot import PivotCache, PivotTable
wb = wolfxl.Workbook()
ws = wb.active
ws.append(["region", "quarter", "revenue"])
for row in [("East", "Q1", 120), ("East", "Q2", 135),
("West", "Q1", 90), ("West", "Q2", 110)]:
ws.append(row)
# The cache is workbook-scoped; the table is sheet-scoped.
src = Reference(ws, min_col=1, min_row=1, max_col=3, max_row=5)
cache = wb.add_pivot_cache(PivotCache(source=src))
pt = PivotTable(cache=cache, location="E2", rows=["region"],
cols=["quarter"], data=[("revenue", "sum")])
ws.add_pivot_table(pt)
# Optional pivot chart: pivot_source emits <c:pivotSource> and <c:fmtId>.
chart = BarChart()
chart.add_data(Reference(ws, min_col=3, min_row=1, max_row=5),
titles_from_data=True)
chart.pivot_source = pt
ws.add_chart(chart, "E10")
wb.save("pivot.xlsx")
# Read the pivot tables back from the saved file.
for handle in wolfxl.load_workbook("pivot.xlsx").active.pivot_tables:
print(handle.name, handle.location) # PivotTable1 E2:H6
unzip -l pivot.xlsx then lists xl/pivotCache/pivotCacheDefinition1.xml, xl/pivotCache/pivotCacheRecords1.xml, xl/pivotTables/pivotTable1.xml, and xl/charts/chart1.xml. The cache records are written at save time, so the pivot opens populated with no refresh on open.
- Compatibility matrix: pivot tables: supported pivot operations, including field and aggregation edits on an existing pivot through
ws.pivot_tables. - openpyxl migration: pivot tables: the
openpyxl.pivottowolfxl.pivotmapping, with worked cache, table, and chart examples. - Edit a workbook that already has pivot tables: modify mode changes the cells you name and carries the pivot cache, pivot table, and chart parts across unchanged.
The part graph¶
| Part | Path inside the package | References |
|---|---|---|
| Worksheet | xl/worksheets/sheet1.xml |
<pivotTableParts><pivotTablePart r:id="rId1"/> |
| Worksheet relationships | xl/worksheets/_rels/sheet1.xml.rels |
relationship of type pivotTable to the pivot table part |
| Pivot table | xl/pivotTables/pivotTable1.xml |
<pivotTableDefinition name="PivotTable1" cacheId="1"> and, once the table has a pivot chart, a <chartFormats> collection |
| Pivot cache definition | xl/pivotCache/pivotCacheDefinition1.xml |
<cacheSource><worksheetSource ref="A1:D10000" sheet="Data"/> and an r:id to the records part |
| Pivot cache records | xl/pivotCache/pivotCacheRecords1.xml |
<pivotCacheRecords count="N"> holding one <r> per cached row |
| Chart | xl/charts/chart1.xml |
<c:pivotSource><c:name>PivotTable1</c:name><c:fmtId val="0"/></c:pivotSource> |
| Styles | xl/styles.xml |
the differential formats (dxf) a pivot format index can refer to |
Each part is bound to its parent through a relationships part, never by path convention alone. xl/worksheets/_rels/sheet1.xml.rels is what turns the r:id in <pivotTableParts> into an actual part.
Why pivotTableParts decides whether a pivot exists at all¶
<pivotTableParts> on the worksheet is the only link between a sheet and its pivot tables. A package can contain a complete xl/pivotTables/pivotTable1.xml and cache parts that no worksheet refers to. Excel loads the sheet, finds no pivot table parts, and shows nothing: the parts sit in the ZIP and stay unreachable from the workbook graph.
The same rule applies one level up. The workbook part lists the cache through xl/_rels/workbook.xml.rels, and the cache definition points at its records with its own r:id. Removing any one relationship leaves the remaining parts orphaned rather than raising a clear error.
What pivotSource and fmtId bind in a chart¶
A pivot chart carries two pieces of pivot metadata:
<c:name> matches the name attribute on the pivot table definition, which is how the chart attaches to one specific pivot table rather than to the sheet.
<c:fmtId> carries a pivot format id. ECMA-376 Part 1 §21.2.2.70 defines it as
the chart's link back to the pivot table: the value matches the chart attribute
of a <chartFormat> element in the table part's <chartFormats> collection
(§3.10.1.12), whose format attribute then selects the pivot format in use: the
chart's <c:pivotFmts> collection holds those entries, and the spreadsheet side
describes the same index as a differential format (dxf) in the Styles part.
Microsoft Excel typically writes 0 here. The value is a pivot format index
rather than a workbook number format: it does not resolve through the number
formats in xl/styles.xml, and the number formats a pivot field applies live on
pivotField/@numFmtId in the same pivot table part.
<!-- xl/pivotTables/pivotTable1.xml -->
<pivotTableDefinition name="PivotTable1" cacheId="1">
<chartFormats>
<chartFormat chart="1" format="0" series="0"/>
</chartFormats>
</pivotTableDefinition>
Two consequences follow:
- The two parts refer to each other by name and by index, so renaming the pivot table or renumbering the charts breaks the link without touching the chart's data.
- A
fmtIdthat matches no<chartFormat>entry leaves the chart's pivot formatting unattached, and Excel may drop it or offer to repair the file. The same rewrites that drop the<pivotTableParts>relationship can leave the chart pointing at a table the workbook no longer loads.
Failures that Excel repairs quietly¶
| Symptom in Excel | Cause in the package |
|---|---|
| Pivot table missing after open | Pivot parts exist but the worksheet has no pivotTableParts entry, or the relationship in _rels is absent |
| "We found a problem with some content" on open | Dangling r:id, missing cache records part, or a pivotField/@numFmtId with no matching format |
| Pivot opens with no rows | Cache definition present without a populated pivotCacheRecords part |
| Chart loses its pivot formatting | <c:name> no longer matches the pivot table name, or the chart's fmtId matches no <chartFormat> entry |
| Pivot survives, formatting does not | Pivot parts rewritten from scratch instead of edited in place, discarding format references |
Constructing pivot parts from Python¶
Most Python spreadsheet libraries can preserve pivot parts they read. Emitting a working set of parts is the harder case, because the cache, the table, and the chart's pivot source have to agree on first write.
WolfXL 2.0 constructs the set in modify mode and validates the emitted parts. The
openpyxl migration guide carries the worked examples:
ws.add_pivot_table(pt) for the cache and table, and chart.pivot_source = pt
for the chart binding, which is the attribute that writes the chart's
<c:pivotSource> block naming that table.
- Compatibility matrix records which openpyxl pivot idioms map to WolfXL equivalents and which remain out of scope.
- openpyxl alternatives, measured covers the wider library comparison behind the same receipts.
Checking a package by hand¶
Unzip the .xlsx and read the relationship parts first. They answer most questions about a broken pivot faster than the pivot XML itself:
unzip -o workbook.xlsx -d /tmp/xlsx-parts
cat /tmp/xlsx-parts/xl/worksheets/_rels/sheet1.xml.rels
cat /tmp/xlsx-parts/xl/_rels/workbook.xml.rels
Grep the sheet for pivotTableParts, then confirm the relationship it names exists in _rels. When a chart loses its pivot formatting rather than its data, check that the <c:name> value matches the pivot table's name attribute, and that any <chartFormat> the table declares names the chart's fmtId value in its chart attribute.