Organizations, projects & sources
Organization
The account, billing, and admin boundary. Your admin login belongs to exactly one organization; that organization owns every project and source you create. Org-wide settings (its name, admin accounts) are edited from the dashboard.
Project
A complete, isolated app backend: its own Postgres database, Auth, Storage, and Functions, provisioned in about 30–60 seconds and served at https://<project-ref>.shovelbase.com. Most apps need exactly one project.
Source
A slimmed-down project, scoped to sharing data and logic across an organization: it has its own database and its own edge functions, but no Auth, Storage, Websites, or Domains of its own. A source always starts blank — you build its schema (migrations) and functions the same way you would a project's, just scoped to the source instead.
Every project in the organization can then reach the source's tables through a schema named after it, and call its functions over HTTP. The tables are exposed as foreign tables (Postgres' postgres_fdw), not copies — a project reads the source's live data at query time, so there is exactly one copy and no sync to fall behind. Because foreign tables can't enforce RLS, that schema is granted to the project owner and service_role only; see How projects reach a source below.
The problem a source solves is the one that appears the moment an organization has a second project: some data belongs to the company rather than to any one app. A customer list, a product catalog, a pricing table, an integration with a billing provider. Copy it into each project and you own a synchronization problem forever; pick one project to be the “main” one and every other project now depends on an app that wasn't designed to be a dependency.
A source is that shared data given its own home, one level up. It is deliberately not a full project — no Auth, no Storage — because it isn't an app; it's the thing apps read from. Consumers query it as if its tables were local, so no project needs credentials for another project's database, and there is one place to change a schema or fix an integration.
Reach for one only when something is genuinely shared. A single app with one project is the normal case, and a source added early is just an extra database to migrate.
- Reference data used everywhere
- Product catalog, plans and pricing, country/tax tables — read by the web app, the mobile backend, and the admin tool alike.
- A shared integration
- One function that talks to your billing or CRM vendor, holding that vendor's API key once, instead of the same secret copied into three projects.
- Cross-app reporting
- A project can join its own tables against the source's in a single SQL query, since the foreign tables live in the same database session.
Project vs. source
| Project | Source | |
|---|---|---|
| Own Postgres database | Yes | Yes |
| Own Auth | Yes | No |
| Own Storage | Yes | No |
| Own Functions | Yes | Yes |
| Own Websites / Domains | Yes | No |
| Visible to other projects in the org | No | Yes — tables via a schema, functions via HTTP |
| Portal rail | Full rail (Data, Platform, Web, Monitor…) | Table Editor/SQL Editor/Database, Functions, Logs, Settings |
Creating each
shovelbase projects create my-app # a project# a source is created from the dashboard: Sources → New source (name only)How projects reach a source
There are two routes in, and the difference matters — one is backend-only, the other is safe for app clients.
| Route | Who can use it | Notes |
|---|---|---|
| The imported schema, in the project's own database | The project owner and service_role only | Foreign tables cannot carry RLS, so they are deliberately not granted to anon or authenticated. Query them from the SQL Editor, a migration, or an edge function using the service key — never straight from a browser. |
https://shovelbase.com/src/<slug>/rest/v1/… and /src/<slug>/functions/v1/<name> | Any project's keys or a signed-in user's JWT | The source's own API. RLS does apply here, so this is the user-facing path — a project's token is verified and re-signed with the source's secret, so auth.uid() works in the source's policies without the two stacks sharing a JWT secret. |
Note a source is served under the portal host at /src/<slug> — it does not get its own <slug>.shovelbase.com hostname the way a project does. See Using the portal for where each of these lives in the dashboard.