better-drizzle
Plugins

Timestamps

Automatic createdAt / updatedAt management for better-drizzle, with app-managed and database-managed modes.

better-drizzle/timestamps keeps timestamp handling out of your services. It can manage timestamps in application code, or stay out of the way when your database already owns them via defaults, generated columns, or triggers.

Install

npm install better-drizzle
pnpm add better-drizzle
yarn add better-drizzle
bun add better-drizzle

Usage

import { better } from 'better-drizzle';
import { timestamps } from 'better-drizzle/timestamps';

const client = better(db, {
	schema,
	plugins: [
		timestamps({
			createdAt: 'createdAt',
			updatedAt: 'updatedAt',
			mode: 'app',
		}),
	],
});

Options

Every option is optional.

OptionDefaultDescription
createdAt'createdAt'column set on insert
updatedAt'updatedAt'column set on insert and update
mode'app''app' sets the columns in plugin hooks; 'database' registers no hooks

Modes

mode: 'app'

The plugin fills timestamp columns before the database call. Values are JavaScript Date objects, one now per call.

OperationBehavior
createsets createdAt and updatedAt
createManysets both on every row
update / updateManysets updatedAt
updateEachadds an updatedAt resolver to update, so every matched row gets the same now
upsertsets both on create, sets updatedAt on update
upsertManysets both on every row; on conflict, createdAt is never updated and updatedAt is
reads, delete, deleteManyuntouched

For upsertMany, the conflict update is rewritten by shape: 'all' becomes every column except createdAt, a column list drops createdAt and adds updatedAt, an object drops createdAt and sets updatedAt, and a function result gets the same treatment.

The plugin always writes its own values. A createdAt or updatedAt passed by the caller is overwritten:

const { createdAt, updatedAt } = await client.posts.create({
	data: { title: 'Hello', createdAt: new Date('2020-01-01') },
}); // both are the current time, not 2020

Use $withoutPlugins() for a one-off write that must keep caller-provided timestamps, such as a data import.

mode: 'database'

The plugin becomes a no-op - use it when the database owns timestamps (column defaults like DEFAULT now(), ON UPDATE, triggers, or generated values):

timestamps({ mode: 'database' });

In this mode it adds effectively zero runtime behavior; the database stays the source of truth.

Custom column names

Names are the property keys in your Drizzle table definition, not the database column names.

timestamps({
	createdAt: 'created_at',
	updatedAt: 'updated_at',
});

Behavior details

  • Each column is handled on its own: a table with only updatedAt gets only updatedAt, and tables with neither are skipped.
  • It only mutates write payloads - reads, filters, and result shapes are untouched.
  • Soft deletes from better-drizzle/soft-delete run beneath the plugins, so they do not change updatedAt.

On this page