This TypeScript error occurs when you try to use the 'new' operator on a value that doesn't have a construct signature. This typically happens with incorrect imports, type vs instance confusion, or attempting to instantiate abstract classes, interfaces, or type aliases.
The "This expression is not constructable" error (TS2351) appears when TypeScript detects that you're trying to instantiate something using the 'new' keyword, but the type you're trying to construct doesn't have a construct signature. In TypeScript's type system, a construct signature indicates that a type can be instantiated with 'new'. TypeScript differentiates between the type of a class instance and the type of the class constructor itself. When you reference a class, you're referring to its constructor function. However, if you mistakenly reference an instance of the class or import a namespace instead of a class, TypeScript cannot find the construct signature and raises this error. This error commonly occurs in these scenarios: 1. **Incorrect import syntax**: Using `import * as` imports the module namespace, not the class itself 2. **Type vs instance confusion**: Passing an instance where a constructor is expected 3. **Abstract classes**: Attempting to directly instantiate abstract classes 4. **Interfaces and type aliases**: Trying to use 'new' on interfaces or type aliases, which are compile-time only constructs 5. **Module resolution issues**: Settings like `moduleResolution: "nodenext"` can cause import problems with certain libraries
If you're using import * as, you're importing the module namespace, not the class. Change to named or default imports:
// WRONG - imports namespace object, not constructable
import * as Redis from "ioredis";
const client = new Redis(); // Error: This expression is not constructable
// CORRECT - default import
import Redis from "ioredis";
const client = new Redis();
// CORRECT - named import
import { SchemaBuilder } from "@pothos/core";
const builder = new SchemaBuilder(options);
// ALTERNATIVE - access default export through namespace
import * as RedisModule from "ioredis";
const client = new RedisModule.default(); // Works but not recommendedAfter fixing imports, recompile to verify the error is resolved:
npx tscWhen you need to pass a class constructor as a parameter or use it as a type, use typeof:
class User {
constructor(public name: string) {}
}
// WRONG - UserType is the instance type, not constructable
type UserType = User;
function createInstance(ctor: UserType) {
return new ctor(); // Error: This expression is not constructable
}
// CORRECT - Use typeof to get the constructor type
function createInstance(ctor: typeof User) {
return new ctor("Alice"); // Works!
}
// Example with generics
function instantiate<T>(ctor: new () => T): T {
return new ctor();
}
const user = instantiate(User); // T is inferred as UserThis is especially important when working with dependency injection or factory patterns.
Abstract classes cannot be instantiated directly. Create a concrete subclass instead:
// WRONG - cannot instantiate abstract class
abstract class Animal {
abstract makeSound(): void;
}
const animal = new Animal(); // Error: Cannot create instance of abstract class
// CORRECT - create concrete implementation
class Dog extends Animal {
makeSound() {
console.log("Woof!");
}
}
const dog = new Dog(); // Works!If you're getting this error with a third-party class, check its documentation to see if it's abstract or if you should use a factory function instead.
Interfaces exist only at compile time and cannot be instantiated. If you need to create instances, use a class:
// WRONG - interfaces cannot be instantiated
interface Point {
x: number;
y: number;
}
const point = new Point(); // Error: 'Point' only refers to a type
// CORRECT - use a class instead
class Point {
constructor(public x: number, public y: number) {}
}
const point = new Point(10, 20); // Works!
// ALTERNATIVE - use object literal or factory function
interface Point {
x: number;
y: number;
}
function createPoint(x: number, y: number): Point {
return { x, y };
}
const point = createPoint(10, 20);If you must keep the interface for typing, create a separate class that implements it.
Some library imports fail with moduleResolution: "nodenext". Try using "node" or "bundler" instead:
{
"compilerOptions": {
// If you're getting errors with nodenext
"moduleResolution": "node", // or "bundler"
"module": "commonjs",
"esModuleInterop": true
}
}For modern Node.js projects with ESM, you may need to ensure package.json has correct exports:
{
"type": "module",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
}
}After changing tsconfig.json, restart your TypeScript server:
- VS Code: Cmd/Ctrl + Shift + P → "TypeScript: Restart TS Server"
- Or restart your IDE
Some errors stem from outdated or incorrect type definitions. Update your packages:
# Update the library and its types
npm update package-name @types/package-name
# For specific packages mentioned in searches:
npm update ioredis @types/ioredis
npm update ajv
# Check for breaking changes in CHANGELOG
npm info package-name versionIf the library has poor type definitions, you can augment them:
// types/package-name.d.ts
declare module "package-name" {
export default class PackageName {
constructor(options?: any);
// Add other methods as needed
}
}Ensure this file is included in your tsconfig.json:
{
"include": ["src/**/*", "types/**/*"]
}### Understanding Construct Signatures
TypeScript uses construct signatures to determine if a type can be instantiated. Here's the technical definition:
// This interface has a construct signature
interface Constructable<T> {
new (...args: any[]): T;
}
// Usage in generic constraints
function createInstance<T>(ctor: Constructable<T>): T {
return new ctor();
}
class User {}
const user = createInstance(User); // Works because User has construct signature### Namespace vs Module Exports
When you use import * as Name, you get an object containing all exports:
// library.ts exports
export default class LibraryClass {}
export const helper = () => {};
// Import namespace
import * as Lib from "./library";
// Lib is now: { default: LibraryClass, helper: function }
// Lib.default is constructable, but Lib is not
new Lib.default(); // Works
new Lib(); // Error: not constructable### ESM vs CommonJS Interop
The esModuleInterop flag affects how default exports work:
// Without esModuleInterop
{
"compilerOptions": {
"esModuleInterop": false
}
}// Might require namespace-style import
import * as Redis from "ioredis";
const client = new Redis.default();// With esModuleInterop (recommended)
{
"compilerOptions": {
"esModuleInterop": true
}
}// Allows cleaner default imports
import Redis from "ioredis";
const client = new Redis();### Generic Factory Pattern
For advanced use cases, you might need to pass constructors with specific signatures:
// Define a constructor type with specific parameters
type Constructor<T, Args extends any[]> = new (...args: Args) => T;
// Factory function that requires specific constructor signature
function createWithDeps<T>(
ctor: Constructor<T, [string, number]>,
name: string,
age: number
): T {
return new ctor(name, age);
}
class Person {
constructor(public name: string, public age: number) {}
}
const person = createWithDeps(Person, "Alice", 30); // Type-safe!### Troubleshooting with Compiler Flags
Debug what TypeScript sees by using compiler diagnostics:
# Show how TypeScript resolves modules
npx tsc --traceResolution | grep "module-name"
# Show type information for a specific file
npx tsc --noEmit --pretty --listFiles | grep "filename"### Working with Third-Party Libraries
Some libraries have known issues with TypeScript module resolution. Common workarounds:
AJV (JSON Schema Validator):
// Instead of:
import Ajv from "ajv";
const ajv = new Ajv();
// Use:
import Ajv from "ajv/dist/2020"; // or specific version path
const ajv = new Ajv();IORedis:
// If default import fails, try:
import { Redis } from "ioredis";
const client = new Redis();Always check the library's GitHub issues for TypeScript-specific problems and solutions.
Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript
Type narrowing from typeof check produces 'never'
How to fix "Type narrowing produces never" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript