Build NocoDB Cloud
KINDAreplaces $15/mosaves $180/yrback to the verdict
A no-code database where the difficulty is that users design the schema at runtime: every column type change is a migration on live data, every formula is code someone typed into a cell, and every shared view is a permission decision that has to hold at the row level.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the server, the formula parser and the realtime layer.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - installfree locally
Why User tables are real tables, which needs a database that handles runtime schema changes without long locks.
Get it Docker or your package manager. open ↗
Verify
A column type change on a large table completes without an unacceptable lock - installfree
Why The formula language is the security boundary, and reaching for the language's own evaluator is how this category gets compromised.
Get it Decide the grammar and the function allow list before writing the parser.
Verify
A written grammar exists and the function list is finite - have readyfree
Why Migrations, formulas and sorting all behave differently against real data, which contains a note in a number column and a date nobody can parse.
Get it Import a real spreadsheet you use, including the columns that have inconsistent values.
Verify
At least one column has mixed types and one has values that fail to parse - decidefree
Why Row-level rules and hidden fields have to be enforced in the query, and retrofitting that after building on client-side filtering is a rewrite of the data access layer.
Get it Decide whether row rules exist, what a share link may expose, and whether shared views can be edited. Write it down before phase 4.
Verify
A written note states the rules and what a share token grants
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- bases(id, name, owner_id, created_at) - tables(id, base_id, name, physical_name) - columns(id, table_id, name, kind, options_json, physical_name, ordinal) · kind includes text, number, date, select, link and formula - migrations(id, table_id, from_json, to_json, rows_affected, applied_at, reversible_until) - formulas(column_id, expression, compiled_json, depends_on_column_ids) · dependencies, so a cycle is detectable - views(id, table_id, kind, filters_json, sorts_json, hidden_column_ids, share_token_hash, share_scope) - row_permissions(view_id, rule_json) · row-level rules, evaluated server side and never in the client - A migration records what it changed and remains reversible for a period, because a column type change on real data is not something to do optimistically.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DATABASE_URLsecret | required | postgres://localhost:5432/nocode | Metadata and the user tables themselves. |
MIGRATION_REVERSIBLE_DAYS | required | 14 | How long previous values are kept after a type change. The undo window. |
FORMULA_EVAL_STRINGS | required | never | Present as a constant so the answer is visible. Parsed trees only. |
FORMULA_TIMEOUT_MS | required | 500 | A formula that will not finish must not hold a request. |
SHARE_TOKEN_BYTES | required | 32 | Share links are bearer credentials. Unguessable, hashed at rest. |
MAX_ROWS_PER_TABLE | required | 1000000 | Where the generic query patterns stop being adequate, made explicit rather than discovered. |
The build, in order
Real tables, defined at runtime
A user-created table is a real table with real types.
Not text for everything. The cost of that arrives after there is data.
Files
src/schema.tsSo renaming is a metadata change rather than a migration.
Files
src/columns.tsApplication validation is bypassed by every other code path.
Files
src/schema.ts
done when · tick each as it passeswatch out- Storing every value as text in a generic key-value table makes every filter a scan, every sort wrong for numbers and dates, and every constraint impossible, and by the time that hurts there is user data to migrate. Create real columns with real types, even though it means implementing migrations.
Changing a column that already has data
A type change previews its damage before doing it.
Counts and examples, before anything changes.
Files
src/migrate.tsFor a period. It is what makes undo real rather than aspirational.
Files
src/migrate.tsA migration that locks a table for a minute is an outage.
Files
src/migrate.ts
done when · tick each as it passeswatch out- Converting a text column to a number by dropping whatever fails to parse silently destroys the rows that had a note in them, and nobody notices until someone looks for the note. Count and show the failures first, and keep the original values so the change can be undone.
Formulas, which are user-supplied code
A formula computes correctly and cannot reach anything it should not.
Never evaluate a string. This is the compromise vector for the category.
Files
src/formula/parse.tssrc/formula/parse.test.tsAn allow list. Anything outside it fails at parse time.
Files
src/formula/functions.tsNot at evaluation, where it becomes a hang rather than an error.
Files
src/formula/cycles.ts
done when · tick each as it passeswatch out- Evaluating a formula by passing it to the language's own evaluator gives every user arbitrary code execution on your server, and it is the single most common way this category of product gets compromised. Parse to a restricted expression tree with a fixed function set, and never evaluate a string.
Views that hide what they claim to hide
A hidden column is absent from the response, not hidden in the client.
Not in the serialiser and certainly not in the client.
Files
src/views.tsFetching then filtering means the rows were fetched.
Files
src/permissions.tsList, single row, errors. Errors are where fields leak.
Files
tests/hidden-fields.test.ts
done when · tick each as it passeswatch out- Hiding a column in the interface while the API still returns it means anyone who opens the network tab sees the salary field you thought was hidden, and shared views make that a stranger rather than a colleague. Filter fields in the query itself, and assert in the suite that hidden fields are absent from every response shape.
Sharing, and live updates that respect permissionsproduct builder
A shared link shows exactly what it should, and live changes do not leak.
Before sending. Client-side filtering means it already arrived.
Files
src/realtime.tsAn edit can move a row out of someone's permission. Send a removal.
Files
src/realtime.tstests/realtime-leak.test.tsIncluding for viewers currently connected.
Files
src/share.ts
done when · tick each as it passeswatch out- Broadcasting a change to everyone watching a table and letting each client filter it delivers rows the recipient is not permitted to see, and the filtering happens after the data has already arrived on their machine. Filter per recipient before sending, including working out that an edit made a row newly invisible to them.
That is the whole plan for NocoDB Cloud. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Automations and workflow integrations.
- External database sync connectors.
- Scale beyond a few million rows per table.
- A spreadsheet's full formula library.
- managed hosting, integrations, collaboration, storage, and support
- real-time multiplayer
- automation and integration breadth
- scale and formula depth
- enterprise permissions
- Linked records between tables, which is the feature everyone wants next and complicates every query
- Undo across a session rather than per migration
- An audit log per row, once the permission model is provably correct
Need the files? The project pack on the verdict page hands your agent the whole brief · more databases.