@siyavuyachagi/typesharp
v0.2.5
Published
Generate TypeScript types from C# models with TypeSharp attribute
Maintainers
Readme
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/FILE_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 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
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/typesharpQuick Start
1. Install the NuGet attributes package
In your C# project:
dotnet add package TypeSharp.Attributes2. 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 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",
"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-incrementalFor 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 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 structureVarious 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:
Convert to JavaScript first (recommended):
tsc typesharp.config.ts --module nodenextRun with tsx loader:
node --loader tsx/cjs ./node_modules/@siyavuyachagi/typesharp/bin/typesharp.jsOr 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.
Usage in package.json
Add TypeSharp to your build scripts:
{
"scripts": {
"dev": "typesharp && nuxt dev",
"build": "typesharp && nuxt build",
"typesharp": "npx typesharp generate"
}
}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 theproperty: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, TimeSpan | 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
Test Results
Requirements
- Node.js >= 20
- TypeScript >= 4.5 (if using TypeScript config)
License
MIT © Siyavuya Chagi
Author
Siyavuya Chagi (CeeJay)
- GitHub: @siyavuyachagi
- Email: [email protected]
Built with ❤️ in South Africa 🇿🇦
