The TypeScript error a mapped type may not declare properties or methods occurs when you add extra fields or methods inside a mapped type body.
When TypeScript throws the message a mapped type may not declare properties or methods, it is telling you that the type block mixing a mapped construct with normal fields breaks the rules for mapped types. This often shows up while refactoring interfaces, tightening enums, or shaping API responses with dynamic keys.
Quick goal — By the end of this guide, you will know what the error means, why it happens, and several safe patterns you can reuse to keep mapped types clean while still describing rich objects.
Why A Mapped Type May Not Declare Properties Or Methods Error Happens
The wording of the error sounds abstract, so it helps to unpack what the compiler sees. A mapped type is a block that loops over a union of keys and builds a fresh object type. Inside that block, TypeScript expects only the mapped entries, not extra fields or methods bolted on beside them.
Here is a minimal case that triggers the error in many projects:
enum Status {
Read = "READ",
Unread = "UNREAD",
}
interface StatusLabels {
// <= ts(7061): a mapped type may not declare properties or methods
[K in Status]: string;
}
Interfaces can describe index signatures, but when you switch to a mapped type in this style, the compiler wants you to switch to a type alias instead. The mapped construct sits inside braces, and the rule is simple: inside that block, you only describe the mapped entries.
The fix in this specific case is straightforward:
type StatusLabels = {
[K in Status]: string;
};
The difference looks tiny, yet it lines up with the TypeScript handbook guidance: mapped types live on type aliases, while interfaces work better when you list named properties instead of looping over keys.
Mapped Types In Typescript Without The Error
Before you walk through more examples, it helps to see a clean mapped type in isolation. The compiler is happy as long as the body only contains the mapped entries that describe how each property name should look.
type FeatureFlags = {
[K in keyof T]: boolean;
};
This shape takes an existing type T and produces a new type where each property becomes a boolean flag. No extra fields live alongside the mapped entries, so there is no error.
You can then reuse this mapped type against many different inputs:
type User = {
id: string;
email: string;
};
type UserFlags = FeatureFlags;
// UserFlags: { id: boolean; email: boolean }
You can also combine mapped types with utilities such as Readonly or Partial. These built in helpers follow exactly the same rule: the mapping body stays focused on how each property changes shape, without mixing in methods or stray parent fields.
Once you are comfortable with this pattern, the message a mapped type may not declare properties or methods makes more sense. The compiler is protecting the simple mental model: inside a mapped type block, every property is part of the mapping instead of an extra field with a different rule set.
Common Situations Where The Error Appears
While the short rule is “use a type alias and keep the body pure,” real projects tend to push against that boundary. Several patterns show up again and again when this error fires.
- Using an interface with a mapped block — Swapping
typeforinterfacewhile leaving the mapped body intact. - Adding fixed properties next to the mapping — Mixing a mapped construct with one or two named fields in the same object literal.
- Trying to declare methods on the mapping — Adding functions directly to the mapped type body.
- Combining mapped types with property remapping — Using
asto rename keys while also wanting custom members.
Here is how the “extra property” case usually shows up. You start with a neat mapped type that turns keys from a payload into values, then someone adds one more field to carry status or metadata.
type ApiResponseBad = {
[K in keyof T]: T[K];
status: number; // triggers a mapped type error
};
The mapped part says “loop over each key in T,” but the status line does not come from that loop, so the compiler complains that the mapped type picked up an extra property. The same pattern appears when someone places a method beside the mapping:
type Handlers = {
[K in keyof T]: (value: T[K]) => void;
runAll(): void; // error: method beside the mapped entries
};
Each situation has a simple refactor once you know what TypeScript expects. The table below shows how these cases usually look and the pattern that clears the error.
| Case | Problematic Shape | Safer Pattern |
|---|---|---|
| Interface with mapping | interface Foo { [K in U]: T } |
type Foo = { [K in U]: T } |
| Extra fixed property | { [K in U]: T; extra: X } |
{ [K in U | "extra"]: T | X } or intersection |
| Method on mapping | { [K in U]: T; run(): void } |
Split into separate type and interface |
Quick takeaway — When you see the error while working with a mapped block, search for interfaces, stray properties, or methods that share the same curly braces as the mapping.
How To Fix A Mapped Type May Not Declare Properties Or Methods In Typescript
Most fixes follow a small set of moves. Instead of guessing, you can work through them one by one and stop as soon as the error clears and the type still describes your data accurately.
- Switch interfaces to type aliases — If the offending block is declared with
interface, change it to atypealias and keep the mapped body as is. - Pull extra members into an intersection — When you need extra fixed fields, move them to a second type and combine them with
&. - Wrap mapped results instead of adding siblings — Place the mapped entries under a single property such as
resultinstead of mixing them with parent fields. - Keep methods on a separate interface — Methods pair well with interfaces or classes, while mapped types stay focused on plain data.
Here is what those moves look like in practice.
Switch From Interface To Type Alias
// Before
interface Permissions {
// ts(7061)
[K in "read" | "write" | "execute"]: boolean;
}
// After
type Permissions = {
[K in "read" | "write" | "execute"]: boolean;
};
This change is mechanical and usually safe. Anywhere a type name appears, TypeScript treats an interface and a type alias interchangeably in most real cases.
Use An Intersection For Extra Fields
// Before: mixed mapped fields and fixed fields
type ApiResponseBad = {
[K in keyof T]: T[K];
status: number; // triggers the error
};
// After: split and combine with an intersection
type ApiPayload = {
[K in keyof T]: T[K];
};
type ApiMeta = {
status: number;
};
type ApiResponse = ApiPayload & ApiMeta;
This version keeps the mapped payload clean while still giving the final type a status code. The intersection keeps both parts accurate without fighting the compiler’s rules.
Wrap The Mapping Under A Single Property
type ApiResponseBox = {
message: string;
success: boolean;
result: {
[K in keyof T]: T[K];
};
};
Here the mapped block sits inside result. That inner object has only the mapped entries, so the error does not appear, yet the outer shape still carries message and success fields.
Refactoring Existing Interfaces Without Breaking Callers
Many codebases start with interfaces everywhere, then grow more complex typing patterns over time. When you exchange an interface for a type alias during this growth, callers should not notice as long as you keep the external shape the same.
Soft refactor — Start by introducing a mapped type alias behind the scenes, then extend or intersect it with the old interface name. After tests pass, you can collapse the indirection if you like.
enum EmailStatus {
Read = "READ",
Unread = "UNREAD",
Draft = "DRAFT",
}
type EmailStatusMap = {
[K in EmailStatus]: string;
};
interface EmailTemplateMap extends EmailStatusMap {
// extra named fields live here instead of beside the mapping
description: string;
}
The inner mapped type follows the rule that a mapped type may not declare properties or methods. The outer interface then extends that pure mapped type and adds description as a regular field.
This pattern lines up with common answers from TypeScript users: use mapped type aliases to describe the grid of dynamic keys, then lean on interfaces when you want to attach methods or extra metadata.
When you refactor in a busy codebase, it also helps to change types in small commits. Swap an interface for a mapped alias, run your tests, and scan a few call sites in your editor to confirm that autocomplete still shows the fields you expect.
Practical Tips To Avoid The Error Next Time
Once you have fixed the error a couple of times, you can adjust habits so that new code stays clear of it from the start. Small style choices during type design reduce friction later.
- Reserve interfaces for named fields and methods — Reach for interfaces when you list stable properties that rarely change shape.
- Reach for type aliases when looping over keys — Use a
typealias whenever you use[K in ...]to build a mapped block. - Keep mapped bodies focused — If you catch yourself reaching for an extra field next to the mapped entries, move that field to a separate type immediately.
- Lean on helper types for common mappings — Extract repeated mapped constructs into reusable aliases so that each one stays small and readable.
You can also watch for the lowercase form of the error, since the compiler prints it exactly as a mapped type may not declare properties or methods. Seeing that phrase in your editor is a signal that a mapped block picked up extra work that belongs somewhere else.
Last check — If you see the error, scan for three things in this order: the word interface near the mapping, stray fixed properties inside the same braces, and methods sharing the mapped type body. One of those three almost always explains why TypeScript complained.
Small habit — Keep a scratch file in your project where you try new mapped type patterns with feedback in your editor. When the error pops up, you can test refactors until the pattern feels natural, then just copy the shape into production code.
Team tip — Share one or two simple mapped type helpers in a shared types module, such as a generic Flags or ReadonlyRecord. When everyone uses the same helpers, new contributors are less tempted to create custom mapped blocks with stray extra fields that trigger a mapped type may not declare properties or methods again.
- Limit how many concepts live in one type — If a type starts to describe data, metadata, and behavior all in one block, split it into smaller pieces before adding a mapped section.
- Pair types with tests — When a mapped type guards critical data, add a small test file that checks sample assignments so changes to enums or unions do not quietly break the mapping.
- Check the TypeScript release notes — From time to time, skim the official TypeScript release notes for any changes related to mapped types, property remapping, or index signatures, then adjust helper types if needed.
Config tweak — If your project uses strict compiler settings, keep noImplicitAny and strictNullChecks enabled while you experiment. These options make mapped type mistakes show up quickly, so the error a mapped type may not declare properties or methods appears early in development instead of during a release crunch.
Daily practice — When you review pull requests, glance at new type definitions and scan for mapped sections that share braces with extra fields, and suggest a small refactor before the pattern spreads through the codebase.
