TypeScript Getter Setter Errors: TS1056, TS1028, TS2378 Fix
TypeScript getter/setter errors come from ES target misconfiguration or type mismatches. Fix TS1056, TS1028, and TS2378 in minutes.
Nine times out of ten, TS1056, TS1028, or TS2378 on a getter or setter means "target": "ES3" in tsconfig.json. TypeScript won't emit accessors below ES5 because they compile to Object.defineProperty — an ES5 primitive. Set "target": "ES5" or higher and rebuild.
If the error persists after that, there are two more cases below — getter returning undefined, and a type mismatch between getter and setter.
The errors#
You'll see one or more of these errors in your terminal or IDE when compiling or saving a file:
error TS1056: Accessors are only available when targeting ECMAScript 5 and higher.
error TS1028: 'get' and 'set' accessors are not allowed in an ambient context.
error TS2378: A 'get' accessor must return a value.
It happens when you define a getter or setter inside a class, like this:
class User {
private _name: string;
get name(): string {
return this._name;
}
set name(value: string) {
this._name = value;
}
}
Here, private _name is a backing field guarded by TypeScript's property visibility modifiers—public, private, and protected—which control who can read or write each member. The public getter methods and setter methods form a controlled read/write surface around that private field, so callers interact with user.name instead of touching _name directly. When any part of this contract breaks—an ECMAScript target version that's too low, ambient-context misuse, or a mismatch between the getter's and setter's types—the compiler raises one of the three errors above.
Why it happens#
TypeScript's get and set accessors are syntactic sugar over JavaScript's Object.defineProperty, which only works reliably in ES5 and later. If your tsconfig.json compiler options set "target" to an ECMAScript target version like "ES3", or to a higher version like "ES2015" without enabling "lib": ["ES5"], the compiler refuses to emit accessor syntax because it cannot guarantee runtime support.
// src/types/user.ts
export class User {
private _name: string;
// This line triggers TS1056 if target < ES5
get name(): string {
return this._name;
}
// This line triggers TS2378 if return type is void or missing
set name(value: string) {
this._name = value;
}
}
TypeScript also forbids accessors in ambient contexts—like .d.ts declaration files or declare class blocks—because they describe runtime behavior that must be implemented elsewhere. For example:
// types/user.d.ts
declare class User {
get name(): string; // ❌ TS1028: not allowed in ambient context
set name(value: string);
}
This fails because declaration files must declare signatures, not implementations. The compiler enforces this to prevent accidental omission of accessor logic.
The fix — bump your ES target#
{
"compilerOptions": {
"target": "ES5",
"lib": ["ES5", "DOM"],
"outDir": "./dist",
"rootDir": "./src"
}
}
That single change addresses the cause because ES5+ targets include native support for getter/setter syntax, and TypeScript's emitter now safely converts them to Object.defineProperty calls.
Step by step#
-
Open
tsconfig.json. -
Locate the
"compilerOptions"block. -
Add or update
"target": "ES5"(or"ES2016","ES2020", etc.). -
Ensure
"lib"includes"ES5"or higher (e.g.,"ES2015"implies ES5+). -
Save and restart your dev server (
npm run dev) or re-runtsc.
For declaration files, replace accessors with property signatures:
// types/user.d.ts
declare class User {
name: string; // ✅ Ambient property, no get/set
}
Then implement the actual class in a .ts file:
// src/user.ts
import { User as UserDef } from '../types/user';
export class User implements UserDef {
private _name: string;
get name(): string {
return this._name;
}
set name(value: string) {
this._name = value;
}
}
Check it#
npx tsc --noEmit
Found 0 errors. — done. Still seeing errors? Two other variants:
Variant A — Getter returns undefined or void#
TypeScript infers the return type from the body. If your getter has no return statement, or returns void, you'll get TS2378. These accessor return types must be explicit and consistent: the getter method's return type defines the property's read type, and the setter method's parameter type defines the write type. When those accessor return types and parameter types drift apart, the compiler raises TS2378 to prevent silent coercion bugs.
Fix by ensuring the getter returns a value matching the expected type:
class Config {
private _debug = false;
get debug(): boolean {
return this._debug; // ✅ Explicit return
}
set debug(value: boolean) {
this._debug = value;
}
}
Variant B — Setter parameter type mismatch#
If the setter's parameter type is incompatible with the getter's return type, TypeScript reports TS2378 or TS2345.
Fix by aligning types:
class Temperature {
private _celsius = 0;
get celsius(): number {
return this._celsius;
}
// ❌ Wrong: setter expects string, getter returns number
// set celsius(value: string) { this._celsius = parseFloat(value); }
// ✅ Correct: both use number
set celsius(value: number) {
this._celsius = value;
}
}
Preventing regressions#
Below ES5, TypeScript falls back to __defineGetter__ and __defineSetter__ — deprecated, non-standard, and the wrong choice for anything targeting modern runtimes. ES5+ guarantees Object.defineProperty, which is what you want.
To catch a future target drift in CI, add this ESLint rule:
{
"rules": {
"@typescript-eslint/ban-ts-comment": "error",
"no-undef": "off"
},
"parserOptions": {
"ecmaVersion": 2020
}
}
Also, run tsc --noEmit in your CI pipeline. I cover this in detail in JS to TypeScript: Incremental Migration, No Full Rewrite, where I show how to gradually adopt accessors without breaking existing code.
For deeper type safety, especially when working with Supabase or Next.js, see Complete Type Safety Guide for Next.js and Supabase with TypeScript.
Related#
-
What is TypeScript and why should I use it instead of JavaScript
-
Complete Type Safety Guide for Next.js and Supabase with TypeScript
With these adjustments—appropriate ECMAScript target versions, well-configured tsconfig.json compiler options, carefully chosen property visibility modifiers, and aligned accessor return types—your TypeScript accessors, including getter methods and setter methods, will compile cleanly and behave predictably at runtime, keeping your build green.
Frequently Asked Questions
One email a month — no fluff
RLS gotchas, Next.js cache debugging, and the one Supabase setting that bit me last month.
Continue Reading
Fix TS2564: Property Has No Initializer in TypeScript
TS2564 fires when a class field is typed but never guaranteed to be set. The fix depends on WHY it's unset: a default value, constructor assignment, the definite-assignment `!`, or an optional `?`. Picking the wrong one hides real bugs.
Fix TS2305: Module Has No Exported Member in TypeScript
TS2305 says the export you're importing doesn't exist under that name. The cause is almost always one of: a typo, default-vs-named confusion, a CJS/ESM interop mismatch, or @types drifting from the runtime package. Each has a precise fix.
JS to TypeScript: Incremental Migration, No Full Rewrite
Migrate a JS codebase to TypeScript file-by-file — no full rewrite required. The tsconfig settings, file order, and tricks for handling untyped packages along the way.
Browse by Topic
Find stories that matter to you.
