Skip to main content
Model preferences allow you to easily extend Spree models with configuration options. Thanks to this you can store useful information on Spree models, eg.

Defining Model Preferences

To define a model preference, you need to add them to your model class. Make sure to generate a migration to add the preferences column to the table. This column stores the preferences as JSON.
jsonb is for PostgreSQL, the default database. On MySQL or SQLite use preferences:json. Run the migration.
server/app/models/spree/brand.rb
Any model inheriting from Spree.base_class can declare preferences — the reader and writer methods come with it, so there is nothing to include. The values live in that model’s preferences column.

Declaring the full type

A declaration is also the preference’s contract in the API: Spree turns it into a JSON Schema that the API publishes, the SDKs type and every write is checked against. So a declaration states everything a client needs to know about the value:
server/app/models/my_app/promotion/rules/loyalty_tier.rb
Decimals and amounts travel as exact strings ("9.99"), never as numbers. An :array without of:, a :hash without keys: and values:, or the :any type still works in Spree 6.0, with a deprecation warning and a schema that accepts any value. Spree 6.1 refuses them.

Preferences in the API

Configurable types — promotion rules and actions, calculators, payment methods, integrations, and price, delivery, commission and order routing rules — send their settings as a preferences object next to their type. Each family’s /types endpoint returns the JSON Schema of every registered type’s preferences, extensions included, so a client can render a form for a type it has never seen:
The SDKs also type the settings of every type Spree ships. TypedPromotionRule narrows preferences by type, and an extension adds its own types to PromotionRulePreferencesMap by declaration merging. A write is checked against the schema before anything is saved. An unknown key, a value of the wrong type, a value outside choices, or an id of the wrong model or another store is refused with a 422 whose code is invalid_preferences; details names each failing value by its JSON pointer, such as /preferences/channel_ids/1. A secret reads back masked; sending the masked value back keeps the stored secret, and null clears it.

Exposing a setting in the API

Use this when a record has a fixed set of settings that clients edit field by field, as the store, a channel or an import do. A family of types with different settings per type (rules, calculators, payment methods) sends a preferences object instead, as described above. The API names such a setting by its plain name (featured), never by the preferred_ method. It takes three declarations, one per layer, and each layer stays in charge of its own job. 1. Expose it on the model. exposes_preferences gives each preference a plain reader, writer and predicate:
server/app/models/spree/brand.rb
It refuses a name the model already uses for something else. Errors on preferred_featured are reported to API clients as featured. 2. Return it from the serializer. preference_attributes adds every exposed preference, typed from its declaration, so the generated SDK types follow the model. Name some to add only those, or pass except: to leave some out:
server/app/serializers/spree/api/v3/admin/brand_serializer.rb
3. Accept it in the controller. Exposing a setting does not make it writable: the controller decides, as for any attribute (see request parameters):
server/app/controllers/spree/api/v3/admin/brands_controller.rb
The setting is then read and written like a column:
The Seller API keeps its own list of what sellers may write, so exposing a setting to operators never makes it writable by sellers.
Strings, integers and booleans are cast the way Rails casts attributes ("1", "true" and "on" are true; "42" is 42). A blank value is nil for a nullable: true preference, and the type’s empty value otherwise.

Accessing Model Preferences

Once preferences have been defined for a model, they can be accessed either using the shortcut methods that are generated for each preference or the generic methods that are not specific to a particular preference.

Shortcut Methods

There are several shortcut methods that are generated. They are shown below. Query methods:
Reader methods:
Writer methods:
Remember to run brand.save after setting the preference value to save the changes to the database.
Check if a preference is available:

Generic Methods

When the preference name is only known at runtime, read and write it generically:

Accessing All Preferences

You can get a hash of all stored preferences by accessing the preferences helper:
This hash will contain the value for every preference that has been defined for the model instance, whether the value is the default or one that has been previously stored. Its keys are strings, and it can be read with either strings or symbols (brand.preferences[:featured]). Values are stored as JSON, so read typed values through the preferred_* methods: a :decimal preference comes back as a BigDecimal there, while the raw hash holds its exact string ("9.99"). preferences never holds secrets — see Secret preferences.

Money preferences

Declare an amount of money with the :money type, and a rate or a measure (a percentage, a weight) with :decimal:
server/app/models/spree/calculator/handling_fee.rb
Both are read back as a BigDecimal and take a number or a decimal string ("4.50"); text such as "4,50" is refused rather than misread. The difference is on the API: a :money preference is written with the decimals of the record’s currency preference ("4.50", or "450" in yen), and the dashboard shows it with that currency’s symbol. A money preference on a record with no currency, such as a rule matching an order total in any currency, keeps its exact decimal. An amount with more decimals than its currency has ("4.505" dollars, "450.50" yen) is refused when the preferences are assigned through the API. When the currency lives somewhere other than a currency preference, name its source, another preference or a method of the record:
server/app/models/spree/calculator/handling_fee.rb

Secret preferences

Declare API keys, signing secrets and other credentials with the :password type:
server/app/models/my_app/gateway.rb
A :password preference works like any other — preferred_secret_key, preferred_secret_key = ..., get_preference(:secret_key) — but it is stored apart from the other preferences, in an encrypted secret_preferences column, whenever Active Record encryption keys are configured. The Admin API returns it masked, showing only its last four characters. Payment methods and integrations already have that column. To declare a secret on another model, include Spree::SecretPreferences and add a secret_preferences text column to its table; Spree refuses a :password preference on a model that cannot encrypt it. Always declare a credential as :password. A secret declared as :string, or nested inside a :hash or :array preference, is stored in plain text.

Models with preferences

Around fifty models use preferences, and the pattern is always the same: a family of subclasses that share a table but need different settings. This is why preferences exist rather than columns: Spree::Calculator::FlatRate and Spree::Calculator::TieredPercent are rows in one table with entirely different settings, and neither wants a column the other leaves null.