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
What survives JSON *-keeper.json CSV *-keeper.csv
Logins
JSON Logins: Fully supported.
CSV Logins: Fully supported.
TOTP secrets
JSON TOTP secrets: Fully supported.
CSV TOTP secrets: Fully supported.
Attachments
JSON Attachments: N/A.
CSV Attachments: N/A.
Custom fields
JSON Custom fields: Fully supported.
CSV Custom fields: Limited.
Folders
JSON Folders: Fully supported.
CSV Folders: Fully supported.
Payment cards
JSON Payment cards: Fully supported.
CSV Payment cards: Fully supported.
Password history
JSON Password history: N/A.
CSV Password history: N/A.
Passkeys
JSON Passkeys: N/A.
CSV Passkeys: N/A.
Fully supported
Limited
Not yet supported
N/A

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

Source field Serialized Target field Notes
title Always Title
login Conditional Username
password Conditional Password On an sshKeys record this is the key's passphrase, and imports as one.
login_url Conditional URL Named login_url, not url. Further websites are $url custom fields.
notes Conditional Note
last_modified Conditional Last updated Keeper Commander exports only, in epoch seconds.
$type Always Dropped login, bankCard, address, serverCredentials and so on. Used to pick the mapping, then discarded.
folders[].shared_folder Conditional Tags (derived) The shared folder the record sits in. A slash in its name is part of the name, not nesting.
folders[].folder Conditional Tags (derived) The folder path, inside the shared folder when one is named. One tag per backslash-separated segment, with spaces kept.
$type:label:index Conditional Custom field (derived) Commander writes $type or $type:label with no index. The type runs to the first colon and a trailing :digits is the index, so a label containing a colon survives. An empty label falls back to a name derived from the type. An array value imports as one field per element.
$oneTimeCode Conditional TOTP Anything that will not parse is kept as a secret field rather than dropped.
$secret / $pinCode Conditional Secret field
$multiline Conditional Note field
$securityQuestion Conditional Secret field (derived) A question and an answer. The question becomes the label of a concealed answer.
$date / $birthDate Conditional Custom field (derived) Epoch milliseconds, not a date string. $expirationDate is the same.
$name / $address / $host / $phone Conditional Custom field (derived) Structured objects flattened to text. A name joins first, middle and last; an address joins its six parts with spaces; a host becomes separate Host and Port fields; a phone becomes 5550100 ext. 22.
$paymentCard Conditional Card fields cardNumber, cardSecurityCode, and cardExpirationDate written as 03/2028.
$text:cardholderName Conditional Cardholder (derived) An ordinary text field rather than part of the card object, so it becomes the cardholder only on a bankCard record.
$keyPair Conditional SSH key
references Conditional Dropped Links a login to a card or address record by uid. The linked records import as entries of their own; the link between them does not.
shared_folders[] Always Dropped Who each shared folder is shared with, and on what terms. Nothing in a personal vault to map it onto.

CSV

Source field Target field Notes
column 1 Tags (derived) The folder path. When column 7 names a shared folder, this is the path inside it.
column 2 Title Never empty, and the one column a row needs for the file to be recognized as Keeper's.
column 3 Username
column 4 Password Moves to the SSH passphrase field instead when the row also carries a Key Pair.
column 5 URL
column 6 Note
column 7 Tags (derived) The shared folder name. Who it was shared with is not in the file.
TFC:Keeper TOTP
Hidden Field / Pin Code Secret field Recognized by label. Rename either one and it imports as plain text.
Payment Card Card fields (derived) number | 03/2028 | cvv in one cell. A cell that does not split into exactly three parts is skipped rather than guessed at.
Cardholder Name Cardholder
Bank Account Custom fields (derived) Checking | account | routing. Account and routing numbers land as secret fields, the type as plain text.
Key Pair SSH key (derived) Private | public, in that order. The JSON keys the same two values by name.
Security Question & Answer Secret field (derived) Written as question? answer. Split on the first question mark and space, so it imports the same way the JSON does.
Name / Address / Phone / Date Custom field Display text, imported as written. The name comes out first, last, middle.
any other label Custom field Keeper lets you rename field labels freely, so there is no fixed set to match against. An unrecognized label is imported as itself.

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.