Logo

Elixir CILicense: MITHex version badgeHexdocs badgeREUSE status

AshTypescript

Automatic TypeScript type generation for Ash resources and actions

Generate type-safe TypeScript clients directly from your Elixir Ash resources, ensuring end-to-end type safety between your backend and frontend. Never write API types manually again.

Breaking Changes

0.18.0

Required Manifest Module

AshTypescript now builds a single, app-wide Ash.Info.Manifest at compile time — the source of truth that both codegen and the runtime RPC pipeline read from. This manifest lives in a small module you declare in your app and register via the manifest config key. Codegen and the pipeline raise if manifest is not configured. This release also requires Ash 3.27+.

Migration:

  1. Create the manifest module:
# lib/my_app/ash_typescript_manifest.ex
defmodule MyApp.AshTypescriptManifest do
use AshTypescript.Manifest, otp_app: :my_app
end
  1. Register it in config:
# config/config.exs
config :ash_typescript, manifest: MyApp.AshTypescriptManifest
  1. Bump the dependency to {:ash_typescript, "~> 0.18"} (and ensure Ash ~> 3.27).

By default the module walks Ash.Info.domains(otp_app) to find every domain with a typescript_rpc block. See Configuration → Manifest Module for details.

mix igniter.install ash_typescript creates this module and sets the config automatically — you only need to do this by hand for manual installs or when upgrading a project that predates the manifest module.

Top-Level filter/sort/page Strictness

Requests that pass a top-level filter, sort, or page parameter the action cannot honor now return a descriptive error (filter_not_supported, sort_not_supported, pagination_not_supported) instead of silently dropping the parameter. A parameter is unusable when the action is not a list read (mutations, get? reads, get_by reads), when the option is disabled via enable_filter?: false/enable_sort?: false, or when the action has no pagination configured. The error's details.reason distinguishes disabled (flag) from unsupported (structural). Absent parameters never error; an empty page: {} counts as present.

Generated TypeScript clients are unaffected — the generated types never offered these parameters where they were unusable. Hand-crafted or stale clients that relied on silent dropping must remove the parameters.

New in 0.18.0: Nested Relationship Query Options

has_many/many_to_many relationships can now be paginated, filtered, sorted, and sliced directly inside field selection, on any action that returns the resource:

const todo = await getTodo({
input: { id: todoId },
fields: [
"id",
{
comments: {
page: { limit: 20, offset: 0, count: true },
filter: { rating: { greaterThan: 2 } },
sort: "-rating",
fields: ["id", "content", "rating"],
},
},
],
});
if (todo.success && todo.data) {
const page = todo.data.comments; // paged shape, same as top-level pagination
page.results; // Array<{ id, content, rating }>
page.hasMore; // boolean
page.count; // number | null
}

Other Notable Changes in 0.18.0

Generated TypeScript changes (mostly type-level; re-run codegen and recompile your frontend to see any impact):

0.16.0

Multi-File Output & Project-Root-Relative Import Paths

AshTypescript now generates multiple output files instead of a single monolithic file. Shared types and Zod schemas are extracted into dedicated files (ash_types.ts and ash_zod.ts) that both RPC and controller code import from.

Additionally, import_into_generated and typed_controller_import_into_generated file paths are now project-root-relative instead of JS-relative import paths. The codegen resolves the correct relative import path for each output file automatically.

What changed:

Migration:

  1. Run mix ash_typescript.codegen — new files will be created alongside the existing output
  2. Update any TypeScript imports that referenced types from ash_rpc.ts to import from ash_types.ts instead
  3. If you use Zod schemas, update imports to use ash_zod.ts
  4. Update import paths from JS-relative to project-root-relative:
# Before (JS-relative)
config :ash_typescript,
import_into_generated: [%{import_name: "RpcHooks", file: "./rpcHooks"}]
# After (project-root-relative)
config :ash_typescript,
import_into_generated: [%{import_name: "RpcHooks", file: "assets/js/rpcHooks.ts"}]

No changes are needed if you only import the RPC functions themselves (e.g., import { listTodos } from './ash_rpc').

Compile-Time Verification of public? Actions

Actions and relationship read actions referenced in typescript_rpc blocks are now verified to be public? true at compile time. Previously, non-public actions would silently generate types but fail at runtime. If you see new compile errors like "action :foo is not public?", set public? true on the action or remove it from the typescript_rpc block.

Features

Quick Start

Get up and running in under 5 minutes:

# Basic installation
mix igniter.install ash_typescript
# Full-stack Phoenix + React setup
mix igniter.install ash_typescript --framework react

1. Add Resource Extension

defmodule MyApp.Todo do
use Ash.Resource,
domain: MyApp.Domain,
extensions: [AshTypescript.Resource]
typescript do
type_name "Todo"
end
attributes do
uuid_primary_key :id
attribute :title, :string, allow_nil?: false, public?: true
attribute :completed, :boolean, default: false, public?: true
end
actions do
default_accept [:title, :completed]
defaults [:read, :create, :update, :destroy]
end
end

2. Configure Domain

defmodule MyApp.Domain do
use Ash.Domain, extensions: [AshTypescript.Rpc]
resources do
resource MyApp.Todo
end
typescript_rpc do
resource MyApp.Todo do
rpc_action :list_todos, :read
rpc_action :create_todo, :create
rpc_action :get_todo, :read, get?: true
end
end
end

3. Generate Types & Use

mix ash.codegen --dev
import { listTodos, createTodo } from './ash_rpc';
// Fully type-safe API calls
const todos = await listTodos({
fields: ["id", "title", "completed"],
filter: { completed: { eq: false } }
});
const newTodo = await createTodo({
fields: ["id", "title", "completed"],
input: { title: "Learn AshTypescript" }
});

That's it! Your TypeScript frontend now has compile-time type safety for your Elixir backend.

For complete setup instructions, see the Installation Guide.

Documentation

Getting Started

Guides

Features

Advanced

Reference

Core Concepts

AshTypescript bridges the gap between Elixir and TypeScript by automatically generating type-safe client code:

  1. Resource Definition - Define Ash resources with attributes, relationships, and actions
  2. RPC Configuration - Expose specific actions through your domain's RPC configuration
  3. Type Generation - Run mix ash.codegen to generate TypeScript types and RPC functions
  4. Frontend Integration - Import and use fully type-safe client functions in your TypeScript code

Type Safety Benefits

Example Repository

Check out the AshTypescript Demo by Christian Alexander featuring:

Requirements

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes with tests
  4. Ensure all tests pass (mix test)
  5. Run code formatter (mix format)
  6. Commit your changes (git commit -m 'Add amazing feature')
  7. Push to the branch (git push origin feature/amazing-feature)
  8. Open a Pull Request

Please ensure:

License

This project is licensed under the MIT License - see the LICENSES/MIT.txt file for details.

Support