docs/developer/admin/form-builder.mdx
Spree provides a custom Spree::Admin::FormBuilder that extends Rails' standard FormBuilder with additional helper methods designed specifically for the admin interface. This form builder ensures consistent styling, error handling, and behavior across all admin forms.
The FormBuilder is automatically available in admin forms when using form_with:
<%= form_with model: [:admin, @product] do |f| %>
<%= f.spree_text_field :name %>
<%= f.spree_text_area :description %>
<% end %>
All FormBuilder methods support these common options:
| Option | Type | Description |
|---|---|---|
:label | String or false | Custom label text, or false to hide the label entirely |
:required | Boolean | Adds a red asterisk (*) indicator next to the label |
:help | String | Help text displayed below the field |
:help_bubble | String | Tooltip text displayed in a help bubble icon next to the label |
:class | String | Additional CSS classes for the input element |
Creates a text input field with Spree styling.
<%= f.spree_text_field :name %>
<%= f.spree_text_field :sku, required: true %>
<%= f.spree_text_field :code, help: "Leave blank to auto-generate" %>
<%= f.spree_text_field :slug, help_bubble: "URL-friendly identifier" %>
Options:
text_field optionsCreates a number input field with Spree styling.
<%= f.spree_number_field :price, required: true, step: 0.01 %>
<%= f.spree_number_field :quantity, min: 0 %>
Options:
number_field options (:min, :max, :step)Creates a locale-aware money/currency input field with automatic formatting. This field displays amounts in the currency's locale format (e.g., 1.234,56 for EUR, 1,234.56 for USD) and normalizes values to standard decimal format before form submission.
<%= f.spree_money_field :amount, currency: 'USD' %>
<%= f.spree_money_field :price, currency: 'EUR', required: true %>
<%= f.spree_money_field :compare_at_amount, currency: @product.currency, label: false %>
Options:
| Option | Type | Description |
|---|---|---|
:currency | String | Currency code (e.g., 'USD', 'EUR', 'PLN'). Used to determine decimal/thousands separators and display the currency symbol |
:value | BigDecimal, Float, String | Override the displayed value (will be formatted for the currency's locale) |
:prepend | String | Text to prepend before the input field |
:append | String | Text to append after the input field (defaults to currency symbol when :currency is provided) |
:disabled | Boolean | Disable the input field |
All common FormBuilder options (:label, :required, :help, :help_bubble) are also supported.
Features:
. as decimal separator) before form submission:currency option is providedinputmode="decimal" for appropriate mobile keyboardExample with nested form:
<%= f.fields_for :prices do |price_form| %>
<%= price_form.spree_money_field :amount,
currency: price_form.object.currency,
disabled: !can?(:manage, price_form.object),
label: false %>
<% end %>
Creates an email input field with Spree styling and HTML5 validation.
<%= f.spree_email_field :email, required: true %>
<%= f.spree_email_field :contact_email,
help: "For customer support inquiries" %>
Options:
email_field optionsCreates a date picker input field.
<%= f.spree_date_field :available_on %>
<%= f.spree_date_field :discontinue_on,
help: "Date when product will be discontinued" %>
Options:
date_field options (:min, :max)Creates a datetime picker input field.
<%= f.spree_datetime_field :published_at %>
<%= f.spree_datetime_field :sale_starts_at, required: true %>
Options:
datetime_field optionsCreates a textarea with auto-grow functionality (expands as you type).
<%= f.spree_text_area :description %>
<%= f.spree_text_area :notes, rows: 10 %>
<%= f.spree_text_area :meta_description,
help: "Used for SEO, maximum 160 characters" %>
Options:
:rows - Number of visible rows (default: 5):data - Data attributes (default includes auto-grow Stimulus controller)text_area optionsAuto-grow behavior:
By default, the textarea automatically grows as the user types. This uses the textarea-autogrow Stimulus controller.
Creates a rich text editor using Trix.
<%= f.spree_rich_text_area :description %>
<%= f.spree_rich_text_area :content, label: "Page Content" %>
Features:
Creates a select dropdown field.
<%= f.spree_select :status,
['draft', 'active', 'archived'],
{},
{} %>
<%= f.spree_select :tax_category_id,
Spree::Category.pluck(:name, :id),
{ include_blank: 'Select Tax Category' } %>
With autocomplete:
<%= f.spree_select :country_id,
Spree::Country.pluck(:name, :id),
{ autocomplete: true } %>
Parameters:
method - The attribute namechoices - Array of optionsoptions - Select options (:include_blank, :prompt, :autocomplete)html_options - HTML attributes (:class, :data, :disabled)Options:
:autocomplete - Enables searchable dropdown with autocomplete functionalityselect optionsCreates a select dropdown from a collection of objects.
<%= f.spree_collection_select :shipping_category_id,
ShippingCategory.all,
:id,
:name,
{ include_blank: true },
{} %>
<%= f.spree_collection_select :tax_category_id,
TaxCategory.all,
:id,
:name,
{ autocomplete: true, required: true },
{} %>
Parameters:
method - The attribute namecollection - ActiveRecord collection or array of objectsvalue_method - Method to call for option value (e.g., :id)text_method - Method to call for option text (e.g., :name)options - Select options (:include_blank, :autocomplete)html_options - HTML attributesOptions:
:autocomplete - Enables searchable dropdowncollection_select optionsCreates a styled checkbox with custom controls.
<%= f.spree_check_box :active %>
<%= f.spree_check_box :featured, label: "Feature on homepage" %>
<%= f.spree_check_box :accept_terms, required: true %>
Options:
check_box optionsStyling: Uses custom control classes for consistent appearance across the admin interface.
Creates a styled radio button with custom controls.
<%= f.spree_radio_button :status, 'draft', id: 'status_draft' %>
<%= f.spree_radio_button :status, 'published', id: 'status_published' %>
Parameters:
method - The attribute nametag_value - The value for this radio optionoptions - Options hash (must include :id)Options:
:id - Required HTML ID for the radio buttonradio_button optionsCreates a file upload field with image preview, drag-and-drop support, and optional cropping functionality.
<%= f.spree_file_field :image %>
<%= f.spree_file_field :logo, width: 240, height: 240 %>
<%= f.spree_file_field :avatar, crop: true %>
<%= f.spree_file_field :featured_image, width: 1200, height: 600, crop: true %>
Options:
| Option | Type | Default | Description |
|---|---|---|---|
:width | Integer | 300 | Width of the image preview in pixels |
:height | Integer | 300 | Height of the image preview in pixels |
:crop | Boolean | false | Enable image cropping with recommended size indicator |
:auto_submit | Boolean | false | Automatically submit form when file is selected |
:can_delete | Boolean | true | Show delete button for removing uploaded image |
:css | String | '' | Additional CSS classes for the upload placeholder area |
:allowed_file_types | Array | Image types | Specify the allowed file types |
All common FormBuilder options (:label, :required, :help, :help_bubble) are also supported.
Features:
Example with all options:
<%= f.spree_file_field :hero_video_background,
width: 1200,
height: 630,
crop: false,
auto_submit: false,
can_delete: true,
help_bubble: "This video will loop in the background",
label: 'Hero video background',
allowed_file_types: %w[video/mp4 video/webm video/ogg] %>
Here's a complete example showing various FormBuilder methods:
<div class="card mb-6">
<div class="card-header">
<h5 class="card-title">
<%= Spree.t(:general_settings) %>
</h5>
</div>
<div class="card-body">
<%= f.spree_text_field :name,
required: true,
help: "Product name as displayed to customers" %>
<%= f.spree_text_field :sku,
label: "SKU",
help_bubble: "Stock Keeping Unit - unique product identifier" %>
<%= f.spree_number_field :price,
required: true,
step: 0.01,
min: 0 %>
<%= f.spree_rich_text_area :description,
help: "Detailed product description with rich formatting" %>
<%= f.spree_collection_select :tax_category_id,
Spree::TaxCategory.all,
:id,
:name,
{ include_blank: 'None', required: true },
{} %>
<%= f.spree_date_field :available_on,
label: "Available Date",
help: "Date when product becomes available for purchase" %>
<%= f.spree_check_box :active,
label: "Active" %>
</div>
</div>
All FormBuilder methods automatically display validation errors below the field when present:
<%= f.spree_text_field :name %>
If @product.errors[:name] contains errors, they will be displayed automatically in red text below the input field.
Labels are automatically translated using Rails I18n. The FormBuilder looks for translations in this order:
:label optionspree.{attribute_name} keyactiverecord.attributes.spree/{model_name}.{attribute_name} keyExample translations:
# config/locales/en.yml
en:
spree:
name: "Product Name"
sku: "SKU Code"
activerecord:
attributes:
spree/product:
available_on: "Available On Date"
discontinue_on: "Discontinuation Date"
All FormBuilder methods use consistent styling:
.form-group div.form-control class.custom-select class.custom-control, .custom-checkbox, .custom-radio classesYou can add additional classes via the :class option:
<%= f.spree_text_field :name, class: 'form-control-lg' %>