Question

How to Create Class Constants in TypeScript

typescriptclass-constants

Question

In TypeScript, using the const keyword directly on a class property causes a compiler error:

class Example {
  const MY_CONSTANT = 10;
}

The error is:

A class member cannot have the 'const' keyword.

I want a clear way to express that a class property should not be changed after it is declared, and I want the compiler or IDE to report an error if I try to reassign it.

At the moment, I am using a getter like this:

get MY_CONSTANT(): number {
  return 10;
}

Is there a better way to implement class constants in TypeScript, especially in newer versions such as TypeScript 2.x and later?

Short Answer

By the end of this page, you will understand why const cannot be used for class members in TypeScript, and how to model class constants correctly using readonly and static readonly. You will also learn when to use a getter, when to use an instance property, and when to use a class-level constant.

Concept

In TypeScript, const is for variables, not class members. That is why this is valid:

const MAX_RETRIES = 3;

but this is not:

class Config {
  const MAX_RETRIES = 3;
}

Class members belong to an object instance or to the class itself, and TypeScript uses different keywords to control whether they can be reassigned.

The main tool for this is readonly.

class User {
  readonly id: number;

  constructor(id: number) {
    this.id = id;
  }
}

A readonly property can be assigned:

  • where it is declared, or
  • inside the constructor of the same class

After that, TypeScript will report an error if you try to change it.

If the value should belong to the class itself rather than each instance, use static readonly:

class AppConfig {
  static readonly API_VERSION = 1;
}

This is the closest TypeScript equivalent to a class constant.

Why this matters in real programming:

  • It makes intent clear: this value should not change.
  • It prevents accidental reassignment.
  • It improves autocomplete and tooling.
  • It makes shared configuration and fixed values safer.

So the usual answer is:

  • use readonly for per-instance values
  • use static readonly for class-level constants
  • use const only outside classes for local or module-level constants

Mental Model

Think of const, readonly, and static readonly as labels for different kinds of storage:

  • const = a sealed box sitting on a table
  • readonly = a box attached to each object, sealed after setup
  • static readonly = one sealed box attached to the class itself, shared by all objects

If every object should have its own unchangeable value, use readonly.

If the value is shared and should act like a constant for the whole class, use static readonly.

A getter is like asking a receptionist for the value every time. That works, but if the value is truly just a fixed constant, static readonly is usually simpler and clearer.

Take Quiz

Syntax and Examples

Core syntax

1. readonly instance property

class User {
  readonly role: string = "member";
}

This means each User instance has a role property that cannot be reassigned after initialization.

2. readonly assigned in constructor

class User {
  readonly id: number;

  constructor(id: number) {
    this.id = id;
  }
}

This is common when the value is only known when the object is created.

3. static readonly class constant

class MathConstants {
  static readonly PI = 3.14159;
}

console.log(MathConstants.PI);

This is the most common way to represent a class constant in TypeScript.

4. Getter-based constant

class Example {
  get MY_CONSTANT(): number {
    return 10;
  }
}

This works, but it creates a property accessor rather than a simple constant field. It is more useful when the value is computed rather than fixed.

Beginner-friendly example

class GameSettings {
  static readonly MAX_PLAYERS = 4;
  readonly createdAt: Date;

  constructor() {
    this.createdAt = new Date();
  }
}

console.log(GameSettings.MAX_PLAYERS);

const settings = new GameSettings();
console.log(settings.createdAt);

What this means

  • MAX_PLAYERS belongs to the class, not to individual objects.
  • createdAt belongs to each instance.
  • Both are protected from reassignment by TypeScript.

Step by Step Execution

Consider this example:

class ServerConfig {
  static readonly DEFAULT_PORT = 3000;
  readonly host: string;

  constructor(host: string) {
    this.host = host;
  }
}

const config = new ServerConfig("localhost");

console.log(ServerConfig.DEFAULT_PORT);
console.log(config.host);

// config.host = "example.com"; // Error
// ServerConfig.DEFAULT_PORT = 8080; // Error

Step by step

  1. The class ServerConfig is declared.
  2. static readonly DEFAULT_PORT = 3000 creates a class-level value.
    • You access it as ServerConfig.DEFAULT_PORT.
    • It is shared by all uses of the class.
  3. readonly host: string declares an instance property that cannot be reassigned after setup.
  4. In the constructor, this.host = host is allowed because readonly properties may be assigned during construction.
  5. new ServerConfig("localhost") creates an object.
  6. console.log(ServerConfig.DEFAULT_PORT) prints 3000.
  7. console.log(config.host) prints localhost.
  8. Trying to reassign either property causes a TypeScript compile-time error.

This shows the two most important cases:

  • class constant: static readonly
  • object-specific fixed value: readonly

Real World Use Cases

Where this is used

Configuration values

class ApiClient {
  static readonly BASE_TIMEOUT_MS = 5000;
}

Useful for fixed settings shared across all instances.

Domain identifiers

class Order {
  readonly id: string;

  constructor(id: string) {
    this.id = id;
  }
}

An order ID should not change after creation.

Status or role values

class Account {
  readonly role = "admin";
}

Good when a property should be set once and then remain stable.

Shared limits and rules

class UploadRules {
  static readonly MAX_FILE_SIZE_MB = 20;
}

Useful in validation logic across an app.

Named constants inside a class-based service

class EmailService {
  static readonly RETRY_LIMIT = 3;
}

This keeps related constants close to the code that uses them.

Take Quiz

Real Codebase Usage

In real projects, developers usually choose one of these patterns:

1. static readonly for constants tied to a class

class CacheService {
  static readonly DEFAULT_TTL_SECONDS = 60;
}

This is common when the constant is conceptually part of the class.

2. Module-level const for shared values

const DEFAULT_TTL_SECONDS = 60;

export class CacheService {
  getTtl() {
    return DEFAULT_TTL_SECONDS;
  }
}

This is often preferred when the value does not need to be part of the class API.

3. readonly for values fixed after object creation

class Session {
  constructor(public readonly token: string) {}
}

This shorthand is common in constructors.

4. Guarding important object state

class Payment {
  constructor(public readonly amount: number) {
    if (amount <= 0) {
      throw new Error("Amount must be positive");
    }
  }
}

Developers often combine readonly with validation in constructors.

5. Using getters when the value is derived

class Rectangle {
  constructor(private width: number, private height: number) {}

  get area(): number {
    return this.width * this.height;
  }
}

A getter is usually better for calculated values, not fixed constants.

Practical advice

If you are building a real codebase, ask:

  • Is this value shared by all instances? Use static readonly.
  • Is this value unique per object but immutable after creation? Use readonly.
  • Is this value just a file-level constant? Use const outside the class.
  • Is this value computed dynamically? Use a getter.

Common Mistakes

1. Trying to use const inside a class

Broken code:

class Example {
  const VALUE = 10;
}

Why it fails:

  • const is not valid for class members.

Use this instead:

class Example {
  static readonly VALUE = 10;
}

2. Using a getter for a simple fixed value

class Example {
  get VALUE(): number {
    return 10;
  }
}

This works, but it may suggest computation or custom logic. If the value is just a constant, static readonly is usually clearer.

3. Forgetting that readonly is compile-time protection

class User {
  readonly name = "Ava";
}

TypeScript prevents reassignment in typed code, but runtime JavaScript does not automatically freeze the object.

If you need runtime immutability, you may need Object.freeze() or another runtime strategy.

4. Confusing instance and static members

Broken code:

class Config {
  static readonly PORT = 3000;
}

const c = new Config();
console.log(c.PORT); // Error

Fix:

console.log(Config.PORT);

static members belong to the class, not instances.

5. Reassigning a readonly property outside the constructor

Broken code:

class User {
  readonly id: number;

  constructor(id: number) {
    this.id = id;
  }

  updateId(newId: number) {
    this.id = newId; // Error
  }
}

Avoid this by assigning readonly values only at declaration or in the constructor.

Comparisons

FeatureUse caseCan change later?Belongs to
constLocal or module variableNoCurrent scope
readonlyInstance propertyNo, after initializationObject instance
static readonlyClass constantNo, after initializationClass
get accessorComputed or controlled property accessDepends on implementationObject instance or prototype

readonly vs getter

OptionBest forNotes
readonlyStored value that should not changeSimple and explicit
getComputed value or custom access logicGood when value is derived

static readonly vs module-level const

OptionBest forExample
static readonlyConstant that conceptually belongs to the classHttpClient.DEFAULT_TIMEOUT
module-level constConstant only needed within the file or moduleconst DEFAULT_TIMEOUT = 5000

A simple rule:

  • If the constant is part of the class API, use static readonly.
  • If it is just internal support code, a module-level const may be cleaner.
Take Quiz

Cheat Sheet

// Instance property that cannot be reassigned
class User {
  readonly id: number;

  constructor(id: number) {
    this.id = id;
  }
}

// Class constant
class Config {
  static readonly PORT = 3000;
}

// Constructor shorthand
class Session {
  constructor(public readonly token: string) {}
}

// Module-level constant
const API_URL = "https://example.com";

Rules

  • const cannot be used on class members.
  • Use readonly for instance properties.
  • Use static readonly for class-level constants.
  • A readonly property can be assigned:
    • at declaration
    • in the constructor
  • Access static members with the class name, not an instance.

Good defaults

  • Fixed per object: readonly
  • Fixed per class: static readonly
  • Fixed per file or function: const
  • Computed property: get

Important edge case

readonly is enforced by TypeScript's type system. It does not automatically make runtime JavaScript objects deeply immutable.

FAQ

Why can't I use const for a class property in TypeScript?

Because const is only for variables in a scope, such as local variables or module-level values. Class members use readonly and static readonly instead.

What is the TypeScript equivalent of a class constant?

Usually static readonly.

class App {
  static readonly VERSION = "1.0";
}

Should I use a getter for constants?

Usually no. Use a getter when the value is computed or when access needs custom logic. For a fixed value, readonly or static readonly is clearer.

What is the difference between readonly and const?

const is for variables. readonly is for properties on objects or classes.

Can a readonly property be assigned in the constructor?

Yes. That is one of the normal ways to initialize it.

Does readonly prevent changes at runtime?

Not by itself. It mainly provides compile-time protection in TypeScript.

Should I use static readonly or a top-level const?

Use static readonly if the constant is part of the class API or meaningfully belongs to the class. Use a top-level const if it is just a helper value in the module.

Take Quiz

Mini Project

Description

Build a small TypeScript class that models application settings. This project demonstrates the difference between instance-level immutable properties and class-level constants. It is useful because real applications often have fixed limits, version numbers, and IDs that should not be reassigned accidentally.

Goal

Create a ThemeConfig class with both readonly and static readonly properties, then use it safely in code.

Requirements

  • Create a class-level constant for the default theme name.
  • Create an instance readonly property for a config ID.
  • Initialize the readonly instance property through the constructor.
  • Create an instance of the class and log both values.
  • Verify through code comments which reassignment attempts should fail.
Take Quiz

Keep learning

Related questions

View more