TypeScript Getter Setter Errors: TS1056, TS1028, TS2378 Fix
technology

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.

2026-06-23
8 min read
TypeScript Getter Setter Errors: TS1056, TS1028, TS2378 Fix

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:

plaintext
 
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:

ts
 
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.

ts
 
// 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:

ts
 
// 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#

json
 
{
 
  "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#

  1. Open tsconfig.json.

  2. Locate the "compilerOptions" block.

  3. Add or update "target": "ES5" (or "ES2016", "ES2020", etc.).

  4. Ensure "lib" includes "ES5" or higher (e.g., "ES2015" implies ES5+).

  5. Save and restart your dev server (npm run dev) or re-run tsc.

For declaration files, replace accessors with property signatures:

ts
 
// types/user.d.ts
 
declare class User {
 
  name: string; // ✅ Ambient property, no get/set
 
}
 

Then implement the actual class in a .ts file:

ts
 
// 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#

bash
 
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:

ts
 
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:

ts
 
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:

json
 
{
 
  "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.

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

|

Have more questions? Contact us

Written by

Mahdi Br
Mahdi Br

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote

One email a month — no fluff

RLS gotchas, Next.js cache debugging, and the one Supabase setting that bit me last month.