---
title: bknd.config.ts
tags: ["documentation"]
---

The central configuration file to extend bknd should be placed in the root of your project, so that the [CLI](/usage/cli#using-configuration-file-bknd-config) can automatically pick it up. It allows to:

- define your database connection centrally
- pass in [initial configuration](/usage/database#initial-structure) or [data seeds](/usage/database#seeding-the-database) when booting the first time
- add plugins to the app
- hook into system events
- define custom routes and endpoints

A simple example of a `bknd.config.ts` file:

```typescript title="bknd.config.ts"
import type { BkndConfig } from "bknd/adapter";

export default {
  connection: {
    url: "file:data.db",
  },
} satisfies BkndConfig;
```

The supported configuration file extensions are `js`, `ts`, `mjs`, `cjs` and `json`. Throughout the documentation, we'll use `ts` for the file extension.

## Example

Here is an example of a configuration file that specifies a database connection, registers a plugin, add custom routes using [Hono](https://hono.dev/) and performs a [Kysely](https://kysely.dev/) query.

```typescript
import type { BkndConfig } from "bknd/adapter";
import { showRoutes } from "bknd/plugins";

export default {
  connection: {
    url: process.env.DB_URL ?? "file:data.db",
  },
  onBuilt: async (app) => {
    // `app.server` is a Hono instance
    const hono = app.server;
    hono.get("/hello", (c) => c.text("Hello World"));

    // for complex queries, you can use Kysely directly
    const db = app.connection.kysely;
    hono.get("/custom_query", async (c) => {
      return c.json(await db.selectFrom("pages").selectAll().execute());
    });
  },
  options: {
    plugins: [showRoutes()],
  },
} satisfies BkndConfig;
```


## Configuration (`BkndConfig`)

The `BkndConfig` type is the main configuration object for the `createApp` function. It has
the following properties:

```typescript
import type { App, InitialModuleConfigs, ModuleBuildContext, Connection, MaybePromise } from "bknd";
import type { Config } from "@libsql/client";

type AppPlugin = (app: App) => Promise<void> | void;
type ManagerOptions = {
  basePath?: string;
  trustFetched?: boolean;
  onFirstBoot?: () => Promise<void>;
  seed?: (ctx: ModuleBuildContext) => Promise<void>;
};

type BkndConfig<Args = any> = {
  connection?: Connection | Config;
  config?: InitialModuleConfigs;
  options?: {
    plugins?: AppPlugin[];
    manager?: ManagerOptions;
  };
  app?: BkndConfig<Args> | ((args: Args) => MaybePromise<BkndConfig<Args>>);
  onBuilt?: (app: App) => Promise<void>;
  beforeBuild?: (app?: App) => Promise<void>;
  buildConfig?: {
    sync?: boolean;
  }
};
```

### `connection`

The `connection` property is the main connection object to the database. It can be either an object with libsql config or the actual `Connection` class.

```ts
// uses the default SQLite connection depending on the runtime
const connection = { url: "<url>" };

// the same as above, but more explicit
import { sqlite } from "bknd/adapter/sqlite";
const connection = sqlite({ url: "<url>" });

// Node.js SQLite, default on Node.js
import { nodeSqlite } from "bknd/adapter/node";
const connection = nodeSqlite({ url: "<url>" });

// Bun SQLite, default on Bun
import { bunSqlite } from "bknd/adapter/bun";
const connection = bunSqlite({ url: "<url>" });

// LibSQL, default on Cloudflare
import { libsql } from "bknd";
const connection = libsql({ url: "<url>" });
```

See a full list of available connections in the [Database](/usage/database) section. Alternatively, you can pass an instance of a `Connection` class directly, see [Custom Connection](/usage/database#custom-connection) as a reference.

If the connection object is omitted, the app will try to use an in-memory database.

### `config`

As configuration, you can either pass a partial configuration object or a complete one
with a version number. The version number is used to automatically migrate the configuration up
to the latest version upon boot ([`db` mode](/usage/setup#ui-only-mode) only). The default configuration looks like this:

```json
{
  "server": {
    "cors": {
      "origin": "*",
      "allow_methods": [
        "GET",
        "POST",
        "PATCH",
        "PUT",
        "DELETE"
      ],
      "allow_headers": [
        "Content-Type",
        "Content-Length",
        "Authorization",
        "Accept"
      ],
      "allow_credentials": true
    },
    "mcp": {
      "enabled": false,
      "path": "/api/system/mcp",
      "logLevel": "emergency"
    }
  },
  "data": {
    "basepath": "/api/data",
    "default_primary_format": "integer",
    "entities": {},
    "relations": {},
    "indices": {}
  },
  "auth": {
    "enabled": false,
    "basepath": "/api/auth",
    "entity_name": "users",
    "allow_register": true,
    "jwt": {
      "secret": "",
      "alg": "HS256",
      "expires": 0,
      "issuer": "",
      "fields": [
        "id",
        "email",
        "role"
      ]
    },
    "cookie": {
      "path": "/",
      "sameSite": "strict",
      "secure": true,
      "httpOnly": true,
      "expires": 604800,
      "partitioned": false,
      "renew": true,
      "pathSuccess": "/",
      "pathLoggedOut": "/"
    },
    "strategies": {
      "password": {
        "type": "password",
        "enabled": true,
        "config": {
          "hashing": "sha256"
        }
      }
    },
    "guard": {
      "enabled": false
    },
    "roles": {}
  },
  "media": {
    "enabled": false,
    "basepath": "/api/media",
    "entity_name": "media",
    "storage": {}
  },
  "flows": {
    "basepath": "/api/flows",
    "flows": {}
  }
}
```

<Callout type="warn" title="Guard lockout warning">
  Setting `auth.guard.enabled` to `true` without first creating a user with an admin role will lock you out of the admin portal. See [Securing Your Admin Portal](/modules/auth#securing-your-admin-portal) for the required steps before enabling the Guard.
</Callout>

You can use the [CLI](/usage/cli/#getting-the-configuration-config) to get the default configuration:

```sh
npx bknd config --default --pretty
```

To validate your configuration against a JSON schema, you can also dump the schema using the CLI:

```sh
npx bknd schema
```

To create an initial data structure, you can use helpers [described here](/usage/database#data-structure).

### `app`

The `app` property is a function that returns a `CreateAppConfig` object. It allows accessing the adapter specific environment variables. This is especially useful when using the [Cloudflare Workers](/integration/cloudflare) runtime, where the environment variables are only available inside the request handler.

```typescript
import type { BkndConfig } from "bknd/adapter";

export default {
  app: (env) => ({
    connection: {
      url: env.DB_URL,
    },
  }),
} satisfies BkndConfig;
```

See [Database](/usage/database) for more information on how to configure the database connection.

### `beforeBuild`

The `beforeBuild` property is an async function that is called before the app is built. It allows to modify the app instance that may influence the build process.

```typescript
import type { BkndConfig } from "bknd/adapter";

export default {
  beforeBuild: async (app: App) => {
    // do something that has to happen before the app is built
  },
};
```

### `onBuilt`

The `onBuilt` property is an async function that is called after the app has been built. It allows to hook into the app after it has been built. This is useful for defining event listeners or register custom routes, as both the event manager and the server are recreated during the build process.

```typescript
import { type App, AppEvents } from "bknd";
import type { BkndConfig } from "bknd/adapter";

export default {
  onBuilt: async (app: App) => {
    console.log("App built", app.version());

    // registering an event listener
    app.emgr.onEvent(AppEvents.AppRequest, (event) => {
      console.log("Request received", event.request.url);
    });

    // registering a custom route
    app.server.get("/hello", (c) => c.text("Hello World"));
  },
};
```

### `options.plugins`

The `plugins` property is an array of functions that are called after the app has been built,
but before its event is emitted. This is useful for adding custom routes or other functionality.
A simple plugin that adds a custom route looks like this:

```ts
import type { AppPlugin } from "bknd";

export const myPlugin: AppPlugin = (app) => {
  app.server.get("/hello", (c) => c.json({ hello: "world" }));
};
```

Since each plugin has full access to the `app` instance, it can add routes, modify the database
structure, add custom middlewares, respond to or add events, etc. Plugins are very powerful, so
make sure to only run trusted ones.

Read more about plugins in the [Plugins](/extending/plugins) section.

### `options.seed`

<Callout type="info">
  The seed function will only be executed on app's first boot in `"db"` mode. If a configuration already exists in the database, or in `"code"` mode, it will not be executed.
</Callout>

The `seed` property is a function that is called when the app is booted for the first time. It is used to seed the database with initial data. The function is passed a `ModuleBuildContext` object:

```ts
type ModuleBuildContext = {
  connection: Connection;
  server: Hono;
  em: EntityManager;
  emgr: EventManager;
  guard: Guard;
};

const seed = async (ctx: ModuleBuildContext) => {
  // seed the database
  await ctx.em.mutator("todos").insertMany([
    { title: "Learn bknd", done: true },
    { title: "Build something cool", done: false },
  ]);
};
```

## Framework & Runtime configuration

Depending on which framework or runtime you're using to run bknd, the configuration object will extend the `BkndConfig` type with additional properties.

### `RuntimeBkndConfig`

[Runtime adapters](/integration/runtime) need additional configuration to serve static assets for the admin UI.

```typescript
export type RuntimeBkndConfig<Args = any> = BkndConfig<Args> & {
  // the path to the dist folder to serve static assets for the admin UI
  distPath?: string;
  // custom middleware to serve static assets for the admin UI
  serveStatic?: MiddlewareHandler | [string, MiddlewareHandler];
  // options for the admin UI
  adminOptions?: AdminControllerOptions | false;
};
```

### `FrameworkBkndConfig`

[Framework adapters](/integration/framework) may need additional configuration based on the framework's requirements. For example, the `NextjsBkndConfig` type extends the `BkndConfig` type with the following additional properties:

```typescript
type NextjsEnv = NextApiRequest["env"];
export type NextjsBkndConfig<Env = NextjsEnv> = FrameworkBkndConfig<Env> & {
  cleanRequest?: { searchParams?: string[] };
};
```

Next.js adds the mounted path to the request object, so that the `cleanRequest` property can be used to remove the mounted path from the request URL. See other frameworks for more information on how to configure them.

## Using the configuration file

The configuration file is automatically picked up if you're using the [CLI](/usage/cli). This allows interacting with your application using the `bknd` command. For example, you can run the following command in the root of your project to start an instance:

```bash
npx bknd run
```

When serving your application, you need to make sure to import the contents of your configuration file. If you're using Next.js for example, it's recommended to follow these steps:

1. create a `bknd.config.ts` file in the root of your project which defines the connection to the database, adds event listeners and custom routes.
2. create a `bknd.ts` file inside your app folder which exports helper functions to instantiate the bknd instance and retrieve the API.
3. create a catch-all route file at `src/api/[[...bknd]]/route.ts` which serves the bknd API.

This way, your application and the CLI are using the same configuration.
