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:
keyof Tgets all the keys from your original typeExclude<keyof T, K>removes the keys you want to omitPick<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:
| Scenario | Use This | Why |
|---|---|---|
| Excluding fewer properties than keeping | Omit<T, K> | More concise when you're removing 1-2 fields from a 10-field type |
| Including fewer properties than excluding | Pick<T, K> | Clearer when you only need 2-3 fields from a large type |
| Creating public API types | Omit<T, K> | Better for "everything except sensitive fields" |
| Building minimal DTOs | Pick<T, K> | Better for "only these specific fields" |
| Not sure which to use? | Count the keys | Use 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, whilePick<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 withOmit<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:
- Use
Omit<T, K>when you're excluding fewer properties than you're keeping - Remember it's compile-time only—you still need to handle runtime data sanitization
- Break complex type transformations into smaller, named types
- Combine with other utility types like
Partial<T>,Pick<T, K>, andRequired<T>for maximum flexibility - 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.