Ash extension for generating PhoenixGenApi function configurations from Ash resources.
This extension allows you to define PhoenixGenApi endpoints directly in your
Ash resource DSL, automatically generating FunConfig structs that can be
pulled by gateway nodes.
Usage
Add the extension to your resource:
defmodule MyApp.Chat.DirectMessage do
use Ash.Resource,
extensions: [AshPhoenixGenApi.Resource]
gen_api do
service "chat"
nodes {ClusterHelper, :get_nodes, [:chat]}
choose_node_mode :random
action :send_direct_message do
request_type "send_direct_message"
timeout 10_000
response_type :async
request_info true
end
action :get_conversation do
timeout 5_000
end
mfa :ping do
request_type "ping"
mfa {MyApp.Chat.Api, :ping, []}
arg_types %{}
end
end
endDSL
See the DSL documentation for the full list of configuration options.
gen_api
Configure PhoenixGenApi at the resource level.
The gen_api section defines which Ash actions should be exposed as
PhoenixGenApi endpoints, along with their routing, timeout, permission,
and other settings.
Section-level options serve as defaults for all action and mfa
entities in this resource. Each entity can override these defaults.
Nested DSLs
Examples
gen_api do
service "chat"
nodes {ClusterHelper, :get_nodes, [:chat]}
choose_node_mode :random
timeout 5_000
response_type :async
request_info true
action :send_direct_message do
request_type "send_direct_message"
timeout 10_000
end
action :get_conversation do
timeout 5_000
end
end
# Minimal configuration with defaults
gen_api do
service "chat"
action :create
action :read
end
Options
| Name | Type | Default | Docs |
|---|---|---|---|
service | any | The service name for this resource's API endpoints. This is used by PhoenixGenApi to group and route API calls. Accepts a string or atom. Example: "chat", "user_service", :notification | |
nodes | any | :local | Default target nodes for all actions in this resource. Can be: - A list of node atoms: [:"node1@host", :"node2@host"] - An MFA tuple that returns a node list at runtime: {ClusterHelper, :get_nodes, [:chat]} - :local - Execute on the local node (default) |
choose_node_mode | any | :random | Default node selection strategy for all actions. - :random - Select a random node (default) - :hash - Hash-based selection using request_type - {:hash, key} - Hash-based selection using the specified argument key - :round_robin - Round-robin across nodes |
timeout | any | 5000 | Default timeout in milliseconds for all actions. Individual actions can override this. Accepts a positive integer or :infinity. |
response_type | atom | :async | Default response mode for all actions. - :sync - Client waits for the result - :async - Client receives an ack, then the result later (default) - :stream - Client receives streamed chunks - :none - Fire and forget |
request_info | boolean | true | Default for whether to pass request info (user_id, device_id, request_id) as the last argument to the MFA function. |
check_permission | any | false | Default permission check mode for all actions. - false - No permission check (default) - :any_authenticated - Requires a valid user_id - {:arg, "arg_name"} - The specified argument must match user_id - {:role, ["admin"]} - User must have one of the listed roles |
permission_callback | any | Default permission callback MFA for all actions. When set, takes precedence over check_permission. Accepts {Module, :function, []} or nil. The callback function receives request_type (string) and args (map) as arguments and returns true (continue) or false (permission denied). The callback function signature: @callback checkpermission(request_type :: String.t(), args :: map()) :: boolean() Example callback: def check_permission(request_type, args) do case request_type do "delete_user" -> args["role"] == "admin" "update_profile" -> args["user_id"] == args["target_user_id"] -> true end end When both permission_callback and check_permission are set, permission_callback takes precedence and is stored as {:callback, {Module, :function, []}} in the FunConfig's check_permission field. Defaults to nil. | |
version | String.t | "0.0.1" | Default version string for all actions. Used for PhoenixGenApi API versioning. |
retry | any | Default retry configuration for all actions. - nil - No retry (default) - A positive number n - Equivalent to {:all_nodes, n} - {:same_node, n} - Retry on the same node(s) - {:all_nodes, n} - Retry across all available nodes | |
code_interface? | boolean | true | Whether to auto-generate code interface functions for the gen_api actions on the resource module. When true, a function matching each action name will be defined on the resource module that calls the action through the Ash framework. Individual actions can override this with their own code_interface? option. Defaults to true. |
result_encoder | any | :struct | Default result encoding mode for all actions. Determines how the result returned from the action MFA call is encoded before being returned to the caller. - :struct — Return the Ash resource struct as-is (default) - :map — Convert the Ash resource struct to a map containing only public fields (using Ash.Resource.Info.public_fields/1 to filter; falls back to Map.from_struct/1 for non-Ash-resource structs) - {Module, :function, args} — Custom encoder MFA. The function receives the result as its first argument, followed by args, and must return the encoded result. Individual actions can override this with their own result_encoder option. For :map encoding, Ash resource structs are converted to maps containing only their public fields (attributes, calculations, aggregates, relationships). Lists of structs are mapped accordingly. Non-Ash-resource structs fall back to Map.from_struct/1. For custom MFA encoders, the function receives the result and must return the encoded value. Defaults to :struct. |
gen_api.action
action nameConfigures a PhoenixGenApi endpoint for an Ash resource action.
Each action entity maps an Ash action to a PhoenixGenApi FunConfig.
If request_type is not specified, it defaults to the action name as a string.
If arg_types and arg_orders are not specified, they are auto-derived
from the Ash action's accepted attributes and arguments. arg_orders
defaults to :map, which derives the order from arg_types keys. Set
to a list of argument name strings to specify explicit ordering.
Examples
action :send_direct_message do
request_type "send_direct_message"
timeout 10_000
response_type :async
request_info true
check_permission {:arg, "from_user_id"}
end
action :get_conversation do
timeout 5_000
response_type :async
end
# Minimal config - auto-derives request_type and args from Ash action
action :create
Arguments
| Name | Type | Default | Docs |
|---|---|---|---|
name | atom | The name of the Ash resource action to expose as a PhoenixGenApi endpoint. Must match an existing action defined on the resource. |
Options
| Name | Type | Default | Docs |
|---|---|---|---|
request_type | String.t | The PhoenixGenApi request type string used by clients to call this endpoint. Defaults to the action name converted to a string. Example: "send_direct_message", "get_conversation" | |
timeout | any | Timeout in milliseconds for the function call. Defaults to the gen_api section-level timeout (which defaults to 5000). Accepts a positive integer or :infinity. | |
response_type | atom | The response mode for this endpoint. - :sync - Client waits for the result - :async - Client receives an ack, then the result later - :stream - Client receives streamed chunks - :none - Fire and forget Defaults to the gen_api section-level response_type (which defaults to :async). | |
request_info | boolean | Whether to pass request info (user_id, device_id, request_id) as the last argument to the MFA function. When true, the generated MFA will append a map with %{user_id: ..., device_id: ..., request_id: ...} to the function arguments. Defaults to the gen_api section-level request_info (which defaults to true). | |
check_permission | any | Permission check mode for this endpoint. - false - No permission check - :any_authenticated - Requires a valid user_id - {:arg, "arg_name"} - The specified argument must match user_id - {:role, ["admin", "moderator"]} - User must have one of the listed roles Defaults to the gen_api section-level check_permission (which defaults to false). | |
permission_callback | any | A custom callback MFA for permission checking. When set, takes precedence over check_permission. Accepts {Module, :function, []} or nil. The callback function receives request_type (string) and args (map) as arguments and returns true (continue) or false (permission denied). The callback function signature: @callback checkpermission(request_type :: String.t(), args :: map()) :: boolean() Example callback: def check_permission(request_type, args) do case request_type do "delete_user" -> args["role"] == "admin" "update_profile" -> args["user_id"] == args["target_user_id"] -> true end end When nil, inherits from the section-level permission_callback. When both permission_callback and check_permission are set, permission_callback takes precedence and is stored as {:callback, {Module, :function, []}} in the FunConfig's check_permission field. Defaults to the gen_api section-level permission_callback (which defaults to nil). | |
choose_node_mode | any | Node selection strategy for this endpoint. Overrides the section-level setting. - :random - Select a random node - :hash - Hash-based selection using request_type - {:hash, key} - Hash-based selection using the specified argument key - :round_robin - Round-robin across nodes | |
nodes | any | Target nodes for this endpoint. Overrides the section-level setting. Can be: - A list of node atoms: [:"node1@host", :"node2@host"] - An MFA tuple: {ClusterHelper, :get_nodes, [:chat]} - :local - Execute on the local node | |
retry | any | Retry configuration when execution fails. - nil (default) - No retry - A positive number n - Equivalent to {:all_nodes, n} - {:same_node, n} - Retry on the same node(s) - {:all_nodes, n} - Retry across all available nodes | |
version | String.t | Version string for this API endpoint. Used for API versioning. Defaults to the gen_api section-level version (which defaults to "0.0.1"). | |
mfa | any | Explicit MFA tuple to call instead of the auto-generated one. When specified, this overrides the default MFA derived from the resource module and action name. The auto-generated MFA is {ResourceModule, :action_name, []}. The actual function is called with the converted arguments plus request_info (if enabled). Example: {MyApp.Interface.Api, :send_direct_message, []} | |
arg_types | any | Explicit argument types map. When provided, overrides the auto-derived arg_types from the Ash action's attributes and arguments. Simple Format (Backward Compatible) - Uses just the type atom: elixir arg_types: %{"user_id" => :string, "age" => :num} Extended Format (New) - Uses a keyword list with :type and optional parameters: elixir arg_types: %{ "user_id" => [type: :string, max_bytes: 255, allow_nil?: true], "age" => [type: :num, default_value: 18], "tags" => [type: :list_string, max_items: 10, max_item_bytes: 100] } Extended Format Options: - type: - Required. The argument type (:string, :num, :boolean, etc.) - allow_nil?: - Optional. When true, allows nil values (default: false) - default_value: - Optional. Default value if argument is missing from request - max_bytes: - For :string type, max byte size - max_items: - For list/map types, max number of items - max_item_bytes: - For :list_string, max bytes per item Nil Attribute Support: When an Ash attribute has allow_nil? true, the auto-derived arg_types will use the extended format with allow_nil?: true. This allows PhoenixGenApi clients to properly handle optional fields. Example: %{"user_id" => :string, "count" => :num, "tags" => {:list_string, 1000, 50}} | |
arg_orders | any | :map | Explicit argument order list, or :map to derive from arg_types keys. - A list of argument name strings overrides the auto-derived order. Example: ["user_id", "content", "file_id"] - :map (default) derives arg_orders from the arg_types map keys. |
disabled | boolean | false | When true, this endpoint is disabled and will not be included in the generated FunConfig list. Useful for temporarily disabling an endpoint without removing its configuration. |
code_interface? | boolean | nil | Whether to generate a code interface function for this specific action. When true, a function matching the action name will be defined on the resource module that calls the action through the Ash framework. When nil (the default), inherits from the section-level code_interface? setting. Set to false to disable code interface generation for this action while keeping it enabled for others. | |
result_encoder | any | How to encode the result returned from the action MFA call. - :struct — Return the Ash resource struct as-is (default behavior) - :map — Convert the Ash resource struct to a map containing only public fields (using Ash.Resource.Info.public_fields/1 to filter; falls back to Map.from_struct/1 for non-Ash-resource structs) - {Module, :function, args} — Custom encoder MFA. The function receives the result as its first argument, followed by args, and must return the encoded result. When nil (the default), inherits from the section-level result_encoder setting. The encoding is applied in the generated code interface functions. For :map encoding, Ash resource structs are converted to maps containing only their public fields (attributes, calculations, aggregates, relationships). Lists of structs are mapped accordingly. Non-Ash-resource structs fall back to Map.from_struct/1. For custom MFA encoders, the function receives the result and must return the encoded value. Defaults to the gen_api section-level result_encoder (which defaults to :struct). |
Introspection
Target: AshPhoenixGenApi.Resource.ActionConfig
gen_api.mfa
mfa nameConfigures a standalone PhoenixGenApi MFA endpoint.
Unlike action entities which map an Ash resource action to a FunConfig,
mfa entities define endpoints that call an arbitrary MFA function directly.
This is useful for exposing custom functions that don't map to standard
Ash CRUD actions, such as utility endpoints, batch operations, or
service-to-service calls.
Both request_type and mfa are required. arg_types must be explicitly
provided since there is no Ash action to auto-derive from. arg_orders
defaults to :map, which passes arguments as a map with string keys.
Examples
mfa :my_custom do
request_type "my_custom"
mfa {MyApp.Interface.Api, :my_custom, []}
arg_types %{"user_id" => :string}
timeout 5_000
end
mfa :batch_process do
request_type "batch_process"
mfa {MyApp.BatchProcessor, :run, []}
arg_types %{"items" => {:list_string, 1000, 50}, "mode" => :string}
arg_orders ["items", "mode"]
response_type :async
request_info true
end
Arguments
| Name | Type | Default | Docs |
|---|---|---|---|
name | atom | A unique identifier for this MFA endpoint. Used to distinguish this endpoint from others in the same resource. |
Options
| Name | Type | Default | Docs |
|---|---|---|---|
request_type | String.t | The PhoenixGenApi request type string used by clients to call this endpoint. Must be unique across all endpoints in the same resource. Example: "my_custom", "batch_process" | |
mfa | any | The MFA tuple to call when this endpoint is invoked. The function is called with predefined_args ++ converted_args ++ info_args: - predefined_args — the third element of this tuple (e.g., []) - converted_args — request arguments (a map when arg_orders is :map, or positional values when arg_orders is a list) - info_args — request info map if request_info is true Example: {MyApp.Interface.Api, :my_custom, []} | |
arg_types | any | Argument types map. Required since there is no Ash action to auto-derive from. Keys are argument name strings, values are PhoenixGenApi type atoms/tuples: - :string - String values - {:string, max_bytes} - String with custom max byte size - :num - Numeric values - :boolean - Boolean values - :datetime - ISO 8601 datetime string, auto-converted to DateTime - :naive_datetime - ISO 8601 datetime string, auto-converted to NaiveDateTime - :map - Generic map - {:map, max_items} - Map with max items constraint - :list - Generic list - {:list, max_items} - List with max items constraint - {:list_string, max_items, max_item_length} - List of strings - {:list_num, max_items} - List of numbers Example: %{"user_id" => :string, "count" => :num, "tags" => {:list_string, 1000, 50}, "metadata" => :map, "sent_at" => :datetime} | |
arg_orders | any | :map | Argument order list, or :map to pass arguments as a map (default). - A list of argument name strings specifies positional ordering. Example: ["user_id", "content", "file_id"] - :map (default) passes arguments as a map with string keys. |
timeout | any | Timeout in milliseconds for the function call. Defaults to the gen_api section-level timeout (which defaults to 5000). Accepts a positive integer or :infinity. | |
response_type | atom | The response mode for this endpoint. - :sync - Client waits for the result - :async - Client receives an ack, then the result later - :stream - Client receives streamed chunks - :none - Fire and forget Defaults to the gen_api section-level response_type (which defaults to :async). | |
request_info | boolean | Whether to pass request info (user_id, device_id, request_id) as the last argument to the MFA function. When true, the MFA will receive a map with %{user_id: ..., device_id: ..., request_id: ...} as the last argument. Defaults to the gen_api section-level request_info (which defaults to true). | |
check_permission | any | Permission check mode for this endpoint. - false - No permission check - :any_authenticated - Requires a valid user_id - {:arg, "arg_name"} - The specified argument must match user_id - {:role, ["admin", "moderator"]} - User must have one of the listed roles Defaults to the gen_api section-level check_permission (which defaults to false). | |
permission_callback | any | A custom callback MFA for permission checking. When set, takes precedence over check_permission. Accepts {Module, :function, []} or nil. The callback function receives request_type (string) and args (map) as arguments and returns true (continue) or false (permission denied). When nil, inherits from the section-level permission_callback. When both permission_callback and check_permission are set, permission_callback takes precedence and is stored as {:callback, {Module, :function, []}} in the FunConfig's check_permission field. Defaults to the gen_api section-level permission_callback (which defaults to nil). | |
choose_node_mode | any | Node selection strategy for this endpoint. Overrides the section-level setting. - :random - Select a random node - :hash - Hash-based selection using request_type - {:hash, key} - Hash-based selection using the specified argument key - :round_robin - Round-robin across nodes | |
nodes | any | Target nodes for this endpoint. Overrides the section-level setting. Can be: - A list of node atoms: [:"node1@host", :"node2@host"] - An MFA tuple: {ClusterHelper, :get_nodes, [:chat]} - :local - Execute on the local node | |
retry | any | Retry configuration when execution fails. - nil (default) - No retry - A positive number n - Equivalent to {:all_nodes, n} - {:same_node, n} - Retry on the same node(s) - {:all_nodes, n} - Retry across all available nodes | |
version | String.t | Version string for this API endpoint. Used for API versioning. Defaults to the gen_api section-level version (which defaults to "0.0.1"). | |
disabled | boolean | false | When true, this endpoint is disabled and will not be included in the generated FunConfig list. Useful for temporarily disabling an endpoint without removing its configuration. |