A general-purpose backend for the cloud
Para is an open-source, stateless and schemaless backend with a REST API over any database, search index and cache.
Bring your own database. Batteries-included. No schema. No lock-in.
Para is built on several swappable layers. Start simple, then make it fit your requirements.


Schemaless objects
Plain JSON in, plain JSON out. No migrations, no ORM ceremony. Works on key-value stores and traditional databases alike.


Three layers
Every object is persisted, indexed and cached by default. Swap the database, the search engine and the cache independently.


Stateless scaling
No sticky sessions, no node-local state. Scaling out just requires more nodes, not a re-architecture.


Multitenancy
Each Para app is fully isolated and gets its own table, index and cache. Better efficiency and resource utilization.


Pluggable modules
SQL, MongoDB, Cassandra or DynamoDB. Lucene or Elasticsearch. Caffeine or Hazelcast. Choose per deployment, not per product.
sqlmongodbcassandradynamodbluceneelasticsearchcaffeinehazelcast

Built-in auth
JWT, passwordless logins, LDAP, SAML and social login for all the major identity providers.
jwtsocialpasswordlessldapsamloauth2

Webhooks
Signed webhook payloads on object events. Wire Para into anything that speaks HTTP.


MCP server
A built-in MCP server lets AI agents read and write your data directly, with the same permissions as any client.
Why not Supabase?
Para is a general-purpose backend, not a hosted database with extras. Different trade-offs, fewer lock-ins.
| Para | Supabase | Firebase | Parse | |
|---|---|---|---|---|
| Databases | any (SQL, NoSQL, in-memory) | PostgreSQL only | proprietary | MongoDB |
| Data model | schemaless JSON | schema + migrations | proprietary JSON | schemaless JSON |
| Full-text search | yes, auto-indexing | tedious, manual indexing | tedious, manual indexing | tedious, manual indexing |
| MCP server for AI agents | yes | yes | yes | no |
| Self-host anywhere | yes | yes | no | yes |
| License | Apache-2.0 | Apache-2.0 | Proprietary | BSD-3 (community) |
Para was designed to provide a straightforward developer experience and is optimized for LLM-based workflows and rapid prototyping. Para is a good fit if you ask yourself any of the following questions:
Self-host in minutes
One JAR, one config file, any database. The quickstart has you persisting objects before your coffee cools.
Prefer containers? Run Para in Docker with one command:
$ docker run -it erudikaltd/para:latest_stableUse the client you know
All Para clients share the same REST API. Create, read, update, and delete a user, then search users with one query.
import com.erudika.para.client.ParaClient;
import com.erudika.para.core.ParaObject;
import com.erudika.para.core.Sysprop;
import java.util.List;
ParaClient pc = new ParaClient("app:myapp", "secret");
pc.setEndpoint("http://localhost:8080");
Sysprop draft = new Sysprop();
draft.setType("user");
draft.setName("Ada");
ParaObject created = pc.create(draft);
ParaObject user = pc.read("user", created.getId());
user.setName("Ada Lovelace");
ParaObject updated = pc.update(user);
List<ParaObject> users = pc.findQuery("user", "*");
pc.delete(updated);Maven · com.erudika:para-client
View SDK sourceimport { ParaClient, ParaObject } from "para-client-js";
const pc = new ParaClient("app:myapp", "secret");
pc.setEndpoint("http://localhost:8080");
const draft = new ParaObject();
draft.setType("user");
draft.setName("Ada");
const created = await pc.create(draft);
const user = await pc.read("user", created.getId());
user.setName("Ada Lovelace");
const updated = await pc.update(user);
const users = await pc.findQuery("user", "*");
await pc.delete(updated);npm · para-client-js
View SDK sourcefrom paraclient import ParaClient, ParaObject
pc = ParaClient("app:myapp", "secret")
pc.setEndpoint("http://localhost:8080")
draft = ParaObject(type_="user")
draft.name = "Ada"
created = pc.create(draft)
user = pc.read("user", created.id)
user.name = "Ada Lovelace"
updated = pc.update(user)
users = pc.findQuery("user", "*")
pc.delete(updated)pip · paraclient
View SDK source<?php
use Para\ParaClient;
use Para\ParaObject;
$pc = new ParaClient("app:myapp", "secret");
$pc->setEndpoint("http://localhost:8080");
$draft = new ParaObject(null, "user");
$draft->setName("Ada");
$created = $pc->create($draft);
$user = $pc->read("user", $created->getId());
$user->setName("Ada Lovelace");
$updated = $pc->update($user);
$users = $pc->findQuery("user", "*");
$pc->delete($updated);Composer · erudika/para-client-php
View SDK sourceusing Para.Client;
using System.Collections.Generic;
var pc = new ParaClient("app:myapp", "secret");
pc.setEndpoint("http://localhost:8080");
var draft = new ParaObject();
draft.type = "user";
draft.name = "Ada";
ParaObject created = pc.create(draft);
ParaObject user = pc.read("user", created.id);
user.name = "Ada Lovelace";
ParaObject updated = pc.update(user);
List<ParaObject> users = pc.findQuery("user", "*");
pc.delete(updated);NuGet · para_client_csharp
View SDK sourceimport ParaClient
let pc = ParaClient(accessKey: "app:myapp", secretKey: "")
pc.setEndpoint("https://your-para-host")
pc.signIn("password", providerToken: "[email protected]::PASSWORD", callback: { session in
guard session != nil else { return }
let draft = ParaObject()
draft.type = "user"
draft.name = "Ada"
pc.create(draft, callback: { created in
guard let created else { return }
pc.read("user", id: created.id, callback: { user in
guard let user else { return }
user.name = "Ada Lovelace"
pc.update(user, callback: { updated in
guard let updated else { return }
pc.findQuery("user", query: "*", callback: { users in
print("Found \(users?.count ?? 0) users")
pc.delete(updated)
})
})
})
})
})Swift Package Manager · para-client-ios
View SDK sourceimport android.content.Context;
import android.util.Log;
import com.erudika.para.client.Listener;
import com.erudika.para.client.ParaClient;
import com.erudika.para.core.ParaObject;
import com.erudika.para.core.Sysprop;
import java.util.List;
void runExample(Context context) {
ParaClient pc = new ParaClient("app:myapp", null, context);
pc.setEndpoint("https://your-para-host");
pc.signIn("password", "[email protected]::PASSWORD", new Listener<Sysprop>() {
@Override public void onResponse(Sysprop session) {
if (session == null) return;
Sysprop draft = new Sysprop();
draft.setType("user");
draft.setName("Ada");
pc.create(draft, new Listener<ParaObject>() {
@Override public void onResponse(ParaObject created) {
if (created == null) return;
pc.read(Sysprop.class, created.getId(), new Listener<Sysprop>() {
@Override public void onResponse(Sysprop user) {
if (user == null) return;
user.setName("Ada Lovelace");
pc.update(user, new Listener<ParaObject>() {
@Override public void onResponse(ParaObject updated) {
if (updated == null) return;
pc.findQuery("user", "*", null, new Listener<List<ParaObject>>() {
@Override public void onResponse(List<ParaObject> users) {
Log.d("Para", "Users: " + (users == null ? 0 : users.size()));
pc.delete(updated, null);
}
});
}
});
}
});
}
});
}
});
}Gradle · com.erudika:para-client-android
View SDK sourceKeep secret keys on trusted servers. Browser and mobile apps should leave the secret key blank, sign in, and use the permissions granted to that user.
Prefer a managed BaaS?
Para Cloud is our fully managed backend-as-a-service — hosted, patched and production-ready.
- Managed hosting by the makers of Para
- Automatic updates, backups and monitoring
- Basic support included
Free plan available · 25€ / app / month