PHI

HomeDocsUpdate Hooks

Update Hooks

Update hooks allow you to intercept Update, UpdateMany, and UpdateManyAndReturn operations before queries hit the database. You can validate or hash input fields, auto-stamp audit timestamps, mutate target predicates, or pre-load relations on returned records.


Schema Context

The examples in this guide reference the following Prisma schema:

model User {
  id         String   @id @default(cuid())
  email      String   @unique
  phoneNum   String   @unique
  password   String?
  role       UserRole @default(STUDENT)
  loginCount Int      @default(0)
  createdAt  DateTime @default(now())

  posts      Post[]
}

model Post {
  id        String   @id @default(cuid())
  title     String
  content   String
  published Boolean  @default(false)
  authorId  String?
}

Registration & Chaining

Register hooks on a model delegate using .Use(), passing a model extension (e.g. user.Extension):

db.User.Use(user.Extension{
    Update: func(ctx context.Context, args *phi.UserUpdateArgs, next phi.UserUpdateQuery) (*phi.User, error) {
        // Pre-hook: normalize email if being updated
        if args.Data.Email != nil {
            lower := strings.ToLower(*args.Data.Email)
            args.Data.Email = &lower
        }

        res, err := next(ctx, args)

        // Post-hook: runs after query execution
        return res, err
    },
})

Hook Signatures

Hook Field Signature Return Type
Update func(ctx context.Context, args *phi.UserUpdateArgs, next phi.UserUpdateQuery) (*phi.User, error)
UpdateMany func(ctx context.Context, args *phi.UserUpdateManyArgs, next phi.UserUpdateManyQuery) (int64, error)
UpdateManyAndReturn func(ctx context.Context, args *phi.UserUpdateManyAndReturnArgs, next phi.UserUpdateManyAndReturnQuery) ([]*phi.User, error)

1. Mutating Update Data (`args.Data`)

All update structs use optional pointer fields (*string, *int32, etc.).

  • nil: The field is unmodified by the query.
  • non-nil: The field will be updated to the dereferenced value.
db.User.Use(user.Extension{
    Update: func(ctx context.Context, args *phi.UserUpdateArgs, next phi.UserUpdateQuery) (*phi.User, error) {
        // Lowercase email if updated
        if args.Data.Email != nil {
            lower := strings.ToLower(*args.Data.Email)
            args.Data.Email = &lower
        }

        // Hash password if updated
        if args.Data.Password != nil {
            hashed := hashPassword(*args.Data.Password)
            args.Data.Password = &hashed
        }

        return next(ctx, args)
    },
})

2. Targeting & Where Clauses (`Where`)

Inspect or append predicates to restrict which records can be updated:

db.User.Use(user.Extension{
    UpdateMany: func(ctx context.Context, args *phi.UserUpdateManyArgs, next phi.UserUpdateManyQuery) (int64, error) {
        // Restrict bulk updates to STUDENT accounts only
        args.Where = append(args.Where, user.Role.EQ(phi.UserRole_STUDENT))
        return next(ctx, args)
    },
})

Setters for Retargeting

SetWhere replaces the active target filter:

db.User.Use(user.Extension{
    Update: func(ctx context.Context, args *phi.UserUpdateArgs, next phi.UserUpdateQuery) (*phi.User, error) {
        args.SetWhere(user.Email.EQ("target@example.com"))
        return next(ctx, args)
    },
})

3. Relation Selection on Returned Records

Update and UpdateManyAndReturn support args.Select for preloading relations on updated records:

db.User.Use(user.Extension{
    UpdateManyAndReturn: func(ctx context.Context, args *phi.UserUpdateManyAndReturnArgs, next phi.UserUpdateManyAndReturnQuery) ([]*phi.User, error) {
        // Return updated users along with their published posts
        args.Select.Posts = post.Query().Where(post.Published.EQ(true))
        return next(ctx, args)
    },
})