This error, TS2369: A parameter property is only allowed in a constructor parameter list., means that you’re trying to use the shorthand syntax for declaring class properties directly in the constructor’s parameter list, but you’ve accidentally applied it to a method parameter instead.

Here are the common causes and how to fix them:

  1. Incorrectly applying parameter property syntax to a method: This is the most frequent culprit. You’ve likely written something like this:

    class MyClass {
        constructor(private myValue: string) {}
    
        someMethod(public anotherValue: number) { // TS2369 error here
            this.anotherValue = anotherValue;
        }
    }
    

    Diagnosis: Look for public, private, protected, or readonly modifiers on parameters of methods other than the constructor.

    Fix: Remove the access modifier from the method parameter. The parameter will still be available within the method’s scope, and if you need to store it on the class, you’ll do so explicitly.

    class MyClass {
        constructor(private myValue: string) {}
    
        someMethod(anotherValue: number) { // Fixed
            this.anotherValue = anotherValue; // Explicitly assign if needed
        }
    }
    

    Why it works: TypeScript’s parameter property syntax is a syntactic sugar specifically for the constructor to automatically declare and initialize class members. It’s not designed to be a general-purpose shorthand for methods.

  2. Mistakenly copying constructor syntax to another method: Sometimes, developers might copy and paste the constructor signature and forget to adapt it for a regular method.

    Diagnosis: Visually scan your class for method signatures that look suspiciously like a constructor signature, especially if they have access modifiers on their parameters.

    Fix: Treat the method parameter as a regular parameter. If you intend for this parameter to be a class member, declare and assign it explicitly within the method body.

    class DataProcessor {
        private processedData: string[] = [];
    
        constructor(initialData: string[]) {
            this.processedData = initialData;
        }
    
        // Incorrect:
        // processItem(public item: string) { // TS2369
        //     this.processedData.push(item);
        // }
    
        // Corrected:
        processItem(item: string) { // Fixed
            this.processedData.push(item);
        }
    }
    

    Why it works: This adheres to the standard TypeScript syntax for methods, where parameters are just values passed into the function, and class members are managed separately.

  3. Accidental readonly modifier on a method parameter: Even readonly is a parameter property modifier and is only allowed in the constructor.

    Diagnosis: Search for readonly on any parameter that isn’t in the constructor.

    Fix: Remove readonly from the method parameter. If you need to ensure the parameter isn’t modified within the method, you can use a local variable or enforce immutability through other means. If you intended it to be a read-only class property, it should be declared in the constructor as readonly.

    class ConfigLoader {
        private settings: Record<string, any>;
    
        constructor(initialConfig: Record<string, any>) {
            this.settings = initialConfig;
        }
    
        // Incorrect:
        // updateSetting(readonly key: string, value: any) { // TS2369
        //     if (this.settings[key] !== undefined) {
        //         this.settings[key] = value;
        //     }
        // }
    
        // Corrected:
        updateSetting(key: string, value: any) { // Fixed
            if (this.settings[key] !== undefined) {
                this.settings[key] = value;
            }
        }
    }
    

    Why it works: Similar to other access modifiers, readonly on a method parameter is not a valid construct in TypeScript. The readonly modifier for parameter properties is exclusively for constructors to create immutable class members.

  4. Using protected or private on a method parameter: These are also parameter property modifiers.

    Diagnosis: Look for private or protected on parameters of any method that is not the constructor.

    Fix: Remove the access modifier. These modifiers are only for constructor parameters that should become private or protected class members.

    class Logger {
        private logEntries: string[] = [];
    
        constructor(private prefix: string) {}
    
        // Incorrect:
        // writeLog(private message: string) { // TS2369
        //     this.logEntries.push(`${this.prefix}: ${message}`);
        // }
    
        // Corrected:
        writeLog(message: string) { // Fixed
            this.logEntries.push(`${this.prefix}: ${message}`);
        }
    }
    

    Why it works: private and protected on method parameters are syntactically invalid because they are reserved for constructor parameter properties, which are designed to create class members.

  5. Typo in the method name, accidentally looking like a constructor: Less common, but if a method name is very similar to constructor (e.g., constuctor, contructor), and it has parameter properties, the compiler might get confused or flag it. However, the primary error TS2369 specifically points to parameter properties outside the constructor. This scenario is more about accidentally declaring a parameter property in the wrong place.

    Diagnosis: Review method names for any resemblance to constructor. If a method has parameter properties and a name that could be mistaken for constructor by a human, it’s worth checking.

    Fix: Rename the method to something distinct and remove any parameter property modifiers.

    class Service {
        private serviceId: string;
    
        constructor(id: string) {
            this.serviceId = id;
        }
    
        // Imagine a typo like 'construcor' instead of 'processRequest'
        // processRequest(private data: any) { // TS2369
        //     console.log(`Processing on ${this.serviceId}:`, data);
        // }
    
        // Corrected (assuming 'processRequest' was intended)
        processRequest(data: any) { // Fixed
            console.log(`Processing on ${this.serviceId}:`, data);
        }
    }
    

    Why it works: Ensures method names are clear and distinct, and that parameter properties are only used where syntactically allowed (the constructor).

The next error you’ll likely encounter if you’ve fixed this and are still doing something unusual is a type mismatch if you’ve removed a parameter property without correctly assigning it to a class member elsewhere, or a TS2551: Property 'X' does not exist on type 'Y' if you try to access a property that was meant to be a parameter property but was never declared or assigned.

Want structured learning?

Take the full Typescript course →