Menu

Developer documentation

Exporting your data

/docs/exporting-your-data

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.json

The 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.dump

Portable 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.csv

Plugin 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.csv

For 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.csv

The 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.csv

The 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.

Enter to open · Esc to close