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
- 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 Support β
string?βstring | null - Collection Handling β Supports
List<T>,IEnumerable<T>, arrays and generic collections - Dictionary Mapping β
Dictionary<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 typestatements between output files - Multi-Project β Scan multiple
.csprojfiles in a single run - Obsolete Support β
[Obsolete]/[Obsolete("...")]β/** @deprecated ... */JSDoc
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
npm install -D @siyavuyachagi/typesharpIn your C# project:
dotnet add package TypeSharp.AttributesUse [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; }
}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 jsThis creates typesharp.config.json:
{
"source": [
"C:/Users/User/Desktop/MyApp/Api/Api.csproj",
"C:/Users/User/Desktop/MyApp/Domain/Domain.csproj"
],
"outputPath": "./app/types",
"targetAnnotation": "TypeSharp",
"singleOutputFile": false,
"namingConvention": "camel"
}npx typesharp
# ----- OR -------
npx typesharp generate
# or with custom config
npx typesharp generate --config ./custom-config.tsFor more advanced usage docs/usage
| 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 |
targetAnnotation |
string |
'TypeSharp' |
C# attribute name to look for |
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 |
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"
}
}TypeSharp preserves your C# file organization. Here's how it works:
| C# File Structure | singleOutputFile: false |
singleOutputFile: true |
|---|---|---|
One class per fileUser.cs β 1 class |
user.ts (1 interface) |
All classes in types.ts |
Multiple classes per fileUserDtos.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 |
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):
TypeScript config files require one of the following approaches:
-
Convert to JavaScript first (recommended):
tsc typesharp.config.ts --module nodenext
-
Run with tsx loader:
node --loader tsx/cjs ./node_modules/@siyavuyachagi/typesharp/bin/typesharp.js
-
Or use a
.jsconfig file instead (simplest)
If you prefer TypeScript configs, JSON format is the easiest alternative that provides type safety through JSDoc in most editors.
Add TypeSharp to your build scripts:
{
"scripts": {
"generate-types": "typesharp",
"dev": "typesharp && nuxt dev",
"build": "typesharp && nuxt build"
}
}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 theproperty:attribute target because C# must know the attribute applies to the generated property, not the constructor parameter itself.
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;
}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;
}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[]>;
}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;
}[TypeSharp("auth_response")]
public class AuthResponse
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
}export interface auth_response {
accessToken: string;
refreshToken: string;
}{
"source": [
"C:/MyApp/Api/Api.csproj",
"C:/MyApp/Domain/Domain.csproj",
"C:/MyApp/Contracts/Contracts.csproj"
],
"outputPath": "./src/types"
}const config: TypeSharpConfig = {
source: "./Backend/Backend.csproj",
outputPath: "./src/types",
singleOutputFile: true,
};const config: TypeSharpConfig = {
source: "./Backend/Backend.csproj",
outputPath: "./src/types",
namingConvention: {
dir: "kebab",
file: "camel",
},
};| C# Type | TypeScript Type |
|---|---|
bool |
boolean |
byte, decimal, double, float, int, long |
number |
DateTime, DateOnly, TimeOnly |
string |
Guid, string |
string |
object |
any |
| C# Type | TypeScript Type |
|---|---|
List<T>, ICollection<T>, IEnumerable<T>, T[] |
T[] |
Dictionary<K, V>, IDictionary<K, V>, IReadOnlyDictionary<K, V> |
Record<K, V> |
| C# Type | TypeScript Type |
|---|---|
IFormFile, FormFile |
File |
IFormFileCollection |
File[] |
FileStream, MemoryStream, Stream |
Blob |
import { generate } from "typesharp";
async function generateTypes() {
await generate("./path/to/config.ts");
}
generateTypes();- Node.js >= 20
- TypeScript >= 4.5 (if using TypeScript config)
MIT Β© Siyavuya Chagi
Siyavuya Chagi (CeeJay)
- GitHub: @siyavuyachagi
- Email: syavuya08@gmail.com
Built with β€οΈ in South Africa πΏπ¦
