documentation/reference/configuration.md

<!--
SPDX-FileCopyrightText: 2025 Torkild G. Kjevik
SPDX-FileCopyrightText: 2025 ash_typescript contributors <https://github.com/ash-project/ash_typescript/graphs/contributors>

SPDX-License-Identifier: MIT
-->

# Configuration Reference

This document provides a comprehensive reference for all AshTypescript configuration options.

## Application Configuration

Configure AshTypescript in your `config/config.exs` file:

```elixir
# config/config.exs
config :ash_typescript,
  # File generation (multi-file architecture)
  output_file: "assets/js/ash_rpc.ts",
  types_output_file: nil,             # Auto-derives as ash_types.ts in output_file dir
  zod_output_file: nil,               # Auto-derives as ash_zod.ts in output_file dir

  # RPC endpoints
  run_endpoint: "/rpc/run",
  validate_endpoint: "/rpc/validate",

  # Field formatting
  input_field_formatter: :camel_case,
  output_field_formatter: :camel_case,

  # Multitenancy
  require_tenant_parameters: false,

  # Zod schema generation
  generate_zod_schemas: false,
  zod_import_path: "zod",
  zod_schema_suffix: "ZodSchema",

  # Validation functions
  generate_validation_functions: false,

  # Phoenix channel-based RPC actions
  generate_phx_channel_rpc_actions: false,
  phoenix_import_path: "phoenix",

  # Custom type imports
  import_into_generated: [],

  # Type mapping overrides
  type_mapping_overrides: [],

  # TypeScript type for untyped maps
  untyped_map_type: "Record<string, any>",

  # RPC resource warnings
  warn_on_missing_rpc_config: true,
  warn_on_non_rpc_references: true,

  # RPC namespace files
  enable_namespace_files: false,      # Generate separate files for namespaced RPC actions
  namespace_output_dir: nil,          # Directory for RPC namespace files (defaults to output_file dir)

  # Typed channel event subscriptions
  typed_channels: [],
  typed_channels_output_file: nil,

  # Typed controllers
  typed_controllers: [],
  router: nil,
  routes_output_file: nil,
  typed_controller_mode: :full,
  typed_controller_path_params_style: :object,
  typed_controller_base_path: "",             # Base URL prefix for all generated route URLs
  enable_controller_namespace_files: false,  # Generate separate files for namespaced routes
  controller_namespace_output_dir: nil,       # Directory for controller namespace files

  # Typed controller lifecycle hooks
  typed_controller_before_request_hook: nil,
  typed_controller_after_request_hook: nil,
  typed_controller_hook_context_type: "Record<string, any>",
  typed_controller_import_into_generated: [],

  # Typed controller error handling
  typed_controller_error_handler: nil,
  typed_controller_show_raised_errors: false,

  # Dev codegen behavior
  always_regenerate: false,

  # Get action behavior
  not_found_error?: true,

  # Developer experience - JSDoc
  add_ash_internals_to_jsdoc: false,
  source_path_prefix: nil,

  # Developer experience - Manifest
  manifest_file: nil,
  add_ash_internals_to_manifest: false
```

## Multi-File Output

AshTypescript generates multiple TypeScript files, each with a specific responsibility:

| File | Config Key | Default | Contents |
|------|-----------|---------|----------|
| RPC functions | `output_file` | `assets/js/ash_rpc.ts` | RPC functions, hook types, helpers |
| Shared types | `types_output_file` | Auto-derived as `ash_types.ts` | Type aliases, resource schemas, filter types, utility types |
| Shared Zod schemas | `zod_output_file` | Auto-derived as `ash_zod.ts` | Zod schemas for all resources (when `generate_zod_schemas: true`) |
| Route helpers | `routes_output_file` | `nil` (disabled) | Path helpers, typed fetch functions, controller input types |
| Typed channel functions | `typed_channels_output_file` | `nil` (disabled) | Channel factory, subscription helpers, cleanup functions |
| RPC namespace re-exports | `namespace_output_dir` | Same dir as `output_file` | Per-namespace re-export files (when `enable_namespace_files: true`) |
| Controller namespace re-exports | `controller_namespace_output_dir` | Same dir as `routes_output_file` | Per-namespace re-export files (when `enable_controller_namespace_files: true`) |

`types_output_file` and `zod_output_file` auto-derive from the `output_file` directory — set `output_file` and the others follow. Override individually if needed.

## Quick Reference

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `output_file` | `string` | `"assets/js/ash_rpc.ts"` | Path where generated TypeScript code will be written |
| `types_output_file` | `string \| nil` | `nil` | Path for shared types file (auto-derives from `output_file` dir as `ash_types.ts`) |
| `zod_output_file` | `string \| nil` | `nil` | Path for shared Zod schemas file (auto-derives from `output_file` dir as `ash_zod.ts`) |
| `run_endpoint` | `string \| {:runtime_expr, string}` | `"/rpc/run"` | Endpoint for executing RPC actions |
| `validate_endpoint` | `string \| {:runtime_expr, string}` | `"/rpc/validate"` | Endpoint for validating RPC requests |
| `input_field_formatter` | `:camel_case \| :snake_case` | `:camel_case` | How to format field names in request inputs |
| `output_field_formatter` | `:camel_case \| :snake_case` | `:camel_case` | How to format field names in response outputs |
| `require_tenant_parameters` | `boolean` | `false` | Whether to require tenant parameters in RPC calls |
| `generate_zod_schemas` | `boolean` | `false` | Whether to generate Zod validation schemas |
| `zod_import_path` | `string` | `"zod"` | Import path for Zod library |
| `zod_schema_suffix` | `string` | `"ZodSchema"` | Suffix for generated Zod schema names |
| `generate_validation_functions` | `boolean` | `false` | Whether to generate form validation functions |
| `generate_phx_channel_rpc_actions` | `boolean` | `false` | Whether to generate Phoenix channel-based RPC functions |
| `phoenix_import_path` | `string` | `"phoenix"` | Import path for Phoenix library |
| `import_into_generated` | `list` | `[]` | List of custom modules to import |
| `type_mapping_overrides` | `list` | `[]` | Override TypeScript types for Ash types |
| `untyped_map_type` | `string` | `"Record<string, any>"` | TypeScript type for untyped maps |
| `warn_on_missing_rpc_config` | `boolean` | `true` | Warn about resources with extension not in RPC config |
| `warn_on_non_rpc_references` | `boolean` | `true` | Warn about non-RPC resources referenced by RPC resources |
| `enable_namespace_files` | `boolean` | `false` | Generate separate files for namespaced RPC actions |
| `namespace_output_dir` | `string \| nil` | `nil` | Directory for RPC namespace files (defaults to `output_file` dir) |
| `typed_channels` | `list(module)` | `[]` | TypedChannel modules to generate event subscription helpers for |
| `typed_channels_output_file` | `string \| nil` | `nil` | Output file for typed channel functions (when `nil`, generation is skipped) |
| `typed_controllers` | `list(module)` | `[]` | TypedController modules to generate route helpers for |
| `router` | `module \| nil` | `nil` | Phoenix router module for path introspection |
| `routes_output_file` | `string \| nil` | `nil` | Output file path for generated route helpers |
| `typed_controller_mode` | `:full \| :paths_only` | `:full` | Generation mode: `:full` generates path helpers + fetch functions, `:paths_only` generates only path helpers |
| `typed_controller_path_params_style` | `:object \| :args` | `:object` | Path parameter style in generated functions |
| `typed_controller_base_path` | `string \| {:runtime_expr, string}` | `""` | Base URL prefix for all generated route URLs |
| `enable_controller_namespace_files` | `boolean` | `false` | Generate separate files for namespaced controller routes |
| `controller_namespace_output_dir` | `string \| nil` | `nil` | Directory for controller namespace files (defaults to `routes_output_file` dir) |
| `typed_controller_before_request_hook` | `string \| nil` | `nil` | Function called before typed controller requests |
| `typed_controller_after_request_hook` | `string \| nil` | `nil` | Function called after typed controller requests |
| `typed_controller_hook_context_type` | `string` | `"Record<string, any>"` | TypeScript type for typed controller hook context |
| `typed_controller_import_into_generated` | `list(map)` | `[]` | Custom imports for generated routes file |
| `typed_controller_error_handler` | `mfa \| module \| nil` | `nil` | Custom error transformation handler |
| `typed_controller_show_raised_errors` | `boolean` | `false` | Show exception messages in 500 responses |
| `always_regenerate` | `boolean` | `false` | Skip diff check and always write generated files |
| `not_found_error?` | `boolean` | `true` | Global default: `true` returns error on not found, `false` returns null |
| `add_ash_internals_to_jsdoc` | `boolean` | `false` | Show Ash resource/action details in JSDoc |
| `source_path_prefix` | `string \| nil` | `nil` | Prefix for source file paths (monorepos) |
| `manifest_file` | `string \| nil` | `nil` | Path to generate Markdown manifest |
| `add_ash_internals_to_manifest` | `boolean` | `false` | Show Ash details in manifest |

## Lifecycle Hook Configuration

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `rpc_action_before_request_hook` | `string \| nil` | `nil` | Function called before RPC action requests |
| `rpc_action_after_request_hook` | `string \| nil` | `nil` | Function called after RPC action requests |
| `rpc_validation_before_request_hook` | `string \| nil` | `nil` | Function called before validation requests |
| `rpc_validation_after_request_hook` | `string \| nil` | `nil` | Function called after validation requests |
| `rpc_action_hook_context_type` | `string` | `"Record<string, any>"` | TypeScript type for action hook context |
| `rpc_validation_hook_context_type` | `string` | `"Record<string, any>"` | TypeScript type for validation hook context |
| `rpc_action_before_channel_push_hook` | `string \| nil` | `nil` | Function called before channel push for actions |
| `rpc_action_after_channel_response_hook` | `string \| nil` | `nil` | Function called after channel response for actions |
| `rpc_validation_before_channel_push_hook` | `string \| nil` | `nil` | Function called before channel push for validations |
| `rpc_validation_after_channel_response_hook` | `string \| nil` | `nil` | Function called after channel response for validations |
| `rpc_action_channel_hook_context_type` | `string` | `"Record<string, any>"` | TypeScript type for channel action hook context |
| `rpc_validation_channel_hook_context_type` | `string` | `"Record<string, any>"` | TypeScript type for channel validation hook context |
| `typed_controller_before_request_hook` | `string \| nil` | `nil` | Function called before typed controller requests |
| `typed_controller_after_request_hook` | `string \| nil` | `nil` | Function called after typed controller requests |
| `typed_controller_hook_context_type` | `string` | `"Record<string, any>"` | TypeScript type for typed controller hook context |

See [Lifecycle Hooks](../features/lifecycle-hooks.md) and [Typed Controllers](../guides/typed-controllers.md#lifecycle-hooks) for complete documentation.

## Domain Configuration

Configure RPC actions and typed queries in your domain modules:

```elixir
defmodule MyApp.Domain do
  use Ash.Domain, extensions: [AshTypescript.Rpc]

  typescript_rpc do
    resource MyApp.Todo do
      # Standard CRUD actions
      rpc_action :list_todos, :read
      rpc_action :get_todo, :get
      rpc_action :create_todo, :create
      rpc_action :update_todo, :update
      rpc_action :destroy_todo, :destroy

      # RPC action options
      rpc_action :list_limited, :read, allowed_loads: [:user]
      rpc_action :list_no_filter, :read, enable_filter?: false
      rpc_action :list_no_sort, :read, enable_sort?: false

      # Typed queries for SSR
      typed_query :dashboard_todos, :read do
        ts_result_type_name "DashboardTodo"
        ts_fields_const_name "dashboardTodoFields"
        fields [:id, :title, :priority, %{user: [:name]}]
      end
    end
  end
end
```

## RPC Action Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `allowed_loads` | `list(atom \| keyword)` | `nil` | Whitelist of loadable fields |
| `denied_loads` | `list(atom \| keyword)` | `nil` | Blacklist of loadable fields |
| `enable_filter?` | `boolean` | `true` | Enable client-side filtering |
| `enable_sort?` | `boolean` | `true` | Enable client-side sorting |
| `get?` | `boolean` | `false` | Return single record |
| `get_by` | `list(atom)` | `nil` | Fields for single-record lookup |
| `not_found_error?` | `boolean` | `nil` | Override global not_found_error? |
| `identities` | `list(atom)` | `[:_primary_key]` | Allowed identity lookups |
| `show_metadata` | `list(atom) \| false \| nil` | `nil` | Metadata fields to expose |
| `metadata_field_names` | `keyword` | `nil` | Metadata field name mappings |

See [RPC Action Options](../features/rpc-action-options.md) for complete documentation.

## Dynamic RPC Endpoints

For separate frontend projects, use runtime expressions:

```elixir
config :ash_typescript,
  # Environment variables
  run_endpoint: {:runtime_expr, "process.env.RPC_RUN_ENDPOINT || '/rpc/run'"},

  # Vite environment variables
  # run_endpoint: {:runtime_expr, "import.meta.env.VITE_RPC_RUN_ENDPOINT || '/rpc/run'"},

  # Custom functions
  # run_endpoint: {:runtime_expr, "MyAppConfig.getRunEndpoint()"}
```

## RPC Resource Warnings

AshTypescript provides compile-time warnings for configuration issues:

### Missing RPC Configuration Warning

Appears when resources have `AshTypescript.Resource` extension but are not in any `typescript_rpc` block.

### Non-RPC References Warning

Appears when RPC resources reference other resources that are not configured as RPC resources.

**To disable warnings:**

```elixir
config :ash_typescript,
  warn_on_missing_rpc_config: false,
  warn_on_non_rpc_references: false
```

## Always Regenerate Mode

By default, `mix ash_typescript.codegen --check` compares the generated output against existing files and raises `Ash.Error.Framework.PendingCodegen` if they differ. This is useful for CI but in development—especially when using `AshPhoenix.Plug.CheckCodegenStatus`—you may want to skip the diff check and always write the generated files.

```elixir
# config/dev.exs
config :ash_typescript, always_regenerate: true
```

When enabled, `--check` mode will write files directly instead of comparing, so the `PendingCodegen` error page is never shown during development.

## Typed Channel Configuration

Configure typed channels to generate TypeScript event subscription helpers from Ash PubSub publications. Both `typed_channels` and `typed_channels_output_file` must be configured for generation to run.

```elixir
config :ash_typescript,
  typed_channels: [MyApp.OrgChannel, MyApp.ActivityChannel],
  typed_channels_output_file: "assets/js/ash_typed_channels.ts"
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `typed_channels` | `list(module)` | `[]` | Modules using `AshTypescript.TypedChannel` |
| `typed_channels_output_file` | `string \| nil` | `nil` | Output file for channel functions (when `nil`, generation is skipped) |

Channel types (branded types, payload aliases, event maps) are appended to the shared types file (`ash_types.ts`). Channel functions (factory, subscription helpers) go into `typed_channels_output_file` and import their types from `ash_types.ts`.

See [Typed Channels](../features/typed-channels.md) for complete documentation.

## Typed Controller Configuration

Configure typed controllers to generate TypeScript path helpers and typed fetch functions for Phoenix controller routes. All three settings (`typed_controllers`, `router`, `routes_output_file`) must be configured for route generation to run.

```elixir
config :ash_typescript,
  # List of TypedController modules
  typed_controllers: [MyApp.Session],

  # Phoenix router for path introspection
  router: MyAppWeb.Router,

  # Output file for generated route helpers
  routes_output_file: "assets/js/routes.ts",

  # Generation mode (optional)
  typed_controller_mode: :full,              # :full (default) or :paths_only
  typed_controller_path_params_style: :object, # :object (default) or :args
  typed_controller_base_path: "",            # Base URL prefix (string or {:runtime_expr, "..."})

  # Namespace files (optional)
  enable_controller_namespace_files: false,  # Generate separate files per namespace
  controller_namespace_output_dir: nil,      # Directory for namespace files (defaults to routes_output_file dir)

  # Lifecycle hooks (optional)
  typed_controller_before_request_hook: "RouteHooks.beforeRequest",
  typed_controller_after_request_hook: "RouteHooks.afterRequest",
  typed_controller_hook_context_type: "RouteHooks.RouteHookContext",
  typed_controller_import_into_generated: [
    %{import_name: "RouteHooks", file: "./routeHooks"}
  ],

  # Error handling (optional)
  typed_controller_error_handler: {MyApp.ErrorHandler, :handle, []},
  typed_controller_show_raised_errors: false  # true only in dev
```

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `typed_controllers` | `list(module)` | `[]` | Modules using `AshTypescript.TypedController` |
| `router` | `module` | `nil` | Phoenix router for path introspection |
| `routes_output_file` | `string` | `nil` | Output file path (when `nil`, generation is skipped) |
| `typed_controller_mode` | `:full \| :paths_only` | `:full` | `:full` generates path helpers + fetch functions; `:paths_only` generates only path helpers |
| `typed_controller_path_params_style` | `:object \| :args` | `:object` | Path parameter style in generated TypeScript |
| `typed_controller_base_path` | `string \| {:runtime_expr, string}` | `""` | Base URL prefix for all generated route URLs |
| `enable_controller_namespace_files` | `boolean` | `false` | Generate separate files for namespaced routes |
| `controller_namespace_output_dir` | `string \| nil` | `nil` | Directory for namespace files (defaults to `routes_output_file` dir) |
| `typed_controller_before_request_hook` | `string \| nil` | `nil` | Function called before typed controller requests |
| `typed_controller_after_request_hook` | `string \| nil` | `nil` | Function called after typed controller requests |
| `typed_controller_hook_context_type` | `string` | `"Record<string, any>"` | TypeScript type for hook context |
| `typed_controller_import_into_generated` | `list(map)` | `[]` | Custom imports (`%{import_name: "Name", file: "./path"}`) |
| `typed_controller_error_handler` | `mfa \| module \| nil` | `nil` | Custom error transformation handler |
| `typed_controller_show_raised_errors` | `boolean` | `false` | Show exception messages in 500 responses |

See [Typed Controllers](../guides/typed-controllers.md) for complete documentation.

## Detailed Documentation

For in-depth configuration guides, see:

- [Custom Types](../advanced/custom-types.md) - Custom Ash types with TypeScript integration
- [Field Name Mapping](../advanced/field-name-mapping.md) - Mapping invalid field names
- [Developer Experience](../features/developer-experience.md) - Namespaces, JSDoc, and manifest generation
- [Lifecycle Hooks](../features/lifecycle-hooks.md) - HTTP and channel lifecycle hooks
- [Phoenix Channels](../features/phoenix-channels.md) - Channel-based RPC configuration
- [Typed Channels](../features/typed-channels.md) - Typed event subscriptions from PubSub
- [Multitenancy](../features/multitenancy.md) - Tenant parameter configuration
- [Form Validation](../guides/form-validation.md) - Zod schema configuration
- [Typed Controllers](../guides/typed-controllers.md) - Controller route helpers

## See Also

- [Installation](../getting-started/installation.md) - Initial setup
- [Mix Tasks Reference](mix-tasks.md) - Code generation commands
- [Troubleshooting Reference](troubleshooting.md) - Common problems and solutions