> ## Documentation Index
> Fetch the complete documentation index at: https://spreecommerce.org/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Model

> Create a custom Brand model in Spree with database columns, a rich text description, and an Active Storage logo upload using a single generator command.

In this step we'll create the `Spree::Brand` model with everything it needs: a `name` column, a rich text `description`, and an uploadable `logo`.

## Step 1: Generate the Model

Spree ships a `spree:model` generator that produces a model and migration following all Spree conventions — `Spree::` namespacing, prefixed IDs, presence validations, and API filtering allowlists. It understands Rails attribute types including the virtual ones, so rich text and file attachments are part of the same command:

<CodeGroup>
  ```bash Spree CLI (Docker) theme={"theme":"night-owl"}
  spree generate model Brand name:string:index description:rich_text logo:attachment
  ```

  ```bash Without Spree CLI theme={"theme":"night-owl"}
  bin/rails g spree:model Brand name:string:index description:rich_text logo:attachment
  ```
</CodeGroup>

<Info>
  Throughout this tutorial every command is shown in two forms. **Spree CLI** is for projects created with [create-spree-app](/developer/create-spree-app/quickstart), where the app runs in Docker and `spree <command>` routes into the container. **Without Spree CLI** is for apps running Spree backend directly on your machine.
</Info>

This creates two files:

* `app/models/spree/brand.rb` — the model
* `db/migrate/XXXXXXXXXXXXXX_create_spree_brands.rb` — the migration

Now apply the migration:

<CodeGroup>
  ```bash Spree CLI (Docker) theme={"theme":"night-owl"}
  spree migrate
  ```

  ```bash Without Spree CLI theme={"theme":"night-owl"}
  bin/rails db:migrate
  ```
</CodeGroup>

This creates the `spree_brands` table with an indexed `name` column. The `description` and `logo` attributes don't add columns — rich text lives in Action Text's `action_text_rich_texts` table, and uploads live in Active Storage's tables.

## Step 2: Understand the Generated Model

Open `app/models/spree/brand.rb`:

```ruby app/models/spree/brand.rb theme={"theme":"night-owl"}
module Spree
  class Brand < Spree.base_class
    has_prefix_id :brand

    has_rich_text :description
    has_one_attached :logo

    validates :name, presence: true

    self.whitelisted_ransackable_attributes = %w[name]
    self.whitelisted_ransackable_associations = %w[]
    self.whitelisted_ransackable_scopes = %w[]
  end
end
```

Line by line:

* **`Spree.base_class`** — inherits all Spree model functionality (multi-store scoping helpers, preferences, and more). The class is namespaced under `Spree::`, so it's available as `Spree::Brand`.
* **`has_prefix_id :brand`** — records get Stripe-style public IDs like `brand_k5nR8xLq`. APIs never expose raw database IDs.
* **`has_rich_text :description`** — formatted content via [Action Text](https://guides.rubyonrails.org/action_text_overview.html).
* **`has_one_attached :logo`** — file uploads via [Active Storage](https://guides.rubyonrails.org/active_storage_overview.html), with image processing and direct-to-storage uploads.
* **`validates :name, presence: true`** — generated automatically for required columns.
* **`whitelisted_ransackable_attributes`** — controls which attributes API clients may filter and sort by. Only allowlisted attributes are queryable, so adding `name` here is what later makes `?q[name_cont]=nike` work.

## Step 3: Try It in the Console

<CodeGroup>
  ```bash Spree CLI (Docker) theme={"theme":"night-owl"}
  spree console
  ```

  ```bash Without Spree CLI theme={"theme":"night-owl"}
  bin/rails console
  ```
</CodeGroup>

```ruby theme={"theme":"night-owl"}
brand = Spree::Brand.create!(name: "Wilson")
brand.prefixed_id           # => "brand_k5nR8xLq"

brand.update!(description: "<h1>Hello</h1><p>World</p>")
brand.description.to_s             # => rendered rich text HTML
brand.description.to_plain_text    # => "Hello World"

Spree::Brand.find_by_prefix_id!("brand_k5nR8xLq")  # lookup by public ID
```

We'll upload the logo through the admin UI in the next step — the model side is already done.

<Tip>
  The generator accepts more attribute types and options — references with auto-resolved class names (`user:belongs_to`), unique indexes (`slug:string:uniq`), soft delete (`--paranoid`), and custom fields support (`--metafields`). Run it with `--help` to see everything.
</Tip>

## Next Step

The model is complete. Now let's give admins a UI to manage brands: [Admin Dashboard](/developer/tutorial/admin).
