Overview
A digital asset provider decides how a digital asset’s deliverable is produced at download time. Spree ships with one built-in provider,Spree::DigitalAssetProvider::File, which serves an uploaded file from private storage through a short-lived signed URL. That is the default: every asset with a blank provider_type uses it, so an uploaded file is simply the provider each asset already had.
A custom provider replaces that upload with something you produce on demand — a license key minted by your billing system, an entitlement granted by internal software, a signed link to a file that lives on your own host. The customer’s download flows through the same authorized, counted grant either way; only the last step, “hand something over”, changes.
Reach for a provider when the deliverable comes from outside Spree. If you only need to serve a file that a merchant uploads, the built-in File provider already does that — you don’t need to build anything.
What you will build
Two pieces:
Unlike delivery-rate or tax providers, a digital asset provider does not resolve credentials through a
Spree::Integration. It is host-app glue into your own software and owns its own configuration — an environment variable, an internal endpoint, a shared credential. The base gives it only the asset.
1. Implement the provider
SubclassSpree::DigitalAssetProvider::Base and implement #deliver. It receives the authorized Spree::DigitalLink (the customer’s download grant) and returns a Spree::DigitalDelivery.
app/models/spree/digital_asset_provider/license_key.rb
Spree::DigitalDelivery carries exactly one of two shapes:
- A redirect — set
redirect_url. The customer is sent to it (an external file host, a signed storage URL). This is whatFilereturns. - An inline body — set
inline_valueandcontent_type. The value is rendered directly as the response body — a license key astext/plain, a code image asimage/png.
nil) to mean “nothing to hand over”. The download controller treats that as a failure and refuses the download without spending the customer’s allowance — see The download contract below.
Per-asset settings
Some providers need a value that differs from one asset to the next — which license pool this asset draws from, which external product it maps to. Declare each as asetting, and the dashboard renders a small form when the merchant adds an asset backed by this provider:
:string, :number, :boolean, and :select (pass in: for the choices). Your declarations become the provider’s settings_schema, which the admin providers endpoint exposes so the dashboard can render the form. The merchant’s answers are stored on the asset — under one key, metadata['provider'], so they never collide with other developer metadata — and read back through digital_asset.provider_settings, a plain hash keyed by the setting name:
2. Register the provider
config/initializers/spree.rb
provider_type set and no file attached.
Registration is what makes a
provider_type valid. An asset validates that its provider_type names a registered provider, so an unregistered or misspelled class name is rejected with a 422 rather than failing at download time. A blank provider_type is always the built-in File provider.The download contract
Every download follows the same order, and a provider only participates in the last step:- Check the grant — is it still authorizable (attempts left, not reset)?
- Check the window — is the signed-URL lifetime still positive?
- Produce the deliverable — call your
#deliver. This is fallible and must not charge anything. - Charge the grant — increment the download counter under a lock.
- Deliver — redirect, or render the inline body.
#deliver free of side effects that assume the download will succeed.
Provider contract reference
DigitalDelivery
expires_in is the lifetime the store allows for any signed URL you build. It is already clamped to the store’s cap, so pass it straight through to whatever signs your link — never widen it.
Related documentation
- Digital products — what the feature does for merchants
- Products — how digital assets, links, and downloads fit together
- Build a Custom Delivery Rate Provider — a provider strategy that does use an integration, for contrast

