---
title: "Actions"
description: "Define server-side actions in teenybase.ts and call them at POST /api/v1/action/{name}: counters, transactions, aggregations, in SQL or steps mode."
url: https://teenybase.com/docs/guides/actions
last_updated: 2026-04-19
---

> Documentation index: https://teenybase.com/llms.txt lists every teenybase docs page with a link to its Markdown version. All docs in one file: https://teenybase.com/llms-full.txt

# Actions

Actions are server-side logic defined in `teenybase.ts` and callable via `POST /api/v1/action/{name}`. Use them for anything CRUD endpoints can't do: counters, transactions, aggregations, bulk operations.

> **Using a coding agent?** Paste this prompt:
>
> Add a teenybase action to this app. Use SQL mode for raw queries or steps mode for expression-based logic. See the actions guide for examples.

## Define an action

Add an `actions` array to your `teenybase.ts`:

```ts
import { DatabaseSettings, sql, sqlValue } from 'teenybase'

export default {
  // ...tables, jwtSecret, etc.
  actions: [
    {
      name: 'increment_views',
      params: { post_id: 'string' },
      sql: {
        type: 'UPDATE',
        table: 'posts',
        set: { views: sql`views + 1` },
        where: sql`id = {:post_id}`,
        returning: ['views'],
      },
    },
  ],
} satisfies DatabaseSettings
```

Call it:

```bash
curl -X POST http://localhost:8787/api/v1/action/increment_views \
  -H 'Content-Type: application/json' \
  -d '{"post_id": "abc123"}'
```

Response:

```json
[[{"views": 42}]]
```

## Parameters

Define typed parameters. They are validated before execution.

```ts
params: {
  email: 'string',
  amount: 'number',
  active: 'boolean',
  page: { type: 'integer', optional: true, default: 1 },
}
```

Types: `string`, `number`, `integer`, `boolean`. Extra parameters not in the definition are rejected.

## Access control

Two layers: `requireAuth` and `guard`.

```ts
{
  name: 'admin_action',
  requireAuth: true,                   // 401 if not authenticated
  guard: "auth.role == 'admin'",       // 403 if not admin
  params: { user_id: 'string' },
  sql: { /* ... */ },
}
```

Order: `requireAuth` > `guard` > parameter validation > execution.

## SQL mode

Raw query objects. Bypasses table rules entirely.

Every query needs `type` and `table`. The other properties depend on the type:

| Type | Required | Optional |
|---|---|---|
| `SELECT` | | `where`, `selects`, `orderBy`, `limit`, `offset`, `groupBy`, `distinct` |
| `UPDATE` | `set` | `where`, `returning`, `or` |
| `INSERT` | `values` | `returning`, `or` |
| `DELETE` | | `where`, `returning` |

### SELECT

```ts
{
  name: 'active_users',
  params: { role: 'string' },
  sql: {
    type: 'SELECT',
    table: 'users',
    where: sql`role = {:role} AND email_verified = true`,
    orderBy: 'created DESC',
    limit: 50,
  },
}
```

### UPDATE

```ts
{
  name: 'increment_counter',
  params: { id: 'string', amount: 'number' },
  sql: {
    type: 'UPDATE',
    table: 'counters',
    set: { value: sql`value + {:amount}` },
    where: sql`id = {:id}`,
    returning: ['*'],
  },
}
```

### INSERT

```ts
{
  name: 'log_event',
  params: { user_id: 'string', action: 'string' },
  sql: {
    type: 'INSERT',
    table: 'audit_log',
    values: {
      user_id: sql`{:user_id}`,
      action: sql`{:action}`,
      timestamp: sql`CURRENT_TIMESTAMP`,
    },
    returning: ['id'],
  },
}
```

### DELETE

```ts
{
  name: 'cleanup_expired',
  requireAuth: true,
  guard: "auth.role == 'admin'",
  sql: {
    type: 'DELETE',
    table: 'sessions',
    where: sql`created < datetime('now', '-30 days')`,
    returning: ['id'],
  },
}
```

### Multiple queries (transaction)

Pass an array. All queries execute in a single batch.

```ts
{
  name: 'transfer_credits',
  requireAuth: true,
  params: { from_id: 'string', to_id: 'string', amount: 'integer' },
  sql: [
    {
      type: 'UPDATE',
      table: 'accounts',
      set: { credits: 'credits - {:amount}' },
      where: sql`id = {:from_id} AND credits >= {:amount}`,
    },
    {
      type: 'UPDATE',
      table: 'accounts',
      set: { credits: 'credits + {:amount}' },
      where: sql`id = {:to_id}`,
    },
  ],
}
```

Response for multiple queries is an array of arrays, one per query:

```json
[[{"credits": 95}], [{"credits": 105}]]
```

### Parameter substitution

Use `{:paramName}` inside `sql` tagged templates. This generates parameterized SQL, safe from injection.

```ts
where: sql`email = {:email} AND id = {:id}`
```

## Steps mode

Steps mode is the alternative to SQL mode. Instead of writing raw SQL, you write expressions using the same language as [RLS rules](https://teenybase.com/docs/guides/rls). Steps go through the table layer, which means your table's RLS rules can be applied to them (controlled by `applyTableRules`, default `true`).

Use steps mode when you want actions that respect your existing access rules. Use SQL mode when you need full SQL control and want to handle access via `guard` instead.

```ts
{
  name: 'archive_post',
  requireAuth: true,
  params: { post_id: 'string' },
  steps: {
    type: 'UPDATE',
    table: 'posts',
    setValues: { status: 'archived' },
    where: "id = params.post_id & author_id = auth.uid",
  },
}
```

Steps use the same expression language as [RLS rules](https://teenybase.com/docs/guides/rls): `auth.uid`, `auth.role`, `params.*`, column names, and operators like `==`, `!=`, `&`, `|`.

Set `applyTableRules: false` to bypass RLS (default is `true`). Use `guard` for access control when bypassing rules.

## Calling from worker code

Use `db.runAction()` in SSR routes or custom middleware without an HTTP round-trip:

```ts
const db = c.get('$db')
const results = await db.runAction('increment_views', { post_id: 'abc123' })
```

For non-critical operations (view counting, logging):

```ts
c.executionCtx.waitUntil(
  db.runAction('increment_views', { post_id: slug }).catch(() => {})
)
```

## Full reference

For complete query object properties, conflict strategies, sensitive field filtering, and error responses, see the [Actions Reference](https://teenybase.com/docs/reference/actions-guide).
