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 thepreferences 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
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 apreferences 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:
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 apreferences 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
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
server/app/controllers/spree/api/v3/admin/brands_controller.rb
- Admin SDK
- cURL
The Seller API keeps its own list of what sellers may write, so exposing a setting to operators never makes it writable by sellers.
"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:Remember to run
brand.save after setting the preference value to save the changes to the database.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 thepreferences helper:
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
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
: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.
