One Codebase, Eleven Co-ops: Multi-Tenant Architecture for Short Food Supply Chains
How to run many independent food co-ops as tenants on one platform: what belongs in shared code, what must be tenant configuration, where data isolation between rival co-ops matters, and where shared infrastructure stops working.

Why a food co-op is a harder tenant than a typical SaaS customer
A food co-op is a harder tenant because it is not a department inside a company, it is an independent legal entity with its own members, its own commission vote, its own delivery geography and its own producers. Two tenants on the same platform can be commercial rivals selling overlapping catalogues in overlapping postcodes.
Most multi-tenant SaaS writing assumes tenants are interchangeable: same workflow, different logo, different row of data. That holds for a CRM. It does not hold here. One co-op charges a flat 15% commission on every line; another charges 8% plus a fixed weekly stall fee; a third takes nothing from producers and charges shoppers a basket fee instead. One runs a Tuesday cut-off with Thursday delivery; another runs standing boxes with no cut-off at all. One requires the market committee to approve every new producer; another lets an administrator add a grower in ninety seconds.
None of those are feature requests in the usual sense. They are governance decisions made by member votes, and they change. A tenant that voted for flat commission in March can vote for tiered commission in November, and the platform has to absorb that without a code deployment and without touching the other ten tenants. That constraint, more than data volume or traffic, is what shapes the architecture.
The line between shared code and tenant configuration
The rule that holds up in practice: anything a co-op could vote to change belongs in tenant configuration, and anything that describes how food physically moves belongs in shared code. Commission rates, cut-off times, delivery zones and approval rules are configuration. Order lines, batch traceability, pick-and-pack splits and payout arithmetic are shared.
That split works because the physical reality of a short food supply chain is genuinely identical across tenants. A crate still leaves a farm, still gets consolidated at a hub, still gets split across routes, still needs a batch reference if an inspector asks. Nobody votes on that. What co-ops vote on is money, membership and timing, and those map cleanly onto configurable values rather than branching logic.
The failure mode to avoid is the tenant conditional: an if-statement somewhere in the settlement run that checks which co-op this is. Once one exists, more appear, and within a year the codebase has eleven behaviours nobody can test together. The discipline is to convert every such request into a named, documented setting with a sensible default. When a co-op asks for something that cannot be expressed as a value, that is a signal the underlying model is wrong, not that the tenant is unusual.
- Tenant configuration: commission model and rate, payout schedule, cut-off and delivery day, delivery zones and fees, producer onboarding approval flow, refund and substitution policy, VAT handling, packaging deposit rules, branding and domain
- Shared code: order and order line model, stock declaration mechanics, pick-and-pack split, route assignment, batch traceability chain, settlement arithmetic and rounding, role permissions, audit logging
- Shared but tenant-scoped data: producers, shoppers, catalogue, orders, routes, payouts, reports
Data isolation when two tenants are rivals in the same county
Isolation between independent co-ops has to be enforced at the query layer, not the interface layer. Every query that touches tenant data carries a tenant scope by default, so a missing filter fails closed and returns nothing rather than leaking a neighbouring co-op's producer roster. Hiding a rival's data in the UI while it sits in shared API responses is not isolation.
The commercially sensitive material in a short food supply chain is more specific than people expect. It is not just customer lists. It is which producers a co-op has signed, what commission they agreed to, which postcodes are profitable enough to deliver to, and what the weekly sell-through on a given product looks like. A competing co-op two valleys over would find all of that extremely useful, and some producers sell through more than one co-op, which means the same person exists as a producer record in two tenants with two different price lists. Those records must never reconcile across the boundary.
Practical consequences follow. Search indexes are per-tenant or tenant-filtered at query time. Exports are generated inside a tenant scope and never from a cross-tenant admin view. Background jobs, which are the classic leak point because they run without a user session, carry an explicit tenant context rather than inheriting whatever was last set. And aggregate analytics used to improve the product are derived from anonymised, non-identifying counts, because the moment a benchmark is fine-grained enough to identify one co-op's performance, it stops being a product feature and becomes a disclosure.
Controller and processor boundaries across independent tenants
Under GDPR, each co-op is the data controller for its own shoppers, producers and drivers, and the platform is the processor acting on its instructions. That relationship is per tenant, so eleven co-ops means eleven separate controller relationships, eleven processing agreements and eleven independent sets of retention and deletion decisions.
This is where multi-tenancy stops being an engineering convenience and becomes a legal structure. A subject access request arriving at Co-op A cannot be answered with data from Co-op B, even if the same shopper buys from both. A deletion request is executed inside one tenant only. If a producer sells through two co-ops, they have two controllers, and each one answers separately for the sales history it holds. Retention periods can legitimately differ: one tenant may keep driver location trails for 30 days, another for 7, because their members decided differently.
It also means the platform cannot unilaterally repurpose tenant data. Improving a forecasting model with one co-op's order history is processing on that controller's behalf unless there is a lawful basis and an agreement that covers it. The clean position is that models trained on tenant data either stay inside the tenant or are built from data that has been aggregated past the point of identifiability, with the tenant told which applies.
Where shared infrastructure stops working
Shared infrastructure stops working at four predictable points: legal residency requirements, per-tenant scale that starves the others, customisation that cannot be expressed as configuration, and contractual demands for isolated backups or independent uptime guarantees. Each one is a reason to move a tenant onto dedicated resources, not a reason to fork the codebase.
Residency is the most common. A co-op operating under a public grant or a county partnership may be required to keep personal data in a specific jurisdiction, which means a separate database or a separate deployment in that region. Scale is the second: a tenant running a Saturday market with several thousand simultaneous baskets while another tenant does thirty orders a week creates a genuine noisy-neighbour problem, and the fix is isolating the heavy tenant's database and job queues rather than over-provisioning for everyone.
The third, deep customisation, deserves resistance before capitulation. Most requests that appear to need a fork are governance decisions in disguise and can become settings. But some are real: a tenant that needs an integration with a specific municipal invoicing system, or a payout model that is genuinely different in kind rather than in value. The honest answer there is a per-tenant extension point with a clear contract against the shared core, and a frank conversation about who pays to maintain it. The fourth is contractual. If a tenant wants its own backup schedule, its own restore testing and its own uptime commitment, that is a hosting tier, priced accordingly, and it should be sold as one rather than absorbed quietly.
- Residency: personal data must stay in a named jurisdiction, so the tenant gets its own database or regional deployment
- Scale: one tenant's peak load degrades others, so isolate its database, cache and job queues
- Customisation: the requirement cannot be expressed as a configuration value, so it needs a documented extension point and a maintenance owner
- Contract: dedicated backups, restore testing or an independent SLA, which is a hosting tier with a price, not a favour
Running your own co-op as a tenant on your own platform
The most useful architectural test is being a tenant on your own system. Plodovi.hr runs on Plodie as one tenant among others, with no privileged code path, no special-case branch and the same configuration surface every other co-op gets. If something cannot be configured, we feel it on a Thursday delivery like everyone else.
This changes which shortcuts survive. A tenant conditional written for convenience becomes visible the moment it makes our own settlement run behave differently from a customer's. A missing setting becomes obvious when a commission change requires a developer instead of an administrator. And it keeps the isolation honest, because our own producer roster and sell-through numbers are exactly the kind of data another tenant should never be able to reach, and we have the same interest in that boundary holding as they do.
It also constrains the roadmap in a healthy way. Features that only make sense for one co-op's particular habits get filtered out early, because building them means either polluting the shared core or admitting they belong in configuration. What is left is the part that is genuinely common to short food supply chains: declaration-based stock, multi-producer splits, route-bound delivery and settlement that has to reconcile to the cent, wrapped in enough configurability that eleven independent co-ops can each run the operation their members actually voted for.
Key Takeaways
- Treat anything a co-op could vote to change (commission, cut-off, zones, approval rules) as tenant configuration, and anything describing how food physically moves as shared code.
- Enforce tenant isolation at the query layer so a missing filter fails closed, and give background jobs an explicit tenant context rather than an inherited one.
- Each co-op is its own GDPR controller, so eleven tenants means eleven processing agreements, eleven retention policies and subject requests answered strictly inside one tenant.
- Shared infrastructure stops working at data residency, per-tenant scale, customisation that cannot be a setting, and contractual backup or uptime demands. Each is a hosting or extension decision, not a codebase fork.
- Running your own co-op as an ordinary tenant on the same codebase exposes special-case branches and missing settings faster than any internal review.
For the operator's side of that last point, see how Plodovi.hr drives our innovation, the co-op we run as a live tenant on the same platform our customers use.
Frequently Asked Questions
Should each food co-op get its own database or share one?
Start with a shared database and strict tenant scoping on every query, because eleven separate databases multiply migration, backup and monitoring work without improving safety if the query layer is disciplined. Move a tenant to its own database when there is a concrete trigger: a data residency requirement, load that degrades other tenants, or a contractual demand for isolated backups and restore testing.
How do you handle a producer who sells through two different co-ops on the same platform?
They exist as two separate producer records, one inside each tenant, with independent price lists, commission terms and sales history. The records must not be linked or reconciled across the tenant boundary, because each co-op is a separate data controller for the relationship it holds. In practice the producer logs into each co-op's producer app separately and declares stock for each.
What is the difference between multi-tenant and white-label for food hub software?
White-label usually means branding: your logo, your domain, someone else's fixed workflow. Multi-tenant architecture done properly for co-ops goes further, letting each tenant configure commission model, cut-off times, delivery zones, approval rules and payout schedules against the same shared code. Branding is the easy part. Governance configurability is the part that decides whether a co-op can actually run its own rules.
Can a platform use data from all its co-op tenants to build better forecasting or pricing models?
Only with a lawful basis and an agreement that covers it, because each co-op is the controller of its own data and the platform is a processor acting on instructions. The clean approaches are models trained and used strictly inside a single tenant, or models built from data aggregated past the point where any individual co-op, producer or shopper is identifiable. Tenants should be told explicitly which applies.
How do you roll out a change when eleven co-ops have different rules?
Ship the change behind a configuration default that preserves existing behaviour, so no tenant sees a difference until an administrator opts in. Test against the full matrix of tenant settings rather than a single reference configuration, since the risk lives in combinations, such as a tiered commission tenant with a packaging deposit and a basket fee. Announce changes per tenant in their own operational language, not as a generic release note.


