Config Reference
Your backend is defined in teenybase.ts. This file exports a DatabaseSettings object.
Top-level settings
export default {
appUrl: 'http://localhost:8787', // Base URL (used in OAuth redirects, emails)
appName: 'My App', // Used in email templates
jwtSecret: '$JWT_SECRET', // Global JWT signing secret
jwtIssuer: '$db', // JWT issuer claim (optional)
tables: [], // Table definitions
authCookie: { name: 'teeny_auth' }, // Optional, needed for OAuth redirect flows and SSR cookie sessions
authProviders: [], // OAuth providers (optional)
// email: { ... }, // Email sending config (optional)
actions: [], // Server-side actions (optional)
} satisfies DatabaseSettingsValues prefixed with $ (like $JWT_SECRET) are environment variables. They're read from .dev.vars when running locally and from .prod.vars in production. This keeps secrets out of your config file.
Tables
{
name: 'posts',
autoSetUid: true, // Auto-generate unique ID on insert
fields: [...],
triggers: [...],
extensions: [...],
r2Base: 'posts', // R2 path prefix for file fields (optional)
autoDeleteR2Files: true, // Delete files when record deleted (optional)
}Fields
{ name: 'title', type: 'text', sqlType: 'text' }
{ name: 'count', type: 'number', sqlType: 'integer', default: '0' }
{ name: 'published', type: 'bool', sqlType: 'boolean', default: '0' }
{ name: 'data', type: 'json', sqlType: 'text' }
{ name: 'avatar', type: 'file', sqlType: 'text' }Use scaffolds for common patterns:
import { baseFields, authFields, createdTrigger, updatedTrigger } from 'teenybase/scaffolds/fields'
// baseFields: id, created, updated
// authFields: username, email, email_verified, password, password_salt, name, avatar, role, metaAuth extension
Adds sign-up, login, password reset, email verification, and OAuth endpoints to a table.
{
name: 'auth',
jwtSecret: '$JWT_SECRET_USERS', // Table-specific secret (combined with global)
jwtTokenDuration: 3600, // Token TTL in seconds (default: 3600)
maxTokenRefresh: 5, // Required. Set 0 for unlimited refreshes
passwordConfirmSuffix: 'Confirm', // Optional. Requires passwordConfirm on sign-up/reset
} as TableAuthExtensionDataRules extension
Row-level security. Each rule is an expression evaluated on every request to decide whether the operation is allowed. The expression can reference auth.uid (the signed-in user's ID), auth.verified (email verified), auth.role (role), and any column on the current row.
{
name: 'rules',
listRule: 'published == true', // Who can list records
viewRule: 'published == true', // Who can view a single record
createRule: 'auth.uid != null', // Who can insert
updateRule: 'auth.uid == author', // Who can update
deleteRule: 'auth.uid == author', // Who can delete
} as TableRulesExtensionDataSpecial values:
'true'— allow everyone, including unauthenticated users'false'or omitted — block everyone (admins still bypass rules)auth.uid— the signed-in user's record ID (null if not signed in)auth.verified— true if the user's email is verifiedauth.role— the user's role/audience claim from their JWT
Auth providers
authProviders: [
{
name: 'google', // Preset: google, github, discord, linkedin
clientId: '$GOOGLE_CLIENT_ID',
clientSecret: '$GOOGLE_CLIENT_SECRET', // Omit for One Tap only
redirectUrl: 'https://myapp.com/dashboard',
},
]For browser-based OAuth redirect flows, also configure authCookie:
authCookie: {
name: 'teeny_auth',
httpOnly: true,
secure: true,
sameSite: 'Lax',
path: '/',
}Redirect flows (/auth/oauth/...) and google-login set this cookie automatically when configured. JSON auth endpoints like login-password and sign-up still return tokens in the response body — your frontend stores them as bearer tokens, or your SSR layer sets the cookie itself using the returned token.
Email
email: {
from: 'My App <noreply@myapp.com>',
variables: {
company_name: 'My App',
company_url: 'https://myapp.com',
company_address: '123 Main St, San Francisco, CA',
company_copyright: 'Copyright 2026 My App',
support_email: 'support@myapp.com',
},
resend: {
RESEND_API_KEY: '$RESEND_API_KEY',
},
}Use mailgun instead of resend if you prefer Mailgun. Email verification and password reset only work when this section is configured.
Practical notes:
- the
fromaddress should belong to a sender/domain your email provider accepts - local testing is easiest with
mock: true - the
request-password-resetandconfirm-password-resetroutes are only mounted whenemailis configured
For a full setup and test flow, see Email Verification & Password Reset.
Actions
Server-side logic callable via POST /api/v1/action/{name}.
actions: [
{
name: 'increment-views',
params: { post_id: 'string' },
sql: sql`UPDATE posts SET views = views + 1 WHERE id = ${sqlValue(':post_id')}`,
},
]