# Site databases

Source: https://modulify.ai/docs/data/databases

Every project can have its own Postgres database, created and shaped by the AI.

A site database is a real Postgres database that belongs to one project. Your published site reads and writes it at runtime, and you browse and edit the contents from the CMS tab.

It is not a spreadsheet or a proprietary content store. It is Postgres, with tables, columns, foreign keys, constraints and triggers.

## Getting a database

Projects start without one. Open the CMS tab in the editor toolbar and you will see an empty state that says **No database** with the description "Ask the AI to create a database." and a single **Ask AI** button.

The button drops the prompt "Create a database for me." into chat. You can also just describe what you want to store, and the AI provisions the database itself before it starts building:

```markdown
Add a blog with posts, authors and tags. Posts need a title, cover image, body and publish date.
```

Provisioning takes a few seconds. When it finishes, the connection string is available to your site and the CMS tab switches from the empty state to the collections browser.

## How the AI creates tables

The AI has direct SQL access to your database and uses it to create tables, add columns, write indexes and constraints, run migrations and insert seed data. It follows a fixed set of conventions so the CMS can read the result:

- Every table gets a generated UUID primary key: `id uuid PRIMARY KEY DEFAULT gen_random_uuid()`. Auto incrementing integer ids are not used.
- A human readable URL key lives in a separate `slug` column, never as the primary key.
- Relations point at the target table's `id`, so foreign key columns are `uuid` too.
- Timestamps are named exactly `created_at` and `updated_at`, typed `timestamptz`. `created_at` is set by its default, and `updated_at` is re-stamped on every edit by a trigger created alongside the table.

The CMS treats `id`, `created_at` and `updated_at` as system managed. They show in the row editor as read only, and on a new row they read "Auto-generated".

### Field metadata

Some things Postgres cannot express on its own. A column that stores an image URL is just `text` as far as the database is concerned. Modulify keeps that extra information in a table called `_collection_fields`, which the AI writes to in the same migration that creates the column.

That metadata is what makes a column render as a color swatch, a rich text editor, a file uploader, an image uploader, a multi reference picker, or one field with [language tabs](/docs/data/languages). `_collection_fields` is never shown as a collection of its own. See [Column types](/docs/data/column-types) for what each one looks like.

## Running SQL

SQL runs through the AI, not through a console. There is no query editor in the product: you ask for the change in chat and the AI runs the statement for you.

```markdown
Add a "featured" boolean to posts, defaulting to false, and set it true for the three newest posts.
```

The SQL tool has real limits. A single statement is capped at 20,000 characters, each statement gets a 15 second timeout, and a result set comes back to the AI capped at 200 rows. Long running work has to be broken into steps.

When the AI runs a statement that changes the schema, the CMS refreshes on its own, so a new collection appears in the sidebar without a reload.

## How your site reads the database

The connection string is stored as an encrypted project secret named `DATABASE_URL` and injected into your site's environment. There is one value, shared by the preview, development and production machines.

Read it from server side code with any Postgres client. The AI typically installs `pg`, `drizzle-orm` or `kysely` and reads the value from `process.env`:

```typescript
const connectionString = process.env.DATABASE_URL
```

The value is never hardcoded into the codebase, and it is never read from the browser.

### The DATABASE_URL secret is locked

`DATABASE_URL` appears in the [Secrets](/docs/data/secrets) tab with a **Managed** badge. Hovering it says "This secret is managed by Modulify and cannot be edited or removed." You can reveal and copy the value, but you cannot change or delete it, and neither can the AI. It lives and dies with the database itself.

## What you can and cannot do

You can browse every collection, page through rows, search them, create rows, edit them, duplicate them, delete them, clear a collection, export data, and toggle whether system collections are visible.

You cannot add, rename or drop a column from the CMS. You cannot create a collection by clicking a button, run your own SQL, connect an external Postgres database, or see database user credentials. Every schema change goes through chat.

## When the database is unavailable

If the database is not reporting as running, the CMS shows **Database unavailable** with "Try again in a few moments." If Modulify cannot reach the database service at all, the message is "Could not reach the database service. Your data is safe, please try again in a moment!" Neither affects your data. Use the refresh button in the Collections header to retry.


## From an AI client over MCP

`get_site_database` reads the connection details and `create_site_database` gives a site a database if it does not have one, which is safe to call even when it already does.

Running arbitrary SQL is not available over MCP. A connected client works through the CMS tools instead, or asks the site AI to run a migration for it.

See [MCP tools](/docs/mcp/tools).
## Next

- [The CMS](/docs/data/cms) walks through browsing and editing your content.
- [Column types](/docs/data/column-types) lists every Postgres type and the editor it gets.
- [Clearing and exporting](/docs/data/clearing-and-exporting) covers getting your data out and wiping it.