Single-Tenant Mode
Build admin-only or public-facing apps without multi-tenant organization overhead.
Single-tenant mode disables the Better Auth organization plugin, removing the organizationId requirement from your API. This is ideal for personal sites, blogs, admin panels, or any app with a single tenant.
Enabling Single-Tenant Mode
Set features.organizations to false in your config:
import { defineConfig, defineRuntime, defineDatabase, defineAuth } from "@quickback/compiler";
export default defineConfig({
name: "my-site",
template: "hono",
features: {
organizations: false,
},
providers: {
runtime: defineRuntime("cloudflare"),
database: defineDatabase("cloudflare-d1"),
auth: defineAuth("better-auth"),
},
});What Changes
| Aspect | Multi-Tenant (default) | Single-Tenant |
|---|---|---|
| Organization plugin | Enabled | Disabled |
organizationId columns | Required on resources | Not used |
| Role source | Org membership table | user.role field |
| Firewall scopes | owner, organization, team, exception | owner, exception only |
| Admin org endpoints | /admin/v1/organizations generated | Skipped |
ctx.activeOrgId | From session/JWT | Always undefined |
ctx.roles | From org member role | From user.role |
Firewall Configuration
In single-tenant mode, resources use either owner scope (user-scoped data) or exception (public/system data):
// User-scoped data (requires authentication)
export default defineTable(posts, {
firewall: { owner: {} },
crud: {
list: { access: { roles: ['user', 'admin'] } },
create: { access: { roles: ['admin'] } },
},
});
// Public data (no ownership filtering)
export default defineTable(pages, {
firewall: { exception: true },
crud: {
list: { access: { roles: ['PUBLIC'] } },
get: { access: { roles: ['PUBLIC'] } },
create: { access: { roles: ['admin'] } },
update: { access: { roles: ['admin'] } },
delete: { access: { roles: ['admin'] } },
},
});Using firewall: { organization: {} } or firewall: { team: {} } with organizations: false will cause a compile error. These scopes require the organization plugin.
Public Routes
Use the PUBLIC role to expose routes without authentication. This works the same as in multi-tenant mode:
crud: {
list: { access: { roles: ['PUBLIC'] } }, // No auth required
get: { access: { roles: ['PUBLIC'] } }, // No auth required
create: { access: { roles: ['admin'] } }, // Admin only
}Role-Based Access
Roles come from the user.role field (managed by Better Auth's admin plugin):
| Role | Description |
|---|---|
admin | Full access, set via Better Auth admin plugin |
user | Default role for authenticated users |
PUBLIC | Unauthenticated access (special keyword) |
// Admin-only action
export default defineActions({
publish: {
access: { roles: ['admin'] },
execute: async ({ ctx, db }) => {
// ctx.userRole === 'admin'
// ctx.roles === ['admin']
},
},
});Consuming the API
Since single-tenant mode produces a JSON API, pair it with any frontend framework:
// Astro, Next.js, SvelteKit, etc.
const posts = await fetch('https://api.mysite.com/api/v1/posts').then(r => r.json());
// Admin operations require authentication
const res = await fetch('https://api.mysite.com/api/v1/posts', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ title: 'New Post', content: '...' }),
});