Exporting from Keeper: JSON, CSV and KDBX
Records other people shared with you are left out unless you tick one box, and nothing in the file says they are missing.
Checked against Keeper Desktop 18.6.3
Keeper's export dialog offers four formats, and they have little in common. The JSON keys every value by type and label. The CSV fixes seven columns by position and then runs label and value pairs to the end of the row. The KDBX is a KeePass database, and the PDF is a printout. We exported one vault as JSON and as CSV.
| Form | File | Encrypted |
|---|---|---|
| JSON Every record with its type, its typed custom fields and its folder placement. The one to take. | *-keeper.json |
No |
| CSV No header row and no record type. Seven fixed columns, then label and value pairs to the end of the row. | *-keeper.csv |
No |
| KeePass KDBX A KeePass database, locked with a password you set when exporting. The only encrypted Keeper export. Buddy does not read this file yet. | *-keeper.kdbx |
Yes |
The JSON is the only form that records the type of each record and custom field, so take it if you can. The KDBX is the right choice if you are moving to KeePass or KeePassXC, which open it directly; Buddy does not read it yet.
How to export
In the desktop app, open the menu under your username, then Settings → Export. Under Export File, pick CSV, JSON, PDF or KDBX and choose Export. The Shared Records Report further down that page lists which of your records are shared with other users or teams, and holds none of their contents.
Tick “Include records shared with me” before you export. It is off by default, and with it off, every record someone else shared with you is left out of the file. Nothing in the export says anything was skipped.
Files are named after the moment they were written, in epoch milliseconds:
1790450916765-keeper.json was made at 19:28:36 UTC on September 26, 2026. Neither
the JSON nor the CSV records a date anywhere inside, so the filename is the only evidence of how
old an export is.
JSON format
Document layout
The root object has exactly two keys, shared_folders and records:
{
"shared_folders": [
{
"uid": "c4VbRwgIwPRT8q_yY2XN0g",
"path": "Work",
"manage_users": false,
"manage_records": false,
"can_edit": false,
"can_share": false,
"permissions": [
{ "name": "jane@example.com", "manage_users": true, "manage_records": true }
]
}
],
"records": [ ... ]
}
shared_folders describes sharing and nothing else: each shared folder's path, your
own rights in it, and everyone it is shared with. No record lives inside it. A shared folder
with nothing in it still appears here, and appears nowhere else.
Records
{
"title": "GitHub",
"notes": "Personal account",
"$type": "login",
"custom_fields": {
"$oneTimeCode::1": "otpauth://totp/GitHub:octocat?secret=JBSWY3DPEHPK3PXP&issuer=GitHub&algorithm=SHA1&digits=6&period=30"
},
"login": "octocat",
"password": "correct-horse-battery",
"login_url": "https://github.com/login",
"folders": [
{ "shared_folder": "Work", "folder": "Dev Tools", "can_edit": false, "can_share": false }
]
}
The core keys are title, login, password,
login_url and notes. The address is login_url, not
url. A core key that would be empty is left out, so a record with no password has
no password key at all. A record at the top of the vault, in no folder, has no
folders key either.
Keeper Commander, Keeper's command-line tool, can write the same JSON from its
export command. Its records add a uid and a
last_modified time in epoch seconds, the only per-record date any Keeper export
carries. Buddy keeps it as the entry's last updated date.
$type names the template the record was made from: login,
bankCard, address, serverCredentials,
sshKeys and the rest of Keeper's list. Everything a template adds beyond the core
keys lives in custom_fields.
Custom field keys
custom_fields is an object whose keys are three parts joined by colons: type, label
and index.
"custom_fields": {
"$oneTimeCode::1": "otpauth://totp/...",
"$secret::1": "X7K2-9QPL-M4TR",
"$text::1": "internal only",
"$text::2": "a second text field",
"$pinCode::1": "4821",
"$text:cardholderName:1": "Jane Doe"
}
The label is empty unless one was set, and most keys in a real export look like
$secret::1. The index is what tells two fields of the same type apart.
Commander drops the index, and the label with it when there is none: $paymentCard,
$text:cardholderName. Reading only the three-part form loses most of a Commander
export without an error. Take the type up to the first colon and treat a trailing
: plus digits as the index; what remains is the label, colons and all, since a label
is free text and can hold one of its own.
The type prefix is the reason to prefer this export. $secret and
$pinCode mean the value should stay concealed, $multiline marks a
note, and $oneTimeCode marks a second factor. The CSV has none of that.
Structured values
Several types hold an object instead of a string:
"$paymentCard::1": { "cardNumber": "4111111111111111", "cardExpirationDate": "03/2028", "cardSecurityCode": "123" }
"$address::1": { "street1": "1 Main St", "street2": "Apt 4", "city": "Springfield", "state": "IL", "zip": "62701", "country": "US" }
"$name::1": { "first": "Jane", "middle": "Q", "last": "Doe" }
"$phone::1": { "number": "5550100", "ext": "22" }
"$host::1": { "hostName": "10.0.0.12" }
"$securityQuestion::1": { "question": "Name of first pet", "answer": "not-a-real-answer" }
A field holding more than one value is written as an array of them, in the same key. A login with two extra websites, or a contact with two phone numbers:
"$url::1": ["https://login.example.com", "https://logout.example.com"]
"$phone::1": [{ "region": "US", "number": "5550100", "type": "Home" },
{ "region": "US", "number": "5550199", "ext": "22" }]
Code that expects one string or one object per key keeps neither. Buddy imports each element as a field of its own.
A card's holder is not in the card object. It is the separate text field
$text:cardholderName:1 shown earlier, one of the few that arrive with a label
already filled in.
Dates
$date, $expirationDate and $birthDate are JSON numbers
holding epoch milliseconds, not date strings. An importer that reads them as seconds puts your
birthday tens of thousands of years in the future.
Folders
A record carries its own location in folders, never the other way round. A record
in a shared folder names it in shared_folder and, when it sits deeper, the path
below it in folder. The GitHub record above is in
Dev Tools inside the shared folder Work. Buddy makes a tag of each,
keeping a name with a space in it as one tag.
A slash in a shared folder's name is part of the name. One called shared/folder/1
is a single folder, not three.
Linked records
A login can point at a card or an address stored as a record of its own. The link is written as
a references object holding the other records' ids:
"references": {
"$cardRef::1": [1],
"$addressRef::1": [2]
}
Each linked record imports as an entry in its own right. The link between them does not survive.
CSV format
Row shape
"Dev Tools","GitHub","octocat","correct-horse-battery","https://github.com/login","","Work","TFC:Keeper","otpauth://totp/GitHub:octocat?secret=JBSWY3DPEHPK3PXP&issuer=GitHub&algorithm=SHA1&digits=6&period=30"
"","Build server","deploy","","","","","Hostname or IP Address","10.0.0.12"
"","Staging","","","","","Work","Hidden Field","X7K2-9QPL-M4TR","Text","internal only"
Most password manager CSVs are a header row and then rows of the same shape. Keeper's has neither. There is no header, seven columns are fixed by position (folder, title, username, password, URL, notes, shared folder), and everything after them is a flat run of label and value pairs. Every value is quoted, empty ones included, lines end in a plain line feed, and the last row has no line break after it.
So a row always has an odd number of columns: seven fixed, plus pairs. That parity is most of what identifies the file, along with a title in the second column, since there is no header to match.
There is no record type either. The build server above is a serverCredentials
record in the JSON; here it is a row with a username and one extra pair, and the only hint of what
it was is the label Hostname or IP Address.
Folders take two columns. The first is the folder path, and the seventh is the shared folder.
When both are filled in, as on the GitHub row, the first column is the path inside the shared
folder, so the full location is Work then Dev Tools.
Labels
The labels are Keeper's display names, not type names: TFC:Keeper for a TOTP code,
Hidden Field, Pin Code, Multi-line Text,
Website Address and so on. With the type gone, the label is the only clue to what a
value is, and Keeper lets you rename labels freely. Rename a hidden field and it
arrives as plain text.
It is tempting to recognize the file by looking for Keeper's known labels. That is the trap in this format: a whitelist rejects legitimate files from anyone who tidied up their field names. Shape is the only thing that holds.
Composite values
Anything structured in the JSON is flattened into one cell of display text:
Payment Card 4111111111111111 | 03/2028 | 123
Bank Account Checking | 000123456 | 110000000
Key Pair -----BEGIN...----- | ssh-ed25519 AAAA...
Address 1 Main St | Apt 4 | Springfield | IL 62701 | US
Security Question & Answer Name of first pet? not-a-real-answer
Name Jane Doe Q
Phone Mobile US (+1) 5550100 22
Date 04/15/2026
Card is number, expiry, security code. Bank is type, account, routing. Key Pair is private then public. A cell that does not split into exactly the expected number of parts is better skipped than guessed at, since a card number and a security code cannot be told apart by looking.
The rest are display text. The address joins state and postal code with a space. The security question gets a question mark and a space added after it, then the answer. The name comes out first, last, middle. The phone gains a type and country prefix, and the date is written month first. Buddy splits the card, bank account, key pair and security question, and imports the others as written.
The LastPass export format packs
structured records into text as well, as one Key:Value line per field inside a
single column.
Key pairs
On a row carrying a Key Pair, the password in column four is not an account
password. It is the passphrase protecting the private key. The JSON makes this explicit with a
record type of sshKeys, and an import that treats it as a login password leaves you
with an SSH key you cannot use and a credential that was never real.
KDBX format
The KDBX option writes a KeePass database, locked with a password you set when exporting. It is the only Keeper export that is encrypted, and KeePass, KeePassXC and Strongbox open it directly. Buddy has no KDBX importer today.
The file's header, which is readable without the password, shows how it is protected: KDBX 4, AES-256, and an Argon2d key derivation set to 1 MiB of memory and 2 iterations. That is a light setting, and it leaves the password doing nearly all the work, the same position Proton Pass's PGP export puts you in.
What no Keeper export carries
No file attachments, in any format; Keeper's own export dialog says so. Save anything attached to a record out of Keeper by hand first.
No password history. We changed a password before exporting, and only the current one reached the JSON or the CSV.
No sharing. The JSON lists who each shared folder is shared with, but a personal vault has nothing to map that onto, and the CSV keeps only the shared folder's name.
Buddy field mapping
JSON
CSV
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.