Wiki source code of Confluence CSV Format
Last modified by superadmin on 2026/10/14 18:25
Show last authors
| author | version | line-number | content |
|---|---|---|---|
| 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}} |