The autonomous backend platform for agentic coding. PostgreSQL, REST APIs, auth, storage, realtime, and functions, driven by your coding agent over MCP, with every change planned, verified, and reversible.
backenly.com · Docs · Pricing · Client libraries
backenly-demo.mp4
⭐ Star the repo to follow releases and help other builders find Backenly.
Most backend platforms hand you primitives and leave you owning schema design, API wiring, RLS policies, monitoring, and recovery. Newer agent-native backends hand an agent raw SQL and no safety net.
Backenly does neither. You describe the product you're building through the coding agent you already use (Claude Code, Cursor, Codex) over an MCP server. Backenly derives the data model, generates the endpoints, writes the policies, applies the change, and then verifies it against the live runtime. A continuous autonomy loop keeps watching after that, repairing drift, missing indexes, broken triggers, and RLS gaps on its own.
The distinction that matters: Backenly does not just generate backend
resources, it manages backend change safely. Every mutation, whether it comes
from an agent, from the dashboard, or from an automated repair, goes through one
typed action kernel with dry-run, audit, and rollback. There is deliberately no
raw-SQL path for mutating structure. Reads are standard SQL, and your data is
never locked in: direct Postgres connection strings and full pg_dump exports
are one command away.
- Intent-first. An LLM planner derives entities, relations, and actions from natural language. No table designer, no hand-written migrations.
- One governed kernel. Every mutation flows through
executeAction, so the agent's model of the database and the database itself cannot silently diverge. - Closed-loop autonomy. The loop heals the reversible safe band by itself. Anything risky, such as auth, external credentials, or destructive and irreversible changes, always waits for a human.
- Multi-tenant by construction. Each project gets its own PostgreSQL schema
(
workspace_{projectId}). Isolation is enforced by Postgres grants and RLS, never by application-level string filtering. - PostgREST data plane. Tables are served through PostgREST, so the query grammar you already know works unchanged.
| Capability | What you get |
|---|---|
| Database | PostgreSQL with a schema per project, served through PostgREST |
| Auth & Users | Email/password and social sign-in, JWT sessions, RLS-forced user tables |
| Storage | Public and private buckets with per-file access control |
| Realtime | Shared LISTEN/NOTIFY hub for table change subscriptions |
| Functions | Serverless route modules, validated before they ship and self-healed if they break |
| Integrations | One ctx.integrations.<id>.request() surface for third-party APIs |
| Autonomy | MAPE-K loop that observes, detects, proposes, applies, and verifies repairs |
| Monitoring | Request logs with stability and reliability scoring |
| Branches | Preview branches with their own sequences, plus diff and merge |
| Deploy | Governed rollout with restore points and an audit ledger |
The MCP server advertises 20 tools. The catalog is capped deliberately, because tool-selection accuracy degrades as it grows, while the dispatcher stays wider so an agent pinned to an older manifest never gets a 404.
| Group | Tools |
|---|---|
| Understand | read_backend_state · get_table_schema · run_query · fetch_docs |
| Build | apply_migration · enable_auth · set_rls · create_bucket · generate_function · enable_realtime |
| Data | db_insert · db_update · db_delete |
| Operate | branch · create_api_key · set_env_var · get_database_credentials · check_approval · generate_types |
| Natural language | backend_chat, the fall-through for anything not named above |
Agents can also browse live project state as MCP resources (backenly://state,
tables, apis, buckets, triggers) instead of spending a tool call to ask.
Create a project at backenly.com, then point your agent at it. Nothing to install or operate.
One deployment is one project.
git clone https://github.com/backenly/backenly.git
cd backenly
npm install
npm run selfhostThat is the whole install. It generates the secrets, starts the Compose stack, creates the tables, runs the superuser steps, issues the PostgREST credential and reconciles until the deployment reports ready. Then:
npm run dev # dashboard :3000 · runtime :3001Then claim the deployment. The installer prints a claim link carrying a setup token. Open it and create your account: the first account to present the token becomes the administrator and takes ownership of this deployment's single project in the same step. There is no second command to run. The signup page also has a Setup token field while the deployment is unclaimed, if you would rather paste the token than follow the link.
The token is in .env as BACKENLY_SETUP_TOKEN. It gates the claim because a
deployment is often reachable before its operator gets to it — an open port on
a VPS, a preview environment, a colleague pointed at the wrong host — and
without it the single administrator slot would go to whoever loaded the page
first. Once the deployment is claimed the token stops working, whatever it is
set to.
Claiming the deployment needs no email. Two things do: password-reset
codes, and the code a new account must enter before it exists if you open
registration with BACKENLY_ALLOW_PUBLIC_SIGNUP=true. Set any SMTP provider in
.env:
SMTP_HOST=smtp.resend.com # or SES, Mailgun, SendGrid, Gmail
SMTP_PORT=587 # STARTTLS; 465 is rewritten to 587
SMTP_USER=resend
SMTP_PASS=...
SMTP_FROM=Backenly <noreply@yourdomain.com> # a domain your provider has verifiedWithout it, the reset page says email is unavailable rather than pretending a code was sent, and later signups are refused rather than created unverified. To reset a password without email, run this on the server:
npm run auth:reset-password -- --email you@example.com # prompts
npm run auth:reset-password -- --email you@example.com --generate # prints oneIt ends every session for that account. Access to the machine is the authority, as it is for the setup token.
The install creates a separate PostgreSQL role for each job, rather than one that does everything:
| Role | Used by | Properties |
|---|---|---|
backenly_user |
the install scripts only | SUPERUSER. Creates roles, installs event triggers. Never in DATABASE_URL. |
backenly_app |
web and runtime | NOSUPERUSER NOBYPASSRLS. Owns the platform tables and workspace schemas, so governed typed actions keep their DDL rights. |
backenly_authenticator |
PostgREST | NOINHERIT; can only switch into anon / authenticated / service_role. |
backenly_backup |
pg_dump only |
NOSUPERUSER BYPASSRLS, with CONNECT, USAGE, SELECT and nothing else. |
backenly_app not being a superuser is the one that matters most. A superuser
bypasses row-level security — including FORCE ROW LEVEL SECURITY — so an
application running as one has its own policies applied only by convention.
Ownership plus FORCE is what keeps DDL working while leaving RLS in force.
Restore runs over the application connection, not the backup one, so the application keeps ownership of the schema it restored.
npm run selfhost is safe to rerun. It fills in what is missing and never
rotates a secret that already exists, so a rerun cannot sign out your sessions
or break a running PostgREST.
It does not hide the steps or replace them: it runs the same sequence
documented below, driven by the same module that
npm run bootstrap prints its guidance from. Read that section if you are
bringing your own database, if a step failed and you want to run it yourself,
or if you would simply rather see what is being done to your machine.
The installer generates everything except one choice: which AI provider
serves the natural-language tools (backend_chat, function generation). Set
ANTHROPIC_API_KEY or OPENAI_API_KEY in .env, or AI_PROVIDER=bedrock
with BEDROCK_REGION to use Claude through your AWS account. Without one,
those tools say AI is unavailable; the typed tools and the self-healing loop
need no model.
The same install, one step at a time. npm run bootstrap is the centre of it:
it provisions the project, and it is a reconciler rather than an installer, so
rerunning it repairs whatever is missing and changes nothing else. You will run
it at least twice, and that is the intended path, not a failure.
You install on the host:
- Node.js 20+ — the app, the scripts and the CLI all run here, not in a container
- Docker, with Compose
The Compose stack provides:
- PostgreSQL 15, with
pg_stat_statementspreloaded - Redis
- PostgREST, pinned — this is the
/db/*data plane
Docker does not provide Node. If you would rather run your own PostgreSQL or PostgREST, read Bringing your own database first; the steps below describe the Compose path.
Use a PostgreSQL cluster dedicated to this deployment.
PostgREST authenticates through cluster-global roles, and the list of schemas it serves is stored as a role setting with no
IN DATABASEscope. Two Backenly databases on one cluster therefore overwrite each other's served-schema registry, and the loser's data plane answersPGRST106on every table. This is a known limitation being redesigned, not something you can configure around. The Compose stack gives you a dedicated one.
git clone https://github.com/backenly/backenly.git
cd backenly
npm install
cp .env.example .env
docker compose -f docker-compose.dev.yml up -dpostgrest will restart in a loop until step 4 issues its password. That is
expected, and it is deliberate that it serves nothing until then.
Then set these in .env, adding any line the template does not already carry —
today that is BACKENLY_PROJECT_ID:
BACKENLY_PROJECT_ID=<uuid> # any UUID, e.g. `uuidgen`
JWT_SECRET=<openssl rand -hex 32>
POSTGREST_JWT_SECRET=<openssl rand -hex 32>
STORAGE_SECRET=<openssl rand -hex 32>
ANTHROPIC_API_KEY=<your key> # or OPENAI_API_KEY, or AI_PROVIDER=bedrock + BEDROCK_REGIONBACKENLY_EDITION is already single-tenant in the template and you can leave
it there. Backenly resolves an unset edition to single-tenant too, so the line
is not what makes this a self-host install — it is there because
scripts/deploy.sh composes through scripts/compose-cloud.sh, which refuses to
guess an edition rather than risk cloning a private repository you cannot read.
DATABASE_URL already matches the Compose stack, so leave it alone unless you
changed POSTGRES_USER or POSTGRES_PASSWORD.
BACKENLY_PROJECT_ID is the identity of this deployment and must never change
afterwards: it names the workspace_<uuid> schema your tables live in. Bootstrap
refuses to run against a database that already holds a different one. You may
leave it unset and pin the id bootstrap generates instead, but then step 4's
command has to be copied from bootstrap's output rather than from here.
JWT_SECRET signs every platform session. POSTGREST_JWT_SECRET is separate and
must match the value Compose passes to PostgREST, because the gateway mints
short-lived internal tokens with it. STORAGE_SECRET signs the access tokens for
stored files — without it every upload fails with a 500 whose only explanation is
in the server log. None of the three has a default, deliberately: a fallback
baked into a public repository is a key everybody already knows.
The AI provider key powers the natural-language tools; .env.example lists the
providers, per-purpose model overrides and monthly AI spending limits. See
.env.example for the rest.
Skip this on the Compose path. The stack already uses backenly_user, which
is what the SQL defaults to.
It applies only if you brought your own database and the role in your
DATABASE_URL is something else, in which case run, as a superuser:
psql -c "ALTER DATABASE <db> SET backenly.app_role = '<your role>'"Skipping it there makes the next section fail with
role "backenly_user" does not exist.
npm run db:generate && npm run db:push
npm run bootstrap
db:pushbelongs to this step only — do not rerun it later.The PostgREST support objects are created by SQL in step 4, not by Prisma: a registry table and two event triggers that fire on schema DDL.
prisma db pushdoes not know about them, so on an installed deployment it treats them as objects to drop, which is exactly what--accept-data-lossauthorises. The registry is what the data plane reads to decide which schemas PostgREST serves, so losing it takes/db/*down.
npm run bootstrapis the command that is safe to rerun. It is a reconciler and repairs whatever is missing.
This first run is expected to exit 3. Bootstrap creates the project row, its workspace schema and its signing secret, then reports what it cannot install itself. Its exit code is the state, and deployment automation should read it:
| exit | meaning |
|---|---|
0 |
ready |
2 |
refused. The database is in a state bootstrap will not touch, either more than one project or a BACKENLY_PROJECT_ID that does not match what is already there. Nothing was written. |
3 |
core bootstrapped, prerequisites still unmet. Not ready: the /db/* data plane will answer PGRST106 until step 4 is done. |
These reach the database as a superuser. Bootstrap prints this same list, with your real project id filled in; run what it prints.
# 1. support objects: registry functions, DDL event triggers, and the
# anon / authenticated / service_role / backenly_authenticator roles.
bash scripts/postgrest-install.sh
# 2. passwords, role membership and the per-schema grants.
# Prints the connection string PostgREST authenticates with. Store it: it is
# not recoverable, and this command will not print it again.
npx tsx scripts/setup-postgrest-roles.ts --project <PROJECT_ID> --apply
# 3. put that password in .env as POSTGREST_AUTHENTICATOR_PASSWORD, then start
# the data plane. It has been restarting in a loop until now.
docker compose -f docker-compose.dev.yml up -d postgrest
# 4. optional. Only needed to hand out direct psql credentials to a project.
# Backenly is fully operational without it.
bash scripts/install-sql.sh scripts/setup-direct-access.sqlThose scripts talk to PostgreSQL through one explicit mechanism, chosen by
BACKENLY_DB_ADMIN, so the same command works wherever the database lives:
BACKENLY_DB_ADMIN |
how it connects |
|---|---|
docker (default) |
psql inside the Compose postgres service |
url |
psql -d "$BACKENLY_ADMIN_DATABASE_URL" — any reachable database |
local |
sudo -u postgres psql — a cluster installed on this host |
That order is not arbitrary and cannot be collapsed into one command. The first
has to create the roles, because the event triggers it installs grant to them,
and a CREATE SCHEMA that grants to a role nobody created aborts. The second
grants per workspace schema, so it needs the schema npm run bootstrap already
created above. This is why bootstrap runs, reports NOT ready, and is rerun: it is
a reconciler, and rerunning is the mechanism.
Then rerun:
npm run bootstrapExit 0 means ready. Anything else, read what it printed and fix that; it is safe to rerun as many times as you need.
npm run dev # dashboard :3000 · runtime :3001npm run dev starts both processes together. There is no account yet, so sign up
in the dashboard, then run npm run bootstrap once more to issue the anon key
your frontend embeds.
The self-healing loop runs by itself. There is nothing to schedule.
instrumentation.ts starts an in-process scheduler when the app boots, and it
is active whenever VERCEL is unset — which is every self-hosted deployment. The
reconciler ticks every minute. .env.example already sets the two flags it
needs, so a .env copied from it has autonomy on:
ENABLE_AUTONOMY_RECONCILER=true # the loop runs at all
ENABLE_AUTONOMY_LIVE_EXECUTION=true # it may apply fixes, not just observeConfirm it is ticking by watching the process output:
[AutonomyReconciler] Cron run complete — 1 projects, 0 fixes applied, 0 frozen
0 projects is normal on a new install, and does not mean it is broken. A
project is only reconciled once something has been built in it (a table other
than the built-in users table, a function, a bucket, a trigger, a webhook or
enabled auth) and it shows recent activity. A project that has only been named
has nothing to be right or wrong about, so the loop skips it and files nothing
against it. Create a table through your agent and the count becomes 1 on the
next tick.
CRON_SECRET is not required for any of the above. It guards one ad-hoc
endpoint, for forcing a tick without waiting for the minute:
CRON_SECRET=<openssl rand -hex 32> # add to .env, then restart
curl -H "x-cron-secret: $CRON_SECRET" http://127.0.0.1:3000/api/cron/autonomyNothing calls that URL on its own. The in-process scheduler is the mechanism; the endpoint is for operators.
Backups are a Backenly Cloud capability and the routes answer 404 on a self-hosted deployment. Nothing below is needed to run Backenly; it is here because the same code runs in both editions and the failure it describes is silent.
A backup is pg_dump of one project's workspace_<id> schema, gzipped under
BACKUP_DIR. It carries tables, rows, indexes, constraints, RLS policies,
FORCE ROW LEVEL SECURITY and the end-user users table. It is not PITR
and not deployment-level disaster recovery: project secrets, API keys, storage
objects and function definitions all live outside that schema.
npm run selfhost creates the backup credential for you. It converges a
dedicated backenly_backup role and records BACKUP_DATABASE_URL in .env,
as the last step of the install — last because the objects it must be able to
read are created at different times: the platform tables by the migration
chain, the PostgREST registry and event triggers by the elevated SQL after it,
and the workspace schema by bootstrap after that.
This used to be a manual step, and the instructions here were incomplete: they
granted the workspace schema and not public, so an operator who followed them
exactly still could not produce a Deployment Recovery bundle. pg_dump --schema public takes a LOCK TABLE on every table in the schema and stopped at the
registry:
pg_dump: error: query failed:
ERROR: permission denied for table backenly_pgrst_schema_registry
The credential is needed because workspace tables are FORCE ROW LEVEL SECURITY, and under FORCE even the owner is subject to its policies, so
pg_dump over the application role aborts on the first protected table:
pg_dump: error: query failed: ERROR: query would be affected by
row-level security policy for table "users"
On production that meant every nightly backup failed for four days while the
pruner deleted the last good ones, ending at zero on disk. The Compose stack
hides it, because POSTGRES_USER there is the bootstrap superuser and
superusers bypass RLS. Managed Postgres does not hide it.
Do not fix this by granting BYPASSRLS to the application role — that
disables every RLS policy on every tenant at once. The installer creates a role
with exactly this much and no more:
LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE BYPASSRLS NOINHERIT
CONNECT on the database
USAGE + SELECT on public and on every workspace schema
no CREATE, no INSERT/UPDATE/DELETE/TRUNCATE, member of nothing, owns nothing
To converge it by hand on an existing deployment, or after adding a schema:
npx tsx scripts/setup-backup-role.ts --applyRestore deliberately writes back over DATABASE_URL instead, so the
application keeps ownership of its own schema — restoring over the backup role
would re-own every table to it, and FORCE ROW LEVEL SECURITY keys on the
owner. tests/integration/backup-role-contract.spec.ts builds both roles for
real and proves the whole contract, including that the application role is
refused where the backup role succeeds.
The Compose stack is the supported path. If you already operate PostgreSQL, or run PostgREST under your own service manager, the differences are:
- Point
DATABASE_URLandDIRECT_URLat your database, and do step 2 above. - Run the step 4 scripts with
BACKENLY_DB_ADMIN=urlandBACKENLY_ADMIN_DATABASE_URLset to a superuser connection string, or withBACKENLY_DB_ADMIN=localif PostgreSQL is on this machine. - Start your own PostgREST against the connection string step 4.2 prints, with
POSTGREST_URLin.envpointing at it and its JWT secret set toPOSTGREST_JWT_SECRET. Use the version pinned indocker-compose.dev.yml: PostgREST reads its configuration out of the database, so a version difference changes what the data plane serves. - Enable
pg_stat_statementsyourself:
# postgresql.conf, then restart the server
shared_preload_libraries = 'pg_stat_statements'CREATE EXTENSION IF NOT EXISTS pg_stat_statements;It is how Backenly finds indexes that are missing by measurement — the columns Postgres is actually spending milliseconds filtering on — rather than only by schema shape. Without it that check reports itself as unchecked rather than passing, so nothing claims a guarantee it never evaluated. The Compose stack sets it already.
Backenly is built to be driven over MCP. Point your agent at the MCP server:
npx @backenly/mcp-server initWorks with Claude Code, Cursor, Codex, Cline, and Claude Desktop. Keys are scoped and revocable, and read-only keys serve a reduced tool set.
Restart your MCP host after installing. Tools stay absent until it reconnects, which looks like a broken install but is not one.
Verify the connection by asking your agent:
Call Backenly's read_backend_state tool and tell me what exists in this project.
Then describe what you want. The SDK is for the app you ship:
const backend = new BackenlyClient({ projectId, apiKey })
await backend.auth.signUp({ email, password })
await backend.posts.create({ title: 'Hello' })
await backend.posts.list({ filter: { published: true } })| Path | What lives there |
|---|---|
app/ |
Next.js routes: dashboard UI, platform APIs, and the public /api/v1/* runtime |
lib/ai/ |
The Brain: planning, the tool loop, and executeAction, the governed mutation kernel |
lib/orchestration/ |
The nine-phase pipeline from intent to verified change |
lib/autonomy/ |
The MAPE-K loop that monitors and repairs running backends |
lib/execution/ |
Schema writes, migrations, and rollback beneath the kernel |
lib/tenant/ |
Schema isolation and the tenant boundary |
packages/ |
Client libraries: SDK, CLI, MCP server (MIT) |
prisma/ |
Platform schema: the public schema, not tenant data |
server/ |
Express runtime serving the end-user API |
scripts/ |
Operational tooling, probes, and the demo recording pipeline |
AGENTS.md is the deeper architectural guide, and is written for
coding agents working in this repo as much as for people.
Self-hosting is free and complete. This repository is the whole platform: runtime, governance, and the full self-healing engine, not a stripped community edition. You bring the servers, the Postgres, and an AI provider key.
Backenly Cloud runs the same codebase, and
handles infrastructure, backups, upgrades, and the autonomy tokens. pg_dump
moves your data between the two in either direction.
Pull requests are open and welcome. Bug reports, feature requests, questions,
and code all help. Good first places to look are open issues, the probe and
detector suite under lib/autonomy/, and client library ergonomics in
packages/. If a change is large or moves an architectural boundary, open an
issue first so we can agree the approach before you spend the time.
Before opening a PR:
npm run lint
npx tsc --noEmit
npm test
npx tsx scripts/preflight-oss.ts --tree # no credentials in what you committedTwo things worth knowing before you write code here. Tests run against a real
PostgreSQL instance, and the database is never mocked, because mocking it has
caused production incidents here before. And every schema mutation goes through
executeAction; a patch that writes DDL around the kernel will be sent back, no
matter how correct the SQL is.
See CONTRIBUTING.md for the full guide.
Please do not open a public issue for a security problem. See SECURITY.md for the private reporting route.
The platform is licensed under the Apache License 2.0.
The client libraries under packages/, the SDK, CLI, and MCP
server, are MIT, so they impose nothing on the applications that embed them.
Apache-2.0 §6 grants no rights to the Backenly name or logo, and this project does not grant them separately. You may fork, modify, self-host, and commercialise the software. Distributing it under the Backenly name requires written permission.
Describing your project as "built on Backenly" or "a fork of Backenly" is accurate and always welcome. See TRADEMARK.md for the full policy, including what you may do without asking.
Star Backenly on GitHub to get notified about new releases.