Internal tools that know who may see what, and who changed it
Internal tools, admin panels, portals and dashboards on Next.js and Postgres: roles enforced in the database, an audit trail, soft delete, reports and exports, in your repository from the first commit.
An internal tool is software your own people use to run the business: an admin panel over a database, a back-office system, a portal for clients or contractors, a dashboard that answers the question a platform's default widgets cannot. This page is about when to have one built, what separates a good one from a form bolted onto a database, and how it is handed over. Our custom ERP page covers the system that holds the books. This one covers the rest of what teams need built.
Our default is to say no first. Many internal tools should be an Airtable base or a monday.com board, and the first thing we do is check whether yours should be. When a build is right, it is usually because of what the tool has to guarantee and not because of what it has to display: who may see which rows, who changed which figure and what it was before, and what happens to a record someone deletes.
Five client builds bear on this page, and their states differ. A trading group's ERP and a school group's ERP are written up as built, with no usage figures. A solar installer's operating system is live with the client's team on interim hosting. A staffing platform is on staging and not live with real workers, and a heat-pump platform is in build with no customers. We say which is which below, because built and live are different claims.
When a platform is enough, and when it is not
Settle this before anything is scoped. A tool built where a platform would do is money spent acquiring maintenance.
| If this is true | Then | Why |
|---|---|---|
| The process is simple, stable and owned by the people who will maintain it | Configure a platform you already pay for | It is the cheapest route that works. Our Airtable and monday.com pages say where each one stops. |
| Outsiders need to see or update a slice of the data without a paid seat | A portal on top of the platform | On the fleet build, installers get a magic link to a page showing only their own jobs, with status written back to the board. No seat and no password. |
| The rules need memory, a once-only guarantee, per-user visibility or an audit trail | Custom, with the rules in the database | A platform's run history is not an audit trail, and a rule enforced by one screen is eventually not enforced. |
| Per-seat licences have overtaken the cost of owning the alternative | Do the arithmetic, then decide | Our build-or-buy article sets out licence drift, integration tax and workaround cost over the horizon you plan to keep the tool. |
| A prototype built with an AI tool now holds real data | Have it reviewed before you decide to build or rebuild | See our vibe code security review and the vibe coding to production service. |
The middle option is usually right: keep the platform and build only the piece it cannot express, such as a portal, a parser for inbound documents or a report. That is a far smaller commitment than replacing the platform.
Six things a useful internal tool has that a quick one does not
None of these shows in a demo and all of them show up in month six. They are the parts we build first, and each is something one or more of the builds below actually contains.
Roles and permissions that hold
On the trading ERP, roles are a grid of eighteen modules against five actions, editable per role and overridable per person. Navigation hides what you cannot view, routes redirect, the write layer checks, and a database trigger discards any change a non-administrator makes to their own role. On the school ERP, row-level security carries both role and branch, so a branch administrator's queries return their branch's rows whichever screen asked.
An audit trail nobody can rewrite
Every write on the trading ERP stamps the acting user and appends a field-level diff to an append-only table that staff can add to and never edit. When a figure is wrong you can say who changed it, when, and what it was before, which the legacy ledger it replaced could not do.
Delete that can be undone
On the trading ERP, deleting sets a timestamp and does not destroy the row, only an administrator can purge, and the audit entry outlives the record. On the staffing platform, which is on staging, removing a worker anonymises them while keeping the history and the paperwork already issued.
Reports that reconcile
On the school ERP, fees, payments, expenses and payroll feed one accounting layer, so the reports agree without a spreadsheet in between. On the trading ERP, totals exclude invoices that have not been approved, on the lists and the dashboard alike. Start from the question leadership asks in the meeting, and build the view from that and not from whatever the default widgets offer.
Exports people can use
Every change across the trading ERP is attributable, filterable and exportable, and our custom development page lists CSV and PDF export among the standard deliverables. Decide early which exports are needed for whom. An export is a data leak with a file name, so it follows the same permissions as the screen it comes from.
Ways in that are closed by default
On the school ERP, public sign-up is disabled and account creation sits behind one privileged server-side function that holds the only elevated credential. On the staffing platform, a client portal reads only through views that filter themselves to what that client may see, and a test pins the set so adding one without review fails the build.
From the exception list to a handover
Six steps. The order matters more than the names.
- 01
Start with what people touch by hand
In the last month, what did somebody fix, re-key or chase, and why? That exception list is most of the scope and most of the estimate, and a team that cannot answer is describing the process as designed and not as run.
- 02
Decide who owns each record
Which table is the source of truth, who may change it, and what happens when two things disagree. Rules that must hold go in the database (permissions, allowed state changes, the audit log), because screens get refactored and a rule only one screen enforces is a rule that eventually is not enforced.
- 03
Ship something you can click every week
Deployed increments and not a reveal at the end, with written decisions in place of remembered ones. Working hours are arranged to overlap with UK, US and Australian teams, so the review of each increment happens in a shared window.
- 04
Keep the feedback inside the tool
On the solar operating system every screen carries a feedback control that captures the route, severity and a screenshot or recording. A ticket cannot be closed without a written resolution, and the person who raised it is emailed the outcome. The client's team test produced sixty-one tickets, and the system's latest change came from one of them.
- 05
Bring the history across safely
On the solar operating system, imports dry-run first and cannot be applied until the dry-run has been viewed, park files they do not recognise and treat a blank cell as nobody having answered, never as an instruction to erase. On the trading ERP, loads were made idempotent so a re-run corrects rather than duplicates.
- 06
Hand it over
The repository and the accounts are in your name from the first commit, the handover covers the schema, the deploy path and a runbook, and anything unfinished is listed. The next section says more.
What each build shows, and what state it is in
The state column is as each case study states it. Where a case study states none, we say so and make no claim about use.
| Build | State, as written up | What it shows |
|---|---|---|
| Trading ERP | No status line; written up as built, with no usage figures | Eighteen modules, permissions per role and per person, an append-only audit log, soft delete, and an installable app for staff on phones in a warehouse. |
| School ERP | No status line; written up as built, with no usage figures | Role and branch scope enforced in the database, one ledger for fees, payments and payroll, WhatsApp fee reminders from templates, and real-time sync between sessions. |
| Solar operating system | Live with the client team, on interim hosting | CRM, projects, quotes, purchase orders and a customer portal in one system, a pipeline that names the missing data before a deal advances, and an in-product feedback loop. |
| Staffing platform | On staging, not live with real workers | Three front ends over one database, a client portal that cannot read pay, and rules held as database functions and triggers with tests that fail the build. |
| Heat-pump platform | In build, with no customers | Stage changes owned by one function, an audit table that refuses to be rewritten, and business rules held as validated settings and not typed into code. |
None of the case studies states a price, a duration or a time saving, and we claim none.
Who owns it, where it runs, and what you get at the end
You own the code, in your own repository from the first commit, and it deploys to your accounts: your Vercel project, your database, your domain. If you take the work in-house or move to another partner, nothing needs to be untangled first.
The test of a good handover is that another competent engineer can pick the tool up without an introduction. So the code is written on the assumption that someone else will read it, and the handover covers the schema, the deploy path and a runbook. You can take it fully in-house or keep a support arrangement for changes, and both are fine.
What moves the cost of an internal tool
Most of our projects land between $5,000 and $10,000 (US dollars). Larger systems cost more than $10,000. That is a typical range, not a quote: we quote after a discovery conversation, once we have seen the process, its exceptions and the systems involved. None of the case studies above states a price or a duration, so the range is not attributed to them. What moves the number is knowable before anyone writes a quote.
- How much of the work is exception and not happy path. Each approval variant or document type is a case to build and test.
- Who sees what. A tool with one role is a different job from one with branch-scoped roles and per-person overrides.
- How many systems still have to agree, and whether history has to be imported and reconciled first.
When an internal tool should not be built
Buy or configure wherever a product fits. If a board or a base does the job and the people who will maintain it can change it themselves, that is the cheaper and better route, and we will say so on the first call. A build is also wrong where the process is undifferentiated, where nobody will own the result, and where the tool exists to compensate for a process nobody has decided.
Do not commission a dashboard before you know the decision it supports. Reports built without a question are the first thing a team stops reading. The better middle is usually to keep the platform and build the one piece it cannot express.
Where it has shipped
Related projects that were not this job
Here for the argument rather than as evidence of this exact work. Each one bears on a decision above, and the note says which.
- A UK event-staffing agencyA staffing platform that keeps its rules in the databaseOn staging and not live with real workers, so it is not offered as shipped work. It is the closest we have to a multi-audience internal system: a back-office portal, an installable staff app and a read-only client portal over one Postgres database, with the client portal's blindness to pay proved by a test on every merge. Email, push and scheduled jobs wait on the client's keys.Case study
- A UK heat-pump installerHeat-pump quotes with the compliance gates written into the codeIn build, with nothing in front of customers. It is a regulated sales platform and not a general internal tool. It is here for how the rules are held: stage changes through one function, an audit table that refuses updates and deletes, and prices and copy held as validated settings and not typed into code.Case study
- A fleet-telematics providerOrders that file themselves, and an SLA clock that never forgetsNot a standalone internal tool, since it extends a monday.com board. It is here for the portal piece: an installer page behind a magic link that shows only that installer's jobs and writes status back to the board, with no seat and no password.Case study
The disciplines behind it
Platforms that bear on it
Related reading
Internal tools development, specifically
Start with the platform. If the process is simple, stable and owned by the people who will maintain it, configure what you already pay for. Build when the rules need memory, a once-only guarantee, per-user visibility or a permanent audit trail, when per-seat licences have overtaken the cost of owning the alternative, or when the workarounds have grown larger than the tool. Often the answer is both: keep the platform and build only the piece it cannot express, such as a portal or a report.
Most of our projects land between $5,000 and $10,000 (US dollars). Larger systems cost more than $10,000. That is a typical range, not a quote: we quote after a discovery conversation, once we have seen the process, its exceptions and the systems involved. Three things move it: the exception count, how many roles and views the tool needs, and how many systems must agree. History that has to be imported and reconciled sits on top.
Often, and keeping the database while building the interface your team needs is one of the middle options we like. We would read the schema and any access rules first. If tables lack sensible keys or rules, those get fixed before a screen is built over them, because an admin panel inherits every gap in the data underneath it.
Yes, that is what a portal is for. On the fleet build, each installer gets a magic link to a page showing only their own jobs, with their status updates written back to the board: no seat, no password to reset and no exposure to anything else. For a client-facing portal over a database, we scope what each client may read at the database and not only in the screens.
It should be recoverable. On the trading ERP, deleting sets a timestamp and does not destroy the row, only an administrator can purge, and the audit entry outlives the record. Whether a given record type may ever be purged is a decision we make with you, because some must be kept for legal reasons and some must be erasable.
You do, from the first commit, in your own repository, and it deploys to your accounts: your Vercel project, your database, your domain. The handover covers the schema, the deploy path and a runbook, and anything unfinished is listed so nothing is hidden in the code.
It is as safe as the review that follows. We build with AI coding tools under engineer review, and the permissions and audit rules are the parts we write and read most carefully. If you already have an AI-built tool that is about to hold real data, our vibe code security review is the quickest way to find out what needs fixing first.
Page copy drafted with AI assistance. Skofi is accountable for it. Editorial policy
Tell us about the process that keeps costing you.
A 15-minute call is usually enough to say whether this is an automation, an integration, or something that should be built, and whether we are the right people for it.
What happens on the call
- 15 minutes, no deck
- We map the process on the call
- A written scope, or an honest no