Bring your own Mongo
A project can be created against your own MongoDB connection string. Groveback then layers its features — the policy-gated data API, auth, functions, realtime, vector search, the dashboard — on top of data you already have, without taking ownership of it.
What lives where
Section titled “What lives where”The split is deliberate: your cluster holds only your data collections, and everything Groveback needs to run the project stays in the platform’s own database.
| In your database | In the platform’s database |
|---|---|
| Your data collections, and any created later through the API | users, sessions, verification_tokens |
| Indexes and search indexes on them | __schemas, __policies, __roles, __enduser_roles |
| Change streams for realtime | __api_keys, __audit, __usage, __vectorconfigs |
| Vectors, stored next to the documents | __functions, __function_logs, __function_versions |
The ctx.db bridge target |
__integrations, __webhooks, __config, buckets and blobs |
Your database stays pure — you keep using it with your own tooling, backups and other apps, and there is nothing of Groveback’s to garbage-collect if you leave. Write-heavy operational data (audit, usage, function logs) never touches your cluster.
The decrypted connection string never leaves the connection pool: it is never attached to a project reference or handed to any service.
Creating a BYO project
Section titled “Creating a BYO project”curl -X POST "$URL/api/v1/control/projects" \ -H "authorization: Bearer $CONTROL_TOKEN" -H 'content-type: application/json' \ -d '{"name":"acme","mongoUrl":"mongodb+srv://user:pass@cluster0.x.mongodb.net/mydb"}'Rules:
- Scheme must be
mongodb://ormongodb+srv://. - A database name in the URL path is required — no silent default to
test. - URLs targeting a
groveback_*database are rejected. - In cloud mode (
GROVEBACK_CLOUD=true), private and loopback hosts are rejected (SSRF) and TLS is required. Self-hosting skips both, since localhost clusters are the normal case.
The URL is encrypted at rest and never returned. Reads show only:
{ "byoMongo": { "host": "cluster0.x.mongodb.net", "db": "mydb", "capabilities": { "realtime": true, "preImages": false } } }Required permissions
Section titled “Required permissions”The create probes connectionStatus { showPrivileges: true } against the target database and
checks what the authenticated user can actually do.
Required — readWrite on the target database: find, insert, update, remove,
createCollection, createIndex. Missing any of these fails the create, with a message
naming the missing actions and the fix.
Optional — dbAdmin on the target database: collMod, needed to enable change-stream
pre-images, which is what makes realtime delete events reach filtered subscribers. Missing
it never fails the create: the project stores preImages: false, the response carries a
warnings entry, and the factory skips pre-image enablement with a single log line.
Resource matching is conservative. A privilege counts database-wide only via anyResource, a
whole-database grant, or an any-database grant — a grant scoped to one named collection does
not, because it cannot cover collections created later.
An inconclusive probe (auth disabled, or the command failing) blocks nothing, so self-hosting without auth keeps working.
Capabilities
Section titled “Capabilities”At create, Groveback probes and stores what your cluster can do:
| Capability | Determined by | If absent |
|---|---|---|
realtime |
hello.setName — change streams need a replica set. |
Realtime degrades with a warning, never an error. |
preImages |
The collMod privilege. |
Realtime delete events are dropped for filtered subscribers. |
Vector search is not probed at create; it keeps the runtime
VECTOR_SEARCH_UNAVAILABLE path.
Mapping existing collections
Section titled “Mapping existing collections”BYO projects get an introspection API — the wizard for mapping what you already have.
curl "$URL/api/v1/admin/introspect" -H "authorization: Bearer $ADMIN_KEY"curl "$URL/api/v1/admin/introspect/orders" -H "authorization: Bearer $ADMIN_KEY"Introspection is read-only. It uses only listCollections, estimatedDocumentCount,
indexes and $sample. Registering a collection writes nothing to your cluster — the
schema and policy live in Groveback’s own database.
A report looks like:
{ "collection": "orders", "count": 120400, "sampled": 100, "indexes": [ … ], "schema": { … }, "importable": true, "blockers": [], "emptySchema": false }Blockers
Section titled “Blockers”| Blocker | Meaning |
|---|---|
reserved-name |
The name is reserved. BYO projects only reserve the __* and system.* prefixes, so a collection literally named users is fine. |
id-conflict |
An existing id field that is non-string or has duplicate values. The data API needs a unique public string id. |
unsupported-type |
A field whose only observed types are outside the schema subset. |
How the schema is inferred
Section titled “How the schema is inferred”- Root is always
type: "object";requiredis the set of fields present in ≥ 99% of samples. _idis always dropped.__*fields — ODM artifacts like Mongoose’s__v— are dropped at every level and never counted as blockers.- ObjectId →
{ type: "string", format: "objectId" }; Date →format: "date-time"; Decimal128 →format: "decimal". - Numbers become
integerornumberby sample; a field seen as both collapses tonumber. - Nested objects recurse; arrays become
{ type: "array", items }with element types unioned. - Low-cardinality string fields (2–12 distinct sampled values) are suggested as an
enum. - Mixed or ambiguous types widen to
stringwith a note, rather than failing.
The result is always editable before import, and always stays inside the validator’s supported subset, so it round-trips through the dashboard’s schema builder unchanged.
Importing
Section titled “Importing”curl -X POST "$URL/api/v1/admin/introspect/orders/import" \ -H "authorization: Bearer $ADMIN_KEY" -H 'content-type: application/json' \ -d '{"schema": { … }}'Importing grants nobody access. An explicit, visible deny-all policy is written — read
and list rules with an unsatisfiable filter ({ "$expr": false }) and no create, update or
delete rules at all. Even a permission-holder matches nothing. You open it deliberately
afterwards in the policy editor.
An empty collection imports schema-less (emptySchema: true) rather than failing — it stays
fully usable through the generic document API, and the project’s typed GraphQL schema is not
broken by it.
Backfill
Section titled “Backfill”The one mutation this feature makes to your cluster:
curl -X POST "$URL/api/v1/admin/introspect/orders/backfill" \ -H "authorization: Bearer $ADMIN_KEY"It re-checks the id-conflict blocker, sets id from _id on documents missing it, creates
a unique index on id, and returns { matched, modified }.
This runs as a single updateMany. Very large collections may want batching — plan a
maintenance window.
Reserved names, relaxed
Section titled “Reserved names, relaxed”BYO projects reserve only the __* and system.* prefixes, dropping the named system list.
So an existing collection called users or projects can be mapped, which is normally the
whole reason the relaxation exists.
Environments
Section titled “Environments”An environment is an ordinary project with a parent, so the same rules apply per environment:
mongoUrlpresent → the environment’s data lives in your infrastructure. A separate staging cluster per environment is fine.mongoUrlabsent → the environment’s data lives in the platform’s database, even when the root project is BYO.
A parent’s URL is never inherited. Pointing two projects at the same external database is an explicit choice you make per create. Mixed families — production on your cluster, throwaway environments on the platform’s — are expected, not an edge case.
Operational notes
Section titled “Operational notes”BYO clients are LRU-capped (GROVEBACK_BYO_MAX_CLIENTS, default 50) and released when a
project changes. Realtime and functions cross clusters — change streams watch yours, everything
else reads the platform’s — which is the accepted cost of keeping your database clean.