Should I vibe code
Compose an internal dashboard from a database and a fixed widget library
"Internal" is an adjective, not an access control. The panel with DELETE on production is on the public internet.
?
Their verdict, the Business price and the build-time estimate come from their entry, MIT-licensed. Checked 2026-08-04.
?
Our verdict, the regret score and everything below it. Editorial and unsponsored — nobody can pay to be moved.
The honest answer
why the verdict is what it is
Nobody actually rebuilds Appsmith. What you build is the one panel you wanted from it — a table of customers, a search box, and four buttons that refund, suspend, delete and resend. That is not a dashboard, it is a remote-execution surface over your production database with a nicer font, and every generated version of it makes the same three moves: the connection string has full write access because that was the one in the .env, the query is assembled from something the browser sent, and the destructive buttons run on the first click with no confirmation, no dry run and no record of who pressed them. The word doing all the work in "internal tool" is internal, and it stops being true the week you send the URL to a contractor. What makes this land at YOUR FUNERAL rather than DEMO ONLY is the direction of the damage: a bug in a customer-facing app annoys people, and a bug in an admin panel changes the rows those people's accounts are made of. There is no undo, and the honest alternative is sitting right there — Appsmith itself is Apache-2.0 and runs from a Docker image for free, with credential isolation and roles already built.
What actually breaks
not "if". the specific failures.
- The database credential, because the one that ended up in the panel's environment is the same superuser string your migrations use, and it can drop tables as easily as it can select from them
- The query layer, once the browser starts choosing what runs — a table name, a sort column, a filter expression or, in the worst generated version, raw SQL posted to an endpoint
- The destructive buttons, which fire on the first click with no typed confirmation, no preview of the affected row count and no way to tell afterwards which of the 4,000 rows they touched
- The auth, which in an internal tool is usually a shared password in an environment variable, a check that runs in the React component instead of the API route, or nothing at all because the URL is unguessable
- The /api routes, which get built without the session check the pages have, so the panel is protected and the endpoints behind it are not
- The audit trail, which does not exist, so the question "who changed this customer's plan on the 14th" has no answer and the only log entry says POST /api/update 200
- Bulk actions, the feature every internal tool grows in month two, where a missing WHERE clause is the difference between one refund and every refund
- Schema drift, because the panel hardcodes column names and a migration silently turns a page into a stack trace or, worse, into blank fields that look like empty data
- Exports, since the fastest way to get a CSV out of an admin panel is also the fastest way to put your entire customer table on somebody's laptop
The support lead asked for a way to close out test accounts, and the panel grew a checkbox column and a Delete Selected button that afternoon. It worked correctly for five weeks. On the Thursday, someone filtered the table, clicked the header checkbox to select the page, then changed the filter — and the selection state was held in the client, keyed by array index rather than by row ID, so the eleven boxes that were still ticked now pointed at eleven different customers. The confirm dialog said "Delete 11 records?", which was true. The rows went, along with everything cascading off them. Restoring from the nightly backup would mean rolling back nine hours of everyone else's writes, so instead you spend the night reconstructing eleven accounts by hand from Stripe, the mail provider's logs and a Slack thread, and you never do find out which eleven were originally selected, because the panel logged the request and not the rows.
Is that you?
the verdict is a default, not a law
- It is read-only, over a replica or a restricted role that has SELECT and nothing else
- It runs on your laptop against a database you can afford to restore from this morning's dump
- Every user is an employee behind your existing identity provider, and there is exactly one of them so far
- The data has no personal information in it at all and you would be relaxed about the whole table being public
- The connection string in the panel's environment can write, and you have not made a second, narrower one
- Any part of the query — table, column, filter, order, limit — is chosen by something the browser sends
- There is a button that refunds, cancels, deletes or emails a customer, and no typed confirmation in front of it
- You cannot answer "who did what, to which row, when" for the last ninety days
- The auth is a shared password, a hardcoded check, an unguessable URL, or a session test that only runs in the page and not in the API route
- A contractor, a VA or an agency has the URL — at which point "internal" has already stopped being true
If you build it anyway
the checklist, then the prompt that enforces it
- Start with a dedicated database role that has SELECT on named tables and nothing else. Add write permission one table at a time, deliberately, and never give the panel the credential your migrations use.
- Nothing the browser sends may name a table, a column, an operator or a limit. Queries live server-side as named, parameterised functions; the client sends values, never structure.
- Build the read-only panel first and use it for a week. Most internal tools turn out to be reports, and a report cannot destroy anything.
- Every destructive action gets a preview: show the exact rows and the count, require the operator to type the record identifier, and refuse anything that would affect more rows than a hard ceiling you set in code.
- Prefer soft deletes and reversible state changes. An internal tool that only sets deleted_at is one you can be wrong in.
- Write the audit log before the first write action exists: actor, action, table, row ID, before and after, timestamp, in a table the panel itself cannot update. This is the single artefact that makes an incident survivable.
- Put authentication in the API layer, not the UI layer, and use your existing identity provider. A page that redirects to /login while its own /api route answers unauthenticated is the most common shape of this bug.
- Bind exports to a role and log them. A CSV of the customer table is a data transfer, not a convenience.
- Before writing any of this: run the official Appsmith Docker image and see whether it just does the job. Apache-2.0, no per-user licence, datasource credentials held server-side, and roles that already exist.
I am building an internal admin panel over my production database: a table of
customers, search, and buttons that change their records. Assume it ends up on a
public URL, used by someone I have not met. Follow this order, and do not reorder it.
1. First ask whether I have tried running Appsmith itself: Apache-2.0, one Docker
image, no per-user fee, server-side datasource credentials and roles already
built. If it fits, say so and stop.
2. If we build: create a dedicated database role with SELECT on named tables and
nothing else. Refuse my existing superuser connection string and say why.
3. Ship read-only first. No mutations at all in the first version.
4. No table name, column, operator, sort field or limit ever comes from the request
— server-side parameterised queries only. If I ask for a generic /query endpoint
or a "just run this SQL" box, refuse and call it what it is: remote code
execution against production wearing a dropdown.
5. Authentication goes in the API layer before any UI exists, using my existing
identity provider, checked in every route handler. A protected page with an
unprotected /api route is the failure I care about most — write a test that
calls every endpoint with no session and asserts 401.
6. Then the audit log: actor, action, table, row ID, before, after, timestamp,
written in the same transaction as the change, into a table this app cannot
update or delete. Build it before the first write action exists.
7. Only now write actions, one at a time. Each previews the exact rows and count,
requires the operator to type the record identifier, and refuses outright above
a hard row ceiling defined in code.
8. Prefer soft deletes and reversible state transitions. If I ask for a hard delete,
say no once and offer deleted_at instead.
9. Bulk selection is keyed by row ID, never by index, and is revalidated server-side
against the current filter. Say out loud that this is where bulk actions go wrong.
10. Out of scope until I ask: dashboards, charts, uploads, anything that emails a
customer. If more than a couple of people will use this, tell me self-hosted
Appsmith is free and Business is $15 a user.That one keeps you out of trouble. For the prompt that actually builds it, canivibecodeit.com has one.
their build prompt ↗Or don’t build it
the boring option, and the way back out
The unusual thing about this entry is that the first alternative is free. Appsmith's Community edition is Apache-2.0, runs from a Docker image, has no per-user licence, and already keeps datasource credentials server-side with three roles and Google SSO — which is more access control than the panel you were about to generate. Pay the $15 a user for Business when you need granular per-role permissions and audit logs, and pay it immediately if anyone outside your payroll is going to touch the tool. The comparison that matters is not $15 against your weekend; it is $15 against the night you spend rebuilding customer records by hand.
$15/mo is cheaper than your weekend.
Almost nothing valuable lives in the panel — the database is the asset and it stays where it is — so switching off is mostly deleting a deployment and, importantly, revoking the database role it used rather than just deleting the .env. Do that even if you are certain the credential never leaked; a connection string that has lived in a hosting provider's environment for a year is not one you can call private. Take two things with you: the audit log, because questions about who changed what arrive after the shutdown and it is your only answer, and the list of queries the panel ran, which is a surprisingly good specification for rebuilding the same views in Appsmith or Retool. Moving to self-hosted Appsmith is genuinely a same-day job for a small panel, because the queries port directly and the widgets are the part you were not attached to anyway.
The product itself, Apache-2.0 and actively developed, with server-side datasource credentials and a real permission model — self-hosting this is the honest default before building anything.
Open-source low-code builder for internal tools, with roles, an internal database and a similar self-host story.
Questions
Appsmith is open source. Why is this rated at all?
Because the thing people actually build is not a competitor to Appsmith, it is the single panel they wanted out of it — and that panel is where the danger lives. The entry exists to make the free option loud: self-hosted Appsmith is Apache-2.0 with no per-user licence and already solves credential isolation and roles. If your answer to this page is "docker run", the page worked.
It's behind a login. Isn't that enough?
Only if the login is enforced where the data is. The recurring shape is a Next.js app whose pages redirect to /login while its route handlers answer any request that reaches them, so the panel is protected and the API behind it is not. Write a test that calls every endpoint with no session and asserts 401, and run it in CI. That one test catches the most common version of this bug.
What is the most dangerous single feature to let an agent add?
Bulk actions. A checkbox column and a Delete Selected button take about ten minutes, and they convert a one-row mistake into an all-rows mistake. If you must have them, key selection by row ID rather than index, revalidate the selection server-side against the current filter, and put a hard ceiling in code above which the action simply refuses.
Is a read-only dashboard fine?
Broadly, yes, and it is most of what people need. Give it a database role with SELECT on named tables, put it behind your identity provider, and be honest that a read-only panel over customer records is still a data-export surface — the browser can copy anything it renders. That is a much smaller problem than an UPDATE with no WHERE, which is what the rest of this page is about.
- GDPR Art. 32 — security of processing
- GDPR Art. 5 — principles relating to processing of personal data
Every week, someone ships something they shouldn’t have.
New verdicts, the worst thing that landed in the trap, and the occasional incident report. No other email, ever.
An internal tool is just a production database with a friendlier delete button.
A client portal is multi-tenant software. If the filter is a URL parameter, so is everyone else's data.
An agent will happily hand you a spreadsheet with UPDATE on production. Postgres has no undo button.
last reviewed 2026-08-04 · verdict is editorial and unsponsored · shared entry data from canivibecodeit under MIT · not legal advice