Proton Pass's three exports

The PGP passphrase encrypts the vault but not the attachments packed beside it.

Checked against Proton Pass 1.40.2 exports Sources: Proton: How to export data from Proton Pass, ProtonMail/WebClients, RFC 4880: OpenPGP Message Format

Proton Pass offers three exports, and two of them are the same ZIP: plaintext JSON inside one, and that JSON sealed in an OpenPGP message inside the other. The third is a CSV, thinner than either and still holding the items you put in the trash.

Form File Encrypted
ZIP One folder holding data.json: every vault and every item, in plaintext. Proton Pass_export_*.zip No
PGP-encrypted ZIP The same ZIP with data.json swapped for data.pgp, an OpenPGP message sealed with a passphrase. Decrypts to the same JSON document. Proton Pass_export_*.zip Yes
CSV Twelve columns. Cards and identities ride inside the note column as JSON, and trashed items come along with nothing marking them. Proton Pass_export_*.csv No

We exported one vault all three ways:

What survives ZIP Proton Pass_export_*.zip PGP-encrypted ZIP Proton Pass_export_*.zip CSV Proton Pass_export_*.csv
Logins
ZIP Logins: Fully supported.
PGP-encrypted ZIP Logins: Fully supported.
CSV Logins: Fully supported.
TOTP secrets
ZIP TOTP secrets: Fully supported.
PGP-encrypted ZIP TOTP secrets: Fully supported.
CSV TOTP secrets: Fully supported.
Attachments
ZIP Attachments: Fully supported.
PGP-encrypted ZIP Attachments: Fully supported.
CSV Attachments: N/A.
Custom fields
ZIP Custom fields: Fully supported.
PGP-encrypted ZIP Custom fields: Fully supported.
CSV Custom fields: N/A.
Folders
ZIP Folders: Fully supported.
PGP-encrypted ZIP Folders: Fully supported.
CSV Folders: Fully supported.
Payment cards
ZIP Payment cards: Fully supported.
PGP-encrypted ZIP Payment cards: Fully supported.
CSV Payment cards: Fully supported.
Password history
ZIP Password history: N/A.
PGP-encrypted ZIP Password history: N/A.
CSV Password history: N/A.
Passkeys
ZIP Passkeys: Not yet supported.
PGP-encrypted ZIP Passkeys: Not yet supported.
CSV Passkeys: N/A.
Fully supported
Limited
Not yet supported
N/A

Either ZIP carries the whole vault. Take the encrypted one if the file will sit anywhere you do not control, and leave the CSV for tools that read nothing else.

How to export

Open Settings from the gear icon, which sits in the bottom left of the web app and behind the ☰ menu in the browser extension and the desktop app. Then open the Export tab. The mobile apps cannot export.

Pick the PGP-encrypted ZIP, the unencrypted ZIP or the CSV. Choosing encryption brings up a passphrase field, and that passphrase is all that protects data.pgp, for reasons covered below.

Either ZIP has a switch for including file attachments. Turning it on warns you, in so many words, that the attachments will not be encrypted. That applies to the PGP export too.

Before exporting a CSV, restore anything in the trash you want to keep and empty the rest. The ZIPs mark trashed items and the CSV does not, so anything left in there arrives looking live.

JSON format

Archive layout

The ZIP holds one folder, named after the app:

Proton Pass/
  data.json     the unencrypted export
  data.pgp      the encrypted export, in place of data.json
  files/        attachments, when the export includes them

The folder is what identifies the archive. An importer matching on data.json alone also claims a Bitwarden ZIP, which keeps a data.json of its own at the root.

Safari on macOS opens downloaded ZIPs by default, which leaves a loose Proton Pass folder in Downloads instead of the archive. Buddy reads data.json or data.pgp on its own, so the file inside that folder still imports. Hand it the archive rather than the file when you can, though. Attachments live outside the document and come across only when Buddy has the ZIP to read them out of.

Document layout

Vaults are keyed by their share id, and every item sits inside the vault that holds it:

{
  "version": "1.40.2",
  "userId": "k3Jx8vQzW0pN5tYbR2mL...",
  "vaults": {
    "Hq7cT1nZ4wKe9sVdA6fU...": {
      "name": "Personal",
      "description": "Personal vault",
      "display": { "color": 0, "icon": 0 },
      "items": [ ... ]
    }
  }
}

version is the Proton Pass release that wrote the file. userId is your Proton account's id, so the file identifies you to anyone you hand it to.

Items

An item keeps what you typed in a data object and Proton's bookkeeping around it. A login:

{
  "itemId": "Pw4rN8xKc2mQ7tLz...",
  "shareId": "Hq7cT1nZ4wKe9sVdA6fU...",
  "data": {
    "metadata": { "name": "GitHub", "note": "Personal account", "itemUuid": "4f2a91c7" },
    "extraFields": [],
    "type": "login",
    "content": {
      "itemEmail": "octocat@example.com",
      "itemUsername": "octocat",
      "password": "correct-horse-battery",
      "totpUri": "otpauth://totp/GitHub:octocat?secret=JBSWY3DPEHPK3PXP&issuer=GitHub",
      "urls": ["https://github.com/login"],
      "autofillUrls": [{ "url": "https://github.com/login", "mode": 0 }],
      "passkeys": []
    }
  },
  "state": 1,
  "aliasEmail": null,
  "contentFormatVersion": 8,
  "createTime": 1789426882,
  "modifyTime": 1789426882,
  "pinned": false,
  "shareCount": 0,
  "files": []
}

A login has two identity fields, itemUsername and itemEmail, and Proton's form fills in either or both. Buddy uses the username when there is one and keeps the email beside it as a field. When the email is all there is, it becomes the username.

urls and autofillUrls hold the same addresses, the second with each one's autofill mode attached. The first becomes the entry's URL and the rest become URL fields.

Item types

data.type says which shape content takes:

login        itemUsername, itemEmail, password, urls, totpUri, passkeys
alias        empty: the address is aliasEmail, one level up
note         empty: the text is metadata.note
creditCard   cardholderName, number, verificationNumber, expirationDate, pin
identity     37 keys across personal, address, contact and work details
sshKey       privateKey, publicKey, sections
wifi         ssid, password, security, sections
custom       sections, and the template's fields in extraFields

A card's expirationDate is written year first, as 2028-03, and becomes 03/28. Its pin imports as a secret in the card section.

An identity fills Buddy's address section from streetAddress, city, stateOrProvince, zipOrPostalCode and countryOrRegion. Everything else it holds becomes a field under Proton's own label, with the social security, passport and license numbers concealed.

custom covers more than its name suggests. Proton's new-item menu offers a bank account, a passport, a driver's license, a medical record, a membership, a reward program, an API credential, a database, a server and several more, and every one of them is written out as custom. What distinguishes them is a set of extraFields the template prefilled, whose labels you can rename like any other. Nothing in the file records which template you started from. By the time they reach the export, a passport and a scratch item built by hand are the same kind of thing.

Aliases

An alias is a forwarding address that Proton runs. The address is the only thing it holds, and it is not in content:

{
  "data": {
    "metadata": { "name": "Newsletters", "note": "", "itemUuid": "84b7b598" },
    "extraFields": [],
    "type": "alias",
    "content": {}
  },
  "aliasEmail": "shop.x7k2@passmail.net"
}

content is an empty object and aliasEmail sits a level up beside state and createTime, so an importer that reads only content brings every alias across as a blank entry. It is null on every other item type.

Buddy imports the address as the entry's username. The forwarding stays on Proton's servers, which means an alias delivers mail only while the Proton account behind it exists.

Custom fields

extraFields holds the fields you added and the ones a template put there for you. Each one's type says which key its value is under:

"extraFields": [
  { "fieldName": "Employee ID",   "type": "text",      "data": { "content": "12345" } },
  { "fieldName": "Recovery code", "type": "hidden",    "data": { "content": "X7K2-9QPL-M4TR" } },
  { "fieldName": "Backup 2FA",    "type": "totp",      "data": { "totpUri": "JBSWY3DPEHPK3PXP" } },
  { "fieldName": "Expires",       "type": "timestamp", "data": { "timestamp": "2026-09-24" } }
]
  • text imports as a text field.
  • hidden becomes a secret field.
  • totp is the entry's own code when the item has none, and a secret field when it does.
  • timestamp is a plain YYYY-MM-DD date, and an empty string when unset.

totpUri is named for the otpauth:// URI Proton writes when you scan a QR code, but the key holds whatever you pasted, down to a bare base32 secret with no URI around it. Buddy reads both shapes, and keeps anything it cannot parse as a code in a secret field rather than dropping it.

SSH key, Wi-Fi and custom items add sections, each a sectionName and a list of fields in this same shape. Identities do it through extraSections and four extra…Details lists. Buddy imports every field in them, without the section names.

Vaults

Vaults are how Proton Pass groups items, and there is no folder level beneath them. Buddy turns the vault name into a tag when the export holds more than one vault with items in it. A single-vault export gains no tags, since a tag on every entry distinguishes nothing.

pinned has no equivalent and is dropped.

Trash

state is 1 for a live item and 2 for one in the trash. Both ZIPs include trashed items marked this way, and Buddy leaves them behind.

Passkeys

A login carries a passkeys array. The passkeys stay behind, and the login gains a Passkey (not imported) field naming the site in their place. Those fields add up to a list of the accounts to enroll again by hand.

Attachments

Attachments are opt-in, and the switch for them sits next to the format. With it on, the files land under Proton Pass/files/ and the item that owns them lists their names:

"files": ["Screenshot 2026-09-09 at 7.17.02 PM.426d8f9a45a37a6a.png"]

The name in files is the name of the entry in the archive, hash and all. Buddy matches the two without guessing and imports the file onto the entry that owns it. The match is byte-exact: a macOS screenshot carries its narrow no-break space through both, which is why nothing here tidies the name up first.

Switch attachments off and the item still lists them, which leaves a name with no file behind it. Buddy keeps that as an Attachment (not in export) field rather than a reference to nothing, and does the same for a loose data.json, which has no archive around it to hold the bytes.

Whichever ZIP you pick, these files are written in the clear. The PGP export seals data.json and nothing else, so an encrypted export carrying attachments hands them to anyone who has the archive. Treat that file as if the attachments were loose, because they are.

PGP-encrypted format

The encrypted export is the same ZIP with data.json replaced by data.pgp, and with files/ untouched. That one file is a standard OpenPGP message sealed with a passphrase rather than a key, so GnuPG opens it without any Proton software:

gpg --decrypt "Proton Pass/data.pgp" > data.json

Decrypted, it is the same JSON document the unencrypted ZIP holds, and the two import to identical entries, attachments included. The passphrase buys you the vault rather than the archive.

The armor

The file is ASCII-armored text in 60-character lines, with no header lines and a CRC-24 checksum at the end:

-----BEGIN PGP MESSAGE-----

wy4ECQMIDlfqBn8OVjD/knP/MLXFj0rRPXNyg0+hDROAQ6Z6LIY8V9p0f8tX
kFfb0s9tAZrl48gklzPQDI4OOlOkxKlB4JxWqV1dXIe8jYe4aD6wnj+jEE7p
...
=7Lzf
-----END PGP MESSAGE-----

Packets

Decoded, the message is two packets. The first says how to get a key from your passphrase, and the second is the vault. From a test export:

c3 2e                        tag 3: symmetric-key encrypted session key, 46 bytes
   04                        version 4
   09                        AES-256
   03 08                     iterated and salted S2K, SHA-256
   0e 57 ea 06 7f 0e 56 30   salt
   ff                        count: 65,011,712 bytes
   (33 bytes)                the session key, encrypted
d2 cf 6d                     tag 18: encrypted, integrity-protected data, 4,141 bytes
   01                        version 1
   (4,140 bytes)             the payload

Iterated and salted S2K runs the salt and passphrase, repeated end to end, through SHA-256 until 65,011,712 bytes have gone in. That count byte, ff, is the highest the format can express. The hash unwraps a random 32-byte session key, and the session key decrypts the payload with AES-256 in OpenPGP's CFB mode.

Inside the payload is one literal data packet holding data.json, uncompressed. After it comes a modification detection code, a SHA-1 of everything before it, which is how a version 1 packet checks its integrity from before OpenPGP had authenticated encryption.

Passphrase strength

S2K is not memory-hard. Each guess at your passphrase costs 65 megabytes of SHA-256 and nothing more, which a GPU gets through far faster than the Argon2d guarding a Dashlane .dash. The length of your passphrase is the whole of this file's protection. Make it long and random.

Wrong passphrases

A wrong passphrase shows itself before any JSON is parsed, through three checks of rising cost. First, the session key unwraps to noise whose first byte names AES-256 only once in 256 tries. Past that, the payload opens with 16 random bytes and repeats their last two, which noise matches once in 65,536. Anything that gets through both fails the SHA-1 at the end.

CSV format

Example file

type,name,url,autofillUrls,email,username,password,note,totp,createTime,modifyTime,vault
login,GitHub,https://github.com/login,"[{""url"":""https://github.com/login"",""mode"":0}]",octocat@example.com,octocat,correct-horse-battery,Personal account,otpauth://totp/GitHub:octocat?secret=JBSWY3DPEHPK3PXP&issuer=GitHub,1789426882,1789426882,Personal
creditCard,Travel card,,,,,,"{""cardholderName"":""A Person"",""cardType"":1,""number"":""4111111111111111"",""verificationNumber"":""123"",""expirationDate"":""2028-03"",""pin"":""1234"",""note"":""travel only""}",,1789426920,1789426920,Personal
alias,Newsletters,,,shop.x7k2@passmail.net,,,,,1789426950,1789426950,Personal
custom,Office door code,,,,,,,,1789427001,1789427001,Personal
login,Old login,https://example.com/,"[{""url"":""https://example.com/"",""mode"":0}]",,olduser,old-password,,,1789427044,1789427050,Personal

An alias puts its address in email and leaves username empty. The custom row is what every item type the CSV has no columns for looks like: a name, a vault, two timestamps, and nothing of what you stored in it. The last row is in the trash, and nothing in the file says so.

Cards and identities

A card or identity row has no columns of its own. Proton writes the item's whole content into note as JSON, with the real note tucked inside under a note key. Read that column as text and every card arrives as a note full of JSON. Parse it and the card is complete, PIN included.

What the CSV leaves out

Custom fields never reach the file, and a FIXME in Proton's exporter says as much. Logins, aliases, cards, identities and notes have their content written out. SSH key and custom items keep their name and note and nothing else, and a Wi-Fi item keeps its password but loses its network name.

That takes a TOTP code with it. The totp column carries a login's own code, but a code you added as a custom field is a custom field, and it is gone. If any of your two-factor secrets live on anything other than a login, the CSV is the wrong file to leave with.

url holds only the addresses in Proton's default autofill mode, joined with a comma and a space. autofillUrls holds every one of them as JSON, and is the column to read.

createTime and modifyTime are Unix seconds, so a CSV import keeps each entry's real age. vault names the vault, and becomes a tag when the file spans more than one.

Buddy field mapping

ZIP and PGP-encrypted ZIP

Source field Serialized Target field Notes
data.metadata.name Always Title An empty name falls back to the URL host, then the username, then the word Untitled.
data.metadata.note Always Note
data.extraFields[] Always Custom fields text imports as text and hidden as a secret. The first totp field becomes the entry's code when the login has none, and a timestamp imports as a date. Proton's item templates fill this list, so a bank account or a passport arrives through it.
state Always Dropped 1 is live and 2 is the trash. Trashed items are left behind rather than imported.
createTime / modifyTime Always Created / Modified Unix seconds.
vaults{}.name Always Tags (derived) Only when more than one vault holds items. A single-vault export gains no tags.
aliasEmail Always Username (derived) Null on everything but an alias, whose content is empty. The address becomes the username.
pinned Always Dropped Pinning has no equivalent.
itemId / shareId / itemUuid Always Dropped Proton's identifiers. Every imported entry gets a new one.
files[] Always Attachments The names of the item's attachments, matching the entries under files/ exactly. A name with no file behind it is kept as a text field instead.
userId Conditional Dropped Your Proton account's id, at the top of the file.
content.itemUsername Conditional Username
content.itemEmail Conditional Custom field (derived) The username when itemUsername is empty, and an Email field when it is not.
content.password Conditional Password On logins, and on Wi-Fi items as the network password.
content.urls[] Conditional URL The first becomes the entry URL, the rest become custom URL fields.
content.autofillUrls[] Conditional Dropped The same URLs again, each with its autofill mode.
content.totpUri Conditional TOTP An unparseable value is kept as a secret custom field.
content.passkeys[] Conditional Custom field (derived) The passkey itself is not imported. A Passkey (not imported) field naming the site records that one existed.
content.cardholderName Conditional Cardholder
content.number Conditional Card number
content.verificationNumber Conditional Card CVV
content.expirationDate Conditional Card expiry (derived) Written 2028-03, year first, and converted to 03/28.
content.pin Conditional Custom field (derived) A concealed PIN field in the card section.
content.cardType Conditional Dropped
content.streetAddress Conditional Street Identity items. city, stateOrProvince, zipOrPostalCode and countryOrRegion fill the rest of the address section the same way.
content.floor / county Conditional Custom fields (derived) Kept in the address section, which has no role for either.
content.socialSecurityNumber Conditional Custom field (derived) Imported concealed, and so are passportNumber and licenseNumber.
content.fullName, email, … Conditional Custom fields The rest of an identity's 37 keys, each under Proton's own label. Empty ones are skipped.
content.extra*Details[] Conditional Custom fields An identity's personal, address, contact and work custom fields, read the same way as extraFields.
content.privateKey / publicKey Conditional SSH key
content.ssid Conditional Custom field Wi-Fi items.
content.security Conditional Dropped The Wi-Fi security type.
content.sections[] Conditional Custom fields (derived) SSH key, Wi-Fi and custom items group their fields into named sections. The fields import without the section names.

CSV

Source field Target field Notes
type Dropped Picks the mapping for the row, then discarded.
name Title
autofillUrls URL Every URL as JSON, each with its autofill mode. The first becomes the entry URL, the rest become custom URL fields.
url URL Only the URLs in Proton's default autofill mode, joined with a comma and a space. Read when autofillUrls is empty.
email Custom field (derived) The username when username is empty, and an Email field when it is not. On an alias row it holds the alias address, which becomes the username.
username Username
password Password
note Note (derived) On a card or identity row, the whole item as JSON, with the actual note inside it under a note key.
totp TOTP An unparseable value is kept as a secret custom field.
createTime / modifyTime Created / Modified Unix seconds.
vault Tags (derived) Only when the file spans more than one vault.

Buddy is a desktop password manager for macOS and Windows that imports these files. If your export looks different from what this page describes, tell us. Other managers are on the export formats page.