# How to Implement Sharing and Access Control in Agent-Native Using the `accessFilter`

> Learn to implement sharing and access control in Agent-Native with accessFilter. This guide shows how to generate SQL WHERE clauses for ownership and permissions.

- Repository: [Builder.io/agent-native](https://github.com/BuilderIO/agent-native)
- Tags: how-to-guide
- Published: 2026-07-02

---

**Agent-Native centralizes access control through the `accessFilter` helper in `@agent-native/core/sharing`, which automatically generates SQL WHERE clauses that enforce ownership, organization boundaries, and explicit share permissions across all Drizzle ORM queries.**

The **Agent-Native** framework (`BuilderIO/agent-native`) provides a type-safe security layer for multi-tenant AI applications. When you implement sharing and access control in Agent-Native using the `accessFilter`, you eliminate boilerplate authorization logic by composing a single predicate that respects three permission dimensions: resource ownership, workspace membership, and direct shares.

## How `accessFilter` Enforces the Three Permission Dimensions

The `accessFilter` function—implemented in [`packages/core/src/sharing.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/sharing.ts)—constructs a composite SQL predicate that checks three distinct axes of permission:

- **Owner**: Validates that the `owner_email` column matches the authenticated user’s email.
- **Organization**: Confirms that the user’s `org_id` matches the resource’s workspace identifier.
- **Explicit Shares**: Joins against a dedicated shares table (e.g., `folderShares`) to discover grants to individual users or groups.

Because the filter returns a raw Drizzle SQL expression, the database engine can utilize composite indexes defined on the shares tables while ensuring no endpoint can accidentally bypass authorization.

## The Request Context Flow

Authorization depends on the **request context** populated by server-side middleware. The `runWithRequestContext` function (typically invoked in server plugins like [`server/plugins/agent-chat.ts`](https://github.com/BuilderIO/agent-native/blob/main/server/plugins/agent-chat.ts)) validates the session and attaches the following to the request object:

- `userId`: The authenticated user’s primary key.
- `email`: The user’s email address for ownership checks.
- `orgId`: The active organization/workspace identifier.
- `groupIds`: An array of role-based group memberships.

`accessFilter` reads these values at query time, ensuring that permission checks always reflect the current caller’s identity without manual parameter passing.

## Querying Resources with `accessFilter`

To protect read operations, import `accessFilter` from the core sharing package and pass it to Drizzle’s `.where()` clause along with the resource table and its corresponding shares table.

```typescript
// templates/videos/actions/list-folders.ts
import { accessFilter } from "@agent-native/core/sharing";
import { db } from "@/server/db";

export const listFolders = async (ctx) => {
  const { schema } = ctx;
  
  const folders = await db
    .select()
    .from(schema.folders)
    .where(accessFilter(schema.folders, schema.folderShares));

  return folders;
};

```

This pattern ensures that `listFolders` only returns folders where the caller is the owner, belongs to the same organization, or appears in the `folderShares` mapping table.

## Enforcing Write Permissions with `assertAccess`

For mutations, use `assertAccess` to throw an authorization error before executing destructive operations. This function performs the same checks as `accessFilter` but validates a specific resource ID and required permission level.

```typescript
// templates/forms/actions/delete-file.ts
import { assertAccess } from "@agent-native/core/sharing";

export const deleteForm = async (ctx, { formId }) => {
  const { schema } = ctx;
  
  // Throws if caller lacks 'owner' permission on this specific form
  await assertAccess(schema.forms, schema.formShares, formId, "owner");
  
  await db.delete(schema.forms).where({ id: formId });
};

```

Unlike the read filter, `assertAccess` is designed for imperative gate-keeping inside action handlers.

## Checking Access Conditionally with `resolveAccess`

When rendering UI elements that depend on permission state (e.g., showing an "Edit" button), use `resolveAccess` to obtain a boolean without throwing errors.

```typescript
// templates/design/actions/edit-design.ts
import { resolveAccess } from "@agent-native/core/sharing";

export const canEditDesign = async (ctx, designId) => {
  const { schema } = ctx;
  
  return resolveAccess(schema.designs, schema.designShares, designId, "editor");
};

```

This returns `true` only if the user holds the specified role (or higher) on the resource, enabling fine-grained conditional rendering.

## Defining Share Tables in Your Schema

To enable sharing on a custom resource, define a link table that references both the resource and the user or group being granted access. Below is the pattern used across templates like `videos`, `slides`, and `forms`:

```typescript
// server/db/schema.ts
import { pgTable, serial, text, integer, primaryKey } from "drizzle-orm/pg-core";

export const myResources = pgTable("my_resources", {
  id: serial("id").primaryKey(),
  owner_email: text("owner_email").notNull(),
  org_id: integer("org_id").notNull(),
  // additional columns...
});

export const myResourceShares = pgTable("my_resource_shares", {
  resource_id: integer("resource_id")
    .notNull()
    .references(() => myResources.id),
  user_id: integer("user_id").references(() => users.id),
  group_id: integer("group_id").references(() => groups.id),
}, (table) => ({
  pk: primaryKey({ columns: [table.resource_id, table.user_id, table.group_id] })
}));

```

After defining the schema, protect queries by passing both tables to `accessFilter`:

```typescript
.where(accessFilter(schema.myResources, schema.myResourceShares))

```

## Key Implementation Files

| File | Purpose |
|------|---------|
| [`packages/core/src/sharing.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/sharing.ts) | Core implementation of `accessFilter`, `assertAccess`, and `resolveAccess`. |
| [`templates/videos/actions/list-folders.ts`](https://github.com/BuilderIO/agent-native/blob/main/templates/videos/actions/list-folders.ts) | Example of read-level filtering in the videos template. |
| [`templates/forms/actions/delete-file.ts`](https://github.com/BuilderIO/agent-native/blob/main/templates/forms/actions/delete-file.ts) | Example of write-level protection using `assertAccess`. |
| [`templates/design/actions/edit-design.ts`](https://github.com/BuilderIO/agent-native/blob/main/templates/design/actions/edit-design.ts) | Example of conditional UI logic with `resolveAccess`. |
| `templates/*/server/db/schema.ts` | Defines owner columns and `<resource>Shares` junction tables. |
| `templates/*/server/plugins/db.ts` | Contains index definitions optimized for the filter’s WHERE clauses. |
| `packages/core/docs/content/sharing.mdx` | Official documentation for the sharing API surface. |

## Summary

- **Agent-Native** centralizes authorization logic in `@agent-native/core/sharing`, exposing `accessFilter` for reads and `assertAccess` for writes.
- The system evaluates **ownership**, **organization membership**, and **explicit shares** through a single SQL predicate that composes with Drizzle queries.
- Request context is injected via `runWithRequestContext` middleware, making user identity available without manual thread-local storage.
- Share tables follow a consistent junction-table pattern (e.g., `folderShares`) indexed for fast lookup alongside owner and visibility columns.
- You can combine `accessFilter` with additional `.where()` constraints using Drizzle’s `and()` operator for complex business logic.

## Frequently Asked Questions

### What is the difference between `accessFilter` and `assertAccess`?

**`accessFilter`** returns a SQL expression suitable for Drizzle `.where()` clauses, filtering result sets to only visible records. **`assertAccess`** performs an imperative check on a specific resource ID and throws an authorization error if the user lacks the required permission, making it ideal for guarding mutations.

### How does `accessFilter` handle public or visibility-flagged resources?

The function automatically includes predicates for public visibility flags (e.g., `visibility = 'public'`) when the `owner_email` and `org_id` checks fail. As implemented in [`packages/core/src/sharing.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/sharing.ts), the generated OR clause ensures public resources are appended to the result set without compromising the security of private records.

### Can I compose `accessFilter` with other WHERE clauses in Drizzle?

Yes. Because `accessFilter` returns a standard Drizzle SQL expression, you can combine it with additional constraints using `and()` or `or()` operators. For example: `.where(and(accessFilter(schema.folders, schema.folderShares), eq(schema.folders.status, 'active')))`.

### Where is the request context populated in the Agent-Native stack?

The context is populated by **`runWithRequestContext`** middleware, typically invoked in server plugins such as `templates/*/server/plugins/agent-chat.ts`. This middleware validates the session token, extracts the user’s email, `orgId`, and group memberships, and binds them to the request object so that `accessFilter` can access them during query execution.