---
title: Lakebase Postgres development
sidebar_label: Development
---

# Lakebase Postgres development

## AppKit plugin API

The `lakebase()` plugin provides a standard `pg.Pool` with automatic OAuth token refresh. Once registered, access it via `AppKit.lakebase`:

```typescript
import { createApp, server, lakebase } from "@databricks/appkit";

const AppKit = await createApp({
  plugins: [server(), lakebase()],
});

// Standard parameterized query
const { rows } = await AppKit.lakebase.query<{ id: number; name: string }>(
  "SELECT id, name FROM app.items WHERE active = $1",
  [true],
);

// ORM-ready config (Drizzle, Prisma, TypeORM, etc.)
const ormConfig = AppKit.lakebase.getOrmConfig();
// Returns: { host, port, database, ssl, user, ... }

// pg-compatible config
const pgConfig = AppKit.lakebase.getPgConfig();

// Raw pg.Pool for advanced usage
const pool = AppKit.lakebase.pool;
```

### Pool configuration

Override connection pool defaults by passing a `pool` object:

```typescript
lakebase({
  pool: {
    max: 10, // max connections (default: 10)
    connectionTimeoutMillis: 5000, // connection timeout ms (default: 10000)
    idleTimeoutMillis: 30000, // idle timeout ms (default: 30000)
  },
});
```

### Caching integration

Lakebase Postgres also backs the [AppKit caching plugin](https://databricks.com/devhub/docs/appkit/v0/plugins/caching) when healthy. For the full API, ORM integration, and connection configuration, read the [plugin reference](https://databricks.com/devhub/docs/appkit/v0/plugins/lakebase).

## Auth model

Lakebase Postgres authenticates database connections using OAuth tokens or native Postgres passwords. The method depends on where your app runs.

**Deployed apps**: When you add it as a resource to a Databricks App, Databricks creates a service principal automatically, grants it a matching Postgres role, and injects connection details as environment variables. AppKit's `lakebase()` plugin handles OAuth token refresh automatically.

**Local development**: Your personal Databricks identity connects with an OAuth token generated by `databricks postgres generate-database-credential`. Tokens expire after one hour, but expiration is enforced only at login. Open connections remain active after the token expires. Run `databricks apps deploy` at least once before running `npm run dev`. [Local setup](#local-setup) explains why order matters and what to do if you hit permission errors.

[About authentication](https://docs.databricks.com/aws/en/oltp/projects/authentication) covers Postgres password auth, token rotation, and machine-to-machine flows.

## Local setup

`databricks apps init` populates `.env` with the correct Lakebase Postgres connection values. Run `databricks apps deploy` before `npm run dev`. Deploying sets up a managed identity (the app's service principal) that creates the `app` schema and tables on first startup and owns them. If `npm run dev` runs first instead, your personal credentials create those objects. The deployed app then can't access them and hits `permission denied for schema app`.

### Local database access

If you created the Lakebase Postgres project, your identity already has the access it needs. After `databricks apps deploy` runs once, `npm run dev` works.

For collaborators and other identities that need local read/write access, add them in the Lakebase UI: open **Branch Overview**, click **Add role**, select the identity, and check the `databricks_superuser` system role.

[Postgres password authentication](https://docs.databricks.com/aws/en/oltp/projects/authentication#overview) is a simpler alternative that avoids OAuth role setup. Set a password in the Branch Overview page and use it as `PGPASSWORD` in `.env`.

You can also generate a short-lived credential for use with any PostgreSQL client (DBeaver, pgAdmin, DataGrip, or a language driver):

```bash
databricks postgres generate-database-credential \
  projects/my-project/branches/production/endpoints/primary
```

The [AppKit plugin docs: local development](https://databricks.com/devhub/docs/appkit/v0/plugins/lakebase#local-development) cover fine-grained permission alternatives for teams that need schema-scoped access.

## Feature branches

Use Lakebase Postgres branches to isolate schema changes and test migrations without affecting production:

```bash title="Common"
databricks postgres create-branch projects/my-project feature-xyz
```

```bash title="All Options"
databricks postgres create-branch \
  projects/$PROJECT_ID \
  $BRANCH_ID \
  --json '{"spec": {"source_branch": "projects/$PROJECT_ID/branches/$SOURCE_BRANCH_ID", "no_expiry": true}}' \
  --debug \
  -o json \
  --target $TARGET \
  --no-wait \
  --timeout 10m \
  --profile $DATABRICKS_PROFILE
```

<details>
<summary>Options</summary>

| Option      | Required | Description                                                                                                                                                                 |
| ----------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PARENT`    | yes      | Project resource path: `projects/{project_id}`                                                                                                                              |
| `BRANCH_ID` | yes      | Unique branch identifier (1-63 chars, lowercase)                                                                                                                            |
| `--json`    | no       | JSON spec with `source_branch` and expiration policy (`no_expiry`, `ttl`, or `expire_time`). If omitted, branches from the project's default branch with default expiration |
| `--no-wait` | no       | Return immediately with operation details                                                                                                                                   |
| `--timeout` | no       | Max time to wait for completion                                                                                                                                             |
| `--debug`   | no       | Enable debug logging                                                                                                                                                        |
| `-o json`   | no       | Output as JSON (default: text)                                                                                                                                              |
| `--target`  | no       | Bundle target to use (if applicable)                                                                                                                                        |
| `--profile` | no       | Databricks CLI profile name                                                                                                                                                 |

</details>

A `primary` read-write endpoint is created automatically, inheriting the project's `default_endpoint_settings`. Branches require an expiration policy (`ttl`, `expire_time`, or `no_expiry: true`). [Branch expiration](https://docs.databricks.com/aws/en/oltp/projects/manage-branches#expiration) details the available policies.

Delete when done:

```bash title="Common"
databricks postgres delete-branch projects/my-project/branches/feature-xyz
```

```bash title="All Options"
databricks postgres delete-branch \
  projects/$PROJECT_ID/branches/$BRANCH_ID \
  --no-wait \
  --timeout 10m \
  --debug \
  -o json \
  --target $TARGET \
  --profile $DATABRICKS_PROFILE
```

<details>
<summary>Options</summary>

| Option      | Required | Description                                                        |
| ----------- | -------- | ------------------------------------------------------------------ |
| `NAME`      | yes      | Branch resource path: `projects/{project_id}/branches/{branch_id}` |
| `--no-wait` | no       | Return immediately with operation details                          |
| `--timeout` | no       | Max time to wait for completion                                    |
| `--debug`   | no       | Enable debug logging                                               |
| `-o json`   | no       | Output as JSON (default: text)                                     |
| `--target`  | no       | Bundle target to use (if applicable)                               |
| `--profile` | no       | Databricks CLI profile name                                        |

</details>

## Off-platform apps

For apps hosted outside Databricks (AWS, Vercel, Netlify, and others), the platform does not inject connection details or refresh OAuth tokens automatically. Token rotation is the app's responsibility. [About Lakebase authentication](https://docs.databricks.com/aws/en/oltp/projects/authentication) covers token rotation and machine-to-machine patterns. The [Lakebase Off-Platform](https://databricks.com/devhub/templates/lakebase-off-platform) template includes a complete implementation with environment setup and Drizzle ORM integration.

<details>
<summary>Manual provisioning (without a template)</summary>

### Create a project

```bash title="Common"
databricks postgres create-project my-project
```

```bash title="All Options"
databricks postgres create-project $PROJECT_ID \
  --json '{"spec": {
    "display_name": "My Lakebase Postgres Project",
    "pg_version": 17,
    "history_retention_duration": "172800s",
    "default_endpoint_settings": {
      "autoscaling_limit_min_cu": 0.5,
      "autoscaling_limit_max_cu": 1.0,
      "suspend_timeout_duration": "300s"
    }
  }}' \
  --no-wait \
  --timeout 10m \
  --debug \
  -o json \
  --target $TARGET \
  --profile $DATABRICKS_PROFILE
```

<details>
<summary>Options</summary>

| Option       | Required | Description                                                                                                                                     |
| ------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `PROJECT_ID` | yes      | Unique project identifier (1-63 chars, lowercase letter start, lowercase/numbers/hyphens)                                                       |
| `--json`     | no       | Inline JSON or `@path/to/file.json` with project spec (`display_name`, `pg_version`, `history_retention_duration`, `default_endpoint_settings`) |
| `--no-wait`  | no       | Return immediately with operation details instead of waiting for completion                                                                     |
| `--timeout`  | no       | Max time to wait for completion (for example, `10m`). Ignored with `--no-wait`                                                                  |
| `--debug`    | no       | Enable debug logging                                                                                                                            |
| `-o json`    | no       | Output as JSON (default: text)                                                                                                                  |
| `--target`   | no       | Bundle target to use (if applicable)                                                                                                            |
| `--profile`  | no       | Databricks CLI profile name                                                                                                                     |

</details>

The optional `display_name` sets a human-readable label. This creates a project with a default `production` branch, a `databricks_postgres` database, and a read-write endpoint.

### Get connection values

```bash title="Common"
databricks postgres list-endpoints projects/my-project/branches/production -o json
```

```bash title="All Options"
databricks postgres list-endpoints \
  projects/$PROJECT_ID/branches/$BRANCH_ID \
  -o json \
  --page-size 100 \
  --limit 100 \
  --debug \
  --target $TARGET \
  --profile $DATABRICKS_PROFILE
```

<details>
<summary>Options</summary>

| Option        | Required | Description                                                                 |
| ------------- | -------- | --------------------------------------------------------------------------- |
| `PARENT`      | yes      | Branch resource path: `projects/{project_id}/branches/{branch_id}`          |
| `-o json`     | no       | Output as JSON (default: text)                                              |
| `--page-size` | no       | Items per API request (upper bound per page; must be at least `10` if set)  |
| `--limit`     | no       | Maximum total results to return; CLI auto-paginates and stops at this count |
| `--debug`     | no       | Enable debug logging                                                        |
| `--target`    | no       | Bundle target to use (if applicable)                                        |
| `--profile`   | no       | Databricks CLI profile name                                                 |

</details>

<details>
<summary>Example response</summary>

```json
[
  {
    "create_time": "2026-01-15T10:30:00Z",
    "name": "projects/my-project/branches/production/endpoints/primary",
    "parent": "projects/my-project/branches/production",
    "status": {
      "autoscaling_limit_max_cu": 1,
      "autoscaling_limit_min_cu": 1,
      "current_state": "ACTIVE",
      "disabled": false,
      "endpoint_type": "ENDPOINT_TYPE_READ_WRITE",
      "group": {
        "enable_readable_secondaries": false,
        "max": 1,
        "min": 1
      },
      "hosts": {
        "host": "ep-cool-breeze-abc123.database.cloud.databricks.com"
      },
      "settings": {}
    },
    "uid": "ep-cool-breeze-abc123",
    "update_time": "2026-01-15T10:31:00Z"
  }
]
```

</details>

```bash title="Common"
databricks postgres list-databases projects/my-project/branches/production -o json
```

```bash title="All Options"
databricks postgres list-databases \
  projects/$PROJECT_ID/branches/$BRANCH_ID \
  -o json \
  --page-size 100 \
  --limit 100 \
  --debug \
  --target $TARGET \
  --profile $DATABRICKS_PROFILE
```

<details>
<summary>Options</summary>

| Option        | Required | Description                                                                 |
| ------------- | -------- | --------------------------------------------------------------------------- |
| `PARENT`      | yes      | Branch resource path: `projects/{project_id}/branches/{branch_id}`          |
| `-o json`     | no       | Output as JSON (default: text)                                              |
| `--page-size` | no       | Items per API request (upper bound per page)                                |
| `--limit`     | no       | Maximum total results to return; CLI auto-paginates and stops at this count |
| `--debug`     | no       | Enable debug logging                                                        |
| `--target`    | no       | Bundle target to use (if applicable)                                        |
| `--profile`   | no       | Databricks CLI profile name                                                 |

</details>

<details>
<summary>Example response</summary>

```json
[
  {
    "create_time": "2026-01-15T10:30:00Z",
    "name": "projects/my-project/branches/production/databases/db-abc123",
    "parent": "projects/my-project/branches/production",
    "status": {
      "postgres_database": "databricks_postgres",
      "role": "projects/my-project/branches/production/roles/rol-xyz789"
    },
    "update_time": "2026-01-15T10:30:05Z"
  }
]
```

</details>

Key values from the output:

| Value                    | JSON path                    | Used for                     |
| ------------------------ | ---------------------------- | ---------------------------- |
| Endpoint host            | `status.hosts.host`          | `PGHOST`                     |
| Endpoint resource path   | `name`                       | `LAKEBASE_ENDPOINT`          |
| Database resource path   | `name` (from list-databases) | `lakebase.postgres.database` |
| PostgreSQL database name | `status.postgres_database`   | `PGDATABASE`                 |

### Connect

The simplest way to connect is with `databricks psql`:

```bash title="Common"
databricks psql --project my-project
```

```bash title="All Options"
databricks psql \
  --project $PROJECT_ID \
  --branch $BRANCH_ID \
  --endpoint $ENDPOINT_ID \
  --autoscaling \
  --max-retries 3 \
  --debug \
  -o json \
  --target $TARGET \
  --profile $DATABRICKS_PROFILE \
  -- -c "SELECT 1"
```

<details>
<summary>Options</summary>

| Option          | Required | Description                                                                                       |
| --------------- | -------- | ------------------------------------------------------------------------------------------------- |
| `--project`     | no       | Project ID. With a TTY, omit to choose from prompts. In CI or scripts, set explicitly when needed |
| `--branch`      | no       | Branch ID (default: auto-select when only one exists)                                             |
| `--endpoint`    | no       | Endpoint ID (default: auto-select when only one exists)                                           |
| `--autoscaling` | no       | Only show Lakebase Autoscaling projects                                                           |
| `--provisioned` | no       | Only show Lakebase Provisioned instances                                                          |
| `--max-retries` | no       | Connection retries, 0 to disable (default: 3)                                                     |
| `--debug`       | no       | Enable debug logging                                                                              |
| `-o json`       | no       | Output as JSON (default: text)                                                                    |
| `--target`      | no       | Bundle target to use (if applicable)                                                              |
| `--profile`     | no       | Databricks CLI profile name                                                                       |
| `-- PSQL_ARGS`  | no       | Additional arguments passed through to `psql`                                                     |

</details>

Without a TTY (for example in CI), the CLI auto-selects when only one branch or endpoint exists. When multiple exist, specify `--project`, `--branch`, and `--endpoint` explicitly so the command does not block on prompts.

If you don't have a `psql` client installed, generate a short-lived credential and use it with any PostgreSQL client (DBeaver, pgAdmin, DataGrip, or a language driver):

```bash title="Common"
databricks postgres generate-database-credential \
  projects/my-project/branches/production/endpoints/primary
```

```bash title="All Options"
databricks postgres generate-database-credential \
  projects/$PROJECT_ID/branches/$BRANCH_ID/endpoints/$ENDPOINT_ID \
  --json '{}' \
  --debug \
  --target $TARGET \
  -o json \
  --profile $DATABRICKS_PROFILE
```

<details>
<summary>Options</summary>

| Option      | Required | Description                                                                                  |
| ----------- | -------- | -------------------------------------------------------------------------------------------- |
| `ENDPOINT`  | yes      | Endpoint resource path: `projects/{project_id}/branches/{branch_id}/endpoints/{endpoint_id}` |
| `--json`    | no       | Inline JSON or `@path/to/file.json` with request body                                        |
| `--debug`   | no       | Enable debug logging                                                                         |
| `-o json`   | no       | Output as JSON (default: text)                                                               |
| `--target`  | no       | Bundle target to use (if applicable)                                                         |
| `--profile` | no       | Databricks CLI profile name                                                                  |

</details>

Use the returned token as the password, with your Databricks email as the username and the endpoint host from `list-endpoints` above.

</details>

## Long-running operations

Create, update, and delete commands block until complete by default. Use `--no-wait` to return immediately and poll status:

```bash
databricks postgres create-project my-project \
  --json '{"spec": {"display_name": "My Project"}}' \
  --no-wait

databricks postgres get-operation projects/my-project/operations/<operation-id>
```

## Declarative Automation Bundles

Declarative Automation Bundles (DABs) let you define Lakebase Postgres infrastructure as code in `databricks.yml`, versioned alongside your application. A bundle specifies `postgres_projects`, `postgres_branches`, and `postgres_endpoints` under `resources`.

<details>
<summary>Example <code>databricks.yml</code> with a project, dev branch, and read-only replica</summary>

```yaml
bundle:
  name: my-lakebase-app

resources:
  postgres_projects:
    my_app:
      project_id: "my-lakebase-app"
      display_name: "My Lakebase Postgres App"
      pg_version: 17
      history_retention_duration: "172800s"
      default_endpoint_settings:
        autoscaling_limit_min_cu: 0.5
        autoscaling_limit_max_cu: 1.0
        suspend_timeout_duration: "300s"
        pg_settings:
          log_min_duration_statement: "1000"

  postgres_branches:
    dev_branch:
      parent: ${resources.postgres_projects.my_app.id}
      branch_id: "dev"
      no_expiry: true
      is_protected: false

  postgres_endpoints:
    read_replica:
      parent: ${resources.postgres_branches.dev_branch.id}
      endpoint_id: "replica"
      endpoint_type: "ENDPOINT_TYPE_READ_ONLY"
      autoscaling_limit_min_cu: 0.5
      autoscaling_limit_max_cu: 0.5
```

</details>

### Validate and deploy

```bash
databricks bundle validate
databricks bundle deploy
```

`bundle deploy` is idempotent. It creates new resources and updates existing ones to match the configuration. Unlike Databricks Jobs or Apps, there is no `bundle run` step; Lakebase Postgres resources are active once deployed. The [Declarative Automation Bundles documentation](https://docs.databricks.com/aws/en/dev-tools/bundles/) covers all options.

## Update masks

Update commands require an update mask specifying which fields to modify. The `--json` payload contains the new values. Only masked fields change.

```bash
databricks postgres update-branch \
  projects/my-project/branches/production \
  spec.is_protected \
  --json '{"spec": {"is_protected": true}}'
```

For multiple fields, use a comma-separated update mask (for example, `spec.autoscaling_limit_min_cu,spec.autoscaling_limit_max_cu`).

## Troubleshooting

For Databricks Apps configuration issues (resources in `databricks.yml` and `app.yaml`), [Add a Lakebase resource to a Databricks app](https://docs.databricks.com/aws/en/dev-tools/databricks-apps/lakebase) has the resource and environment variable reference. For connection problems including idle wake-up and endpoint format, [Troubleshooting in Connect external apps](https://docs.databricks.com/aws/en/oltp/projects/external-apps-connect#troubleshooting) has fixes.

- **`permission denied for schema app` (deployed app)**: `npm run dev` ran before `databricks apps deploy`, so the schema is owned by your personal credentials and the app's service principal can't access it. The app fails to read or write to the database. To fix without losing data: connect via `databricks psql` and run `ALTER SCHEMA app OWNER TO "<sp-client-id>";` and `ALTER TABLE app.<your-table> OWNER TO "<sp-client-id>";` for each table (the client ID is in the app's **Authorization** tab), then redeploy. To start fresh: `databricks psql --project <project-id> -- -c "DROP SCHEMA IF EXISTS app CASCADE;"` then `databricks apps deploy`.
- **`permission denied for schema app` (local dev, collaborator)**: Only the Lakebase project creator has `databricks_superuser` access automatically. To grant a teammate local access, the project creator opens **Branch Overview** in the Lakebase UI, clicks **Add role**, selects the teammate's identity, and checks `databricks_superuser`. Postgres password auth is a simpler alternative: set a password in **Branch Overview** and add `PGPASSWORD=<password>` to `.env`.
- **`Unknown field path in update_mask: 'spec.suspend_timeout_duration'`**: Use `spec.suspension` as the update mask for all endpoint-level suspension changes with `update-endpoint`. To disable scale to zero, pass `{"spec": {"no_suspension": true}}`; to change the timeout, pass `{"spec": {"suspend_timeout_duration": "300s"}}`. Setting `no_suspension: false` is not supported.
- **Connection refused after period of inactivity**: Lakebase Autoscaling scales to zero when idle. The first connection after inactivity triggers a wake-up and may take a few seconds. If your connection library doesn't retry automatically, add a short retry loop.

## AppKit docs

Access the AppKit API reference, component docs, and plugin docs from the terminal:

```bash
npx @databricks/appkit docs                    # browse the documentation index
npx @databricks/appkit docs "lakebase"         # view Lakebase Postgres plugin docs
```

Or view the [AppKit Lakebase Postgres plugin reference](https://databricks.com/devhub/docs/appkit/v0/plugins/lakebase) on this site.

## Where to next

[Templates](https://databricks.com/devhub/templates) cover common Lakebase Postgres patterns. Browse them to find a starting point, or copy one into your coding agent to scaffold a working app.
