Skip to content

siyavuyachagi/typesharp

TypeSharp

License: MIT npm version npm downloads GitHub commits GitHub last commit GitHub stars Sponsor

Generate TypeScript types from C# models with ease! TypeSharp scans your ASP.NET Core projects and automatically generates TypeScript interfaces from your C# classes and records decorated with the [TypeSharp] attribute.

Project structure: docs/project-structure

Features

  • Automatic Type Generation – Convert C# models to TypeScript interfaces
  • Record Support – Positional records, record class, record struct, and body-only records
  • Custom Attribute Targeting – Use [TypeSharp] or any custom attribute
  • Nullable Supportstring?string | null
  • Collection Handling – Supports List<T>, IEnumerable<T>, arrays and generic collections
  • Dictionary MappingDictionary<K, V>Record<K, V>
  • Generic Types – Preserves generic type definitions like Response<T>Response<T>
  • Inheritance – Preserves class and record inheritance using extends
  • Computed Properties – Expression-bodied and block getter properties are included
  • Naming Conventions – Convert property names (camel, pascal, snake, kebab)
  • Flexible Output – Single file or multiple files
  • Enum Support – Converts C# enums to TypeScript string enums
  • File Grouping – Preserves C# file organization (multiple classes per file stay together)
  • Auto Imports – Automatically generates import type statements between output files
  • Multi-Project – Scan multiple .csproj files in a single run
  • Obsolete Support[Obsolete] / [Obsolete("...")]/** @deprecated ... */ JSDoc

How TypeSharp Compares

This is not an OpenApi-based tool !

Feature TypeSharp NSwag openapi-typescript TypeGen
Direct C# parsing
Attribute targeting ! !
Non-API models
Generics preserved ! ! !
Record support
File grouping
Naming control ! !
API client generation

Also see docs/why-typesharp

Installation

npm install -D @siyavuyachagi/typesharp

Quick Start

1. Install the NuGet attributes package

In your C# project:

dotnet add package TypeSharp.Attributes

2. Decorate your C# models or DTOs

Use [TypeSharp] on classes, records, or enums:

[TypeSharp]
public class User
{
  public int Id { get; set; }
  public string? Name { get; set; }
  public string Email { get; set; }
  public List<UserRole> Roles { get; set; }
  public List<string> Permissions { get; set; }
  public DateTime CreatedAt { get; set; }
}

[TypeSharp]
public record ProductSummary(int Id, string Name, decimal Price);

[TypeSharp]
public enum UserRole
{
  Admin,
  User,
  Guest
}

[TypeSharp]
public class ApiResponse<T>
{
  public bool Success { get; set; }
  public string? Message { get; set; }
  public T Data { get; set; }
  public List<string> Errors { get; set; }
}

3. Create a configuration file

In your frontend project run the following script

# Create TypeScript config
npx typesharp init

# ----- OR -------

# Create JSON config
npx typesharp init --format json

# Create TypeScript config (default)
npx typesharp init --format ts

# Create JavaScript config
npx typesharp init --format js

This creates typesharp.config.json:

{
  "source": [
    "C:/Users/User/Desktop/MyApp/Api/Api.csproj",
    "C:/Users/User/Desktop/MyApp/Domain/Domain.csproj"
  ],
  "outputPath": "./app/types",
  "singleOutputFile": false,
  "namingConvention": "camel"
}

4. Generate TypeScript types

npx typesharp
# OR
npx typesharp generate

# ----- With options -------

# custom config
npx typesharp generate --config ./custom-config.ts
# force clean generation
npx typesharp generate --no-incremental

For more advanced usage docs/usage

Configuration

1. Configuration Options

Option Type Default Description
source string | string[] required Full path(s) to your C# .csproj file(s)
outputPath string required Where to generate TypeScript files
singleOutputFile boolean false Generate one file or multiple files (see below)
namingConvention string | { dir: string, file: string } 'camel' Property/file/dir naming: kebab, camel, pascal, snake
fileSuffix string optional Suffix appended to generated file names: user-dto.ts

2. Naming Convention

namingConvention accepts either a simple string that applies to everything, or a config object for separate control over directories and files:

// Simple — applies to both dirs and files
{ "namingConvention": "kebab" }

// Advanced — separate control
{
  "namingConvention": {
    "dir": "kebab",
    "file": "camel"
  }
}

3. Output File Behavior

TypeSharp preserves your C# file organization. Here's how it works:

C# File Structure singleOutputFile: false singleOutputFile: true
One class per file
User.cs → 1 class
user.ts (1 interface) All classes in types.ts
Multiple classes per file
UserDtos.cs → 3 classes
user-dtos.ts (3 interfaces) All classes in types.ts
Mixed structure
Various C# files
Each C# file → 1 TS file
(preserves grouping)
All classes in types.ts

4. Configuration File Formats

TypeSharp supports multiple configuration formats:

JSON (typesharp.config.json): ⭐ recommended

{
  "source": ["C:/Users/User/Desktop/MyApp/Domain/Domain.csproj"],
  "outputPath": "./src/types"
}

JavaScript (typesharp.config.js):

const config = {
  source: ['C:/Users/User/Desktop/MyApp/Domain/Domain.csproj'],
  outputPath: './src/types',
};

export default config;

TypeScript (typesharp.config.ts): ⚠️ requires special setup

TypeScript config files require one of the following approaches:

  1. Convert to JavaScript first (recommended):

    tsc typesharp.config.ts --module nodenext
  2. Run with tsx loader:

    node --loader tsx/cjs ./node_modules/@siyavuyachagi/typesharp/bin/typesharp.js
  3. Or use a .js config file instead (simplest)

If you prefer TypeScript configs, JSON format is the easiest alternative that provides type safety through JSDoc in most editors.

Usage in package.json

Add TypeSharp to your build scripts:

{
  "scripts": {
    "generate-types": "typesharp",
    "dev": "typesharp && nuxt dev",
    "build": "typesharp && nuxt build"
  }
}

Advanced Examples

1. Records

TypeSharp supports all C# record forms. Records are emitted as TypeScript interfaces identical to classes.

Positional record:

[TypeSharp]
public record ProductSummary(int Id, string Name, decimal Price, bool IsActive);
export interface ProductSummary {
  id: number;
  name: string;
  price: number;
  isActive: boolean;
}

Positional record with nullable and collection parameters:

[TypeSharp]
public record UserRecord(int Id, string? DisplayName, List<string> Tags);
export interface UserRecord {
  id: number;
  displayName: string | null;
  tags: string[];
}

Generic positional record:

[TypeSharp]
public record PagedResult<T>(IEnumerable<T> Items, int TotalCount, int PageSize);
export interface PagedResult<T> {
  items: T[];
  totalCount: number;
  pageSize: number;
}

record class and record struct:

[TypeSharp]
public record class AddressRecord(string Street, string City, string PostalCode);

[TypeSharp]
public record struct CoordRecord(double Lat, double Lng);

Body-only record (no primary constructor):

[TypeSharp]
public record PersonRecord
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

Record inheritance:

[TypeSharp]
public record BaseEvent(Guid Id, DateTime OccurredAt);

[TypeSharp]
public record UserCreatedEvent(Guid Id, DateTime OccurredAt, string Email) : BaseEvent(Id, OccurredAt);
export interface BaseEvent {
  id: string;
  occurredAt: string;
}

export interface UserCreatedEvent extends BaseEvent {
  id: string;
  occurredAt: string;
  email: string;
}

Per-parameter attribute overrides (use the property: target — required by C# for primary constructor parameters):

[TypeSharp]
public record SecureRecord(
    string Name,
    [property: TypeIgnore] string Secret,
    [property: TypeAs("Date")] DateTime CreatedAt,
    [property: Obsolete("Use Name")] string LegacyName
);
export interface SecureRecord {
  name: string;
  /** @deprecated Use Name */
  legacyName: string;
  createdAt: Date;
}

[TypeIgnore], [TypeName("x")], [TypeAs("y")], and [Obsolete] on primary constructor parameters require the property: attribute target because C# must know the attribute applies to the generated property, not the constructor parameter itself.

2. With Inheritance

C#:

[TypeSharp]
public class BaseEntity
{
    public int Id { get; set; }
    public DateTime CreatedAt { get; set; }
}

[TypeSharp]
public class Product : BaseEntity
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Generated TypeScript:

export interface BaseEntity {
  id: number;
  createdAt: string;
}

export interface Product extends BaseEntity {
  name: string;
  price: number;
}

3. Computed Properties

TypeSharp includes expression-bodied and block getter properties:

C#:

[TypeSharp]
public class PollOptionUserLinkDto
{
    public int Id { get; set; }
    public int UserId { get; set; }
    public UserDto User { get; set; }

    // Expression-bodied
    public string? Avatar => User?.Avatar;

    // Block getter
    public string UserFirstName { get { return User.FirstName; } }
}

Generated TypeScript:

export interface PollOptionUserLinkDto {
  id: number;
  userId: number;
  user: UserDto;
  avatar: string | null;
  userFirstName: string;
}

4. Dictionary Types

C#:

[TypeSharp]
public class PermissionMap
{
    public Dictionary<string, bool> Flags { get; set; }
    public IReadOnlyDictionary<string, List<string>> RolePermissions { get; set; }
}

Generated TypeScript:

export interface PermissionMap {
  flags: Record<string, boolean>;
  rolePermissions: Record<string, string[]>;
}

5. Obsolete / Deprecated Properties

C#:

[TypeSharp]
public class Employee
{
    public int Id { get; set; }
    public string Department { get; set; }

    [Obsolete("Use Department instead.")]
    public string? DepartmentName { get; set; }

    [Obsolete]
    public string? LegacyCode { get; set; }
}

Generated TypeScript:

export interface Employee {
  id: number;
  department: string;
  /** @deprecated Use Department instead. */
  departmentName: string | null;
  /** @deprecated */
  legacyCode: string | null;
}

6. Custom Type Name Override

[TypeSharp("auth_response")]
public class AuthResponse
{
    public string AccessToken { get; set; }
    public string RefreshToken { get; set; }
}
export interface auth_response {
  accessToken: string;
  refreshToken: string;
}

7. Multi-Project

{
  "source": [
    "C:/MyApp/Api/Api.csproj",
    "C:/MyApp/Domain/Domain.csproj",
    "C:/MyApp/Contracts/Contracts.csproj"
  ],
  "outputPath": "./src/types"
}

8. Single Output File

const config: TypeSharpConfig = {
  source: './Backend/Backend.csproj',
  outputPath: './src/types',
  singleOutputFile: true,
};

9. Custom Naming Conventions

const config: TypeSharpConfig = {
  source: './Backend/Backend.csproj',
  outputPath: './src/types',
  namingConvention: {
    dir: 'kebab',
    file: 'camel',
  },
};

Type Mappings

Primitives & Common Types

C# Type TypeScript Type
bool boolean
byte, decimal, double, float, int, long number
DateTime, DateOnly, TimeOnly string
Guid, string string
object any

Collections

C# Type TypeScript Type
List<T>, ICollection<T>, IEnumerable<T>, T[] T[]
Dictionary<K, V>, IDictionary<K, V>, IReadOnlyDictionary<K, V> Record<K, V>

ASP.NET / File Types

C# Type TypeScript Type
IFormFile, FormFile File
IFormFileCollection File[]
FileStream, MemoryStream, Stream Blob

Programmatic Usage

import { generate } from 'typesharp';

async function generateTypes() {
  await generate('./path/to/config.ts');
}

generateTypes();

Preview

Terminal Output

TypeSharp

Test Results

TypeSharp

Requirements

  • Node.js >= 20
  • TypeScript >= 4.5 (if using TypeScript config)

License

MIT © Siyavuya Chagi

Author

Siyavuya Chagi (CeeJay)


Built with ❤️ in South Africa 🇿🇦

About

TypeSharp CLI - Automatically generate TypeScript from C# models. Keep your frontend and backend types in perfect sync! Supports nullable types, enums, inheritance, arrays, and custom naming conventions. Perfect for ASP.NET Core + Vue/Nuxt/React projects.

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Contributors