Loading...
Loading...
Use when building a SaaS / multi-tenant platform on Spree 6 with the Enterprise `spree_multi_tenant` gem — many isolated merchants on one deployment, each with its own stores, catalog, customers and orders. Covers licensing and install (`spree_enterprise:install`, `spree_multi_tenant:install`), `Spree::Tenant` and row-level `tenant_id` isolation, `SpreeMultiTenant::Base`, `with_tenant` / `without_tenant`, console tenant context, `insert_all`/`upsert_all` pitfalls, background jobs, `Spree.root_domain`, cookie domain and host authorization, global staff vs per-tenant customers. Common phrasings include "multi-tenant Spree", "SaaS on Spree", "each merchant gets a subdomain", "tenant isolation", "records leaking between tenants", "set the tenant in the console", "Blocked hosts lvh.me".
npx skill4agent add spree/agent-skills spree-multi-tenantEnterprise only. Multi-tenancy ships in the licensed+spree_enterprisegems (private Keygen gem source). It is not part of open-source Spree. Confirm with Spree which gem release supports your Spree 6 version before planning around it.spree_multi_tenant
| Need | Use |
|---|---|
| Many independent merchants, fully isolated data, one deployment (Shopify-style SaaS) | Multi-tenant (this skill) |
| One merchant running several storefronts that share a catalog/customers | Multiple |
| Many sellers listing products under one shared storefront | Marketplace — see |
Spree::Tenanttenant_idacts_as_tenantSpree.base_classSpreeMultiTenant::BaseSpree.base_classSpreeMultiTenant::Basespree_base_uniqueness_scope[:tenant_id]WHERE tenant_id = ?tenant_idstore1.example.comstore.tenant_idcurrent_tenantSpreeMultiTenant.current_tenantSpree::Store.defaultKEYGEN_ACCOUNT_IDKEYGEN_LICENSE_KEYspree ejectserver/Gemfilesource "https://license:#{ENV['KEYGEN_LICENSE_KEY']}@rubygems.pkg.keygen.sh/#{ENV['KEYGEN_ACCOUNT_ID']}" do
gem 'spree_enterprise'
gem 'spree_multi_tenant'
endbundle installserver/DockerfileRUN --mount=type=secret,id=keygen_license_key,env=KEYGEN_LICENSE_KEY ...ARGENVspree bundle installbundle installspree generate spree_enterprise:install && spree generate spree_multi_tenant:install
# classic: bin/rails g spree_enterprise:install && bin/rails g spree_multi_tenant:installconfig/routes.rbSpreeMultiTenant::CustomerUserConcernSpree::Base# server/config/initializers/spree.rb
Spree.root_domain = ENV.fetch('SPREE_ROOT_DOMAIN', 'lvh.me')# server/.env
SPREE_ROOT_DOMAIN=lvh.me # local dev — NOT localhost (browsers reject localhost as a cookie domain)
COOKIE_TLD_LENGTH=2 # labels in the root domain: example.com=2, shop.example.co.uk=3COOKIE_TLD_LENGTHapp.<root>ActionController::InvalidAuthenticityToken# server/config/environments/development.rb (and production equivalent)
if (root_domain = ENV['SPREE_ROOT_DOMAIN']).present?
config.hosts << ".#{root_domain}"
endSpreeMultiTenant::CustomerUserConcern:validatable:validatableconfig/initializers/spree_multi_tenant.rbSpreeMultiTenant::Config[:app_subdomain] = 'app' # app.example.com — signup at /tenants/new
SpreeMultiTenant::Config[:mail_from_name] = ENV.fetch('MAIL_FROM_NAME', 'Your SaaS')
SpreeMultiTenant::Config[:mail_from_address] = ENV.fetch('MAIL_FROM_ADDRESS', "support@#{Spree.root_domain}")mail_from_*Reply-ToSpreeMultiTenant::BaseSpree.base_classtenant_idbelongs_to :tenantspree generate model Spree::Brand tenant:references name:string --parent SpreeMultiTenant::Basespree generate api_resourcespree:modeltenant_idtenant_id[:tenant_id, :slug]SpreeMultiTenant.with_tenant(tenant) do
# everything here is scoped to tenant
end
SpreeMultiTenant.without_tenant do
# cross-tenant: platform billing, global reports, maintenance
endrails runnerSpreeMultiTenant.current_tenant = Spree::Store.find_by(code: 'my_store').tenant
Spree::Current.store = Spree::Store.find_by(code: 'my_store') # Spree 6 code also expects a current storeSpree::Seeds::StoreResources.call(store: store)Spree::Stores::ProvisionDefaults.call(store:, country:)Spree::SampleData::LoadJob.perform_later(store.id)spree-data-modelwithout_tenantSpree::Tenant.find_each do |tenant|
SpreeMultiTenant.with_tenant(tenant) { MyApp::NightlySyncJob.perform_later }
endinsert_allupsert_alltenant_idSpree::Store.defaultSpree::Store.firstcurrent_storeSpree::Current.storetenant_idlocalhostlvh.melocaltest.mebundle installwithout_tenantinsert_alltenant_idnode_modules/@spree/docs/dist/developer/multi-tenant/quickstart.mdnode_modules/@spree/docs/dist/developer/multi-tenant/core-concepts.mdnode_modules/@spree/docs/dist/developer/multi-tenant/configuration.mdspree-data-modelspree-marketplacespree-deploymentspree-security