Elements vs Supabase
Supabase is a database platform: an open-source backend built around Postgres. The company of the same name runs it mainly as a hosted service, and the open-source stack can also be self-hosted. Every project gets a full Postgres database, and around it Supabase runs Auth for user sign-in, Realtime for pushing database changes to browsers, Storage for files, Edge Functions for server-side TypeScript, and a REST API generated automatically from your tables. A web dashboard manages tables, users and policies, and client libraries call the services from a web or mobile frontend. Paid plans charge a monthly fee plus metered usage past each plan's allowances.
Elements is not a database platform; it is an integrated app environment that bundles Postgres and ships the services Supabase adds. It is a coherent system, built for people and their agents, that includes a project server that runs while you work, a default app framework, a package installer, a test runner, a bundled Postgres database, and deploy. The framework covers sign-in and sessions, realtime data, file uploads, typed server functions and background jobs. Elements deploys to ordinary Ubuntu servers you reach over SSH, charges for its tooling per machine, and meters nothing (pricing).
The difference is where those services live. On Supabase, they are hosted services your frontend calls over the network, and authorization is written as policies in the database. In Elements, they are part of your app: written in TypeScript, checked by the same build as the rest of your code, and deployed with it in one command. The build reports every build error in any of them in milliseconds, with no containers to run.
With the services in your app, what Supabase still offers is Postgres hosting. As Postgres hosts, DigitalOcean Managed PostgreSQL and Amazon RDS give more for the money. Both include point-in-time recovery with their automated backups and offer automatic failover as a setting. Supabase sells point-in-time recovery as a paid add-on and reserves failover for Enterprise plans.
Each Supabase service has a counterpart in Elements, and the two also differ on billing and hosting:
| Elements | Supabase | |
|---|---|---|
| Sign-in and sessions | Sessions, built into the default framework | Supabase Auth |
| Realtime | LiveTable and channels: database rows and messages pushed live to the browser | Supabase Realtime |
| File uploads | Sent to the server with the rest of the form, then stored in Postgres or an object store | Supabase Storage |
| Server code | Typed server functions and routes in your app, deployed with it | Edge Functions on a separate Deno-compatible runtime |
| Database API | Functions you write, with the authorization check at the top | Generated from your schema, secured by row level security policies |
| Background jobs | TypeScript jobs in your app, run by a built-in worker with retries | Cron and Queues in the database, with the work done in SQL or by a consumer such as an Edge Function |
| Typed calls from the browser | Automatic for every server function | Generated with supabase gen types, rerun after schema changes |
| Local development | One coherent system, with Postgres bundled and no containers | The full stack in Docker containers |
| Billing | A license per machine; nothing is metered | A plan, plus metered usage past its allowances |
| Postgres | Bundled on your server, or any host you choose | Hosted Postgres |
| Where it runs | Any Ubuntu server you reach over SSH | Supabase's cloud, or self-hosted |
Elements Ships Supabase's Services Inside Your App
Supabase's services let a frontend with no server of its own sign users in, store files and run backend code. An Elements app has its own server. The same services are features of its framework, written in the same TypeScript and deployed with the rest of the app.
Sign-in, uploads and server code all go through @rpc functions. An @rpc is a TypeScript function that runs on the server; the browser calls it like a local function, with typed arguments and return values (rpc). Service by service:
- Sign-in and sessions. Sessions are built in. The same session object works in templates,
@rpcfunctions and route handlers, and a template that reads it re-renders the moment a user signs in (session). A sign-in form calls an@rpcthat checks the password against Postgres and callssession.login()(authentication recipe). - Realtime. A LiveTable is a set of database rows that the server renders into the first response and then keeps in sync in every open browser, applying inserts, updates and deletes as they happen (LiveTable). One trigger in a migration makes writes from psql, jobs or webhooks live too (live from SQL). Channels send any message from the server to watching browsers, and because they run on Postgres
LISTEN/NOTIFY, a database trigger can send one too (channel). - File uploads. A file input binds to a field on a form, and the form goes to an
@rpclike any other (file upload recipe). The manual's recipe saves the bytes in a Postgres table and serves them from a URL that contains a hash of the bytes, so browsers and any CDN can cache them for a year (serving files from the database). - Server code. Supabase deploys Edge Functions to a separate Deno-compatible runtime. An Elements
@rpcor route handler runs in the same process that renders your pages and holds your database connections, and it ships with the rest of the app in oneelements deploy. The same deploy sets up a built-in load balancer with automatic TLS. Once a server is set up, incremental deploys often finish in under a second (deploy). - Background jobs. Supabase Cron and Queues keep schedules and messages in your database, and the work itself runs as SQL or in a consumer you write, such as an Edge Function (Cron, Queues). An Elements job is a TypeScript class in your app, run by a worker process that ships with Elements and retried with backoff when it throws. Cron schedules live in the same code and enqueue jobs (jobs).
An @rpc Replaces the Generated API and Its Policies
The last Supabase service is its generated API. Supabase builds a REST API from your schema with PostgREST, an open-source tool, so "as you update your database the changes are immediately accessible through your API." The browser then queries your tables directly, which moves authorization into row level security policies: rules stored in the database that decide which rows each user may read or write. Supabase's own guidance is to "enable RLS on every table in an exposed schema," because "a table in an exposed schema without RLS is readable and writable by any role with a grant on it."
A generated API exists to save writing endpoints. In Elements, an endpoint is one short function in plain TypeScript and SQL, with the authorization check at the top where anyone can read it. The browser has no path to a table except through an @rpc you wrote, so there is no policy to forget. Here are the two approaches for a signed-in user reading their own posts.
In Supabase, it takes a policy in the database and a query in the browser that the policy filters:
supabase/migrations/20260930120000_posts_policy.sqlSupabasecreate policy "authors read own posts" on posts for select using (auth.uid() = author_id);
src/posts.tsSupabaseconst { data, error } = await supabase .from("posts") .select("*");
In Elements, it takes one function:
app/pages/posts/services.tsElements/** @rpc */ export function myPosts(): Post[] { session.isLoggedInOrThrow(); return sql<Post>(` select * from posts where author_id = ${session.getOrThrow("userId")} `).all(); }
The query never reaches the browser, and Elements makes sure of it: calling sql(), tx() or session.login() from code the browser can reach is a compile error at the call site (rpc).
Your Backend Is Checked in Milliseconds
What helps an agent most is not a client SDK but correction: hearing what it got wrong the moment it gets it wrong. On Supabase, an agent works through supabase-js, the client library, on one side and through row level security policies on the other. That is two more interfaces to look up, with a seam between them that no build checks: a missing or wrong policy shows up only when a query returns the wrong rows. A client library is a shortcut only past what an agent writes quickly from standards, and reading a user's rows after a session check is well inside what it writes in SQL and TypeScript.
Developing on Supabase locally means running its stack. The Supabase CLI runs "the full Supabase stack (Postgres, Auth, Storage, and the rest) on your own machine," in Docker containers. Types for the client library come from a file you generate with supabase gen types and regenerate when the schema changes (generating types).
In Elements, that query and that check are a few lines in an @rpc, and the framework and its build are one coherent system, so the build that checks your pages checks your backend too. Elements runs a project server while you work. It holds the build graph and build state in memory, so on every save the build state is updated almost instantly, and a repeat elements build -json answers in microseconds (build). Your terminal, your editor and every agent share that one build state. An agent gets every build error back as structured JSON: failed tests, migration errors, and what type checking and program analysis turn up. There is no container stack to start: Elements runs its bundled Postgres as a background service on your machine (database). An @rpc needs no type generation step: the browser calls the function's own TypeScript signature, so a signature change that breaks a caller shows up as a type error at the call site.
Nothing in Elements Is Metered
Supabase plans include usage allowances, and usage past them is billed. On Pro, that covers monthly active users, outbound bandwidth (egress), Edge Function invocations and Realtime messages, each on its own meter (pricing). A spend cap is on by default, and scaling past the included quota means switching it off.
Elements is licensed per machine, on a subscription (licenses). Nothing is metered: sign-ins, requests, bandwidth, realtime messages and jobs never appear on an Elements bill.
Start on Bundled Postgres, Move to Any Host
Elements bundles Postgres and runs it on the server you deploy to, so a single Ubuntu server is a complete deployment: app, load balancer and database (deploy, database). That is a good fit for a prototype or a single-server app.
When the app spans several servers, or the data needs point-in-time recovery, failover and scaling, Elements recommends a managed host built for that. The app connects with four lines in its env file: DB_HOST, DB_PORT, DB_USER and DB_PASSWORD. Elements creates the app and test databases on the first build and connects over TLS. Any host running Postgres 16 or newer works (deploy setup, hosted Postgres).
Supabase is one of those hosts. It runs standard Postgres: "Every Supabase project gets a full Postgres database, not a Postgres abstraction." That makes the comparison one of Postgres hosts: Supabase against DigitalOcean and Amazon RDS, on two services: point-in-time recovery after a mistake, and automatic failover when a machine fails.
Point-in-Time Recovery Is a Paid Add-On on Supabase
Point-in-time recovery restores a database to a chosen moment, such as the minute before a bad migration, rather than to the last nightly backup. On the Pro plan, Supabase keeps daily backups for 7 days. Point-in-time recovery is a paid add-on, priced by how many days of history it keeps. It requires at least the Small compute add-on, and during a restore "the project is inaccessible."
So point-in-time recovery on Supabase is three charges on one bill: the Pro plan, a compute add-on, and the recovery add-on (pricing, compute sizes).
DigitalOcean includes point-in-time recovery on every plan: a Managed PostgreSQL cluster keeps write-ahead logs "to allow you to restore to any point-in-time within the previous seven days," with no add-on to buy (pricing). A restore "creates a new copy of the cluster's primary node" rather than overwriting the running one. Amazon RDS automated backups let you "recover your DB instance to any point in time during the backup retention period," a period you set.
Elements also keeps that bad migration away from production data. A deploy runs pending migrations against the test database, then runs the tests, and only then migrates the app database. Pending migrations apply in one transaction, so if one fails, all of them roll back and the database is left as it was. Migrations run once, on the first machine. A failed migration or test stops the deploy before the app database is touched (what a deploy does).
Automatic Failover Takes an Enterprise Plan on Supabase
Automatic failover keeps a standby copy of the database on a second machine and switches to it when the primary fails, so one failed machine does not take your app down. Supabase's pricing page lists no standby or high availability option on Pro or Team, and it lists uptime SLAs for Enterprise only. Its solutions page places failover in the same tier: "Enterprise plans include failover and redundancy for mission-critical applications."
On DigitalOcean, a standby node is a setting on the cluster: "managed databases with a standby node will automatically switch data handling to the standby node to prevent unplanned downtime." On RDS, a Multi-AZ deployment "will automatically failover in the event of a scheduled or unplanned outage."
DigitalOcean and RDS Include What Supabase Sells Separately
On DigitalOcean, a cluster is priced per node, so a primary with a matching standby is two nodes, and seven days of point-in-time recovery come with it (pricing). On Amazon RDS, a Multi-AZ deployment is billed by the instance hour plus storage, with point-in-time recovery part of its automated backups (pricing). On Supabase, point-in-time recovery is an add-on on top of the plan and a compute add-on, and failover takes an Enterprise contract.
Pick DigitalOcean for a fixed monthly price with recovery included (Elements vs DigitalOcean), and RDS when the rest of your infrastructure already runs on AWS (Elements vs AWS). Either way, the Elements app does not change: it reads DB_HOST and connects.
Moving a Lovable App Off Supabase
Supabase is also the backend behind Lovable, a hosted AI app builder. Lovable's integration uses Supabase for data, sign-in, file storage and Edge Functions, and Lovable Cloud "utilizes Supabase's open-source foundation."
Moving such an app to Elements keeps the data: the database is standard Postgres, so it moves with standard tools, or the app keeps pointing at it through DB_HOST. Users keep their passwords too. Supabase Auth stores passwords as bcrypt hashes, and the Elements authentication recipe checks bcrypt hashes with Postgres crypt() (authentication recipe). The rest maps directly: Auth onto sessions, Realtime onto LiveTable, Storage onto file uploads, Edge Functions and client queries onto @rpc functions, and row level security policies onto the check at the top of each @rpc. See Elements vs Lovable.
Questions
What is Supabase?
Supabase is a database platform: an open-source backend built around a Postgres database, with services for sign-in, realtime updates, file storage, edge functions and an API generated from your tables. It runs mainly as a hosted service, and the open-source stack can also be self-hosted.
Is Elements a Supabase alternative?
Yes. Elements ships sessions and sign-in, realtime through LiveTable and channels, file uploads, typed server functions with @rpc, and background jobs, all inside your own app. For the database, Elements bundles Postgres on your server and connects to any managed Postgres host.
Can I use Supabase as the database for an Elements app?
Yes, on Postgres 16 or newer. A Supabase project still on Postgres 15 needs Supabase's in-place upgrade to 17 first. Connect with the direct connection or with Supabase's connection pooler in session mode, since channels and LiveTable use LISTEN/NOTIFY, which the pooler in transaction mode does not keep between transactions.
Does Supabase include point-in-time recovery?
Not in the base plan. Pro keeps daily backups for 7 days, and point-in-time recovery is a paid add-on, which also requires at least the Small compute add-on (pricing). DigitalOcean Managed PostgreSQL includes seven days of point-in-time recovery on every plan, and Amazon RDS provides it with its automated backups.
Does Supabase have automatic failover?
Supabase lists no self-serve standby or high availability option on Pro or Team, and says Enterprise plans include failover and redundancy. DigitalOcean offers standby nodes with automatic failover, and Amazon RDS offers Multi-AZ deployments.
How do I move a Lovable app from Supabase to Elements?
The database is standard Postgres, so the data moves with standard tools, or the app can keep pointing at it through DB_HOST. Supabase Auth stores bcrypt hashes, which the Elements authentication recipe checks, so users keep their passwords. Auth maps to Elements sessions, Realtime to LiveTable, Storage to file uploads, and Edge Functions and client queries to @rpc functions.