Wiki source code of Confluence CSV Format

Last modified by superadmin on 2026/10/14 18:25

Show last authors
1 == Scope ==
2
3 As of version ##9.96.0## (##9.96.2## really, as previous versions are seriously flawed), the Confluence module can import Confluence Cloud CSV backups. This document presents their format. It is targeted at:
4
5 * technical people working on Confluence migration tools or tools involving Confluence exports
6 * technical people running migrations who need to deeply investigate some issues
7 * people curious about the Confluence export format
8
9 == Introduction ==
10
11 In 2026, Atlassian has been phasing out [[Confluence XML>>doc:documentation.extensions.admin.confluence.xml-format.WebHome]] backups on Confluence Cloud. CSV backups replace them.
12
13 Like a Confluence XML package, a Confluence CSV package is a zip file. It contains, among other things, an attachments folder and a CSV file per database table (with a ##csv.gz## extension - mind you, that doesn't always mean they are actually gzipped, see below). While the Confluence XML backup is a hibernate dump, where what's exported is serialized java objects (more or less), CSV files are more directly what's stored in the RDBMS (so for instance if you have ever looked at a PostgresSQL instance containing Confluence server stuff, these CSV file will look quite familiar). Also contrary to Confluence XML backups, there doesn't seem to be an ##exportDescriptor.properties## file: of course, we know where a CSV export comes from: Atlassian's Confluence Cloud.
14
15 Like for Confluence XML exports, there are two types of CSV bakups we know about:
16
17 * A **space backup package**, produced from the settings of a space in Confluence.
18 * A **site backup package, **produced from the global administration in Confluence. It contains all the  spaces, as well as **users and groups**, but it **doesn't necessarily contain the attachments** folder.
19
20 At the time of writing, there are a few major differences between the site export and the space export, effectively making them slightly different formats.
21
22 1. About the CSV tables:(((
23 * In **space backups**, the CSV files are at the root of the ZIP archive, and despite their ##.csv.gz## extension, **are not actually gzipped**, they are stored plain text! (so, on many operating systems, double clicking on them will fail, because your OS will try to gunzip it and of course it can't work.
24 * In **site backups**, they are indeed gzipped, and **they are in a data folder**. {{warning}}What's more, the ##body## field in the ##bodycontent.csv.gz## file is compressed with zstd and then base64-encoded.{{/warning}}
25 )))
26 1. About the attachment folder:(((
27 * In **space backups**, the attachments folder is structured like in Confluence XML backups: an attachment is found at ##attachments/<stable page id>/<stable attachment id>/<version number>##
28 * In **site backups**, an attachment is found at ##attachments/<uuid>## where uuid is given by the FILESTORE_ID property (provided in ##contentproperties.csv.gz##) of the attachment revision record (defined in ##content.csv.gz##)
29 )))
30 1. About the ##whiteboards## folder: only found in site exports. We haven't looked into the whiteboards yet.
31 1. About the ##metadata.json## file: only found in site exports. It's the somewhat spiritual replacement of the exportDescriptor.properties file found in XML backups. It provides a list of the ##.csv.gz## files in the archive with their sizes in bytes, and some stuff like the backup id, account id, the cloud instance id, things probably mostly relevant to Atlassian's setup. Notably absent from this file would be a backup date. We don't currently use this file.
32
33 == Description of the CSV tables ==
34
35 |=Basename|=Columns|=Description|=Notes / Handling
36 |##AO_950DC3_TC_CUSTOM_EV_TYPES##|##BELONG_SUB_CALENDAR_ID##, ##CREATED##, ##ICON##, ##ID##, ##TITLE##|? - related to calendars|ignored
37 |##AO_950DC3_TC_DISABLE_EV_TYPES##|##EVENT_KEY##, ##ID##, ##SUB_CALENDAR_ID##|? - related to calendars|ignored
38 |##AO_950DC3_TC_EVENTS##|##ALL_DAY##, ##CREATED##, ##DESCRIPTION##, ##END##, ##ID##, ##LAST_MODIFIED##, ##LOCATION##, ##ORGANISER##, ##RECURRENCE_ID_TIMESTAMP##, ##RECURRENCE_RULE##, ##REMINDER_SETTING_ID##, ##SEQUENCE##, ##START##, ##SUB_CALENDAR_ID##, ##SUMMARY##, ##URL##, ##UTC_END##, ##UTC_START##, ##VEVENT_UID##|Calendar|ignored
39 |##AO_950DC3_TC_EVENTS_EXCL##|##ALL_DAY##, ##EVENT_ID##, ##EXCLUSION##, ##ID##|=? - related to calendars|ignored
40 |##AO_950DC3_TC_EVENTS_INVITEES##|##EVENT_ID##, ##ID##, ##INVITEE_ID##|=? - related to calendars|ignored
41 |##AO_950DC3_TC_JIRA_REMI_EVENTS##|##ALL_DAY##, ##ASSIGNEE##, ##DESCRIPTION##, ##EVENT_TYPE##, ##ID##, ##ISSUE_ICON_URL##, ##ISSUE_LINK##, ##JQL##, ##KEY_ID##, ##STATUS##, ##SUB_CALENDAR_ID##, ##TICKET_ID##, ##TITLE##, ##USER_ID##, ##UTC_END##, ##UTC_START##|? - related to Jira events|ignored
42 |##AO_950DC3_TC_REMINDER_SETTINGS##|##CUSTOM_EVENT_TYPE_ID##, ##ID##, ##LAST_MODIFIER##, ##PERIOD##, ##STORE_KEY##, ##SUB_CALENDAR_ID##|? - related to reminder settings|ignored
43 |##AO_950DC3_TC_REMINDER_USERS##|##ID##, ##SUB_CALENDAR_ID##, ##USER_KEY##|? - related to reminders|ignored
44 |##AO_950DC3_TC_SUBCALS##|##COLOUR##, ##CREATED##, ##CREATOR##, ##DESCRIPTION##, ##ID##, ##NAME##, ##PARENT_ID##, ##SPACE_KEY##, ##STORE_KEY##, ##SUBSCRIPTION_ID##, ##TIME_ZONE_ID##, ##USING_CUSTOM_EVENT_TYPE_ID##, ##LAST_MODIFIED##|? - Related to calendar subscriptions?|ignored
45 |##AO_950DC3_TC_SUBCALS_IN_SPACE##|##ID##, ##SPACE_KEY##, ##SUB_CALENDAR_ID##|? - Related to space calendars?|ignored
46 |##AO_950DC3_TC_SUBCALS_PRIV_GRP##|##GROUP_NAME##, ##ID##, ##SUB_CALENDAR_ID##, ##TYPE##, ##GROUP_ID##|? - Related to group calendars?|ignored
47 |##AO_950DC3_TC_SUBCALS_PRIV_USR##|##ID##, ##SUB_CALENDAR_ID##, ##TYPE##, ##USER_KEY##|? - Related to user calendars?|ignored
48 |##AO_950DC3_TC_SUBCALS_PROPS##|##ID##, ##KEY##, ##SUB_CALENDAR_ID##, ##VALUE##|? - Related to calendar property?|ignored
49 |##audit_affected_object##|##id##, ##name##, ##type##, ##auditrecordid##|?|ignored
50 |##audit_changed_value##|##id##, ##name##, ##oldvalue##, ##newvalue##, ##hidden_oldvalue##, ##hidden_newvalue##, ##auditrecordid##|?|ignored
51 |##auditrecord##|##auditrecordid##, ##summary##, ##description##, ##category##, ##address##, ##sysamdin##, ##superadmin##, ##authorname##, ##authorfullname##, ##authorkey##, ##objectname##, ##objecttype##, ##searchstring##, ##creationdate##, ##eventtype##, ##spaceid##|##?##|ignored
52 |##bandana##|##bandanaid##, ##bandanacontext##, ##bandanakey##, ##bandanavalue##|?|ignored
53 |##bodycontent##|##bodycontentid##, ##body##, ##contentid##, ##bodytypeid##|Contains the body contents of page revisions|(((
54 fully imported
55
56 (% class="box warningmessage" %)
57 (((
58 NOTE: in site backups, the body field is compressed with zstd than then base64-encoded
59 )))
60 )))
61 |##content##|##contentid##, ##hibernateversion##, ##contenttype##, ##title##, ##lowertitle##, ##version##, ##creator##, ##creationdate##, ##lastmodifier##, ##lastmoddate##, ##versioncomment##, ##prevver##, ##content_status##, ##pageid##, ##spaceid##, ##child_position##, ##parentid##, ##messageid##, ##pluginkey##, ##pluginver##, ##parentccid##, ##draftpageid##, ##draftspacekey##, ##drafttype##, ##draftpageversion##, ##parentcommentid##, ##username##, ##navigationtype##
62 |##content_data_classification_mapping##|##id##, ##classification_level_id##, ##content_id##, ##content_status##, ##creation_date##, ##last_modified_date##, ##classification_source##, ##classification_version_id##|?|ignored
63 |##content_label##|##id##, ##labelid##, ##contentid##, ##pagetemplateid##, ##owner##, ##creationdate##, ##lastmoddate##, ##labelableid##, ##labelabletype##|Maps labels to contents or templates fully imported
64 |##content_perm##|##id##, ##cp_type##, ##username##, ##groupname##, ##external_group_id##, ##cps_id##, ##creator##, ##creationdate##, ##lastmodifier##, ##lastmoddate##|Content permissions|fully imported
65 |##content_perm_set##|##id##, ##cont_perm_type##, ##content_id##, ##creationdate##, ##lastmoddate##|Content permission sets|fully imported
66 |##content_relation##|##relationid##, ##targetcontentid##, ##sourcecontentid##, ##targettype##, ##sourcetype##, ##relationname##, ##creationdate##, ##lastmoddate##, ##creator##, ##lastmodifier##|Documents links between pages and wether a page is a copy of another, and possibly other relations|ignored
67 |##contentproperties##|##propertyid##, ##propertyname##, ##stringval##, ##longval##, ##dateval##, ##contentid##|Content properties|fully imported
68 |##default_spacerole_assignments##|##principalid##, ##principaltype##, ##roleid##, ##creator##, ##creationdate##, ##lastmodifier##, ##lastmodificationdate##, ##role_id##, ##deleted_id##
69 |##?##|##ignored##
70 |##extrnlnks##|##linkid##, ##contenttype##, ##viewcount##, ##url##, ##lowerurl##, ##contentid##, ##creator##, ##creationdate##, ##lastmodifier##, ##lastmoddate##|External links|ignored
71 |##follow_connections##|##connectionid##, ##follower##, ##followee##|Follower/Followee relationship|ignored
72 |##label##|##labelid##, ##name##, ##owner##, ##namespace##, ##creationdate##, ##lastmoddate##|Labels|fully imported
73 |##likes##|##id##, ##contentid##, ##username##, ##creationdate##, ##sequencenum##, ##liked##|Liked contents per user|ignored
74 |##links##|##linkid##, ##destpagetitle##, ##lowerdestpagetitle##, ##destspacekey##, ##lowerdestspacekey##, ##contentid##, ##creator##, ##creationdate##, ##lastmodifier##, ##lastmoddate##|Links between pages|ignored
75 |##logininfo##|##id##, ##curfailed##, ##totalfailed##, ##successdate##, ##prevsuccessdate##, ##faileddate##, ##username##|?|ignored
76 |##notifications##|##notificationid##, ##contentid##, ##labelid##, ##spaceid##, ##username##, ##creator##, ##creationdate##, ##lastmodifier##, ##lastmoddate##, ##digest##, ##network##, ##contenttype##, ##includechildren##|Notifications|ignored
77 |##os_propertyentry##|##entity_name##, ##entity_id##, ##entity_key##, ##key_type##, ##boolean_val##, ##double_val##, ##string_val##, ##text_val##, ##long_val##, ##int_val##, ##date_val##|?|ignored
78 |##pagetemplates##|##templateid##, ##hibernateversion##, ##templatename##, ##templatedesc##, ##pluginkey##, ##modulekey##, ##refpluginkey##, ##refmodulekey##, ##editorversion##, ##content##, ##spaceid##, ##prevver##, ##version##, ##creator##, ##creationdate##, ##lastmodifier##, ##lastmoddate##, ##bodytypeid##|Templates|fully imported
79 |##space_alias##|##id##, ##spaceid##, ##lowerspacealias##, ##active##, ##lastmodifieraaid##, ##creatoraaid##, ##lastmodificationdate##, ##creationdate##, ##spacealias##|?|ignored
80 |##space_owner##|##id##, ##spaceid##, ##ownerid##, ##ownertype##|Space owners|ignored
81 |##spacepermissions##|##permid##, ##spaceid##, ##permtype##, ##permgroupname##, ##externalgroupid##, ##permusername##, ##permalluserssubject##, ##creator##, ##creationdate##, ##lastmodifier##, ##lastmoddate##, ##active##
82 |##Space permissions##|##fully imported##
83 |##spacerole_assignments##|##row_id##, ##principal_id##, ##principal_type##, ##role_id##, ##space_id##, ##creator##, ##creationdate##, ##lastmodifier##, ##lastmodificationdate##, ##deleted_id##|?|ignored
84 |##spacerole_permissions##|##row_id##, ##role_id##, ##permission_id##, ##creator##, ##creationdate##, ##lastmodifier##, ##lastmodificationdate##, ##deleted_id##|?|ignored
85 |##spaceroles##|##role_id##, ##spacerole_type##, ##name##, ##translation_tag##, ##description##, ##internal_use_only##, ##creator##, ##creationdate##, ##lastmodifier##, ##lastmodificationdate##, ##deleted_id##|?|ignored
86 |##spaces##|##spaceid##, ##spacename##, ##spacekey##, ##lowerspacekey##, ##spacedescid##, ##homepage##, ##creator##, ##creationdate##, ##lastmodifier##, ##lastmoddate##, ##spacetype##, ##spacestatus##|Spaces|fully imported
87 |##templateattachmentproperties##|##propertyid##, ##name##, ##value##, ##attachmentid##|?|ignored
88 |##templateattachments##|##attachmentid##, ##hibernateversion##, ##name##, ##version##, ##prevver##, ##templateid##, ##creator##, ##creationdate##, ##lastmodifier##, ##lastmoddate##|?|ignored
89 |##templateproperties##|##propertyid##, ##hibernateversion##, ##name##, ##value##, ##version##, ##prevver##, ##templateid##, ##creator##, ##creationdate##, ##lastmodifier##, ##lastmoddate##|?|ignored
90 |##trackbacklinks##|##linkid##, ##contenttype##, ##viewcount##, ##url##, ##lowerurl##, ##title##, ##blogname##, ##excerpt##, ##contentid##, ##creator##, ##creationdate##, ##lastmodifier##, ##lastmoddate##|?|ignored
91 |##user_mapping##|##user_key##, ##username##, ##lower_username##, ##aaid##|Maps userkey to username|fully imported
92 |##usercontent_relation##|##relationid##, ##targetcontentid##, ##sourceuser##, ##targettype##, ##relationname##, ##creationdate##, ##lastmoddate##, ##creator##, ##lastmodifier##|Links contents to user, defining the kind of relationship between the two.|ignored
93 |##whiteboardContentIds##|##contentId##, ##spaceId##, ##contentType##, ##referenceId##|Seems to define some contents as "whiteboards"|ignored
94
95 == Effectively Working with CSV backups ==
96
97 Unfortunately, we don't have much experience in this yet. Compared to Confluence XML, there's no huge file with everything inside it unordered that can be quite challenging to work with. However, you need to deal with gzipped (or not!) CSV files, and with the body field in the ##bodycontent## table that can base64 encoded zstd compressed stuff.
98
99 Here are some tips anyway.
100
101 === Unzip the relevant files ===
102
103 For instance, get the bodycontent table:
104
105 From a space export (note how we drop the annoying and wrong .gz file):
106
107 {{code language="none"}}
108 unzip -l csv-export.zip bodycontent.csv.gz > bodycontent.csv
109 {{/code}}
110
111
112 From a site export, add a call to gunzip:
113
114 {{code language="none"}}
115 unzip -l csv-export.zip bodycontent.csv.gz | gunzip > bodycontent-with-body-still-encoded.csv
116 {{/code}}
117
118 To get the body column of a site export, ignore the first line (CSV header), then take the second field, base-64 decode and then decompress the resulting zstd content:
119
120 {{code language="none"}}
121 tail -n +2 bodycontent-with-body-still-encoded.csv | cut -d, -f2 | base64 -d | zstd -d > bodies.txt
122 {{/code}}
123
124 Decode a unique body value:
125
126 {{code language="none"}}
127 echo "KLUv/SAMYQAAPHA+aGVsbG88L3A+" | base64 -d | zstd -d <p>hello</p>
128 {{/code}}
129
130 Re-encode a body value:
131
132 {{code language="none"}}
133 echo '<p>hello</p>' | zstd | base64 KLUv/QRYaQAAPHA+aGVsbG88L3A+CkTGPNg=
134 {{/code}}
135
136 {{info}}
137 TODO:
138 * give a convenient way to rebuild a full ##bodycontent.csv## file but with the body field decoded.
139 * give a convenient way to rebuild a full ##bodycontent.csv## file but with the body field encoded.
140 {{/info}}
141
142 {{info}}
143 You can totally mess with a site backup's ##bodycontent## table and re-import a tempered CSV file that doesn't have the body field encoded if you want to import it using our Confluence migrator. You don't need to gzip it again as well. It doesn't even need to have the ##.gz## extension, site or space backup regardless. Everything is automatically detected. We do this in case Atlassian developers evolve their backups formats towards a more rational state, or otherwise make their site and space backups converge, which could make their own lives simpler.
144 {{/info}}
145
146 === Use SQLite to work with the CSV tables ===
147
148 SQLite can import and export CSV files.
149
150 Let's run sqlite3 in a folder where we have extracted a CSV backup:
151
152 {{code language="none"}}
153 $ sqlite3
154 SQLite version 3.53.2 2026-06-03 19:12:13
155 Enter ".help" for usage hints.
156 Connected to a transient in-memory database.
157 Use ".open FILENAME" to reopen on a persistent database.
158 {{/code}}
159
160 We can import one or several CSV files, and they are immediately queriable, together:
161
162 {{code language="none"}}
163 sqlite> .import content.csv content
164 sqlite> .import bodycontent.csv bodycontent
165 {{/code}}
166
167 For instance, let's imagine we have filtered contents using some criterion with something like this:
168
169 {{code language="none"}}
170 sqlite> delete from content where ...;
171 {{/code}}
172
173 We only want to keep the body contents of the remaining content records:
174
175 {{code language="none"}}
176 sqlite> delete from bodycontent where contentid not in (select contentid from content);
177 {{/code}}
178
179 Now, let's update bodycontent.csv. We set the output mode of SQLite to csv, and disable a limit that would silently truncate long fields, which can happen with body contents. We want headers, and we want to output to bodycontent.csv. We select everything, which will output in the CSV file. And then, we revert to stdout tabs output.
180
181 {{code language="none"}}
182 sqlite> .mode csv --charlimit 0
183 sqlite> .headers on
184 sqlite> .output bodycontent.csv
185 sqlite> select * from bodycontent;
186 sqlite> .output stdout
187 sqlite> .mode tabs
188 {{/code}}

Get Connected