# Create Field

> Use when creating custom Backpex field types, implementing the Backpex.Field behaviour, or adding fields to a LiveResource's fields/0 callback.

- Skill: `naymspace/create-field` (Agent Skill)
- Install (CLI): `npx skillmds@latest add naymspace/create-field`
- Raw SKILL.md: https://api.skillmd.com/api/skills/naymspace/create-field/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: naymspace (https://skillmd.com/u/naymspace)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/naymspace/create-field

---


# Creating Backpex Fields

You are an expert at creating fields for Backpex, a Phoenix LiveView admin panel library. When the user wants to add or create a field, follow this process:

1. **Determine if a built-in field works** from the list below
2. **If custom**, generate a module implementing `Backpex.Field`
3. **Wire it into the LiveResource** by updating the `fields/0` callback

## Built-in Field Modules

| Module | Use for |
|--------|---------|
| `Backpex.Fields.Text` | Single-line text inputs |
| `Backpex.Fields.Textarea` | Multi-line text inputs |
| `Backpex.Fields.Number` | Numeric values |
| `Backpex.Fields.Boolean` | Checkboxes / toggles |
| `Backpex.Fields.Select` | Dropdown with static options |
| `Backpex.Fields.MultiSelect` | Multi-value dropdown |
| `Backpex.Fields.Date` | Date picker |
| `Backpex.Fields.DateTime` | Date and time picker |
| `Backpex.Fields.Time` | Time picker |
| `Backpex.Fields.Currency` | Formatted currency values |
| `Backpex.Fields.URL` | URLs with link rendering |
| `Backpex.Fields.Email` | Email addresses |
| `Backpex.Fields.BelongsTo` | belongs_to associations |
| `Backpex.Fields.HasMany` | has_many associations |
| `Backpex.Fields.HasManyThrough` | has_many through associations |
| `Backpex.Fields.InlineCRUD` | Inline editing of embeds_many / has_many |
| `Backpex.Fields.Upload` | File uploads |

## Common Field Options (available on all fields)

| Option | Type | Description |
|--------|------|-------------|
| `module` | atom | **Required.** The field module |
| `label` | string | **Required.** Display label |
| `searchable` | boolean | Enable search on this column |
| `orderable` | boolean | Enable column sorting |
| `visible` | `fn assigns -> bool` | Controls visibility on all views except index |
| `can?` | `fn assigns -> bool` | Controls visibility on all views including index |
| `only` | list | Restrict to specific views: `:new`, `:edit`, `:show`, `:index` |
| `except` | list | Hide from specific views |
| `panel` | atom | Group into a named panel |
| `index_editable` | boolean or `fn assigns -> bool` | Enable inline editing on index |
| `align` | `:left`, `:center`, `:right` | Column alignment on index |
| `align_label` | `:top`, `:center`, `:bottom`, or `fn assigns -> atom` | Label alignment in forms |
| `index_column_class` | string or `fn assigns -> string` | Extra CSS class on index column |
| `render` | `fn assigns -> HEEx` | Override value rendering |
| `render_form` | `fn assigns -> HEEx` | Override form rendering |
| `help_text` | string or `fn assigns -> string` | Text below form input |
| `default` | `fn assigns -> value` | Default value for new items |
| `select` | `dynamic(...)` | Ecto dynamic expression for computed/virtual fields |
| `custom_alias` | atom | Custom alias for the field in queries |
| `translate_error` | `fn {msg, meta} -> {msg, meta}` | Custom error message formatting |

## Creating a Custom Field

Implement `Backpex.Field` with a `@config_schema` for field-specific options.

### Required Callbacks

```elixir
@callback render_value(assigns :: map()) :: %Phoenix.LiveView.Rendered{}
@callback render_form(assigns :: map()) :: %Phoenix.LiveView.Rendered{}
```

`render_value/1` is used on both index and show views. `render_form/1` is used on new and edit views.

### Callbacks With Defaults (overridable)

These are provided by `use Backpex.Field` and can be overridden as needed:

```elixir
@callback render_index_form(assigns)  # For index_editable support (only truly optional callback)
@callback display_field(field)         # Default: returns field name
@callback schema(field, schema)        # Default: returns the schema
@callback association?(field)          # Default: false
@callback assign_uploads(field, socket) # Default: returns socket unchanged
@callback before_changeset(changeset, attrs, metadata, repo, field, assigns) # 6-arity
@callback search_condition(schema_name :: binary(), field_name :: binary(), search_string :: binary()) # Default: ilike
```

### Key Assigns Available in Templates

| Assign | Description |
|--------|-------------|
| `@value` | Current field value |
| `@name` | Field key atom |
| `@field_options` | Merged field options map |
| `@form` | Phoenix.HTML.Form (in form renders) |
| `@item` | The full resource item struct |
| `@live_action` | `:index`, `:edit`, `:new`, or `:show` |
| `@readonly` | Boolean from readonly option |
| `@myself` | LiveComponent reference for phx-target |

### Example Custom Field

```elixir
defmodule MyAppWeb.Fields.ColorPicker do
  @config_schema [
    palette: [
      doc: "List of allowed hex colors.",
      type: {:list, :string}
    ]
  ]

  use Backpex.Field, config_schema: @config_schema

  @impl Backpex.Field
  def render_value(assigns) do
    ~H"""
    <div class="flex items-center gap-2">
      <span class="inline-block h-4 w-4 rounded-full" style={"background-color: #{@value}"}></span>
      <span>{@value}</span>
    </div>
    """
  end

  @impl Backpex.Field
  def render_form(assigns) do
    ~H"""
    <div>
      <Layout.field_container>
        <:label align={Backpex.Field.align_label(@field_options, assigns, :center)}>
          <Layout.input_label for={@form[@name]} text={@field_options[:label]} />
        </:label>
        <BackpexForm.input
          type="color"
          field={@form[@name]}
          translate_error_fun={Backpex.Field.translate_error_fun(@field_options, assigns)}
          help_text={Backpex.Field.help_text(@field_options, assigns)}
          phx-debounce={Backpex.Field.debounce(@field_options, assigns)}
        />
      </Layout.field_container>
    </div>
    """
  end
end
```

### Using it in a LiveResource

```elixir
@impl Backpex.LiveResource
def fields do
  [
    color: %{
      module: MyAppWeb.Fields.ColorPicker,
      label: "Color",
      palette: ["#ff0000", "#00ff00", "#0000ff"]
    }
  ]
end
```

## Declaring Fields in a LiveResource

`fields/0` returns a keyword list. Each key is the Ecto schema field atom, each value is a map of options.

```elixir
@impl Backpex.LiveResource
def fields do
  [
    title: %{
      module: Backpex.Fields.Text,
      label: "Title",
      searchable: true
    },
    body: %{
      module: Backpex.Fields.Textarea,
      label: "Body",
      except: [:index]
    },
    category: %{
      module: Backpex.Fields.BelongsTo,
      label: "Category",
      display_field: :name,
      searchable: true,
      live_resource: MyAppWeb.CategoryLive
    },
    inserted_at: %{
      module: Backpex.Fields.DateTime,
      label: "Created At",
      only: [:index, :show]
    }
  ]
end
```

## Conventions

- **File location**: `lib/my_app_web/fields/<snake_case_name>.ex`
- **Module naming**: `MyAppWeb.Fields.<FieldName>`
- **Always declare `@config_schema`** before `use Backpex.Field` for custom field-specific options
- **Use `Layout.field_container`** and `Layout.input_label`  in `render_form/1` for consistent form layout
- **Use `BackpexForm.input`** for standard input rendering with error handling

