Engineering

The report your platform will not write

Somebody will ask your platform a question it cannot answer. It will not be a hard question. It will sound like this: of the accounts that upgraded last quarter, what share had opened a support ticket in the 30 days before they upgraded, split by plan? An interface builder hands you filters, a group-by and a chart type. That question needs a join across two tables and a time window that moves with each row, and there is no dropdown for it.

Where the interface builder runs out

On Airtable the read surface is one endpoint. GET on the base and table returns a page of records, and the API reference sets pageSize at 100 maximum and 100 by default. If more records exist you get back an offset and ask again. There is no aggregation parameter in the list. filterByFormula is evaluated once per record, so it narrows the rows coming back but it will not count them, group them or compare one to the average of its cohort.

The rate limit is 5 requests per second per base, and exceeding it returns a 429 that makes you wait 30 seconds before the next request succeeds. A 40,000 record table is 400 pages, which is 80 seconds of paging at the ceiling if nothing else is touching the base. Then the actual report gets computed in whatever script did the paging. You have written the reporting layer already. It lives in a cron job nobody reviews, and the number it produces has no audit trail.

Airtable is straight about the shape of this. Their rate limit page says that if you anticipate a higher read volume, use a caching proxy. The list endpoint page notes that requests are rejected above 16,000 URL characters, which encoded formulas hit, and suggests POSTing to listRecords with the parameters in the body instead. Both are sound advice. Both are also the sound of fighting the transport instead of asking the question.

Bubble ships the admission in the box

Bubble includes a SQL Database Connector that opens a connection to Postgres, MySQL or Microsoft SQL and runs queries as actions or data sources. I think the plugin existing is the honest part of the story: the platform knows that past a certain point you need real SQL, so it gives you a door.

Read the constraint on that door. The docs say any SQL statement is allowed, but a SELECT must end in LIMIT N where N is at most 200. You can ask a genuine SQL question from inside Bubble and the answer arrives capped at 200 rows. For a lookup that is plenty. For a report that scans 5,000 rows of raw events to produce 12 summary lines, the cap lands on the wrong side of the aggregation, because the rows you need to read are not the rows you want back.

Notice what the connector assumes. It needs a real database at the other end of the string, with a host, a port and credentials you manage. The platform is not pretending to be the system of record for the data you intend to reason about.

What a read replica actually costs

The alternative is boring, which is the recommendation. Put the data in Postgres, point a read replica at it, and let analysts write SELECT. Supabase documents the replica as a second database kept in sync asynchronously, read-only, with its own endpoint and its own connection pool, and says that you can run complex analytical queries there while the primary keeps serving user writes. Replication is asynchronous, so a replica trails the primary by some replication lag. For a quarterly cohort report that lag does not matter.

Their billing guide prices a worked example rather than making you guess. Pro plan at $25, a Small primary at $15 for 730 hours, a Small replica at another $15, replica disk at $1.25 because a replica disk is 1.25x the primary to hold WAL archives, less $10 of included compute credits. Total $46.25 a month. One caveat worth reading before you provision: the same page says Read Replicas are not covered by the Spend Cap. Airtable's own pricing page lists Business at $45 per user per month billed annually. The replica costs about one seat.

What you get for that is not a nicer chart builder. It is window functions, so avg(salary) OVER (PARTITION BY depname) compares each row against its own cohort in one pass, which is the shape the upgrade question needs. It is materialized views, which Postgres persists in table-like form so a heavy aggregate runs on a schedule instead of on every dashboard load. Point Metabase at the replica and the query is text in version control. Metabase keeps the previous fifteen versions of a question, and on impersonated connections it rejects anything that is not a single SELECT, so the reporting path cannot write.

When staying put is the right answer

If your reports are counts and sums over a few thousand rows, the built-in views will answer them and a replica is a second thing to run for no gain. Airtable's free tier charges nothing for read-only collaborators, form submissions or share links, which covers a lot of what people call reporting. Stay there.

Stay there too if nobody on the team writes SQL and nobody wants to learn. A replica that only one contractor can query is worse than a clumsy interface the whole team can use, and the fix for that is hiring or training, not infrastructure. Same if the questions change weekly and never repeat. Exploration is what the interface builder is good at.

The signal to move is narrower than people assume. It is a report that someone asks for on a schedule, that a script now assembles by paging an API, and that a person has quietly stopped trusting. When Stay World Class moved off Webflow and Xano to Next.js, NestJS and Supabase, the migration ran as a four stage ETL that mapped every legacy ID to a PostgreSQL UUID, specifically so the old records stayed queryable next to the new ones. Lighthouse went from 55.91 to 91 out of 100 and LCP got 77% faster, and the part the team noticed later was being able to ask the database a question and get the same answer twice.

Sources

Is your platform outgrowing its stack?

Book a call and we'll walk through how we'd approach your platform, with an estimate grounded in a working prototype.