🚀 Native Multi-Tenancy Support: Global store_id Scoping & Context Switching #14247
iguzelofficial
started this conversation in
Feature Requests
Replies: 1 comment 2 replies
-
Beta Was this translation helpful? Give feedback.
2 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
🚀 Context & The Problem
We are currently architecting a SaaS solution using Medusa v2. While the current architecture supports Marketplace logic (where multiple vendors exist under one main umbrella), it lacks a native, robust Multi-Tenant structure where data is strictly isolated at the database and API level based on a store context.
Currently, to achieve a true SaaS (Multi-Tenant) structure where a single Medusa instance serves multiple isolated stores (tenants), we face a significant architectural hurdle:
The "20x Workload" Path: We would need to manually extend almost every core entity (Products, Orders, Customers, Carts, etc.) to include a store_id. Consequently, we would have to override existing Services and API endpoints to manually inject and validate this store_id. This creates a massive maintenance burden and breaks compatibility with future core updates.
The "Marketplace" Module Limit: Existing marketplace modules focus on vendor management, not necessarily full data isolation required for a SaaS where Store A should allow absolutely no leakage or cross-access to Store B's data at the core level.
💡 The Proposal: Core-Level store_id Implementation
We propose that the Medusa Core team considers implementing a native Multi-Tenancy Layer. If the core architecture supports a global store_id scope, the implementation effort for adopters drops significantly (from ~20x to ~4x), allowing us to focus on business logic rather than boilerplate isolation.
Key Architectural Requirements:
Database Schema: Add a store_id column to all primary entities (or a generic relationship capability in the DAL) by default.
Context Switching: A middleware or guard that identifies the requested store (via Header, Subdomain, or JWT payload) and sets a Global Context.
Scoped Data Access: The ORM/Service layer should automatically append .where({ store_id: current_context_store_id }) to queries if a store context is active.
📊 Visualizing the Architecture
Here is the proposed flow versus the manual overhead:
In this model, the "Store Context" is resolved before hitting the service layer. The Service Layer then implicitly understands which store it is talking to.
Architecture Note: As illustrated above, for a true multi-tenant system, the store_id scope must strictly extend beyond just catalog data. It requires inclusion in User Management (Credentials), API Keys, Sales Channels, and Regions. Without this comprehensive coverage at the core schema level, developers are forced to build "shadow tables" or complex middleware for every single module, which is the primary source of the "20x workload" mentioned above.
🎯 Benefits & Impact
Drastic Reduction in Boilerplate: Developers won't need to override every list or retrieve method just to add a .where clause.
Security: Reduces the risk of data leakage. If the context is set globally, a developer cannot accidentally forget to filter by store in a custom endpoint.
Ecosystem Growth: This enables a new wave of Medusa-based SaaS platforms (Shopify-like clones, Industry-specific e-commerce builders) to be built rapidly.
We believe this architectural shift would make Medusa the go-to framework not just for single brands or marketplaces, but for Headless Commerce-as-a-Service platforms.
Is this something the core team has on the roadmap, or can we open a discussion on how the community might contribute to this "Store Context" implementation in the core?
Additional Consideration: The Plugin Ecosystem
1. Context-Aware Plugin Configuration & Dynamic Provider Resolution
Currently, Medusa plugins (e.g., Stripe, SendGrid, Meilisearch) rely on static configuration via medusa-config.js or environment variables loaded at startup. This "Singleton" approach works for single stores but fails for Multi-Tenant SaaS architectures where:
Store A uses its own Stripe Account.
Store B uses a completely different Stripe Account.
Store C uses a different email provider (e.g., Mailgun instead of SendGrid).
The Limitation: Since plugins are initialized globally with a single API key, we cannot currently support "Bring Your Own Key" (BYOK) scenarios for tenants without significant hacks or creating a proxy service for every single provider.
The Proposal: We propose extending the Module Loader logic to support Dynamic Provider Resolution. Instead of loading credentials from process.env, the core should allow looking up configuration from the database based on the active store_id context.
2. Implementation & Action Plan
Phase 1: Schema Extension (store_plugin_settings)
We need a standardized table to store encrypted credentials per store.
Action: Create table store_provider_config.
Columns:
Phase 2: Context-Aware Module Loader
The Core Modules (Payment, Notification, Fulfillment) need to be updated to accept a context when resolving the provider.
Current Flow: PaymentService -> this.stripeProvider (Initialized once).
Proposed Flow: PaymentService -> getProvider(context.store_id) -> Initialize new instance with DB credentials -> Process Payment.
Phase 3: Admin UI for Configuration
Action: Add a "Settings > Integrations" section in the Admin Dashboard.
Logic: Allow Tenant Admins to input their own API Keys, which are then encrypted and saved to store_provider_config.
Diagram A: The Isolation Architecture (Flowchart)
Diagram B: Request Lifecycle Sequence (Sequence Diagram)
Beta Was this translation helpful? Give feedback.
All reactions