# Multi-Tenant SaaS in Go on ToGO: The SeatFor Case Study

> How SeatFor, a booking platform where every business gets its own site, admin panel and app, is built on ToGO: plugins, Postgres row-level security, MCP tools, and what was hard.

*2026-10-19T12:48:48Z*

# Multi-tenant SaaS in Go on ToGO: the SeatFor case study

SeatFor is a booking platform where every business gets its own booking site, admin panel, optional custom domain and mobile app. This is how that multi-tenant SaaS in Go is put together on ToGO, with counts taken from the code and the limits stated plainly. The repository is private, so this is architecture in prose, with no code listings.

## 1. The problem

Appointment businesses (spas, salons, clinics, gyms) need online booking that never double-books, a site under their own name, customers and loyalty, inventory and point of sale, and an app. Building that once per business does not scale. Building it once for many businesses means tenant isolation has to be correct everywhere, including in code paths nobody remembered to check.

SeatFor started as a single-business booking app and became a multi-tenant platform. The isolation had to be added without rewriting the domain.

## 2. Why ToGO

- **One Go API, REST and GraphQL, from the same schema.** `togo generate` produces typed queries (sqlc), GraphQL (gqlgen), migrations (Atlas) and an OpenAPI document. The web and mobile clients get a typed client from that document.
- **A microkernel with plugins.** Auth, cache, queue, realtime, storage, i18n and the Postgres driver are plugins, not hand-rolled glue.
- **A familiar workflow.** `togo make:resource`, `togo migrate` and `togo serve` keep the daily loop short.

SeatFor pins ToGO v0.21.0 (from `go.mod`).

## 3. Architecture

**Plugins.** `internal/plugins/plugins.gen.go` blank-imports eight ToGO plugins: auth, auth-dev, cache, db-postgres, i18n, queue, realtime and storage. `go.mod` also pulls in the orm, validation, faker, settings and providers modules.

**API.** A chi router with Huma for REST and OpenAPI 3.1, and gqlgen for GraphQL. `togo.yaml` exposes `/api`, `/graphql` and `/docs`.

**Tenancy.** The tenant is resolved from the request host: a business lives at its own subdomain, or at its own domain once linked. The default database pool connects as a restricted Postgres role under row-level security and sets the tenant for each statement. With no tenant on the context, queries see nothing: it fails closed. Deliberately cross-tenant work (host resolution, the reminder sweep, the operator console) uses a separate platform pool, explicitly. The project rule is that a `WHERE tenant_id` clause is never a substitute for the policy.

**Entitlements.** Features and quotas per business are rules in the database. Monthly quotas for new bookings and customers are enforced by BEFORE INSERT triggers, so no write path can skip them. The API turns the refusals into typed errors, and feature toggles are enforced at the REST edge.

**Booking.** One package holds slot calculation, a transition state machine, checkout, invoices and loyalty points, with golden-file tests where regressions are expensive.

**Events and notifications.** An in-process events hub feeds realtime updates. A notify package composes reminders and push messages, with a reminder sweep across tenants.

**MCP.** `internal/mcp` exposes meeting tools to agents: list meeting types, list and get meetings, create and cancel a meeting, and set availability.

**Clients.** A Next.js 16 web app (Arabic and English, with RTL) and a React Native mobile app, both built against the same OpenAPI document.

## 4. Code

The repository is private, so there are no snippets. The interesting parts are small: a host resolver, a pool wrapper that sets the tenant per statement, and the policies in the schema files.

## 5. Numbers (counted from the repo)

| What | Count | How |
|---|---|---|
| ToGO plugins blank-imported | 8 | `internal/plugins/plugins.gen.go` |
| MCP tools registered | 6 | tool entries in `internal/mcp/tools.go` |
| Numbered schema files | 42 | `internal/db/schema/*.sql` |
| Go test files (excluding web) | 79 | `*_test.go` count |
| ToGO version | v0.21.0 | `go.mod` |

There are no user, revenue or performance numbers here, because none were measured for this article.

## 6. What was hard, and how ToGO helped

- **Isolation you can trust.** Putting tenancy in the database, not in each query, was the decision that mattered. ToGO did not do it for us: row-level security, the host resolver and the pool wrapper are application code. ToGO gave us a stable place to put them, and a generated data layer that flows through the same pool.
- **Cross-tenant jobs.** Reminders and operator tools must see every tenant, so they use an explicit platform pool. Keeping that list short and visible takes discipline.
- **Retrofitting.** Going from one business to many means every test that writes shared rows has to take a lock, because packages run in parallel against one Postgres.
- **Codegen cost.** The generate step (sqlc, gqlgen, Atlas, OpenAPI) is convenient and also one more thing to keep in sync. A schema check runs before generate, migrate and test.
- **ToGO is 0.x.** Several plugins are at 0.1 to 0.3, so we pin versions and expect breaking changes.

## FAQ

### Can you build a multi-tenant SaaS in Go with ToGO?
Yes. SeatFor is one. The tenant comes from the request host, and Postgres row-level security isolates the data.

### Does ToGO give you multi-tenancy out of the box?
No. Tenancy here is application code. ToGO provides the kernel, plugins and code generation around it.

### Which ToGO plugins does SeatFor use?
auth, auth-dev, cache, db-postgres, i18n, queue, realtime and storage.

### Is SeatFor open source?
No, the repository is private.

## Links

- [SeatFor](https://seatfor.com)
- [ToGO on GitHub](https://github.com/togo-framework/togo)
- [ToGO showcase](https://to-go.dev/en/showcase)

## Related reading

- [Expose your Go app as an MCP server with web-mcp](/en/blog/go-mcp-server-togo-web-mcp)
- [ToGO vs Gin, Echo and Fiber](/en/blog/togo-vs-gin-echo-fiber)

---

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