2026, Jan 11 03:00

How to Fix pykeepass Attachment Errors: Add Files via add_binary, Then Link with add_attachment

Learn why pykeepass raises a TypeError when attaching bytes, and fix it with the KeePass two-step flow: add_binary to store data, add_attachment to link entry.

When wiring up automated KeePass workflows with pykeepass, a common stumbling block appears the moment you try to attach a plain .txt file directly to an entry. The code looks innocent, but instead of a smooth save you get a TypeError complaining about bytes. The root cause isn’t in the file encoding at all—it’s the API contract of how attachments are added.

Reproducing the issue

The following snippet demonstrates the failing approach where a text file is read and passed straight into the attachment call:

from pykeepass import PyKeePass
vault = PyKeePass('testDatabase.kdbx', password='passwort')
default_grp = vault.find_groups(name='General', first=True)
entry_rec = vault.add_entry(
    default_grp,
    title='title',
    username='username',
    password='password',
)
with open('loremipsum.txt', 'rb') as fh:
    entry_rec.add_attachment('attachment.txt', fh.read())
vault.save()

This triggers a type error showing that bytes were provided where text was expected. It’s easy to assume the fix is about file mode or encoding, but that’s not the case here.

What’s actually going on

pykeepass handles attachments in two distinct steps. First you add the raw binary data to the database, which returns an internal binary identifier. Then you attach that identifier to an entry along with the filename. Examples on the pykeepass project page illustrate this sequence clearly: data goes in via add_binary as bytes, and add_attachment expects the binary id followed by the filename.

In the failing call, the arguments are inverted: a filename string is provided first, then raw bytes are given where the API is expecting an identifier. That mismatch leads the underlying XML builder to receive bytes where it expects text, which explains the TypeError.

Fixing the code

The correct flow is to register the file contents as a binary first, capture the returned id, and only then reference it from the entry with a filename:

from pykeepass import PyKeePass
vault = PyKeePass('testDatabase.kdbx', password='passwort')
default_grp = vault.find_groups(name='General', first=True)
entry_rec = vault.add_entry(
    default_grp,
    title='title',
    username='username',
    password='password',
)
with open('loremipsum.txt', 'rb') as fh:
    bin_id = vault.add_binary(fh.read())
    attach_ref = entry_rec.add_attachment(bin_id, 'attachment.txt')
vault.save()

This aligns with the documented usage: bytes go into add_binary, and add_attachment links that stored binary to the entry.

Why this matters

Understanding the two-step attachment model saves time and avoids brittle workarounds. It ensures you’re storing binary payloads the way KeePass expects and helps prevent confusing type errors. It also makes your code easier to maintain, because the same binary can be referenced by multiple entries without duplicating data.

Takeaways

Treat attachments in pykeepass as references to stored binaries. Read the file in binary mode when preparing data for add_binary, keep the returned id, and pass that id with the desired filename to add_attachment. If something goes wrong, include the complete traceback when troubleshooting—it often reveals the exact point where argument types aren’t matching what the API expects.