NEWSupero Studio — generate, control, and deploy production apps.Start free →HIRINGSee the roles →

A live ops console on the PostgreSQL you already run

Point Supero at a database you already run, and get a governed application over it — without copying a single row.

ConnectorsBuild estimate ~2 hours9 min readAugust 25, 2026

Most internal tools die on the same question: who gets a copy of the data? Supero's live mode removes the question — queries are compiled with your access rules and run on your database, and no rows are stored on the Supero side.

The brief

The tool nobody is allowed to build

Operations needs a console over the production order database: search, filter, drill into a record, see counts by status, and hand a read-only view to the support team. Everyone agrees it should exist. It never gets built, because the first design review asks where the data will live, and the answer — "we ETL it into a second store" — turns a two-day tool into a quarter-long data project with its own security review, its own staleness bugs and its own copy of your customers' personal data.

Supero's live mode sidesteps the argument. A connector is a governed link, not a pipeline: your query is compiled with the caller's tenant and role rules applied, then executed against your database. Rows come back, get rendered, and are never stored on the Supero side.

0
rows copied out of your database
1
connector, mode chosen per table
~2 h
from credentials to a working console
The core idea

Three modes, chosen per table

Mode is a per-table decision, not a per-connector one. One database connector can sync a small reference table into Supero for speed while reading your 400-million-row events table live.

ModeStorage in SuperoReadsWrites
sync (default)A copy, refreshed on a schedule, webhook or manual triggerServed from the local copyFull CRUD against the copy, synced back on the next run
live_readNoneReal time, from the source, on every requestBlocked — writes return 403
live_readwriteNoneReal time, from the sourcePassed through to the source in real time
  • live_read is available for PostgreSQL, MySQL and SQL Server. Write-through (live_readwrite) is implemented for PostgreSQL and MySQL only — a SQL Server write returns 501.
  • Warehouses (Snowflake, Databricks, ClickHouse, BigQuery, Redshift, Microsoft Fabric) are always live_read — there is no write-back path for an analytical source.
  • HTTP-API sources support live_read (served by a REST planner rather than SQL), but not write-through. File and webhook sources are sync-only.
  • Oracle and MongoDB connect in sync mode.
The mechanism

What happens when someone opens the list

A governed live read
Ops consolebrowserSupero APIcontrol planeAccess engineroles · tenancyConnectorquery plannerYour PostgreSQLyour VPC1GET orders?status=held&sort=-created_at2resolve caller scope from the credential3compile tenant + role predicate; collect hidden fieldsno resolvable scope ⇒ the request fails closed, not open4authorized query plan5prepend the predicate · reject hidden-field referencesLEAVES THE PLATFORM FOR THE FIRST TIME6parameterised SQL — filters, sort and limit pushed down7only the matching page of rows8redact hidden fields · shape to the bound schema9{results, pagination:{has_more, next_cursor}}no row contents stored — each row’s uuid is derived, not saved
The filter is not applied in the browser and not applied to a copy. It becomes part of the SQL your database runs, which is why this scales to tables you would never consider replicating. One clarification, because it is the article’s core claim: no row contents are stored. Each row gets a stable identity derived from the connector, stream and row key so drill-downs, references and RBAC work — that identity is computed, not persisted.

Two properties in that diagram are worth naming, because they are what makes this safe enough to hand to a support team:

The predicate is not optional

Tenant and role scoping is compiled into the query before it is sent. It is not a filter your app remembers to add — there is no code path that omits it.

It fails closed

A request that cannot resolve a tenant scope is refused. The failure mode of a missing rule is "no data", never "all data".

Hidden fields are rejected, not filtered

A query that references a field the caller may not see is refused at the boundary, rather than running and stripping the column afterwards.

It behaves like native data

Same CRUD and query API, same pagination shape, same SDKs — so your screens, your typed clients and the agent all work unchanged. The differences that remain are honest ones your UI can read: an access_mode stamp, and a flag when a scan was truncated.

Step by step

How to build it on Supero

The whole path
FROM CREDENTIALS TO CONSOLEConnecthost + readerDiscovertables · columnsBindtable → schemaSet modelive_readGovernroles · hidden fieldsConsolelive

Create a read-only database user

Do this first and the rest of the review goes quietly. Give Supero a user that can do exactly what the console needs and nothing more.

sql
CREATE ROLE supero_reader LOGIN PASSWORD '••••••••';
GRANT CONNECT ON DATABASE prod TO supero_reader;
GRANT USAGE  ON SCHEMA public TO supero_reader;
GRANT SELECT  ON public.orders, public.order_items, public.customers TO supero_reader;
-- no INSERT, no UPDATE, no DELETE, no other tables

⏱ ~10 minutes

Connect the source

A database connector needs the reachability details and the credential. Credentials are stored as secrets and returned masked — they never enter your app bundle.

json
{
  "plugin": "database",
  "config": {
    "db_type":  "postgresql",
    "host":     "orders-db.internal.example.com",
    "port":     5432,
    "database": "prod",
    "schema":   "public",
    "username": "supero_reader",
    "password": "REDACTED",
    "ssl_mode": "require",
    "query_type": "table",
    "table_names": ["orders"],
    "batch_size": 1000
  }
}

Table selection is always a list under table_names; there is no singular table_name. To expose a joined, pre-narrowed slice of a messy schema, create a database VIEW and bind that — the live planner addresses a named relation. (query_type: "query", a hand-written SQL source, applies to sync mode only.)

⏱ ~10 minutes

Discover and bind

Discovery introspects the source and proposes schemas. Binding maps a source table onto a Supero schema, which is the moment the data becomes ordinary records: typed fields, references, pagination, aggregates, SDKs and the AI agent all start working.

From an AI editor over MCP the same path is six tools: build_list_data_sources → build_connect_data_source → build_discover_source → build_bind_data_source → build_run_data_source → build_list_bound_schemas.

⏱ ~20 minutes

Set each table's mode

Set orders, order_items and customers to live_read. Supero stamps a connector-level access_mode summarising the result — live-ro here — which your UI can read to decide whether to render write controls at all. When a connector mixes modes, the most permissive one wins, so check what it reports rather than assuming.

access_modeMeans
syncFull CRUD against Supero's own copy
live-roRead-only, live from the source; writes rejected
live-rwRead and write, live, straight through
warehouseRead-only analytical queries against a warehouse

⏱ ~5 minutes

Apply roles and hide the columns that should not travel

This is the step that makes it a product rather than a database viewer. Live data honours the same policy model as native data — roles, tenant isolation and field hiding all apply.

python
POLICIES = [
    PolicyDef(role="tenant_admin", default_access="full", rules=[]),

    # Support: read orders and customers, never payment identifiers.
    PolicyDef(role="viewer", default_access="none", rules=[
        PolicyRule(entity="orders",      can_read=True),
        PolicyRule(entity="order_items", can_read=True),
        PolicyRule(entity="customers",   can_read=True,
                   hidden_fields=["payment_token", "card_last4", "tax_id"]),
    ]),
]

Then verify it the only way that counts: sign in as viewer and try to sort or filter by card_last4. A refusal is the correct answer — the query never reaches your database. Hidden columns are also stripped from responses, but the refusal is the part that matters: a filter on a hidden column would otherwise leak its value through the result count.

⏱ ~30 minutes

Build the console on top

From here it is an ordinary Supero app. Generate the screens from the bound schemas, or hand-build the UI against the same CRUD API. Aggregates, group-by counts and distinct-value lookups run on your source, so a status breakdown across a very large table is a query your database is already good at.

The in-app AI agent comes along for free: it derives search_/get_ tools per bound entity plus shared analytics tools (db_stats, db_count_by, db_distinct, db_aggregate), all going through the same access engine. "How many held orders per region this week?" is answered by your database, under the asker's own permissions.

⏱ ~45 minutes

Judgement

When live is the wrong choice

Live mode is not universally better than sync, and an article that pretended otherwise would be selling you something. Choose deliberately:

Choose live_read whenChoose sync when
Data residency or policy forbids a copyThe source is slow, flaky, or occasionally offline
Freshness matters — the console must show current stateYou need full CRUD, history, or write-back through Supero
The table is too large to replicate sensiblyYou want the app to keep working when the source does not
The source is the system of record and must stay soThe source is a warehouse export you own anyway
The economics

Time to deliver

PhaseOn SuperoConventional internal tool
Read-only DB user + network path~10 min~10 min (same either way)
Getting the data somewhere queryable0 — it stays where it is2–6 weeks of pipeline + a second store
Typed API over the tables~20 min (bind)1–2 weeks
Auth, roles, column-level hiding~30 min of policy2–3 weeks
List/detail/search/aggregate UI~45 min2–3 weeks
Security reviewShort — nothing was copiedUsually the long pole
Total~2 hours~1–2 weeks with an internal-tool builder over a copy; ~2 months if you build the pipeline too
The takeaway

The fastest data migration is the one you skip

The reason internal tools take a quarter is rarely the tool. It is the copy — the pipeline, the second store, the staleness, the review, the extra place your customer data now lives. Removing the copy removes most of the project.

What you keep is the part you wanted: a typed API, real access control, a working UI, and an AI agent over data that never left your database. The next article does the same thing to a warehouse, where "do not copy this" is not a preference but a cost model.

Delivery estimates in this guide are for the scope described, built by one experienced developer with any vendor sandbox credentials already in hand. Treat them as a realistic starting point, not a commitment — and check the documentation for the authoritative behaviour of any API named here.

Build this on Supero

Describe it and get schemas, APIs, SDKs and a governed multi-tenant backend — or point your own AI editor at Supero over MCP and build it there.