How to fix Parameter decorator cannot use 'readonly' modifier in TypeScript

TypeScriptINTERMEDIATEMEDIUMUpdated Dec 11, 2025· 86 views

This TypeScript error occurs when you try to use the 'readonly' modifier on a parameter that has a decorator. TypeScript restricts parameter decorators from being combined with the 'readonly' modifier. The fix involves removing the 'readonly' modifier from decorated parameters or restructuring your code to avoid this combination.

Quick fix

Fixes Parameter decorator cannot use 'readonly' modifier

// WRONG - readonly with parameter decorator
class UserService {
  constructor(
    @Inject(HttpService) readonly http: HttpService,  // Error
    @Inject(ConfigService) readonly config: ConfigService  // Error
  ) {}
}

// CORRECT - remove readonly from decorated parameters
class UserService {
  constructor(
    @Inject(HttpService) http: HttpService,  // Fixed
    @Inject(ConfigService) config: ConfigService  // Fixed
  ) {}
}
  1. Remove the 'readonly' modifier from decorated parameters
  2. Use property assignment instead of constructor parameters
  3. Use private/protected instead of readonly for dependency injection
  4. Refactor to use property decorators instead of parameter decorators
typescript — error
// WRONG - readonly with parameter decorator
class UserService {
constructor(
@Inject(HttpService) readonly http: HttpService, // Error
@Inject(ConfigService) readonly config: ConfigService // Error
) {}
}
// CORRECT - remove readonly from decorated parameters
class UserService {
constructor(
@Inject(HttpService) http: HttpService, // Fixed
@Inject(ConfigService) config: ConfigService // Fixed
) {}
}
Parameter decorator cannot use 'readonly' modifier

What this error means

The "Parameter decorator cannot use 'readonly' modifier" error appears when you attempt to combine a parameter decorator with the 'readonly' modifier in TypeScript. This is a language restriction that prevents certain combinations of modifiers and decorators on method or constructor parameters. Parameter decorators are used to annotate or modify parameters in methods and constructors, often for dependency injection frameworks, validation, or metadata attachment. The 'readonly' modifier makes a property or parameter immutable after initialization. TypeScript disallows this combination because: 1. **Semantic conflict**: Parameter decorators often imply runtime modification or inspection of the parameter, while 'readonly' suggests immutability 2. **Implementation complexity**: Supporting both features together would create complex edge cases in the TypeScript compiler 3. **Framework compatibility**: Many dependency injection frameworks that use parameter decorators expect to be able to modify or inject values into parameters This error commonly occurs in: - **Dependency injection frameworks** like Angular, NestJS, or InversifyJS - **Validation libraries** that use parameter decorators for input validation - **ORM frameworks** that decorate constructor parameters for entity instantiation - **Custom decorators** for logging, caching, or other cross-cutting concerns

Typical symptoms

  • Compilation fails with 'Parameter decorator cannot use readonly modifier' error message
  • Error occurs when using '@decorator readonly param: Type' syntax
  • IDE shows red underlines beneath the 'readonly' keyword when used with parameter decorators
  • Code works without the 'readonly' modifier but fails when adding it to decorated parameters
  • Error appears in constructor parameters with both decorators and 'readonly' modifier
  • Build process fails in frameworks like Angular, NestJS, or TypeORM when using this pattern

Common causes

  1. Using '@Inject() readonly service: Service' in constructor parameters
  2. Applying validation decorators like '@IsEmail() readonly email: string' in method parameters
  3. Combining ORM decorators with 'readonly' in entity constructors
  4. Using custom decorators for logging or caching on 'readonly' parameters
  5. Following patterns from other languages that allow this combination (like C#)
  6. Attempting to enforce immutability on dependency-injected parameters
  7. Copying code examples that work in JavaScript but not TypeScript due to this restriction

How to fix "Parameter decorator cannot use 'readonly' modifier"

1Remove the 'readonly' modifier from decorated parameters

The simplest fix is to remove the 'readonly' modifier from parameters that have decorators:

typescript
// WRONG - readonly with parameter decorator
class UserService {
  constructor(
    @Inject(HttpService) readonly http: HttpService,  // Error
    @Inject(ConfigService) readonly config: ConfigService  // Error
  ) {}
}

// CORRECT - remove readonly from decorated parameters
class UserService {
  constructor(
    @Inject(HttpService) http: HttpService,  // Fixed
    @Inject(ConfigService) config: ConfigService  // Fixed
  ) {}
}

For method parameters:

typescript
// WRONG
class AuthController {
  login(
    @Body() readonly credentials: LoginDto,  // Error
    @Headers('Authorization') readonly authHeader: string  // Error
  ) {
    // ...
  }
}

// CORRECT
class AuthController {
  login(
    @Body() credentials: LoginDto,  // Fixed
    @Headers('Authorization') authHeader: string  // Fixed
  ) {
    // ...
  }
}
2Use property assignment instead of constructor parameters

If you need readonly properties with dependency injection, assign them in the constructor body:

typescript
// WRONG - readonly in decorated constructor parameters
class UserService {
  constructor(
    @Inject(HttpService) readonly http: HttpService,  // Error
    @Inject(Logger) readonly logger: Logger  // Error
  ) {}
}

// CORRECT - assign readonly properties in constructor body
class UserService {
  readonly http: HttpService;
  readonly logger: Logger;

  constructor(
    @Inject(HttpService) http: HttpService,
    @Inject(Logger) logger: Logger
  ) {
    this.http = http;
    this.logger = logger;
  }
}

This pattern is common in Angular and NestJS services:

typescript
// Angular/NestJS service pattern
@Injectable()
export class UserService {
  readonly http: HttpService;
  readonly config: ConfigService;

  constructor(
    http: HttpService,
    config: ConfigService
  ) {
    this.http = http;
    this.config = config;
  }
}
3Use private/protected instead of readonly for dependency injection

If you need access control but not immutability, use private or protected modifiers:

typescript
// WRONG - readonly with decorator
class OrderService {
  constructor(
    @Inject(PaymentService) readonly payment: PaymentService,  // Error
    @Inject(EmailService) readonly email: EmailService  // Error
  ) {}
}

// CORRECT - use private/protected
class OrderService {
  constructor(
    @Inject(PaymentService) private payment: PaymentService,  // Fixed
    @Inject(EmailService) protected email: EmailService  // Fixed
  ) {}

  processOrder() {
    // Can still use this.payment and this.email
    this.payment.charge();
    this.email.sendReceipt();
  }
}

Note: 'private' and 'protected' don't prevent reassignment, they only control visibility:

typescript
class Example {
  constructor(
    @Inject(Service) private service: Service
  ) {}

  // Can still reassign (unless you add additional checks)
  changeService(newService: Service) {
    this.service = newService;  // Allowed with private, not with readonly
  }
}
4Refactor to use property decorators instead of parameter decorators

For validation or metadata use cases, consider using property decorators:

typescript
// WRONG - parameter decorator with readonly
class UserDto {
  create(
    @IsEmail() readonly email: string,  // Error
    @MinLength(6) readonly password: string  // Error
  ) {}
}

// CORRECT - use property decorators on a class
class UserDto {
  @IsEmail()
  email: string;

  @MinLength(6)
  password: string;

  constructor(email: string, password: string) {
    this.email = email;
    this.password = password;
  }
}

// Usage
const user = new UserDto('test@example.com', 'password123');
validate(user);  // Validation framework checks property decorators

For class-validator or similar libraries:

typescript
import { IsEmail, MinLength } from 'class-validator';

class CreateUserDto {
  @IsEmail()
  email: string;

  @MinLength(6)
  password: string;

  // Optional: make properties readonly at class level
  constructor(email: string, password: string) {
    this.email = email;
    this.password = password;
    Object.freeze(this);  // Makes the entire instance immutable
  }
}
5Use interfaces or types for readonly constraints

If you need compile-time immutability guarantees, use TypeScript's type system:

typescript
// Define a readonly interface
interface ReadonlyConfig {
  readonly apiUrl: string;
  readonly timeout: number;
  readonly retries: number;
}

class ApiClient {
  constructor(
    @Inject(ConfigService) config: ConfigService  // No readonly needed
  ) {
    // Create readonly object from config
    const readonlyConfig: ReadonlyConfig = {
      apiUrl: config.get('API_URL'),
      timeout: config.get('TIMEOUT'),
      retries: config.get('RETRIES'),
    };

    // Use readonlyConfig which is immutable at compile time
  }
}

Using mapped types for readonly properties:

typescript
type Readonly<T> = { readonly [P in keyof T]: T[P] };

interface Config {
  apiUrl: string;
  timeout: number;
}

class Service {
  private readonly config: Readonly<Config>;

  constructor(@Inject(ConfigService) configService: ConfigService) {
    this.config = {
      apiUrl: configService.apiUrl,
      timeout: configService.timeout,
    };
  }

  // this.config is readonly at compile time
}
6Check compiler configuration and TypeScript version

Ensure you're using a compatible TypeScript version and configuration:

json
// tsconfig.json - ensure proper settings
{
  "compilerOptions": {
    "target": "ES2022",
    "experimentalDecorators": true,  // Required for legacy decorators
    "emitDecoratorMetadata": true,   // Often needed with DI frameworks

    // For TypeScript 5.0+ with stage 3 decorators:
    // "experimentalDecorators": false,  // Not needed for stage 3
    // "useDefineForClassFields": true,

    "strict": true
  }
}

TypeScript version compatibility:

- TypeScript 4.x and earlier: Uses legacy decorators with experimentalDecorators: true
- TypeScript 5.0+: Supports stage 3 decorators (no flag needed) but may have different restrictions

Check your framework's requirements:

- Angular: Requires experimentalDecorators: true (as of Angular 16)
- NestJS: Works with both legacy and stage 3 decorators
- TypeORM: Primarily uses legacy decorators
- class-validator: Works with both, but check version compatibility

If the error persists, try updating TypeScript and related packages:

bash
# Update TypeScript
npm install typescript@latest

# Update framework packages
npm install @angular/core@latest  # or @nestjs/common, typeorm, etc.

Advanced notes

### Understanding the Technical Restriction

The restriction against combining 'readonly' with parameter decorators stems from how TypeScript transforms decorators and how parameters are represented in JavaScript.

Parameter decorators in JavaScript output:
When TypeScript compiles parameter decorators, they become function calls that receive:
1. The class prototype or constructor
2. The method name
3. The parameter index

The 'readonly' modifier affects how the parameter is represented in the emitted JavaScript, potentially conflicting with the decorator's ability to inspect or modify the parameter.

Example compilation:

typescript
// TypeScript source
class Example {
  method(@Decorator() param: string) {}
}

// JavaScript output (simplified)
__decorate([
  __param(0, Decorator)
], Example.prototype, "method", null);

The 'readonly' modifier doesn't exist in JavaScript, so TypeScript must track it differently, creating potential conflicts with decorator transformations.

### Framework-Specific Workarounds

Angular/NestJS Dependency Injection:

typescript
// Instead of readonly in constructor
@Injectable()
export class UserService {
  private readonly userRepo: UserRepository;

  constructor(userRepo: UserRepository) {
    this.userRepo = userRepo;
  }
}

// Angular's inject() function (alternative approach)
@Injectable()
export class UserService {
  readonly userRepo = inject(UserRepository);
}

Validation Libraries (class-validator):

typescript
// Use property decorators instead of parameter decorators
class CreateUserDto {
  @IsEmail()
  @IsNotEmpty()
  readonly email: string;

  @MinLength(6)
  readonly password: string;

  constructor(email: string, password: string) {
    this.email = email;
    this.password = password;
  }
}

TypeORM Entities:

typescript
// Entity with readonly properties
@Entity()
export class User {
  @PrimaryGeneratedColumn()
  readonly id: number;

  @Column()
  readonly email: string;

  // Constructor for dependency injection (if needed)
  constructor(email: string) {
    this.email = email;
  }
}

### Stage 3 Decorators (TypeScript 5.0+)

TypeScript 5.0 introduced ECMAScript stage 3 decorators with a different API. The restriction against 'readonly' with parameter decorators still applies, but the error message and behavior might differ.

Stage 3 parameter decorator signature:

typescript
function LogParameter(target: undefined, context: DecoratorContext) {
  // context.kind === 'parameter'
  // context.name === parameter name or index
}

Migration considerations:
- Stage 3 decorators are not backward compatible with legacy decorators
- Many frameworks are gradually adding stage 3 support
- Check your framework's documentation before upgrading TypeScript

### Alternative Patterns

Factory functions for immutable instances:

typescript
function createUserService(http: HttpService, config: ConfigService) {
  return Object.freeze({
    http,  // References are readonly
    config,
    getUser: (id: string) => http.get(`/users/${id}`)
  });
}

// Usage
const userService = createUserService(httpService, configService);
// userService is immutable

Builder pattern with finalize method:

typescript
class ServiceBuilder {
  private http?: HttpService;
  private config?: ConfigService;

  withHttp(http: HttpService) {
    this.http = http;
    return this;
  }

  withConfig(config: ConfigService) {
    this.config = config;
    return this;
  }

  build(): ReadonlyService {
    if (!this.http || !this.config) {
      throw new Error('Missing dependencies');
    }

    return Object.freeze(new ReadonlyService(this.http, this.config));
  }
}

class ReadonlyService {
  constructor(
    public readonly http: HttpService,
    public readonly config: ConfigService
  ) {}
}

### Performance Considerations

Removing 'readonly' from parameters doesn't significantly affect runtime performance since 'readonly' is a compile-time check. However, alternative patterns like factory functions or builder patterns may have minor overhead.

The primary consideration should be code clarity and maintainability rather than micro-optimizations.

### Testing Implications

When testing classes that previously used 'readonly' parameters:
1. Mock dependencies can still be injected normally
2. The absence of 'readonly' doesn't affect testability
3. Consider adding runtime immutability checks if needed for tests

typescript
// Test example
describe('UserService', () => {
  let mockHttp: jest.Mocked<HttpService>;
  let service: UserService;

  beforeEach(() => {
    mockHttp = { get: jest.fn() } as any;
    service = new UserService(mockHttp, mockConfig);
  });

  test('should not allow http reassignment', () => {
    // Even without 'readonly', you can add runtime check
    expect(() => {
      (service as any).http = {} as HttpService;
    }).toThrow(); // Or check that it doesn't actually reassign
  });
});

Frequently asked questions

What does "Parameter decorator cannot use 'readonly' modifier" mean?
The "Parameter decorator cannot use 'readonly' modifier" error appears when you attempt to combine a parameter decorator with the 'readonly' modifier in TypeScript. This is a language restriction that prevents certain combinations of modifiers and decorators on method or constructor parameters. Parameter decorators are used to annotate or modify parameters in methods and constructors, often for dependency injection frameworks, validation, or metadata attachment.
How do I fix "Parameter decorator cannot use 'readonly' modifier"?
This TypeScript error occurs when you try to use the 'readonly' modifier on a parameter that has a decorator. TypeScript restricts parameter decorators from being combined with the 'readonly' modifier. The fix involves removing the 'readonly' modifier from decorated parameters or restructuring your code to avoid this combination.
What causes "Parameter decorator cannot use 'readonly' modifier"?
Using '@Inject() readonly service: Service' in constructor parameters Applying validation decorators like '@IsEmail() readonly email: string' in method parameters Combining ORM decorators with 'readonly' in entity constructors
Is the "Parameter decorator cannot use 'readonly' modifier" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading