npm / pg-boss
pg-boss
What changed in pg-boss 12, from its official release notes, upgrade guides and advisories. Sources checked 2026-10-21.
15 breaking2 deprecations12 new APIs and options1 performance11 behavior and defaults10 fixes and other
See which of these apply to your code, ranked, in your agent:
npx @unstale/cli initpg-boss 1251 changes
Breaking changes 15
Deletes in batches and resolves to the number of jobs deleted; failures can leave a partial delete.
Deletes in batches and resolves to the number of jobs deleted; failures can leave a partial delete.
Returns matching jobs oldest first and at most 1,000 by default; callers can use limit and after to read further pages.
Reads at most limit jobs of each kind, defaulting to 1,000 and allowing up to 100,000; total is no longer exact and counts only jobs read.
After upgrading, totalCount retains its prior count until the queue's first maintenance pass, then may temporarily be off after concurrent deletes or resumes during counting.
A job naming a missing dead-letter queue now rejects with Dead letter queue <name> does not exist.
Deleting a partitioned queue no longer waits indefinitely on concurrent sends; it retries lock acquisition for about 3 seconds, then rejects and leaves the queue unchanged.
Flow creation now rejects when a queue was deleted by another instance.
Inserting into a queue deleted by another instance now rejects with Queue <name> does not exist instead of reporting nothing done.
Previously stored schedules with empty expressions are skipped by the cron pass with an invalid_schedule warning instead of firing every minute.
schedule() rejects empty, blank, or non-string cron expressions; previously an empty expression could be read as every minute.
schedule() writes on a passed db, and no longer stores db with the schedule.
Sending to a queue deleted by another instance now rejects with Queue <name> does not exist instead of resolving null; in a passed transaction, COMMIT can fail with 23503.
stop() settles in-flight claims before completing; a claim not ready within the timeout is failed without starting its handler, so shutdown can exceed timeout. With graceful: false the job is failed at once.
Upserting into a queue deleted by another instance now rejects with Queue <name> does not exist instead of resolving null or reporting nothing done.
Deprecations 2
The singletonsActive field on getQueue() and getQueues() is deprecated and will be removed in the next major.
backend: 'yugabytedb' still works but emits a Node DeprecationWarning with code PGBOSS_DEP_YUGABYTEDB and will be rejected in the next major.
New APIs and options 12
Returns at most 1,000 rows by default in version and creation order; supports limit and after paging.
Returns at most 1,000 rows by default in key order; supports limit and after paging.
Returns at most 1,000 rows by default, ordered by queue name and id; supports limit and after paging.
Returns at most 1,000 rows by default, ordered by queue name and id; supports limit and after paging.
Returns at most 1,000 rows by default, oldest first; supports limit and after paging.
Returns at most 1,000 rows by default in name order; supports limit and after paging through its options argument.
Returns at most 1,000 rows by default, ordered by queue name and key; supports limit and after paging.
An options type for bounded list reads is now exported.
The options type for previewRedrive is now exported.
getUninstallPlans(schema, { backend }) returns SQL to remove pg-boss objects from a shared schema without CASCADE.
The uninstall command prints SQL to remove pg-boss objects from a shared schema without CASCADE.
Pass attempts to retry start after database failures, waiting 1 second between tries and doubling up to 30 seconds; configuration AssertionErrors are not retried.
Performance 1
Expired jobs and orphaned dependencies are deleted a queue at a time, with each batch in its own transaction, limiting long-held locks and xmin horizons.
Behavior and default changes 11
Once a pending background command is found, pending commands run back to back; commands from one migration on one table run in order and wait for predecessors to complete.
Deletes in batches and resolves to the number of jobs deleted, or null when TRUNCATE is used; failures can leave a partial delete.
On a shared table, deletes jobs in batches before deleting the queue; a failure can leave jobs partially deleted and the queue in place.
Returns one row per status, ordered by status, without a limit.
When enabled, handler_overrun, a failed LISTEN/NOTIFY listener, and a disabled xmin_horizon check are written to the warning table.
Runs pending background migration commands after the migration commits; on failure, exits with an error listing statements left to run by hand.
Without table partitioning, old snapshots are deleted in batches on every supervise pass; with partitioning, whole days are still dropped.
The timeout now bounds how long stop waits for a running background migration command; graceful: false ends the command at once.
Expired jobs are failed in batches, so recovery after a fleet outage uses short transactions.
If a handler outlives its timeout, its worker waits for it to return before fetching again and emits a handler_overrun warning; a handler that never returns holds the worker until stopped.
unschedule() now runs its write on a passed db.
Fixes and other changes 10
A custom adapter's executeSql must return an array with one result per statement for multi-statement queries; returning only the last result breaks maintenance passes and background migration claims.
Expiry no longer deletes jobs from another queue when jobs share an id on the shared job table.
The old index now remains in place until its replacement has been built, preventing slow fetches after a failed background index build.
Multi-statement query results over postgres-js are flattened, allowing pg-boss to find rows from statements before the last.
Redriving from a dead letter queue on the shared job table now touches only that queue's jobs, avoiding deletion or in-place failure of another queue's job with the same id.
Fetches from singleton and stately queues now check whether a key has an active job instead of relying on the cached list, preventing completed-job keys from staying blocked.
A pass that loses the lock now lets the next pass count its queues, keeping counts within about one interval for getQueue(), getQueueStats(), the dashboard, queue_stats history, and large-queue warnings.
Using pg-boss before start() now reports that start() must be called, rather than naming the internal open().
publish() skips a subscribed queue deleted while it is running, unless the send ran on a passed db.
Concurrent upserts with the same singletonKey now agree on one job instead of inserting duplicates; on CockroachDB, a later upsert may fail with 40001 and is retried up to 3 times when pg-boss owns the transaction.