Zema Logo
Composition

Picking and Omitting Fields

Create sub-schemas from an object schema using pick() and omit(): select or exclude specific fields.

pick()

pick() returns a new schema containing only the fields listed:

final userSchema = z.object({
  'id': z.string().uuid(),
  'email': z.string().email(),
  'password': z.string(),
  'name': z.string(),
  'role': z.string(),
});

final publicUserSchema = userSchema.pick(['id', 'email', 'name']);

// publicUserSchema validates only id, email, name
// password and role are not expected and not included in output
publicUserSchema.parse({
  'id': '550e8400-e29b-41d4-a716-446655440000',
  'email': 'alice@example.com',
  'name': 'Alice',
});

Keys not present in the original schema are silently ignored. The original schema is not modified.

omit()

omit() returns a new schema with the listed fields removed:

final userSchema = z.object({
  'id': z.string().uuid(),
  'email': z.string().email(),
  'password': z.string(),
  'name': z.string(),
  'role': z.string(),
});

final publicUserSchema = userSchema.omit(['password']);

// publicUserSchema validates: id, email, name, role (no password)
publicUserSchema.parse({
  'id': '550e8400-e29b-41d4-a716-446655440000',
  'email': 'alice@example.com',
  'name': 'Alice',
  'role': 'user',
});

pick vs omit

Use pick when you want a small subset of fields. Use omit when you want most fields with a few excluded.

MethodUse when
pickKeeping a few fields from a large schema
omitExcluding a few sensitive or server-generated fields

Common patterns

Public vs private views

final userSchema = z.object({
  'id': z.string().uuid(),
  'email': z.string().email(),
  'password': z.string(),
  'salt': z.string(),
  'firstName': z.string(),
  'lastName': z.string(),
  'createdAt': z.dateTime(),
});

// Public API response: no credentials
final publicSchema = userSchema.omit(['password', 'salt']);

// Login request: credentials only
final loginSchema = userSchema.pick(['email', 'password']);

// Admin view: everything
final adminSchema = userSchema;

Form schemas

Server-generated fields (id, timestamps) are absent on creation and optionally absent on update:

final productSchema = z.object({
  'id': z.string().uuid(),
  'name': z.string(),
  'price': z.double().positive(),
  'stock': z.integer().nonNegative(),
  'createdAt': z.dateTime(),
  'updatedAt': z.dateTime(),
});

// Create form: no id or timestamps
final createProductSchema = productSchema.omit(['id', 'createdAt', 'updatedAt']);

// Update form: editable fields only
final updateProductSchema = productSchema.pick(['name', 'price', 'stock']);

// Full response
final productResponseSchema = productSchema;

API request/response

final postSchema = z.object({
  'id': z.string().uuid(),
  'title': z.string(),
  'content': z.string(),
  'authorId': z.string().uuid(),
  'published': z.boolean(),
  'createdAt': z.dateTime(),
  'updatedAt': z.dateTime(),
});

// POST: create new post
final createPostSchema = postSchema.omit(['id', 'createdAt', 'updatedAt']);

// PATCH: update existing post
final updatePostSchema = postSchema.pick(['title', 'content', 'published']);

// GET: full post
final postResponseSchema = postSchema;

Combine with extend

Chain omit or pick with extend to produce derived schemas with additional fields:

final baseUserSchema = z.object({
  'id': z.string().uuid(),
  'email': z.string().email(),
  'password': z.string(),
  'name': z.string(),
});

// Public schema with a computed display name field added
final publicUserSchema = baseUserSchema
    .omit(['password'])
    .extend({'displayName': z.string()});

// publicUserSchema has: id, email, name, displayName

Nested fields

pick and omit only operate on top-level keys. For nested fields, recreate the nested schema manually:

final userSchema = z.object({
  'id': z.string().uuid(),
  'email': z.string().email(),
  'profile': z.object({
    'avatar': z.string().url(),
    'bio': z.string(),
    'website': z.string().url().optional(),
  }),
});

// Pick top-level
final basicSchema = userSchema.pick(['id', 'email']);

// For a nested subset, compose manually
final userWithAvatarSchema = z.object({
  'id': z.string().uuid(),
  'email': z.string().email(),
  'profile': z.object({
    'avatar': z.string().url(),
    // bio and website excluded
  }),
});

Real-world example: user management API

final userSchema = z.object({
  'id': z.string().uuid(),
  'email': z.string().email(),
  'password': z.string(),
  'salt': z.string(),
  'firstName': z.string(),
  'lastName': z.string(),
  'role': z.string().oneOf(['admin', 'user', 'guest']),
  'emailVerified': z.boolean(),
  'createdAt': z.dateTime(),
  'updatedAt': z.dateTime(),
  'lastLogin': z.dateTime().optional(),
});

// POST /auth/register
final registerSchema = userSchema.pick(['email', 'password', 'firstName', 'lastName']);

// POST /auth/login
final loginSchema = userSchema.pick(['email', 'password']);

// GET /users/:id (public profile)
final publicProfileSchema = userSchema.pick(['id', 'firstName', 'lastName', 'createdAt']);

// GET /users/me
final currentUserSchema = userSchema.omit(['password', 'salt']);

// PATCH /users/:id
final updateProfileSchema = userSchema.pick(['firstName', 'lastName']);

// GET /admin/users/:id
final adminUserSchema = userSchema.omit(['password', 'salt']);

API reference

pick(keys)

ZemaObject<Map<String, dynamic>> pick(List<String> keys)

Returns a new schema containing only the specified fields. Keys absent from the original shape are silently ignored. The original schema is not modified.

omit(keys)

ZemaObject<Map<String, dynamic>> omit(List<String> keys)

Returns a new schema with the specified fields removed. Keys absent from the original shape are silently ignored. The original schema is not modified.

Next steps

Copyright © 2026