Postgres SERIAL to IDENTITY Converter
SERIAL is not a real type — it is shorthand that creates an integer column, a sequence and a default, three loosely connected objects that can drift apart. Identity columns, standard since PostgreSQL 10, replace all of it with one coherent definition. The conversion is only a handful of statements, but getting them wrong breaks inserts immediately: forget the setval and the very next INSERT collides with an existing row. This generator writes the full migration in the right order, keeps the sequence positioned correctly, adds the rollback, and can pair the change with an integer-to-bigint widening. Runs entirely in your browser.
Do more than postgres serial to identity converter — meet Chat2DB
Chat2DB is an AI-powered SQL client for Windows, macOS and Linux. Write SQL in natural language, format and optimize queries automatically, and manage MySQL, PostgreSQL, Oracle and 20+ other databases in one workspace.
How to use
- Run the detection query to confirm whether the column is really a SERIAL with an owned sequence or already an identity column.
- Enter the schema, table and column, pick the direction and choose GENERATED BY DEFAULT for a drop-in swap or GENERATED ALWAYS to block explicit id writes.
- Copy the migration into your migration tool, keep the rollback block for the down migration, and read the notes on locking before running it in production.
Frequently asked questions
What is the difference between SERIAL and GENERATED AS IDENTITY in Postgres?
SERIAL is a macro. Writing id SERIAL PRIMARY KEY creates an integer column, a separate sequence named table_id_seq, and a DEFAULT nextval(...) on the column. The three objects are only loosely linked, so the sequence can be altered, dropped or have its permissions changed independently of the table, and the column is really just an integer with a default. GENERATED AS IDENTITY is the SQL-standard equivalent: the sequence belongs to the column, is dumped and restored as part of the column definition, inherits the table's permissions, and cannot be detached by accident. Identity also offers GENERATED ALWAYS, which rejects inserts that supply the value explicitly — something SERIAL cannot enforce at all. PostgreSQL has recommended identity over SERIAL since version 10.
Why does my first INSERT fail after converting to an identity column?
Because ALTER COLUMN ... ADD GENERATED AS IDENTITY creates a brand-new sequence that starts at 1, and it has no idea your table already contains a million rows. The next insert therefore tries to use id 1, which already exists, and fails with a duplicate key violation on the primary key. The fix is the setval call in step 4 of the generated migration: it fast-forwards the new sequence to max(id) + 1 before anything writes to the table. Always run it inside the same transaction as the ALTER, so a failure cannot leave the table accepting writes with a sequence pointing at 1.
Does converting SERIAL to IDENTITY lock the table or rewrite the data?
No data is rewritten. Dropping the default, detaching the sequence and adding the identity property are all catalogue-only changes that finish in milliseconds, so the table is not rewritten and indexes are not rebuilt. They do each take an ACCESS EXCLUSIVE lock, however, which means the migration has to wait for every open transaction touching the table — and while it waits, new queries queue behind it. On a busy table set lock_timeout to a few seconds so the migration fails fast and retries rather than stalling traffic. The one exception is the optional integer-to-bigint widening, which does rewrite every row and rebuild every index. Chat2DB shows column defaults, identity properties and owned sequences together in its schema browser, so you can confirm the change landed correctly: download it at https://chat2db.ai/download or use the web version at https://app.chat2db.ai.
