Replicate mix phx.gen.html error display behaviour in Ash

I am trying to replicate the UX which can be done with mix phx.gen.html in a normal Phoenix setup with Ash. It is a bit of a struggle. I can’t find an example.

I have a product:

  attributes do
    uuid_primary_key :id

    attribute :name, :string do
      allow_nil? false

    attribute :price, :integer do
      allow_nil? false
      constraints min: 0

I have a controller with these functions:

  def edit(conn, %{"id" => id}) do
    product = Product.by_id!(id)

    form =
      AshPhoenix.Form.for_update(product, :update, actor: nil, api: App.Shop)

    render(conn, :edit, product: product, form: form)

  def update(conn, %{"form" => product_params, "id" => id}) do
    product = Product.by_id!(id)

    case Product.update(product, product_params) do
      {:ok, product} ->
        |> put_flash(:info, "Product updated successfully.")
        |> redirect(to: ~p"/products/#{product}")

      {:error, %Ash.Error.Invalid{changeset: changeset}} ->
        form =
          AshPhoenix.Form.for_update(, :update, actor: nil, api: App.Shop)

        |> put_flash(:error, "Product could not be updated.")
        |> render(:edit, product:, form: form)

And the template:

  Edit Product <%= %>
  <:subtitle>Use this form to manage product records in your database.</:subtitle>

<.simple_form :let={f} for={@form} action={~p"/products/#{@product}"}>
  <.input field={f[:name]} type="text" label="Name" />
  <.input field={f[:price]} type="number" label="Price" step="any" />
    <.button>Save Product</.button>

<.back navigate={~p"/products"}>Back to products</.back>

In the Phoenix world the form would include this:

  <.error :if={@changeset.action}>
    Oops, something went wrong! Please check the errors below.

And would display the validation errors while doing an update. How can I do this or something similar in Ash?

I think you want something like this:

  <.error :if={@form.submitted_once?}>
    Oops, something went wrong! Please check the errors below.

The if @changeset.action was always a bit of a weird hack IMO. AshPhoenix.Form provides explicit lifecycle information, i.e submitted_once? and just_submitted?.

The additional lifecycle information (changed? and touched_forms don’t make much sense outside of liveview) can be found here: AshPhoenix.Form — ash_phoenix v1.2.19

For the archive: Here’s the source code for the solution: