# How to Generate TypeScript Types for Nested Go Structs in typescript-go

> Effortlessly generate TypeScript types for nested Go structs using typescript-go. Run the generation script to transform ast.json into accurate TypeScript interfaces.

- Repository: [Microsoft/typescript-go](https://github.com/microsoft/typescript-go)
- Tags: how-to-guide
- Published: 2026-04-25

---

**To generate TypeScript types for nested Go structs in the typescript-go repository, run the [`_scripts/generate-ts-ast.ts`](https://github.com/microsoft/typescript-go/blob/main/_scripts/generate-ts-ast.ts) script, which transforms the central [`ast.json`](https://github.com/microsoft/typescript-go/blob/main/ast.json) schema into TypeScript interfaces that mirror Go's struct embedding and inheritance hierarchies.**

The `microsoft/typescript-go` project maintains a **single source-of-truth schema** ([`_scripts/ast.json`](https://github.com/microsoft/typescript-go/blob/main/_scripts/ast.json)) that describes the TypeScript AST structure. This approach ensures that nested Go structs defined in [`internal/ast/ast_generated.go`](https://github.com/microsoft/typescript-go/blob/main/internal/ast/ast_generated.go) automatically produce corresponding TypeScript interfaces, eliminating manual type synchronization between the Go backend and TypeScript frontend.

## The Schema-Driven Architecture

Instead of maintaining parallel type definitions, `typescript-go` uses a centralized JSON schema to drive two parallel code generators:

- [`_scripts/generate-go-ast.ts`](https://github.com/microsoft/typescript-go/blob/main/_scripts/generate-go-ast.ts) produces [`internal/ast/ast_generated.go`](https://github.com/microsoft/typescript-go/blob/main/internal/ast/ast_generated.go) containing Go structs with embedded base types
- [`_scripts/generate-ts-ast.ts`](https://github.com/microsoft/typescript-go/blob/main/_scripts/generate-ts-ast.ts) produces [`_packages/native-preview/src/ast/ast.generated.ts`](https://github.com/microsoft/typescript-go/blob/main/_packages/native-preview/src/ast/ast.generated.ts) containing TypeScript interfaces with `extends` clauses

Because both generators consume the same [`ast.json`](https://github.com/microsoft/typescript-go/blob/main/ast.json) schema, any changes to nested struct relationships in Go automatically propagate to the TypeScript definitions.

## Generating TypeScript Types from the Schema

To regenerate the TypeScript interfaces after modifying Go structs or the AST schema:

```bash

# Install dependencies (first run only)

npm install

# Generate TypeScript types from the Go struct definitions

node --experimental-strip-types _scripts/generate-ts-ast.ts

```

This command writes the output to [`_packages/native-preview/src/ast/ast.generated.ts`](https://github.com/microsoft/typescript-go/blob/main/_packages/native-preview/src/ast/ast.generated.ts), creating TypeScript interfaces that preserve the exact nesting and inheritance relationships defined in the Go source code.

## How Nested Go Structs Map to TypeScript Interfaces

The transformation from Go embedding to TypeScript extension relies on two key functions in [`_scripts/generate-ts-ast.ts`](https://github.com/microsoft/typescript-go/blob/main/_scripts/generate-ts-ast.ts).

### Inheritance Handling with deriveNodeTsExtends

The `deriveNodeTsExtends` function computes the `extends` clause for each generated interface. It expands Go-only base types to ensure that the public Go hierarchy appears correctly in TypeScript:

**Go struct embedding:**

```go
type QualifiedName struct {
    NodeBase
    Left  EntityName
    Right Identifier
}

```

**Generated TypeScript interface:**

```typescript
export interface QualifiedName extends Node, FlowNode, CompositeBase {
    readonly kind: SyntaxKind.QualifiedName;
    readonly left: EntityName;
    readonly right: Identifier;
}

```

The `extends` clause (`Node, FlowNode, CompositeBase`) reproduces the Go embedding hierarchy, flattening nested struct relationships into TypeScript's inheritance model.

### Member Selection with tsInterfaceMembers

The `tsInterfaceMembers` function filters the generated output to match TypeScript's public API surface:

- Removes fields marked with `noTS` in the schema
- Excludes inherited fields that should remain hidden
- Applies property overrides defined in the schema
- Emits optional members with `?` and list members with `NodeArray<T>`

This filtering ensures that internal Go implementation details do not leak into the public TypeScript types while preserving the structural relationships of nested data.

## Working with Generated Types

### Factory Functions for Nested Structures

The generator creates typed factory functions that respect the nesting rules. For example, `createQualifiedName` accepts the nested types as parameters:

```typescript
export function createQualifiedName(left: EntityName, right: Identifier): QualifiedName {
    return new NodeObject(SyntaxKind.QualifiedName, { left, right }) as unknown as QualifiedName;
}

```

These factories assign fields belonging to Go-only bases to the object's class-level properties rather than the data payload, maintaining consistency with the Go struct memory layout.

### Type Guards for Union Types

For union types involving nested structs, the generator produces type guards based on node aliases defined in the schema:

```typescript
export function isStatement(node: Node): boolean {
    return node.kind === SyntaxKind.Block ||
           node.kind === SyntaxKind.EmptyStatement ||
           node.kind === SyntaxKind.ExpressionStatement ||
           // ... additional statement kinds
           false;
}

```

These guards leverage the `SyntaxKind` discriminant to narrow types across the nested hierarchy.

## Key Implementation Files

| Purpose | Path |
|---------|------|
| **AST Schema** (source of truth) | [`_scripts/ast.json`](https://github.com/microsoft/typescript-go/blob/main/_scripts/ast.json) |
| **TypeScript Generator** | [`_scripts/generate-ts-ast.ts`](https://github.com/microsoft/typescript-go/blob/main/_scripts/generate-ts-ast.ts) |
| **Schema Parsing Utilities** | [`_scripts/schema.ts`](https://github.com/microsoft/typescript-go/blob/main/_scripts/schema.ts) |
| **Generated TS Output** | [`_packages/native-preview/src/ast/ast.generated.ts`](https://github.com/microsoft/typescript-go/blob/main/_packages/native-preview/src/ast/ast.generated.ts) |
| **Generated Go Structs** | [`internal/ast/ast_generated.go`](https://github.com/microsoft/typescript-go/blob/main/internal/ast/ast_generated.go) |
| **Go Generator** | [`_scripts/generate-go-ast.ts`](https://github.com/microsoft/typescript-go/blob/main/_scripts/generate-go-ast.ts) |

## Summary

- **Single source of truth**: The [`ast.json`](https://github.com/microsoft/typescript-go/blob/main/ast.json) schema drives both Go and TypeScript generation, ensuring nested struct relationships remain synchronized.
- **Run the generator**: Execute `node --experimental-strip-types _scripts/generate-ts-ast.ts` to produce TypeScript interfaces from Go struct definitions.
- **Inheritance mapping**: Go struct embedding translates to TypeScript `extends` clauses via the `deriveNodeTsExtends` function.
- **Member filtering**: The `tsInterfaceMembers` function strips Go-specific implementation details while preserving public API structures.
- **Auto-generated utilities**: Factory functions and type guards are emitted alongside interfaces to support the full AST hierarchy.

## Frequently Asked Questions

### How do I update TypeScript types after changing Go struct definitions?

Modify the [`ast.json`](https://github.com/microsoft/typescript-go/blob/main/ast.json) schema or the Go generator in [`_scripts/generate-go-ast.ts`](https://github.com/microsoft/typescript-go/blob/main/_scripts/generate-go-ast.ts), then run `node --experimental-strip-types _scripts/generate-ts-ast.ts`. This regenerates [`_packages/native-preview/src/ast/ast.generated.ts`](https://github.com/microsoft/typescript-go/blob/main/_packages/native-preview/src/ast/ast.generated.ts) with updated interfaces reflecting your nested struct changes.

### Why does the TypeScript interface extend multiple bases when Go uses embedding?

Go achieves inheritance through struct embedding, while TypeScript uses interface extension. The `deriveNodeTsExtends` function in [`_scripts/generate-ts-ast.ts`](https://github.com/microsoft/typescript-go/blob/main/_scripts/generate-ts-ast.ts) flattens Go's embedded base structs (like `NodeBase`) into a flat `extends` clause containing all inherited interfaces, preserving the hierarchical relationship in TypeScript's type system.

### What happens to Go-specific fields that shouldn't appear in TypeScript?

The `tsInterfaceMembers` function filters out fields marked with `noTS` in the schema and removes inherited fields that are implementation details. Only the public API surface suitable for TypeScript consumers gets emitted to [`ast.generated.ts`](https://github.com/microsoft/typescript-go/blob/main/ast.generated.ts).

### Can I manually edit the generated TypeScript files?

No. The [`ast.generated.ts`](https://github.com/microsoft/typescript-go/blob/main/ast.generated.ts) file is automatically generated from [`ast.json`](https://github.com/microsoft/typescript-go/blob/main/ast.json). Any manual edits will be overwritten the next time [`_scripts/generate-ts-ast.ts`](https://github.com/microsoft/typescript-go/blob/main/_scripts/generate-ts-ast.ts) runs. To modify the output, update the schema or the generator script instead.