Signet asks no cooperation from a hosted service to let you leave. Users, accounts, sessions, and password hashes already live in the PostgreSQL database configured by SIGNET_DATABASE_URL or [database] dsn.
Treat every export below as credential material: account.password contains password hashes and session tokens are live credentials until they expire or are revoked. Write into a restricted directory, encrypt at rest, transfer over an authenticated channel, and delete working copies when the migration is verified.
Leaving a SQLite (Hobby) instance
signet backup makes a checkpointed ciphertext copy of the SQLCipher file. It is a disaster- recovery backup for Signet using the same database.key; it is not a portable plaintext export and another database engine cannot restore it.
For a portable, restorable handoff, stop the Hobby instance and migrate the whole logical database into a fresh dedicated PostgreSQL database:
signet migrate-backend \
--config /etc/signet/signet.toml \
--to-postgres-url "$TARGET_DATABASE_URL" \
--same-server-secret-confirmed \
--receipt ./sqlite-to-postgres-receipt.jsonThe target must already have this exact Signet binary's PostgreSQL migrations and no instance data. The command copies password hashes, live sessions, passkeys, two-factor ciphertext, organisations, invitations, clients, keys, tokens, vault rows, and revocations without regenerating any value. Use the same [server] secret on the PostgreSQL instance character for character: cookie HMAC checks, two-factor ciphertext, and private signing keys depend on it. Run once with --dry-run first; it performs and verifies the transaction, then rolls it back.
After the receipt passes, boot Signet on PostgreSQL, prove the retained session and credentials, and only then make the standard pg_dump archive described below. The order matters: migrate-backend first, pg_dump second. Keep the stopped SQLCipher source and its key until the PostgreSQL boot and archive have both been restore-tested.
Complete, restorable export
Use PostgreSQL's native archive when the destination is another Signet instance or you need a full backup. This includes every core and plugin table plus the migration ledger:
umask 077
pg_dump "$SIGNET_DATABASE_URL" \
--format=custom \
--file="signet-$(date +%F).dump"List and restore it with standard PostgreSQL tooling:
pg_restore --list signet-2026-07-22.dump
pg_restore --clean --if-exists \
--dbname "$TARGET_DATABASE_URL" signet-2026-07-22.dumpPortable CSV: users
This is the exact core user projection. Quoted identifiers matter because the table is named user and the schema uses better-auth's camel-case column names:
umask 077
psql "$SIGNET_DATABASE_URL" --csv -c '
SELECT
"id",
"name",
"email",
"emailVerified",
"image",
"createdAt",
"updatedAt"
FROM "user"
ORDER BY "createdAt", "id"
' > signet-users.csvPlugin migrations may add optional user columns such as username, displayUsername, role, and ban state. Use SELECT * FROM "user" instead when the destination needs every installed plugin field.
Portable CSV: accounts and password hashes
Password credentials are account rows. Exporting only user is not enough to preserve sign-in:
umask 077
psql "$SIGNET_DATABASE_URL" --csv -c '
SELECT
"id",
"userId",
"accountId",
"providerId",
"password",
"accessToken",
"refreshToken",
"idToken",
"accessTokenExpiresAt",
"refreshTokenExpiresAt",
"scope",
"createdAt",
"updatedAt"
FROM "account"
ORDER BY "userId", "providerId", "id"
' > signet-accounts.csvFor email/password users, providerId is credential and password is the self-describing hash consumed by the engine. OAuth access/refresh/id tokens can be live provider credentials; keep or omit those columns deliberately for the destination.
One joined handoff file
Some migration tooling wants identity and credential data together:
umask 077
psql "$SIGNET_DATABASE_URL" --csv -c '
SELECT
u."id" AS "user_id",
u."email" AS "email",
u."name" AS "name",
u."emailVerified" AS "email_verified",
u."createdAt" AS "user_created_at",
a."id" AS "account_id",
a."providerId" AS "provider_id",
a."accountId" AS "provider_account_id",
a."password" AS "password_hash",
a."createdAt" AS "account_created_at"
FROM "user" AS u
LEFT JOIN "account" AS a ON a."userId" = u."id"
ORDER BY u."createdAt", u."id", a."providerId", a."id"
' > signet-users-and-accounts.csvThe left join keeps passwordless and social-only users visible. A user with several providers gets several rows; consumers must not treat those as duplicate identities.
Reconciliation receipt
Record counts beside the files so the destination can prove that nothing vanished:
psql "$SIGNET_DATABASE_URL" -At <<'SQL'
SELECT 'users=' || count(*) FROM "user";
SELECT 'accounts=' || count(*) FROM "account";
SELECT 'credential_accounts=' || count(*)
FROM "account" WHERE "providerId" = 'credential';
SQL
sha256sum signet-users.csv signet-accounts.csv signet-users-and-accounts.csvThe CSV commands are an export surface, not a Signet-only encoding. There is intentionally no signet export wrapper: pg_dump and psql --csv remain inspectable, standard, and available even if the Signet binary is absent.