Entries
Titles, usernames, passwords, notes, custom fields, and identities are migrated as Bitwarden item data.
migration notes
what moves
It reads a KeePass 2.x database and creates Bitwarden items through bw serve. Entries, group paths, attachments,
passkey-related fields, OTP-related fields, tags, expiry data, and
login URIs are mapped into Bitwarden shapes instead of being dumped as
plain notes.
Titles, usernames, passwords, notes, custom fields, and identities are migrated as Bitwarden item data.
Files attached to KeePass entries are uploaded after their Bitwarden item exists.
Tags and expiry are kept in a readable KP2BW_META field when Bitwarden has no native slot.
KP2BW_ID lets reruns match the same KeePass entry
instead of guessing by title.
planner options
Organization means shared Bitwarden collections. Personal means your own vault and optional personal folders.
The value passed with -o. Use the Bitwarden
organization ID for the destination org.
The value passed with -c when every imported item
should land in one existing collection.
The final command argument. Point it at the .kdbx database to migrate.
Adds -K when the KeePass database also needs a key
file.
Adds -t values. Empty means every non-excluded entry
is included.
Filters decide which KeePass entries are part of the plan before collections or folders are calculated.
Adds --skip-expired. Expired KeePass entries are
included unless this is enabled.
Adds --include-recycle-bin. Recycle Bin entries are
excluded by default.
folders vs collections
This is the main migration trap. KeePass groups look like folders, but
a Bitwarden organization does not have org folders. Shared structure
in an organization is made with collections. Nested collections are
names with slashes, such as Work/Servers.
Work/ServersWork/ServersWorkWork/ServersInternet/BankingInternet/BankingInternetInternet/BankingFor a Vaultwarden organization migration, the usual shape is nested collections and no personal folders. That keeps shared data in the org model instead of creating a private folder tree beside it.
Want both? Pass --folder alongside -o (the
planner's Also create personal folders toggle) and
every item is filed into its org collection and a personal
folder — the same double-filing Bitwarden's own org import does.
Leave it off unless you specifically want that private copy.
-c nested: KeePass Work/Servers becomes
collection Work/Servers. With -o set,
personal folders are already off, so no extra flag is needed.
-c auto: KeePass Work/Servers and Work/Engineering both land in collection Work.
-c 11111111-1111-1111-1111-111111111111: every
imported item lands in one existing collection.
-o with no collection mapping: items are created in
the organization without a generated hierarchy or personal
folders.
KeePass groups become personal Bitwarden folders. Use this only when importing into a personal vault.
--no-folder: items stay at the personal vault root.
recommended org migration
Use an organization ID, map full KeePass group paths to collections, and omit personal folders:
# .env
KP2BW_KEEPASS_PASSWORD=<keepass password>
KP2BW_BITWARDEN_PASSWORD=<bitwarden password>
kp2bw -o 00000000-0000-0000-0000-000000000000 -c nested vault.kdbx Choose top-level collections if you need fewer collections. Choose one existing collection only when you intentionally want to flatten the KeePass group tree into a single shared place.
running again
Every migrated item carries KP2BW_ID, the KeePass UUID
used to match the same item next time. kp2bw also writes a KP2BW_SYNC content stamp. Those markers let kp2bw decide
whether an existing Bitwarden item is still safe to update.
--force-update.--no-update.--force-update: KeePass content wins over later
Bitwarden edits. Use this when KeePass is still the source of
truth.
Default mode. kp2bw updates migrated items when Bitwarden has not been edited since the last kp2bw run.
--no-update: create missing items only. Existing
migrated items stay untouched, so you do not need to wipe the DB
just to try another pass.
url handling
KeePass fields like KP2A_URL, URL_1, and AndroidApp are folded into Bitwarden login URIs where
they can drive autofill. Free-text fields like API Url stay as custom fields so API endpoints do not accidentally become
login matches.
Plain URLs use your Bitwarden account default. KeePassXC-style quoted URLs become exact matches, and wildcards become starts-with or regex matches. If too many subdomains surface together, use the URI collision report to inspect the problem before changing match behavior.
after migration
kp2bw --strip-ids removes KP2BW_ID and KP2BW_SYNC from migrated items. That is final adoption:
Bitwarden becomes the source of truth and future KeePass reruns become
unreliable because kp2bw can no longer match by KeePass UUID.
This is irreversible. Do not run it while you still expect to rerun a KeePass migration.
credentials
The command can prompt for passwords. For repeated runs, put secrets
in .env instead of in command arguments.
KP2BW_KEEPASS_PASSWORD=<keepass password>
KP2BW_BITWARDEN_PASSWORD=<bitwarden password> kp2bw loads .env automatically. Real environment
variables still win when both are set.