StackBeta

AI agents build features fast.
Stack makes the secure path the only path.

Stack is a config-first Next.js framework with an access-control engine wrapped around every database operation. Rules live in one config; the engine enforces them everywhere — so a feature your agent ships is secure by construction, not by review.

$npm create opensaas-app@latest my-app

The problem

You can't review your way to safety.

Agents multiply how much code ships. They don't multiply how much you can read. Every generated endpoint is another place for data to leak — and the bottleneck is you.

More code than you can read

An agent turns one prompt into hundreds of lines across routes, actions, and queries. Reviewing every data path for leaks stops scaling on day one.

Security as scattered if-statements

When every handler hand-rolls its own checks, one forgotten where clause leaks the lot. Agents forget differently than humans do — but they forget too.

The demo works either way

Insecure code looks identical in the happy path. It compiles, the demo passes, and the leak waits quietly for production traffic.

The turn

Move the guardrails into the framework.

In Stack, application code — yours or your agent's — never touches the database directly. Every operation goes through a secured context. Access rules are declared once, in config, and applied to every query, create, update, and delete. There is no unchecked path to forget.

opensaas.config.ts
typescript
import { config, list } from '@opensaas/stack-core'
import { text, select, relationship } from '@opensaas/stack-core/fields'

export default config({
  db: { provider: 'sqlite', url: 'file:./dev.db' },
  lists: {
    Post: list({
      fields: {
        title: text({ validation: { isRequired: true } }),
        status: select({
          options: [
            { label: 'Draft', value: 'draft' },
            { label: 'Published', value: 'published' },
          ],
          defaultValue: 'draft',
        }),
        author: relationship({ ref: 'User.posts' }),
      },
      access: {
        operation: {
          // Signed-in users see published posts plus their own drafts;
          // anonymous visitors see published posts only.
          query: ({ session }) =>
            session
              ? {
                  OR: [
                    { status: { equals: 'published' } },
                    { authorId: { equals: session.userId } },
                  ],
                }
              : { status: { equals: 'published' } },
          // Only the author can change a post — a row filter, so
          // everyone else simply matches nothing.
          update: ({ session }) =>
            session ? { authorId: { equals: session.userId } } : false,
        },
      },
    }),
  },
})

Denied operations fail silently — null for one record, []for many — so callers can't probe for rows they aren't allowed to see.

See it hold

Same query. Different caller.

This is the config above, doing its job. Switch who's asking and watch the engine scope reads and refuse writes — without a single check in the feature code.

Who's asking
Operation
your-feature.ts
// Who's asking: an anonymous visitor
const context = await getContext()

const posts = await context.db.post.findMany()
result
Shipping the guardrails storypublished · alice
Pricing reworkdraft · alice
Launch retrospectivepublished · bob
Incident notesdraft · bob

2 rows returned. Rows outside the access filter never leave the database — the engine merged the rule into the query's where.

Results precomputed from the access-control engine — this block doesn't hit a live database. Run it for real: npm create opensaas-app@latest

How it works

One config in. A working, guarded app out.

1

Define

Lists, fields, and access rules in one opensaas.config.ts.

2

Generate

Prisma schema, TypeScript types, and a secured context — from the config.

3

Build

Features talk to context.db, never raw Prisma. That includes your agent.

4

Administer

A themeable admin UI at /admin, enforcing the same rules.

Included

The admin UI comes free.

Generated from the same config, themeable down to the token, and bound by the same access rules — fields a session can't read simply aren't there.

admin/posts
Generated admin UI list view with filtering
admin/posts/edit
Generated admin UI edit form
admin
Generated admin UI dashboard

Agent-ready

Built to be built on by agents.

Works with any coding agent. Deepest with Claude Code.

A CLAUDE.md in every app

Scaffolded projects ship the guidance an agent needs to build inside the guardrails — patterns, conventions, and the paths it must use.

MCP tools, rules intact

Every list can be exposed to AI assistants over MCP — and every tool call passes through the same access-control engine as the app itself.

A Claude Code plugin

Install the Stack plugin and describe features in plain language — the agent scaffolds lists, access rules, and UI within the framework's conventions.

Coming from Keystone? Stack speaks your schema — Keystone-compatible schema generation means you can adopt an existing database without a destructive migration.

Migration guide

Make your agent's next feature a safe one.

Scaffold an app, open it in your agent, and describe what you want. The guardrails are already in place.

$npm create opensaas-app@latest my-app

Stack is in beta: the onboarding path and core APIs are stable enough to build on — pin versions for production.