Skip to main content
This guide assumes you’ve completed the Model, Admin, and Rich Text tutorials.
In this tutorial, we’ll add a logo image upload to our Brand model. Spree uses Rails Active Storage for handling file attachments, image processing, and storage.

Step 1: Add Attachment to the Model

First, let’s add a logo attachment to the Brand model:
app/models/spree/brand.rb
module Spree
  class Brand < Spree::Base
    has_one_attached :logo

    # ... existing code ...
  end
end

Step 2: Permit the Attachment Attribute

For the admin form to accept the file upload, we need to permit the logo attribute in the controller. Update your brands controller:
app/controllers/spree/admin/brands_controller.rb
module Spree
  module Admin
    class BrandsController < ResourceController
      private

      def permitted_resource_params
        params.require(:brand).permit(
          :name,
          :description,
          :logo
        )
      end
    end
  end
end

Step 3: Add File Upload Field to the Admin Form

Now let’s add the file upload field to the brand form. Spree provides a spree_file_field helper that handles everything including:
  • Drag and drop upload
  • Image preview
  • Direct upload to storage
  • Optional image cropping
Update your brand form partial:
app/views/spree/admin/brands/_form.html.erb
<div class="card mb-4">
  <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 %>
    <%= f.spree_rich_text_area :description %>
  </div>
</div>

<div class="card mb-4">
  <div class="card-header">
    <h5 class="card-title"><%= Spree.t(:logo) %></h5>
  </div>
  <div class="card-body">
    <%= f.spree_file_field :logo, width: 300, height: 300 %>
  </div>
</div>

Add cropping functionality

To enable cropping functionality, add the crop: true option to the spree_file_field helper.

Step 4: Display the Logo in the Index Table

Let’s show a thumbnail of the brand logo in the admin index view. Update the table header partial:
app/views/spree/admin/brands/_table_header.html.erb
<tr>
  <th><%= Spree.t(:logo) %></th>
  <th><%= Spree.t(:name) %></th>
  <th></th>
</tr>
Update the table row partial to display the logo:
app/views/spree/admin/brands/_table_row.html.erb
<tr id="<%= spree_dom_id brand %>" class="cursor-pointer" data-controller="row-link">
  <td data-action="click->row-link#openLink" class="w-70">
    <div class="d-flex align-items-center gap-2">
      <% if brand.logo.attached? %>
        <%= spree_image_tag brand.logo, width: 48, height: 48, class: 'rounded' %>
      <% else %>
        <div class="bg-light rounded d-flex align-items-center justify-content-center" style="width: 48px; height: 48px;">
          <%= icon('photo-off', class: 'text-muted') %>
        </div>
      <% end %>
      <span class="font-weight-bold"><%= brand.name %></span>
    </div>
  </td>
  <td data-action="click->row-link#openLink" class="w-20">
    <%= spree_date(brand.created_at) %>
  </td>
  <td class="actions w-10">
    <%= link_to_edit(brand, data: { row_link_target: :link, turbo_frame: '_top' }) if can? :edit, brand %>
  </td>
</tr>