> For the complete documentation index, see [llms.txt](https://jacksonkasi.gitbook.io/tablecraft/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://jacksonkasi.gitbook.io/tablecraft/core-concepts/4-security.md).

# Security

Security is built into the core of the TableCraft Engine. You can easily hide sensitive data, enforce tenant isolation, and manage soft deletes.

## 1. Hiding Sensitive Data

You often have columns like `password_hash`, `stripe_token`, or `internal_notes` that should never be exposed via the API.

### Manual Hiding

You can explicitly hide columns using `.hide()`.

```typescript
// src/config/users.ts
import { defineTable } from '@tablecraft/engine';
import { users } from '../db/schema';

export const userConfig = defineTable(users)
  .hide('password', 'salt', 'resetToken') // Always excluded from SELECT *
  .toConfig();
```

### Auto-Hiding (Recommended)

The engine can automatically detect and hide common sensitive column names (like `password`, `token`, `secret`, `key`).

```typescript
export const userConfig = defineTable(users)
  .autoHide() // Automatically hides 'password', 'api_key', etc.
  .toConfig();
```

{% hint style="info" %}
You can inspect what would be hidden without applying it:

```typescript
console.log(defineTable(users).inspectSensitive());
// Output: ['password', 'twoFactorSecret']
```

{% endhint %}

## 2. Multi-Tenancy (Tenant Isolation)

If you are building a SaaS application, ensuring users only see their own organization's data is critical. The engine handles this automatically via the `.tenant()` configuration.

### Configuration

Tell the engine which column stores the Tenant ID.

```typescript
// src/config/orders.ts
export const orderConfig = defineTable(orders)
  .tenant('org_id') // Defaults to 'tenantId' if not specified
  .toConfig();
```

### Usage

When creating the engine instance, pass the `tenantId` in the context. The engine will automatically append `WHERE org_id = ?` to *every* query, ensuring isolation.

```typescript
// src/routes/api.ts
app.use('*', async (c, next) => {
  const user = c.get('user'); // Get user from auth middleware

  const engine = createTableEngine({
    db,
    config: orderConfig,
  });

  // The engine context is separate from the params
  const result = await engine.query(
    c.req.query(),      // params (page, sort, filter)
    { tenantId: user.orgId } // context (security)
  );

  return c.json(result);
});
```

{% hint style="success" %}
**Result:** The user *cannot* bypass this filter, even if they try to pass `?filter[org_id]=other_org`. The context override is secure.
{% endhint %}

## 3. Soft Deletes

Soft deleting allows you to mark a row as deleted without removing it from the database. The engine respects this automatically.

### Configuration

Tell the engine which column tracks deletion.

```typescript
export const productConfig = defineTable(products)
  .softDelete('deleted_at') // Defaults to 'deletedAt'
  .toConfig();
```

### Behavior

By default, queries will automatically filter out deleted rows (`WHERE deleted_at IS NULL`).

If you need to query deleted items (e.g., for an admin trash can view), you can pass `includeDeleted: true` in the query params options (if allowed) or via the engine method options.

```typescript
// Fetch including deleted items (e.g., for admin restore)
await engine.query({ includeDeleted: true });
```

## 4. Access Control (RBAC)

You can define role-based access control directly on the table definition. This relies on the `context` object passed to the engine during execution.

### Configuration

Define which roles or permissions are required to access the table:

```typescript
export const userConfig = defineTable(users)
  .access({
    roles: ['admin', 'manager'],       // Only these roles can query this table
    permissions: ['read:users']        // Or users with this permission
  })
  .toConfig();
```

### Passing Context (Crucial)

For RBAC to work, you **must** provide the user's roles and permissions in the `context`.

{% hint style="info" %}
The engine checks `context.user.roles` and `context.user.permissions` against your configuration.
{% endhint %}

**Adapter Configuration**

Use the `getContext` function in your adapter setup to extract user info from the request (e.g., from a JWT or session).

{% tabs %}
{% tab title="Hono" %}

```typescript
createHonoApp({
  db,
  schema,
  configs,
  getContext: async (c) => {
    // Assuming you have auth middleware that sets 'jwtPayload'
    const user = c.get('jwtPayload');
    return {
      user: {
        id: user.sub,
        roles: user.roles,       // e.g. ['admin']
        permissions: user.perms  // e.g. ['read:users']
      },
      tenantId: user.orgId
    };
  }
});
```

{% endtab %}

{% tab title="Express" %}

```typescript
createExpressMiddleware({
  db,
  schema,
  configs,
  getContext: async (req) => {
    // Express usually attaches user to req.user
    const user = (req as any).user;
    return {
      user: {
        id: user.id,
        roles: user.roles,
        permissions: user.permissions
      },
      tenantId: user.orgId
    };
  }
});
```

{% endtab %}

{% tab title="Next.js" %}

```typescript
createNextHandler({
  db,
  schema,
  configs,
  getContext: async (req) => {
    // In Next.js App Router, you might get session from cookies/headers
    // This is a simplified example
    const session = await getSession(req);
    return {
      user: {
        id: session.user.id,
        roles: session.user.roles,
        permissions: session.user.permissions
      },
      tenantId: session.user.orgId
    };
  }
});
```

{% endtab %}

{% tab title="Elysia" %}

```typescript
createElysiaPlugin({
  db,
  schema,
  configs,
  getContext: async (context) => {
    // Elysia context
    const user = context.store.user;
    return {
      user: {
        id: user.id,
        roles: user.roles,
        permissions: user.permissions
      },
      tenantId: user.orgId
    };
  }
});
```

{% endtab %}
{% endtabs %}

**Direct Engine Usage**

If you use the engine directly, pass the context as the second argument to `query`.

```typescript
const engine = createTableEngine({ db, config });

await engine.query(params, {
  // Context object
  user: {
    roles: ['member'], // This user would be denied if 'admin' is required
    permissions: []
  }
});
```

## Next Steps

Learn how to extend the engine with [Raw SQL & Custom Logic](/tablecraft/core-concepts/5-extending.md) for complex use cases.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://jacksonkasi.gitbook.io/tablecraft/core-concepts/4-security.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
