Skip to main content
To use FrogBot with Postgres, install the package @frogbotai/db-postgres. It leverages Drizzle ORM and node-postgres to interact with a Postgres database that you provide. Alternatively, the @frogbotai/db-vercel-postgres package is also available and is optimized for use with Vercel. It automatically manages changes to your database for you in development mode, and exposes a full suite of migration controls for you to leverage in order to keep other database environments in sync with your schema. DDL transformations are automatically generated. To configure FrogBot to use Postgres, pass the postgresAdapter to your FrogBot Config as follows:

Usage

@frogbotai/db-postgres:
@frogbotai/db-vercel-postgres:
Both adapters implement collection search with PostgreSQL full-text search and pgvector. Lexical, vector, and hybrid queries each run as a single SQL statement, and ranking happens inside the database after access, where, draft, and locale filters are applied.

Prerequisites

Lexical-only indexes need nothing beyond PostgreSQL. An index with vector needs pgvector 0.8.0 or later. Each time the adapter connects, including when you run migrate, it checks pg_extension and runs CREATE EXTENSION IF NOT EXISTS vector if pgvector is missing. migrate:create does not connect, so it generates the schema without this check. pgvector is not a trusted extension, so the connecting role must be a superuser, or you must create the extension ahead of time with a role that can. On a managed host, enable pgvector the way your host documents when the application role cannot create it. If the extension cannot be used, the adapter stops setup with SearchCapabilityError. The message names the first vector index, the reason, and the PostgreSQL SQLSTATE:

Storage and indexes

A vector field that an index selects with vector.field is stored as a native vector(n) column in the collection, locale, and version tables. Vector fields that no index selects stay JSON columns. Setup adds these indexes to the schema, so they appear in development push and in generated migrations:
  • An HNSW index for each vector field and metric: vector_cosine_ops for cosine, vector_l2_ops for euclidean, and vector_ip_ops for dotProduct. pgvector’s HNSW index supports up to 2,000 dimensions. Larger vectors, and indexes with vector.approximate: false, are searched without an approximate index.
  • A GIN index on to_tsvector(language, ...) over an index’s lexical fields. lexical.language is a PostgreSQL text search configuration name such as english or french and defaults to simple. When some lexical fields are localized and others are not, the fields live in different tables, so the query is still correct but has no GIN index.
Collections with drafts get the same indexes on the version table, so draft: true queries rank the latest draft of each document. Changing a vector field’s dimensions requires a migration that rewrites or clears existing vectors.

Ranking

Query text supports web search syntax: quoted phrases, or, and - to exclude a term. It is always sent as a bound parameter. Vector and hybrid queries on an HNSW index report approximate: true. They run in a transaction with SET LOCAL hnsw.iterative_scan = strict_order, so pgvector keeps scanning the index until enough rows pass your access and where filters, in exact distance order. The same transaction sets hnsw.ef_search to the resolved candidate count, with pgvector’s default of 40 as the minimum and its maximum of 1,000 as the cap. Both settings end with the transaction and never reach other pooled connections. Iterative scans still stop at pgvector’s scan limits, so a very selective filter can return fewer than limit hits. Exact queries on an index with vector.approximate: false run with SET LOCAL enable_indexscan = off, so they never use an HNSW index that another search index built on the same field. When the request already runs in a transaction, search reads through it and sees its uncommitted writes. Hybrid search takes up to the resolved candidate count from each component, joins them in the database, and returns the top limit by fused score. A record that matches only one component still ranks, with the other component reported as null.

Limits

  • Localized fields are ranked on the requested locale’s stored value. Fallback-locale values are not ranked.
  • If a search runs before its columns or indexes exist, for example before you run migrations, it fails with SearchReadinessError.

Options

Both adapters accept these options. @frogbotai/db-vercel-postgres takes a different pool and adds two options of its own; see Vercel Postgres options.

Vercel Postgres options

Access to Drizzle

After FrogBot is initialized, this adapter will expose the full power of Drizzle to you for use if you need it. You can query the adapter’s runtime tables directly. These queries are untyped:

Tables, relations, and enums

In addition to exposing Drizzle directly, all of the tables, Drizzle relations, and enum configs are exposed for you via the frogbot.db property as well.
  • Tables - frogbot.db.tables
  • Enums - frogbot.db.enums
  • Relations - frogbot.db.relations

Prototyping in development mode

Drizzle exposes two ways to work locally in development mode. The first is db push, which automatically pushes changes you make to your FrogBot Config (and therefore, Drizzle schema) to your database so you don’t have to manually migrate every time you change your FrogBot Config. This only works in development mode, and should not be mixed with manually running migrate commands. You will be warned if any changes that you make will entail data loss while in development mode. Push is enabled by default, but you can opt out if you’d like. Alternatively, you can disable push and rely solely on migrations to keep your local database in sync with your FrogBot Config.

Migration workflows

In Postgres, migrations are a fundamental aspect of working with FrogBot and you should become familiar with how they work. For more information about migrations, click here.

Drizzle schema hooks

Apps created with create-frogbot-app already depend on drizzle-orm at the adapter’s version; in other apps, install the same version used by your database adapter.

beforeSchemaInit

Runs before the schema is built. You can use this hook to extend your database structure with tables that won’t be managed by FrogBot.
One use case is preserving your existing database structure when migrating to FrogBot. By default, FrogBot drops the current database schema, which may not be desirable in this scenario. To quickly generate the Drizzle schema from your database you can use Drizzle Introspection You should get the schema.ts file which may look like this:
You can import them into your config and append to the schema with the beforeSchemaInit hook like this:
Make sure FrogBot doesn’t overlap table names with its collections. For example, if you already have a collection with slug “users”, you should either change the slug or dbName to change the table name for this collection.

afterSchemaInit

Runs after the Drizzle schema is built. You can use this hook to modify the schema with features that aren’t supported by FrogBot, or if you want to add a column that you don’t want to be in the FrogBot config. To extend a table, FrogBot exposes extendTable utility to the args. You can refer to the Drizzle documentation. The following example adds the extra_integer_column column and a composite index on country and city columns.

Adding tables through rawTables

You can also add tables, columns, and indexes to the adapter’s raw SQL schema through adapter.rawTables in beforeSchemaInit. The raw schema supports fewer features than Drizzle’s table builders.