# Building a multi-tenant SaaS hub in Go: CircleXO on ToGO

> How CircleXO, a hub with one account, one bill and one MCP link for a family of business apps, is built on ToGO: Postgres row-level security per organization, an app contract, and MCP federation.

*2026-10-19T12:49:05Z*

# Building a multi-tenant SaaS hub in Go: CircleXO on ToGO

CircleXO is a hub for business apps: one account, one organization, one bill and one MCP link for a family of products (Mahaam, Moharrik, Hosbah, Zekra and more). It is a **multi-tenant SaaS in Go**. Every request belongs to an organization, and the database must never show one organization's rows to another.

This case study covers how the live hub at [circlexo.com](https://circlexo.com/?utm_source=to-go.dev&utm_medium=case-study) is built on [ToGO](https://github.com/togo-framework/togo): what ToGO provides, what the hub had to build itself, and what was hard. The CircleXO repository is private, so this page describes the architecture in prose and has no code excerpts.

## The problem

Each product in the family had its own sign-up, its own users table and its own billing. A customer using three of them had three accounts and three invoices. Their AI agents also needed three MCP endpoints and three tokens.

The hub had to provide:

- **One identity.** Sign in once (OpenID Connect) and land in any app already signed in.
- **Organizations as tenants.** Members, roles and subscriptions belong to an organization, and every query is scoped to one.
- **An app contract.** Each app describes itself in a manifest: URLs, OIDC redirect URIs, lifecycle hooks (provision, deprovision, health), the webhook events it wants and its MCP tools.
- **MCP federation.** One MCP link for the organization. The hub forwards each tool call to the app that owns the tool, and only if the organization is entitled to that app.

## Why ToGO

CircleXO's API is Go and its web app is React. That is exactly ToGO's shape: a Go API (Huma for REST/OpenAPI, gqlgen for GraphQL), sqlc for typed queries, Atlas-format SQL migrations, and a React frontend in one repo. The hub didn't have to choose and wire those pieces itself. The first commit (24 Aug 2026) is literally "CircleXO rebuild: blueprint + ToGo phase-0 scaffold".

The second reason is the plugin model. Every capability the hub shares with the other products is already a ToGO plugin, and Mahaam, Moharrik, Hosbah and Zekra run on the same plugins. One mental model covers the whole family.

## Architecture

**ToGO plugins in use.** These are imported in `internal/plugins/plugins.gen.go`, the file ToGO generates and `togo install` manages:

| Plugin | What the hub uses it for |
|---|---|
| `auth` (+ `auth-dev` for local sign-in) | sessions and the auth middleware |
| `db-postgres` | the Postgres driver |
| `cache` | short-lived lookups |
| `queue` | background work |
| `realtime` | live updates to the web app |
| `storage` | uploaded media |
| `i18n` | English and Arabic |

All of them sit on the ToGO kernel v0.21.0, as pinned in `go.mod`.

**What the hub builds on top.** The hub's own packages live under `internal/hub/`:

- **identity:** `accounts`, `orgs`, `profile`, and `oidc` (the hub is its own OpenID Connect provider)
- **commerce:** `billing`, `store`, `registry` (app publishers and versions)
- **federation:** `gateway`, plus MCP servers for administration (`adminmcp`), data (`datamcp`) and onboarding journeys (`journeymcp`)
- **tenancy:** `hubdb`

**Tenancy with Postgres row-level security.** The hub doesn't rely on every query remembering a `WHERE org_id = …`. `hubdb` opens an org-scoped transaction and sets three transaction-local settings with `set_config(…, true)`: the organization, the user, and a system flag. The tables' row-level-security policies compare each row against those settings. Because the settings are transaction-local, a pooled connection cannot carry one tenant's context into the next request.

**The app contract.** Each app ships a `circlexo.app.yaml` (manifest v1), checked against a JSON Schema. The hub's CLI validates a manifest with `circlexo app validate`. The repository carries manifests for 14 first-party apps. A manifest declares the MCP endpoint, its transport (streamable HTTP), how it authenticates (token exchange) and the effect of each tool (read or write). The gateway uses those effects in its policy.

**MCP federation.** The gateway builds a per-organization catalog: which apps the organization can reach and which tools each one exposes. It caches tool schemas by the app's version and writes every forwarded call to an audit log. An agent gets one MCP link and sees only the tools its organization is entitled to.

## Numbers

These come from the repository on 19 Oct 2026, nothing else:

- **8 ToGO plugins** imported (the table above), on ToGO v0.21.0. Counted from `plugins.gen.go`.
- **57** hub migration files in `db/hub/migrations`; **31** of them mention row-level security.
- **14** first-party app manifests.
- **3** hub-side MCP servers (admin, data, journeys), plus the federating gateway.
- **236** commits since the first scaffold commit on 24 Aug 2026.

There are no traffic or customer numbers here because none are published.

## What was hard, and where ToGO helped

**Tenancy is the app's job, not the framework's.** ToGO's `auth` plugin tells you who the user is. It does not decide which organization's rows a query may see. The hub wrote that layer itself (`hubdb`), and the hard part was making it impossible to forget: org-scoped transactions plus row-level security, so a missing filter fails closed. ToGO's part was to stay out of the way. Queries and transactions are plain Go `database/sql`, so the scoping wraps them without fighting an ORM.

**Two schemas, one command.** `togo migrate` runs the app's `cmd/migrate`. In CircleXO that command applies the base app schema and then, when a hub database is configured, the hub's own versioned migrations. Extending the framework's migrate step was a few lines in one file, not a fork.

**The plugin set made the family consistent.** The products already speak the same `auth`, `queue`, `realtime` and `i18n` plugins. So "add this app to the hub" became a manifest plus a handful of endpoints (provision, deprovision, health, events), not a re-architecture.

**Honest limit.** CircleXO is in transition. A next version is being designed as a single ToGO monorepo, with the hub and each app built as its own binary. This study describes the hub that runs today.

## Links

- CircleXO: [circlexo.com](https://circlexo.com/?utm_source=to-go.dev&utm_medium=case-study)
- ToGO on GitHub: [github.com/togo-framework/togo](https://github.com/togo-framework/togo)
- Every product built on ToGO: [the ToGO showcase](https://to-go.dev/en/showcase)
- Other case studies: [SeatFor](https://to-go.dev/en/blog/seatfor-togo-case-study), [Zekra](https://to-go.dev/en/blog/zekra-togo-case-study), [Mahaam](https://to-go.dev/en/blog/mahaam-togo-case-study), [Moharrik](https://to-go.dev/en/blog/moharrik-togo-case-study), [Hosbah](https://to-go.dev/en/blog/hosbah-togo-case-study)

## FAQ

**Is CircleXO open source?**
No. The CircleXO repository is private, which is why this case study has no code excerpts. ToGO itself is open source.

**Does ToGO handle multi-tenancy for you?**
No. ToGO gives you auth, the database driver, queues, realtime and the rest as plugins. Tenant isolation is application code. CircleXO does it with org-scoped transactions and Postgres row-level security.

**Why row-level security instead of a WHERE clause in every query?**
A forgotten WHERE clause leaks data. A row-level-security policy fails closed: without the organization set on the transaction, the query sees no tenant rows.

**What is MCP federation?**
One MCP endpoint per organization. The hub forwards each tool call to the app that owns the tool, checks the organization's entitlement first, and logs the call.

**Which ToGO version does CircleXO use?**
The kernel is pinned at v0.21.0 in `go.mod`.

---

Source: https://to-go.dev/en/blog/circlexo-togo-case-study
