What is Keel?

Keel brings everything you need to build for operations

  • Schema-driven data modelling with managed databases with automatic migrations
  • JSON and GraphQL APIs generated from your schema
  • Fully extensible with TypeScript functions
  • Row-level and role-based permissions
  • Built-in authentication (password, Google, OIDC, SAML)
  • Instant apps with a modern, fast UI designed for daily operational use
  • Multi-step workflows with durable execution
  • Full type safety from the database to the UI
  • Barcode scanner and label printer integration
  • Event subscribers for reactive logic
  • Scheduled flows for background jobs
  • File storage and handling
  • Distributed tracing and observability
  • Multiple environments (staging, production, feature branches)
  • Git-based deployments with approval workflows
  • Secrets and environment variable management
  • Local development server with hot reload
  • VSCode extension with schema validation
  • Generated API clients to power custom frontend and apps
  • OpenAPI and GraphQL schema exports
  • Custom HTTP routes for webhooks
  • Computed fields with cross-relationship aggregations
  • Automatic audit trails (createdAt, updatedAt)

Schema-driven development

Your schema is the source of truth:

model Order {
  fields {
    customer Customer
    items OrderItem[]
    total Decimal @computed(SUM(order.items.price))
    status Status @default(Status.Pending)
  }
 
  actions {
    get getOrder(id)
    list listOrders(customer.id?, status?)
    create createOrder() with (customer.id, items.product.id, items.quantity)
  }
 
  @permission(roles: [Staff], actions: [get, list, create])
}

This schema gives you:

  • A PostgreSQL table with automatic migrations
  • JSON and GraphQL APIs with authentication
  • Type-safe SDK for custom functions
  • Console tools for your ops team

Change the schema, push, and everything updates.

APIs without boilerplate

Every action becomes an endpoint. You get:

  • JSON API with OpenAPI spec
  • GraphQL API for flexible queries
  • Generated clients for TypeScript, with full type safety

Authentication, validation, and permissions are handled automatically. When you need custom logic, write a TypeScript function. Keel generates the types from your schema.

import { CreateOrder, models } from "@teamkeel/sdk";
 
export default CreateOrder(async (ctx, inputs) => {
  // Full type safety, database access, secrets
  const order = await models.order.create({
    customerId: inputs.customerId,
    status: "Pending",
  });
 
  await sendConfirmationEmail(ctx.secrets.SENDGRID_KEY, order);
 
  return order;
});

Console for ops teams

The Console is a modern, fast UI that your ops team uses all day. It's not a basic admin panel. It's built for power users who need to move quickly through hundreds of tasks.

Change your schema and the Console updates instantly. Add a field, it appears in forms. Add an action, it gets a tool.

  • Internal tools generated from your schema. Instant, rich apps with search, filtering, sorting. Forms with validation and relationship lookups. Keyboard shortcuts throughout.
  • Flows for multi-step processes. Barcode scanning, approvals, document collection. Durable execution let's your build complex workflows that power anything.
  • Spaces to organize tools by team. Warehouse sees receiving and stock counts. Finance sees invoicing. Each team gets a focused workspace.
  • Hardware integration for printers and scanners. Print shipping labels, scan barcodes during picking.

You define the data model and business logic. The Console gives your team a professional tool to work with.

Permissions in the schema

Row-level and role-based permissions, defined declaratively:

// Staff can view and create orders
@permission(roles: [Staff], actions: [get, list, create])
 
// Customers can only see their own orders
@permission(expression: order.customer.identity == ctx.identity, actions: [get, list])
 
// Managers can update orders over $1000
@permission(
  expression: ctx.identity.role == "Manager" && order.total > 1000,
  actions: [update]
)

Permissions apply to APIs and Console tools automatically.

Flows for complex workflows

When a single action isn't enough, build a flow:

export default ReceiveGoods(async (ctx, inputs) => {
  // Scan the delivery barcode
  const { deliveryId } = await ctx.ui.page({
    content: [ctx.ui.inputs.scan("deliveryId", { label: "Scan delivery note" })],
  });
 
  const delivery = await models.delivery.findOne({ id: deliveryId });
 
  // Scan each item
  const { items } = await ctx.ui.page({
    content: [
      ctx.ui.display.table({ data: delivery.expectedItems }),
      ctx.ui.inputs.scan("items", { mode: "multi" }),
    ],
  });
 
  // Update inventory
  await ctx.step("update stock", async () => {
    for (const item of items) {
      await models.stockItem.update({ id: item.id }, { received: true });
    }
  });
 
  // Print labels
  await ctx.ui.interactive.print({
    jobs: items.map((item) => ({ type: "zpl", data: item.labelZpl })),
  });
});

Flows have durable execution. If something fails, completed steps don't re-run.

Local development

keel run

Starts a local server with your database. Console works locally too. Make changes, test flows, see traces. The VSCode extension (opens in a new tab) adds schema validation and autocomplete.