How to fix Private field '#x' must be declared in an enclosing class in TypeScript

TypeScriptINTERMEDIATEMEDIUMUpdated Dec 11, 2025· 219 views

This TypeScript error occurs when you try to use JavaScript's private field syntax (#field) outside of a class declaration. Private fields with the # prefix must be declared within a class body and cannot be used in standalone functions, global scope, or outside their defining class. The error typically appears when migrating JavaScript code to TypeScript or when mixing class-based and functional programming patterns.

Quick fix

Fixes Private field '#x' must be declared in an enclosing class

// BEFORE: Error - private field outside class
class User {
  #password: string;

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

// Standalone function causing error
const validatePassword = (user: User) => {
  return user.#password.length > 8; // Error: Private field '#password' must be declared...
};

// AFTER: Move function inside class as method
class User {
  #password: string;

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

  // Now a class method - can access #password
  validatePassword(): boolean {
    return this.#password.length > 8; // OK
  }
}

const user = new User("secret123");
console.log(user.validatePassword()); // OK: true
  1. Move the function inside the class as a method
  2. Use bind() to preserve class context
  3. Convert arrow functions to class methods
  4. Avoid destructuring private fields
typescript — error
// BEFORE: Error - private field outside class
class User {
#password: string;
constructor(password: string) {
this.#password = password;
}
}
// Standalone function causing error
const validatePassword = (user: User) => {
return user.#password.length > 8; // Error: Private field '#password' must be declared...
};
// AFTER: Move function inside class as method
class User {
#password: string;
constructor(password: string) {
this.#password = password;
}
// Now a class method - can access #password
validatePassword(): boolean {
return this.#password.length > 8; // OK
}
}
const user = new User("secret123");
console.log(user.validatePassword()); // OK: true
Private field '#x' must be declared in an enclosing class

What this error means

TypeScript enforces JavaScript's ECMAScript private field syntax rules strictly. When you see the error "Private field '#x' must be declared in an enclosing class," it means you're attempting to use a private field identifier (starting with #) in a context where private fields are not allowed. Private fields in JavaScript/TypeScript are a language feature introduced in ES2022 that provide true runtime privacy for class properties. Unlike TypeScript's `private` keyword (which is compile-time only), the # syntax creates fields that are inaccessible outside their class even after compilation. The key restriction is that #privateField syntax can only be used: 1. Within a class declaration (to declare the field) 2. Within instance methods of that same class (to access the field) 3. Within static methods of that class (for static private fields) You cannot use #privateField syntax: - Outside any class (global scope, module scope) - In standalone functions - In arrow functions defined outside the class - In object literals - In interfaces or type definitions This error often appears when: - You have a function that tries to access a private field from outside its class - You're using destructuring with private fields - You have callback functions that reference private fields - You're trying to export or import private fields directly

Typical symptoms

  • TypeScript compiler error: 'Private field '#x' must be declared in an enclosing class'
  • Red squiggly underline under #privateField references in your IDE
  • Code works in plain JavaScript but fails TypeScript compilation
  • Error occurs in arrow functions, callbacks, or standalone functions
  • Private field access in React event handlers or useEffect callbacks
  • Destructuring assignment trying to extract private fields

Common causes

  1. Arrow function defined outside class trying to access private fields
  2. Callback function passed as argument referencing private fields
  3. Event handler in React/Vue trying to access component's private fields
  4. Destructuring assignment attempting to extract private fields from class instance
  5. Module-level function trying to use private fields from imported class
  6. Mixing #privateField syntax with TypeScript's 'private' keyword incorrectly
  7. Trying to export/import private fields directly between modules
  8. Using private fields in object methods that aren't class methods

How to fix "Private field '#x' must be declared in an enclosing class"

1Move the function inside the class as a method

If you have a function that needs to access private fields, define it as a class method instead of a standalone function:

typescript
// BEFORE: Error - private field outside class
class User {
  #password: string;

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

// Standalone function causing error
const validatePassword = (user: User) => {
  return user.#password.length > 8; // Error: Private field '#password' must be declared...
};

// AFTER: Move function inside class as method
class User {
  #password: string;

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

  // Now a class method - can access #password
  validatePassword(): boolean {
    return this.#password.length > 8; // OK
  }
}

const user = new User("secret123");
console.log(user.validatePassword()); // OK: true

Class methods have access to private fields through the this context.

2Use bind() to preserve class context

If you need to pass a method as a callback, use bind() to preserve the this context:

typescript
class Timer {
  #startTime: number;

  constructor() {
    this.#startTime = Date.now();
  }

  // Class method that accesses private field
  getElapsed(): number {
    return Date.now() - this.#startTime;
  }
}

const timer = new Timer();

// BEFORE: Error - loses 'this' context
setTimeout(timer.getElapsed, 1000); // Error: Private field '#startTime'...

// AFTER: Bind the method to preserve context
setTimeout(timer.getElapsed.bind(timer), 1000); // OK

// Or use arrow function that captures 'this'
setTimeout(() => timer.getElapsed(), 1000); // Also OK

Binding ensures the method executes with the correct this context, allowing private field access.

3Convert arrow functions to class methods

If you're using arrow functions as properties (common in React), define them as class methods or use property initializers:

typescript
// BEFORE: Error in React component
class MyComponent extends React.Component {
  #state = { count: 0 };

  // Arrow function property - still has access issues
  handleClick = () => {
    this.#state.count++; // May cause context issues
    this.forceUpdate();
  };

  render() {
    return <button onClick={this.handleClick}>Click</button>;
  }
}

// AFTER: Use class method with bind in constructor
class MyComponent extends React.Component {
  #state = { count: 0 };

  constructor(props: any) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

  // Regular class method
  handleClick() {
    this.#state.count++; // OK - proper 'this' context
    this.forceUpdate();
  }

  render() {
    return <button onClick={this.handleClick}>Click</button>;
  }
}

// OR: Use TypeScript's property initializer syntax
class MyComponent extends React.Component {
  #state = { count: 0 };

  // This works correctly with private fields
  handleClick = (): void => {
    this.#state.count++; // OK - arrow function captures 'this'
    this.forceUpdate();
  };

  render() {
    return <button onClick={this.handleClick}>Click</button>;
  }
}

Arrow function properties capture this at definition time, which usually works with private fields.

4Avoid destructuring private fields

Private fields cannot be destructured. Use getter methods instead:

typescript
class Config {
  #apiKey: string;
  #endpoint: string;

  constructor(apiKey: string, endpoint: string) {
    this.#apiKey = apiKey;
    this.#endpoint = endpoint;
  }

  // Public getters for controlled access
  getApiKey(): string {
    return this.#apiKey;
  }

  getEndpoint(): string {
    return this.#endpoint;
  }
}

const config = new Config("key-123", "https://api.example.com");

// BEFORE: Error - cannot destructure private fields
const { #apiKey, #endpoint } = config; // Error

// AFTER: Use getter methods
const apiKey = config.getApiKey(); // OK
const endpoint = config.getEndpoint(); // OK

// OR: Create a public interface for the data you need
interface ConfigData {
  apiKey: string;
  endpoint: string;
}

class Config {
  #apiKey: string;
  #endpoint: string;

  constructor(apiKey: string, endpoint: string) {
    this.#apiKey = apiKey;
    this.#endpoint = endpoint;
  }

  // Method that returns public data structure
  getConfigData(): ConfigData {
    return {
      apiKey: this.#apiKey,
      endpoint: this.#endpoint,
    };
  }
}

const config = new Config("key-123", "https://api.example.com");
const { apiKey, endpoint } = config.getConfigData(); // OK

Private fields are fundamentally incompatible with destructuring syntax.

5Use TypeScript's 'private' keyword instead of #

If you don't need runtime privacy, consider using TypeScript's private keyword which has fewer restrictions:

typescript
// Using JavaScript private fields (#) - strict rules
class SecureClass {
  #secret: string;

  constructor(secret: string) {
    this.#secret = secret;
  }

  // Can only access #secret in class methods
  getSecret(): string {
    return this.#secret;
  }
}

// Using TypeScript 'private' keyword - more flexible
class FlexibleClass {
  private secret: string;

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

  // Can use in more contexts (though still compile-time only)
  createLogger() {
    // This works with 'private' but not with #
    return () => console.log(this.secret);
  }
}

const flexible = new FlexibleClass("hidden");
const logger = flexible.createLogger();
logger(); // OK: "hidden"

// Key differences:
// #privateField: Runtime privacy, strict access rules
// private field: Compile-time only, more flexible usage
// Choose based on your needs

TypeScript's private keyword is removed during compilation, allowing more flexible patterns.

6Refactor to avoid private field access in problematic contexts

Sometimes the best solution is to redesign your code to avoid needing private field access in difficult contexts:

typescript
// BEFORE: Complex callback chain with private field access
class DataProcessor {
  #data: number[];

  constructor(data: number[]) {
    this.#data = data;
  }

  processWithCallback(callback: (value: number) => void) {
    // Problem: callback tries to access #data
    this.#data.forEach(value => {
      // Can't use #data here if callback needs it
      callback(value);
    });
  }
}

// AFTER: Pass needed data as parameters
class DataProcessor {
  #data: number[];

  constructor(data: number[]) {
    this.#data = data;
  }

  processWithCallback(callback: (value: number, metadata: string) => void) {
    const metadata = this.#getMetadata(); // Compute inside class
    this.#data.forEach(value => {
      callback(value, metadata); // Pass data as parameter
    });
  }

  #getMetadata(): string {
    return `Processing ${this.#data.length} items`;
  }
}

// OR: Use a different architecture
class DataProcessor {
  #data: number[];

  constructor(data: number[]) {
    this.#data = data;
  }

  // Return processed data instead of using callbacks
  process(): ProcessedData[] {
    return this.#data.map(value => ({
      value,
      metadata: this.#getMetadata(),
      timestamp: Date.now(),
    }));
  }

  #getMetadata(): string {
    return `Processing ${this.#data.length} items`;
  }
}

By designing APIs that don't require external code to access private fields directly, you avoid these errors entirely.

Advanced notes

### JavaScript Private Fields vs TypeScript Private Keyword

JavaScript Private Fields (#):
- Introduced in ES2022 (ES2022 private fields)
- Provide true runtime privacy
- Cannot be accessed outside class even with type casting
- Must be declared in class body
- Cannot be accessed via this["#field"] or any dynamic access
- Work with inheritance (subclasses get their own separate private fields)
- Can have static private fields: static #count = 0;

TypeScript Private Keyword:
- Compile-time only feature
- Removed during compilation to JavaScript
- Can be bypassed with type casting: (instance as any).privateField
- More flexible usage patterns
- Compatible with older JavaScript versions

### When to Use Each

Use # private fields when:
- You need true runtime privacy
- Security is critical (API keys, tokens, passwords)
- You're targeting modern environments (ES2022+)
- You want to prevent accidental access even in JavaScript

Use TypeScript `private` when:
- You need compatibility with older JavaScript
- You're using patterns that require more flexibility
- Runtime privacy isn't critical
- You're writing library code consumed by JavaScript projects

### Common Pitfalls with # Private Fields

1. Inheritance issues: Each class in hierarchy gets its own private field, even with same name:

typescript
class Parent {
  #value = "parent";

  getValue() {
    return this.#value;
  }
}

class Child extends Parent {
  #value = "child"; // Different field, not overriding
}

const child = new Child();
console.log(child.getValue()); // "parent" not "child"

2. No dynamic access: Cannot use bracket notation:

typescript
class Example {
  #field = "value";

  getField(key: string) {
    return this[key]; // Error with #field
    return this.#field; // Only way
  }
}

3. JSON serialization: Private fields are not included in JSON.stringify():

typescript
class User {
  #password = "secret";
  public name = "John";

  toJSON() {
    return { name: this.name }; // Must manually include public fields
  }
}

### TypeScript Configuration

Ensure your tsconfig.json supports private fields:

json
{
  "compilerOptions": {
    "target": "ES2022", // or higher
    "lib": ["ES2022", "DOM"],
    // OR for older targets with polyfills:
    "target": "ES2015",
    "lib": ["ES2022", "DOM"]
  }
}

For older targets, TypeScript will downlevel private fields to WeakMaps, which may have performance implications.

### Debugging Private Fields

Since private fields are truly private, debugging can be challenging:
- Use Chrome DevTools: Private fields appear with # prefix in console
- Use getter methods for inspection during development
- Consider temporary public properties for debugging
- Use console.log({ ...instance }) - private fields won't appear

### Performance Considerations

- # private fields are generally fast in modern engines
- When downleveled to ES2015, they use WeakMaps which have some overhead
- Accessing private fields is slightly slower than public properties
- For performance-critical code, benchmark both approaches

Frequently asked questions

What does "Private field '#x' must be declared in an enclosing class" mean?
TypeScript enforces JavaScript's ECMAScript private field syntax rules strictly. When you see the error "Private field '#x' must be declared in an enclosing class," it means you're attempting to use a private field identifier (starting with #) in a context where private fields are not allowed. Private fields in JavaScript/TypeScript are a language feature introduced in ES2022 that provide true runtime privacy for class properties.
How do I fix "Private field '#x' must be declared in an enclosing class"?
This TypeScript error occurs when you try to use JavaScript's private field syntax (#field) outside of a class declaration. Private fields with the # prefix must be declared within a class body and cannot be used in standalone functions, global scope, or outside their defining class. The error typically appears when migrating JavaScript code to TypeScript or when mixing class-based and functional programming patterns.
What causes "Private field '#x' must be declared in an enclosing class"?
Arrow function defined outside class trying to access private fields Callback function passed as argument referencing private fields Event handler in React/Vue trying to access component's private fields
Is the "Private field '#x' must be declared in an enclosing class" error serious?
It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.

Official resources & further reading