ash-phoenix

AshPhoenix integration guidelines for using Ash Framework with Phoenix. Use when working with AshPhoenix.Form, creating forms backed by Ash resources, handling nested forms, union types in forms, or integrating Ash actions with Phoenix LiveViews. Covers form creation, validation, submission, and error handling patterns.

16 stars

Best use case

ash-phoenix is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

AshPhoenix integration guidelines for using Ash Framework with Phoenix. Use when working with AshPhoenix.Form, creating forms backed by Ash resources, handling nested forms, union types in forms, or integrating Ash actions with Phoenix LiveViews. Covers form creation, validation, submission, and error handling patterns.

Teams using ash-phoenix should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/ash-phoenix/SKILL.md --create-dirs "https://raw.githubusercontent.com/diegosouzapw/awesome-omni-skill/main/skills/development/ash-phoenix/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/ash-phoenix/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How ash-phoenix Compares

Feature / Agentash-phoenixStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

AshPhoenix integration guidelines for using Ash Framework with Phoenix. Use when working with AshPhoenix.Form, creating forms backed by Ash resources, handling nested forms, union types in forms, or integrating Ash actions with Phoenix LiveViews. Covers form creation, validation, submission, and error handling patterns.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# AshPhoenix Guidelines

AshPhoenix integrates Ash Framework with Phoenix, providing `AshPhoenix.Form` for forms backed by Ash resources.

## Creating Forms

```elixir
# For creating a new resource
form = AshPhoenix.Form.for_create(MyApp.Blog.Post, :create) |> to_form()

# For updating an existing resource
post = MyApp.Blog.get_post!(post_id)
form = AshPhoenix.Form.for_update(post, :update) |> to_form()

# With initial values
form = AshPhoenix.Form.for_create(MyApp.Blog.Post, :create,
  params: %{title: "Draft Title"}
) |> to_form()
```

## Code Interface Forms

Add the `AshPhoenix` extension to domains for `form_to_*` functions:

```elixir
# In domain
use Ash.Domain,
  extensions: [AshPhoenix]

resources do
  resource MyApp.Accounts.User do
    define :register_with_password, args: [:email, :password]
  end
end

# Usage - generates form_to_register_with_password
MyApp.Accounts.form_to_register_with_password(...opts)
```

### Positional Arguments in Forms

By default, `args` from `define` are ignored for forms. Configure in `forms` section:

```elixir
forms do
  form :register_with_password, args: [:email]
end

# Usage
MyApp.Accounts.form_to_register_with_password(email, ...)
```

Use positional arguments for values that shouldn't be editable in the form (e.g., `user_id` on a user-specific page).

## Form Validation & Submission

```elixir
def handle_event("validate", %{"form" => params}, socket) do
  form = AshPhoenix.Form.validate(socket.assigns.form, params)
  {:noreply, assign(socket, :form, form)}
end

def handle_event("submit", %{"form" => params}, socket) do
  case AshPhoenix.Form.submit(socket.assigns.form, params: params) do
    {:ok, post} ->
      socket =
        socket
        |> put_flash(:info, "Post created successfully")
        |> push_navigate(to: ~p"/posts/#{post.id}")
      {:noreply, socket}

    {:error, form} ->
      {:noreply, assign(socket, :form, form)}
  end
end
```

## Nested Forms

If your action has `manage_relationship`, AshPhoenix automatically infers nested forms:

```elixir
# In resource
create :create do
  accept [:name]
  argument :locations, {:array, :map}
  change manage_relationship(:locations, type: :create)
end
```

```heex
<.simple_form for={@form} phx-change="validate" phx-submit="submit">
  <.input field={@form[:name]} />

  <.inputs_for :let={location} field={@form[:locations]}>
    <.input field={location[:name]} />
  </.inputs_for>
</.simple_form>
```

### Adding Nested Forms

```heex
<.button type="button" phx-click="add-form" phx-value-path={@form.name <> "[locations]"}>
  <.icon name="hero-plus" />
</.button>
```

```elixir
def handle_event("add-form", %{"path" => path}, socket) do
  form = AshPhoenix.Form.add_form(socket.assigns.form, path)
  {:noreply, assign(socket, :form, form)}
end
```

### Removing Nested Forms

```heex
<.button type="button" phx-click="remove-form" phx-value-path={location.name}>
  <.icon name="hero-x-mark" />
</.button>
```

```elixir
def handle_event("remove-form", %{"path" => path}, socket) do
  form = AshPhoenix.Form.remove_form(socket.assigns.form, path)
  {:noreply, assign(socket, :form, form)}
end
```

## Union Forms

For union types with different inputs per type:

```heex
<.inputs_for :let={fc} field={@form[:content]}>
  <.input
    field={fc[:_union_type]}
    phx-change="type-changed"
    type="select"
    options={[Normal: "normal", Special: "special"]}
  />

  <%= case fc.params["_union_type"] do %>
    <% "normal" -> %>
      <.input type="text" field={fc[:body]} />
    <% "special" -> %>
      <.input type="text" field={fc[:text]} />
  <% end %>
</.inputs_for>
```

```elixir
def handle_event("type-changed", %{"_target" => path} = params, socket) do
  new_type = get_in(params, path)
  path = :lists.droplast(path)

  form =
    socket.assigns.form
    |> AshPhoenix.Form.remove_form(path)
    |> AshPhoenix.Form.add_form(path, params: %{"_union_type" => new_type})

  {:noreply, assign(socket, :form, form)}
end
```

## Debugging Form Errors

Errors only display when they implement `AshPhoenix.FormData.Error` protocol and have `field`/`fields` set.

```elixir
# See ALL errors (including ones not shown in UI)
AshPhoenix.Form.raw_errors(form, for_path: :all)

# See errors that will be displayed (implement protocol + have fields)
AshPhoenix.Form.errors(form, for_path: :all)
```

For action errors not tied to fields, display with flash messages or notices at form top/bottom.

## Best Practices

1. **Let the Resource guide the UI** - Well-defined resources with validations make AshPhoenix more effective
2. **Use code interfaces** - Define on domains for clean, consistent API
3. **Load before editing** - Use `Ash.load!/2` to load all required relationships before creating update forms

Related Skills

phoenix-github

16
from diegosouzapw/awesome-omni-skill

Manage GitHub issues, labels, and project boards for the Arize-ai/phoenix repository. Use when filing roadmap issues, triaging bugs, applying labels, managing the Phoenix roadmap project board, or querying issue/project state via the GitHub CLI.

arize-phoenix

16
from diegosouzapw/awesome-omni-skill

Open-source AI observability platform for tracing, evaluating, and improving LLM applications with OpenTelemetry integration

bgo

10
from diegosouzapw/awesome-omni-skill

Automates the complete Blender build-go workflow, from building and packaging your extension/add-on to removing old versions, installing, enabling, and launching Blender for quick testing and iteration.

Coding & Development

atlas

16
from diegosouzapw/awesome-omni-skill

macOS-only AppleScript control for the ChatGPT Atlas desktop app. Use only when the user explicitly asks to control Atlas tabs/bookmarks/history on macOS and the "ChatGPT Atlas" app is installed; do not trigger for general browser tasks or non-macOS environments.

atlan-sql-connector-patterns

16
from diegosouzapw/awesome-omni-skill

Select and apply the correct SQL connector implementation pattern (SDK-default minimal or source-specific custom). Use when building or extending SQL metadata/query extraction connectors.

athena-pr-reviewer

16
from diegosouzapw/awesome-omni-skill

PROACTIVELY USED when reviewing a PR, branch, or Jira story. Handles code review against requirements and provides actionable feedback.

athena-framework

16
from diegosouzapw/awesome-omni-skill

Use this skill when working with Athena Framework for Crystal. Athena is a modular ecosystem of independent, reusable components including: Framework (ATH) for web apps, DependencyInjection (ADI) for IoC containers, Routing (ART) for HTTP routing, Serializer (ASR) for object serialization, Validator (AVD) for validation, Console (ACON) for CLI tools, EventDispatcher (AED) for event-driven architecture, and more. Use for building Crystal web applications, REST APIs, CLI tools, or integrating individual components.

atft-code-quality

16
from diegosouzapw/awesome-omni-skill

Enforce lint, formatting, typing, testing, and security hygiene across the ATFT-GAT-FAN codebase.

atcoder-client

16
from diegosouzapw/awesome-omni-skill

Interface with AtCoder for Japanese competitive programming contests

asyncredux-connector-pattern

16
from diegosouzapw/awesome-omni-skill

Implement the Connector pattern for separating smart and dumb widgets. Covers creating StoreConnector widgets, implementing VmFactory and Vm classes, building view-models, and optimizing rebuilds with view-model equality.

Asyncio Programming

16
from diegosouzapw/awesome-omni-skill

Master asynchronous programming with asyncio, async/await, concurrent operations, and async frameworks

asyncio-concurrency-patterns

16
from diegosouzapw/awesome-omni-skill

Complete guide for asyncio concurrency patterns including event loops, coroutines, tasks, futures, async context managers, and performance optimization