Skip to main content

Typescript Omit: How Remove Properties from Your Types

Omit<T, K> is a TypeScript utility type that lets you create a new type by taking an existing one and removing one or more properties from it. It's particularly useful when you want to exclude certain fields without completely rewriting or duplicating your type.

This utility type is invaluable when you need to:

  • Create public-facing versions of types by removing sensitive fields
  • Build form input types by excluding auto-generated properties
  • Define specialized versions of base types without duplicating code
  • Maintain type safety while working with subsets of your data model

TypeScript Omit<T, K> keeps your code flexible and DRY (Don't Repeat Yourself). Instead of duplicating types or manually redefining everything minus a few fields, you just use Omit<T, K> to trim what's unnecessary. If you ever find yourself thinking, "I need this type, but without that property," that's when Omit<T, K> should come to mind.

How Does Omit Work?

Under the hood, Omit<T, K> is defined like this:

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

Here's what's happening step by step:

  1. keyof T gets all the keys from your original type
  2. Exclude<keyof T, K> removes the keys you want to omit
  3. Pick<T, ...> creates a new type with only the remaining keys

Let's see this in action:

interface User {
id: number;
name: string;
email: string;
password: string;
}

type PublicUser = Omit<User, "password">;

// Behind the scenes:
// 1. keyof User = "id" | "name" | "email" | "password"
// 2. Exclude<"id" | "name" | "email" | "password", "password"> = "id" | "name" | "email"
// 3. Pick<User, "id" | "name" | "email"> = { id: number; name: string; email: string; }

The Omit<T, K> type takes two arguments:

  • T: The original type you want to modify
  • K: The property key(s) you want to remove (can be a single key or a union of keys)

You can omit multiple keys at once using a union type:

type SafeUser = Omit<User, "password" | "id">;
// Result: { name: string; email: string; }

Why use Omit?

It's flexible. You can modify existing types without duplicating code.

TypeScript's Omit<T, K> utility allows you to create a new type based on an existing one while excluding specific properties. This eliminates the need to manually recreate types, making it easier to adjust to changing requirements without introducing redundancy.

interface User {
id: number;
name: string;
email: string;
password: string;
}

type PublicUser = Omit<User, "password">;

// PublicUser will only have id, name, and email properties.

const user: PublicUser = { id: 1, name: "Alice", email: "alice@example.com" };

It makes your code easier to read.

By explicitly stating which properties are omitted, Omit<T, K> improves code readability, so other developers can quickly understand what's being excluded from the type. This is particularly useful when dealing with large or complex types, as it avoids misinterpretation.

interface Product {
id: string;
name: string;
description: string;
createdAt: Date;
updatedAt: Date;
}

// Create a public version without internal fields

type ProductSummary = Omit<Product, "description" | "createdAt" | "updatedAt">;

// TypeScript will enforce the correct shape

const summary: ProductSummary = { id: "123", name: "Gadget" };

Reusability. You can create multiple variations of a type for different use cases.

With Omit<T, K>, you can tailor a single base type into multiple variations, avoiding duplication and promoting consistency across different parts of your application. This makes it easier to maintain and evolve types as your app grows.

interface Post {
id: number;
title: string;
content: string;
authorId: number;
published: boolean;
}

type PostDraft = Omit<Post, "id" | "published">;

type PostView = Omit<Post, "content">;

// PostDraft is used for creating drafts without IDs or published flags.

const draft: PostDraft = { title: "Draft Title", content: "Draft content", authorId: 1 };

// PostView is used to show posts without exposing the full content.

const view: PostView = { id: 1, title: "Post Title", authorId: 1, published: true };

TS Omit Use Cases*

Excluding Sensitive Fields

Let's say you have a User type and need to exclude the password field when sending data to the frontend.

interface User {
id: number;
name: string;
email: string;
password: string;
}

// Remove all sensitive data for client-side use

type PublicUser = Omit<User, "password">;

// TypeScript ensures we only return safe fields

const user: PublicUser = {
id: 1,
name: "Alice",
email: "alice@example.com",
};

console.log(user);

// Output: { id: 1, name: "Alice", email: "alice@example.com" }

Here, the Omit<T, K> utility is used to create a new type, PublicUser, by removing the password property from the User type. This makes sure that when you're sending user data to the frontend, sensitive information like passwords is automatically excluded—clean and safe!

Editable Types for Forms

When building forms, you often need a type that represents user input without system-generated fields. Here's a realistic example from an e-commerce admin panel:

interface Product {
id: string;
sku: string;
name: string;
description: string;
price: number;
categoryId: string;
inStock: boolean;
imageUrls: string[];
createdAt: Date;
updatedAt: Date;
createdBy: string;
}

// Form for creating a new product - exclude all system-managed fields
type CreateProductForm = Omit<Product, "id" | "sku" | "createdAt" | "updatedAt" | "createdBy">;

// Form for editing an existing product - make user fields optional, keep id required
type EditProductForm = { id: string } & Partial<Omit<Product, "id" | "createdAt" | "updatedAt" | "createdBy" | "sku">>;

// Usage in a React component
function ProductForm({ onSubmit }: { onSubmit: (data: CreateProductForm) => void }) {
const handleSubmit = (formData: CreateProductForm) => {
// TypeScript ensures we're only submitting editable fields
onSubmit({
name: formData.name,
description: formData.description,
price: formData.price,
categoryId: formData.categoryId,
inStock: formData.inStock,
imageUrls: formData.imageUrls,
});
};

// Implementation...
}

This pattern keeps your form types in sync with your data model. If someone adds a new system-generated field to Product, you won't accidentally expose it in your forms.

Shaping API Responses

Here's a use case that's common in API development:

interface DatabaseRecord {
id: string;
createdAt: Date;
updatedAt: Date;
deletedAt?: Date;
version: number;
}

interface UserRecord extends DatabaseRecord {
email: string;
name: string;
preferences: Record<string, any>;
}

// Remove internal tracking fields for API responses

type ApiResponse<T extends DatabaseRecord> = Omit<T, "deletedAt" | "version">;

function formatApiResponse<T extends DatabaseRecord>(record: T): ApiResponse<T> {
const { deletedAt, version, ...rest } = record;
return rest as ApiResponse<T>;
}

// Usage

const user: UserRecord = {
id: "123",
email: "user@example.com",
name: "Test User",
preferences: { theme: "dark" },
createdAt: new Date(),
updatedAt: new Date(),
version: 1
};

const apiResponse = formatApiResponse(user);

// Result excludes version and deletedAt fields

This pattern is particularly useful when building APIs where your database models contain internal fields that shouldn't be exposed to clients. By creating a generic ApiResponse<T> type with Omit<T, K>, you can automatically strip out internal tracking fields like version and deletedAt from any database record.

The formatApiResponse function provides a type-safe way to transform any database record that extends DatabaseRecord into its API-friendly form. This approach scales well as your application grows - when you add new models that extend DatabaseRecord, they'll automatically work with your API response formatting, maintaining consistency across your entire API surface.

When to Use Omit vs Pick

Both Omit<T, K> and Pick<T, K> let you create new types from existing ones, but they work in opposite ways. Here's how to decide which one to use:

ScenarioUse ThisWhy
Excluding fewer properties than keepingOmit<T, K>More concise when you're removing 1-2 fields from a 10-field type
Including fewer properties than excludingPick<T, K>Clearer when you only need 2-3 fields from a large type
Creating public API typesOmit<T, K>Better for "everything except sensitive fields"
Building minimal DTOsPick<T, K>Better for "only these specific fields"
Not sure which to use?Count the keysUse whichever requires listing fewer property names

Here's the same goal accomplished both ways:

interface DatabaseUser {
id: string;
email: string;
name: string;
password: string;
salt: string;
createdAt: Date;
updatedAt: Date;
lastLoginAt: Date;
}

// Using Omit (removing 3 properties)
type PublicUser = Omit<DatabaseUser, "password" | "salt" | "lastLoginAt">;

// Using Pick (selecting 5 properties)
type PublicUser = Pick<DatabaseUser, "id" | "email" | "name" | "createdAt" | "updatedAt">;

// In this case, Omit is cleaner because we're excluding fewer properties

Rule of thumb: If you're typing out more property names than you need to, you're probably using the wrong utility type.

Common Mistakes & How to Avoid Them

Mistake 1: Using Omit When Pick Would Be Clearer

If you're omitting more properties than you're keeping, switch to Pick<T, K>.

interface Product {
id: string;
sku: string;
name: string;
description: string;
price: number;
cost: number;
inventory: number;
supplierId: string;
createdAt: Date;
updatedAt: Date;
}

// Don't do this - omitting 8 out of 10 properties
type ProductSummary = Omit<Product, "description" | "cost" | "inventory" | "supplierId" | "createdAt" | "updatedAt" | "sku" | "price">;

// Do this instead - picking the 2 you need
type ProductSummary = Pick<Product, "id" | "name">;

Mistake 2: Thinking Omit Works at Runtime

Omit<T, K> is purely a compile-time construct. It doesn't remove properties from your actual JavaScript objects.

type PublicUser = Omit<User, "password">;

const user: PublicUser = {
id: 1,
name: "Alice",
email: "alice@example.com",
};

// TypeScript won't let you add password here
// user.password = "secret"; // Error!

// But if you receive data from an API...
const apiUser = await fetch("/api/user").then(r => r.json()) as PublicUser;

// This object might still have a password property at runtime!
// You need to explicitly remove it:
const { password, ...safeUser } = apiUser;

Mistake 3: Creating Overly Complex Type Chains

If your type is getting hard to read, break it into smaller named types.

// Hard to read
type UpdatePayload = Partial<Omit<Pick<User, "name" | "email" | "age" | "role">, "role">> & { id: string };

// Much better
type EditableFields = Pick<User, "name" | "email" | "age">;
type OptionalEdits = Partial<EditableFields>;
type UpdatePayload = OptionalEdits & { id: string };

Mistake 4: Omitting Keys That Don't Exist

TypeScript won't error if you omit a property that doesn't exist in the type, which can hide bugs.

interface User {
id: number;
name: string;
email: string;
}

// Typo! Should be "email", not "emailAddress"
// TypeScript silently allows this
type PublicUser = Omit<User, "emailAddress">;

// To catch this, use stricter typing:
type StrictOmit<T, K extends keyof T> = Omit<T, K>;
type SafePublicUser = StrictOmit<User, "email">; // Now it must be a real key

3 Ways to Combine Omit with Other TypeScript Tools

1. Combining with Pick

Omit<T, K> and Pick<T, K> are two sides of the same coin:

  • Omit<T, K> removes properties, while
  • Pick<T, K> selects specific properties.

You can combine them if you need to build very specific shapes of types.

interface User {
id: number;
firstName: string;
lastName: string;
email: string;
password: string;
role: string;
lastLogin: Date;
}

// Using Pick for a minimal profile view

type BasicProfile = Pick<User, "id" | "firstName" | "lastName">;

// Using Omit for a secure profile with everything except sensitive data

type SecureProfile = Omit<User, "password" | "lastLogin">;

// Combining both for specific use cases

type EditableProfileFields = Pick<Omit<User, "id" | "password" | "role" | "lastLogin">, "firstName" | "lastName" | "email">;

This combination is particularly useful when you need precise control over which properties to include and exclude. The EditableProfileFields type shows how you can chain these utilities to create exactly the type you need.

2. With Partial

The Partial<T> utility makes all properties of a type optional. If you need an optional type but want to exclude certain properties, combine Partial<T> with Omit<T, K>.

interface Task {
id: string;
title: string;
description: string;
status: 'todo' | 'in_progress' | 'done';
assignee: string;
dueDate: Date;
createdAt: Date;
updatedAt: Date;
}

// Type for updating tasks - make all fields optional except id

type TaskUpdate = Partial<Omit<Task, "id" | "createdAt" | "updatedAt">> & {
id: string;
};

function updateTask(update: TaskUpdate) {
// Only specified fields will be updated
const validUpdate: TaskUpdate = {
id: "123",
title: "Updated title", // Optional
status: "in_progress" // Optional
};
// Implementation...
}

This can be handy when creating a type for form data or update operations, where some fields are optional, but others (like id) are irrelevant or immutable.

3. With Intersection (&)

Sometimes you want to remove some properties from one type and merge it with another. Use Omit<T, K> with & to create new types that combine and extend existing ones.

interface BaseEntity {
id: string;
createdAt: Date;
updatedAt: Date;
}

interface UserData {
email: string;
password: string;
name: string;
}

// Create a complete user type combining base fields and user-specific data

type User = Omit<BaseEntity, "password"> & UserData;

// Create a type for user creation (without system fields)

type CreateUser = Omit<User, "id" | "createdAt" | "updatedAt">;

// Create a public user type (without password)

type PublicUser = Omit<User, "password">;

const user: User = {
id: "123",
email: "user@example.com",
password: "hashed_password",
name: "John Doe",
createdAt: new Date(),
updatedAt: new Date()
};

const publicUser: PublicUser = {
id: "123",
email: "user@example.com",
name: "John Doe",
createdAt: new Date(),
updatedAt: new Date()
};

This approach is particularly powerful when you're building a system with consistent base types that need to be combined with specific feature sets. It allows you to create complex types while maintaining clear separation of concerns and type safety.

Performance & Type Safety Benefits

Zero Runtime Cost

Since Omit<T, K> is a compile-time construct, it has absolutely no performance impact on your application. When TypeScript compiles to JavaScript, all type information is stripped away:

// TypeScript
type SafeUser = Omit<User, "password">;
const user: SafeUser = { id: 1, name: "Alice", email: "alice@example.com" };

// Compiled JavaScript (all type info is gone)
const user = { id: 1, name: "Alice", email: "alice@example.com" };

This means you can use Omit<T, K> as liberally as you need without worrying about bundle size or runtime overhead. It's purely a development-time tool for type safety.

Compile-Time Safety

The real value of Omit<T, K> is catching errors before your code runs. TypeScript prevents you from accessing omitted properties:

type PublicUser = Omit<User, "password">;

function displayUser(user: PublicUser) {
console.log(user.name); // ✓ Fine
console.log(user.email); // ✓ Fine
console.log(user.password); // ✗ Error: Property 'password' does not exist
}

This catches bugs at development time that would otherwise cause runtime errors or security issues.

Automatic Refactoring Support

When you rename a property in your base type, TypeScript automatically updates everywhere that property is referenced, including Omit<T, K> statements:

interface User {
id: number;
emailAddress: string; // Renamed from "email"
password: string;
}

// TypeScript will flag this immediately if you rename "email" to "emailAddress"
type PublicUser = Omit<User, "email">; // Error: "email" doesn't exist

// Fix it to:
type PublicUser = Omit<User, "emailAddress">;

This prevents silent type mismatches that could slip through if you were manually redefining types.

Using Omit in Convex Backend Logic

Convex simplifies backend development, and Omit<T, K> can be used in Convex functions to ensure type safety while handling sensitive data.

import { mutation } from "./generated/server";
import { DatabaseReader, Doc, Id } from "./generated/dataModel";

type User = Doc<"users">;

// {
// _id: Id<"users">;
// name: string;
// email: string;
// password: string;
// _creationTime: number;
//}

type SafeUser = Omit<User, "password" | "_creationTime">;

export const getPublicUser = async (db: DatabaseReader, userId: Id<"users">): Promise<SafeUser> => {
const user = await db.get(userId);
if (!user) throw new Error("User not found");
const { password, _creationTime, ...rest } = user;
const safeUser: SafeUser = rest;
return safeUser;
});

In this example, Omit<T, K> is used to define a SafeUser type that excludes the password field from the User type. This ensures that when handling sensitive data in a Convex backend function, only the safe, non-sensitive fields like _id, name, and email are returned, maintaining both security and type safety. Convex works seamlessly with TypeScript, letting you handle backend data with the same type safety you expect in the frontend. With Omit<T, K>, you can:

  • Type Safety in Database Operations: When you use Omit<T, K> to create specific subtypes of your document types, Convex ensures that your queries and mutations maintain type safety. This prevents accidental exposure of sensitive fields or unintended modifications to protected fields.
  • API Security: By defining public-facing types using Omit<T, K>, you can systematically exclude sensitive information from your API responses. This creates a clear boundary between your internal data model and your public API surface.
  • Schema Evolution: As your Convex database schema evolves, TypeScript and Omit<T, K> help you maintain consistency across your codebase. When you add new fields to your document types, TypeScript will help ensure that your derived types (created with Omit<T, K>) are updated appropriately.

Real-World Example

Let's say you're building a task management app. You store tasks in the database with fields like id, title, description, createdAt, and completed. When rendering a dashboard, you only need the title and completed status. Using Omit<T, K>, you can create a simplified type:

interface Task {
id: string;
title: string;
description: string;
createdAt: Date;
completed: boolean;
}

type DashboardTask = Omit<Task, "description" | "createdAt">;

// Simplify data for view

const tasks: DashboardTask[] = [
{ id: "1", title: "Fix bug", completed: true },
{ id: "2", title: "Write docs", completed: false },
];

console.log(tasks);

/* Output:
[
{ id: "1", title: "Fix bug", completed: true },
{ id: "2", title: "Write docs", completed: false },
]
*/

This approach keeps your code clean and reduces the risk of accidentally exposing unnecessary or sensitive data.

Advanced Pattern: Omit with Union Types

When working with discriminated unions, you might want to omit properties from multiple related types at once. Here's how to handle that:

The Problem with Standard Omit on Unions

Standard Omit<T, K> doesn't distribute over union types the way you might expect:

type Circle = {
kind: "circle";
radius: number;
color: string;
id: string;
};

type Square = {
kind: "square";
sideLength: number;
color: string;
id: string;
};

type Shape = Circle | Square;

// This works, but only omits properties that exist on ALL union members
type ShapeWithoutId = Omit<Shape, "id">;

// What if we want to omit different properties from each type?

Distributive Omit for Union Types

To properly distribute Omit<T, K> across union types, you can create a distributive version:

type DistributiveOmit<T, K extends PropertyKey> = T extends unknown
? Omit<T, Extract<keyof T, K>>
: never;

// Now we can omit properties that might not exist on all members
type ShapeWithoutMeta = DistributiveOmit<Shape, "id" | "metadata">;

// This correctly creates: Circle without "id" | Square without "id"

Real-World Example: API Event Types

Here's where this pattern shines in practice:

type UserCreatedEvent = {
type: "user.created";
userId: string;
timestamp: Date;
internalTrackingId: string;
metadata: { source: string };
};

type UserUpdatedEvent = {
type: "user.updated";
userId: string;
changes: Record<string, unknown>;
timestamp: Date;
internalTrackingId: string;
};

type UserDeletedEvent = {
type: "user.deleted";
userId: string;
timestamp: Date;
internalTrackingId: string;
reason?: string;
};

type InternalEvent = UserCreatedEvent | UserUpdatedEvent | UserDeletedEvent;

// Remove internal fields for webhooks sent to external systems
type DistributiveOmit<T, K extends PropertyKey> = T extends unknown
? Omit<T, Extract<keyof T, K>>
: never;

type WebhookEvent = DistributiveOmit<InternalEvent, "internalTrackingId" | "metadata">;

// Now WebhookEvent correctly removes those fields from each event type
function sendWebhook(event: WebhookEvent) {
// TypeScript ensures internal fields are stripped from all event types
fetch("https://api.example.com/webhook", {
method: "POST",
body: JSON.stringify(event),
});
}

This pattern is particularly valuable when you're working with event-driven architectures, webhook systems, or any situation where you need to transform multiple related types in the same way.

Final Thoughts

TypeScript's Omit<T, K> utility type is great for crafting clean, reusable, and purpose-driven types. Whether you're filtering data for an API, defining forms, or creating partial models, Omit<T, K> makes your development process faster and safer. When used effectively, it helps you:

  • Keep your types DRY and maintainable
  • Enforce clear boundaries between internal and public data
  • Create purpose-specific types without duplicating code
  • Build type-safe APIs and form handling
  • Catch errors at compile time instead of runtime
  • Maintain flexibility as your application grows

Key Takeaways:

  1. Use Omit<T, K> when you're excluding fewer properties than you're keeping
  2. Remember it's compile-time only—you still need to handle runtime data sanitization
  3. Break complex type transformations into smaller, named types
  4. Combine with other utility types like Partial<T>, Pick<T, K>, and Required<T> for maximum flexibility
  5. For union types, consider using a distributive version to ensure proper type distribution

Combine Omit<T, K> with a type-safe backend like Convex for end-to-end type safety from your database to your UI. Start using these patterns today and watch your TypeScript code become more focused, expressive, and maintainable.