Nobody decides to build a web app. You get there by additions, and the quote is still the one for a website

Where the line between a website and a custom application actually sits, why one is priced per page and the other per state, and what happens to the pages already built the day somebody asks to see «only theirs».

by ·

The question almost always arrives in the wrong shape: «website or web app?». Put that way it has no answer, because neither is a matter of taste or ambition — they are two different trades, with two different ways of pricing, and you end up in the second one by accumulation. A project starts as a website, takes four reasonable requests one at a time, and at some point it is something else: with nobody having decided it, nobody having said it, and the quote still sitting where it was.

The point of this piece is that the line exists, it is sharp, and it doesn't run where everyone looks for it. It isn't the number of pages, it isn't how good the design is, it isn't whether there's an animation. The line is one thing, and this is it: at some point something in the system starts belonging to somebody. As long as every visitor can be shown the identical response, you are on a website and it costs what a website costs. The instant a piece of data is «that client's and not the others'», you need identity, authorisation and an audit trail — and those are three systems, not three pages.

The part that surprises people, and never appears in a quote, is that crossing the line doesn't only cost you what lies beyond it: it puts back in play what has already been built and paid for. Below is the exact sequence in which this happens, what changes in how you count, and how to decide up front instead of finding out halfway through.

1 · The crossing, and how you get through it without noticing

This is a real sequence, in the order it arrives. Each request, taken alone, looks small: that's precisely why they all get accepted. The red line is where the project changes trade — and from there on, watch the mark that appears on the requests already accepted above it.

website territory

the crossing

application territory

A page presenting the services

Content identical for everyone, computed once and served to anybody. This is the case where a website genuinely is cheap: the same response works for the first visitor and the hundred-thousandth, it can be cached anywhere in the world, and there is nothing to protect because there is nothing private.

No effect on anything else: it's a page, and it stays a page.

A form that emails you

The form collects and sends. It doesn't remember: what was submitted lives in your inbox, not in the system. It is still a website, because there is nothing to keep, nothing to show again, and nothing to show one person and not another.

Adds one dependency — whoever sends the mail — and nothing more.

A price list PDF to download

A file behind a link. As long as it's the same file for everyone it is content, exactly like an image: you upload it, you replace it, you serve it. Plenty of people think this is already a private area. It isn't, and that distinction is worth the whole article.

None. It's the request that looks closest to the line and doesn't touch it at all.

Each client must see THEIR price list

Here the trade changes, and it changes in one line. For «their» price list to exist you have to know who they are: identity. For them not to see somebody else's, you need a rule that prevents it: authorisation. And for that rule to hold when someone guesses a URL or calls the API by hand, it has to live in the database, not in the page. This isn't one more page: it's three systems that weren't there before.

And the three requests above have to be redone: the services page now exists in two versions, signed in and out; the form has to know who is writing; and the PDF can no longer sit at a public address a client can forward to a competitor.

Sales staff edit prices from the browser

Writing, no longer reading. You need a second kind of user with different powers from the first, somewhere changes get validated before they count, and a behaviour decided in advance for when two people touch the same row in the same minute. That last one isn't a rare edge case: it's an ordinary Tuesday morning in a sales office.

It doubles the paths to test: every screen now exists in the version for someone reading and the version for someone editing.

Knowing who changed what, and when

An audit trail. It isn't a feature to bolt on at the end: it's a constraint on how EVERY write is performed, because a half-kept log is worse than no log — it offers a certainty it doesn't have, and the first time it genuinely matters is also the first time you discover a row is missing.

It touches every write already written, including the ones made in the weeks before anyone asked for a log.

The fourth request isn't the culprit, and that's the mistake people make when the bill lands: that request is almost always legitimate, and it is often the entire commercial reason for the project. The problem is having received it after quoting the first three as though it would never come.

The question that moves the conversation

Not «website or application?» but: «is there anything one person must see and another must not?». If the answer is yes, even for one thing, even a year from now, you are past the crossing — and you may as well know it now, while it costs a decision instead of a rewrite.

2 · One is counted in pages, the other in states

This is where quotes diverge, and the reason is arithmetic, not commercial. A website's work grows with pages: ten pages cost roughly twice five. An application's work grows with states — the combinations of who you are, what you're looking at, and what condition it's in — and combinations multiply against each other instead of adding up.

WebsiteCustom application
What makes the work growThe number of pagesThe number of states: roles × screens × conditions
How it growsBy addition: one more page is one more pageBy multiplication: one more role doubles everything that already exists
What you test before shippingThat every page opens and reads wellThat every role sees what it should and does NOT see the rest — the second half takes far longer
What happens if you get it wrongAn ugly page, or stale copyOne client seeing another client's prices
Where the rule livesIn the design of the pageIn the database, because it has to hold for people who never open the page
The cost after deliveryUpdating contentKeeping identity, permissions, backups and the audit trail alive

Why the «if you get it wrong» row is the one that sets the price

The first five rows explain the difference in work; the last explains the difference in liability. A website that breaks is embarrassing. An application that breaks in the wrong place shows one client another client's data — and that is why a serious quote for an application contains line items that simply don't exist on a website, and that look padded on first reading.

3 · The three things that appear the day after

Past the line, three pieces enter the project that had no reason to exist before. They aren't optional and they don't get deferred to phase two: they are why the quote changes, and it's worth recognising them by name when you read one.

The test you can run in the meeting

Ask whoever is quoting you: «if one client guesses the URL of another client's document, what happens?». If the answer is about how the page is built, the project is treating an application like a website. If it's about a rule on the data, you're in the right place.

4 · Where the crossing shows up in the code

The difference shows up in two tables. They look remarkably alike — both have columns, an identifier and a timestamp — and the entire distance between the two professions runs between them. Look at one thing only: whether the row has an owner.

crossing.sql

-- BEFORE THE CROSSING. A message from the contact form.
-- It has no owner, because it never has to go back to anybody:
-- it comes in, it reaches your inbox, and its life ends there.
create table messages (
  id         bigint generated always as identity primary key,
  sent_at    timestamptz not null default now(),
  email      text not null,
  body       text not null
);

-- AFTER THE CROSSING. A price. It has an owner, and the rule saying
-- who may see it does NOT live in the page: it lives down here, where
-- it also holds for anyone who never opens the page at all.
create table price_list (
  id           bigint generated always as identity primary key,
  client_id    uuid not null references clients (id),
  sku          text not null,
  price        numeric(10,2) not null,
  -- the audit trail is not a table you add later: it starts here
  updated_at   timestamptz not null default now(),
  updated_by   uuid not null references auth.users (id)
);

alter table price_list enable row level security;

create policy "everyone sees only their own price list"
  on price_list for select
  using (
    client_id = (select client_id from profiles where id = auth.uid())
  );

The two tables differ by one column — client_id — and by the four lines at the bottom. That column is the crossing: it says the row belongs to somebody. The four lines are the consequence: the rule protecting it lives in the database, so it holds for the page, for the API, for a colleague's script, and for anyone arriving by a route you hadn't anticipated.

Why hiding the button isn't enough

The most common shortcut is checking permissions in the interface: if it isn't you, the button doesn't appear. That works for as long as everyone only uses the interface. A rule written on the data can't be walked around, because there is no route that doesn't pass through it — and there's a whole piece on this blog about how to write one.

5 · How to decide up front instead of halfway through

The decision gets made in a meeting, not in a forty-page document, and it gets made by answering three questions in this order. If all three come back no, take a website and sleep well for years. If even one comes back yes — including in the form of «not now, but probably next year» — the choice is already made, and it belongs in today's quote.

The number not to take literally

The two-to-four multiplier is a range observed across different projects, not a price list: it depends almost entirely on how many roles exist and how many screens change face depending on who is looking. With one extra role and three screens it stays near two; with four roles and an approval flow it goes past four. Anyone giving you a precise percentage before counting the roles is guessing.

Half an hour on your case, and you know which side of the line you're on

If you have a website quote in hand and the feature list contains even one line starting with «the client will be able to see», send it over: in half an hour we'll tell you which side of the crossing you're actually on, which line items are missing, and whether it's worth building the website now and the application later — which is sometimes the right answer.

And if the three questions come back no three times, we'll say that just as plainly: you need a website, not an application, and anyone proposing a credentialed portal for a catalogue that is identical for everybody is selling you complexity you'll maintain for years without ever having needed it.

The service behind this article

Custom web application development

If this was useful, carry on from here

FAQ

What is the difference between a website and a web application?

There is one line: on a website every visitor can be shown the identical response, in an application something belongs to somebody. It isn't the number of pages and it isn't the quality of the design. The instant a piece of data is «that client's and not the others'», you need identity, authorisation and a record of who changed what — and those are three systems to maintain, not three pages to design.

How much does a custom web app cost compared to a website?

The typical range is two to four times, but the multiplier has nothing to do with the graphics: it depends on how many roles exist and how many screens change face depending on who is looking. With one extra role and three screens it stays near two; with four roles and an approval flow it goes past four. Anyone giving you a precise figure before counting the roles is guessing.

When do you genuinely need a custom web application rather than a website?

Three questions, in this order. Must somebody see something somebody else must not? Must somebody change data from the browser, and must those changes persist? Will you have to prove in six months who changed what? If all three come back no, you need a website and you'll sleep well for years. If even one comes back yes — including as «not now, but probably next year» — it belongs in today's quote.

Can I just add a login area to the website I already have?

It depends where the rule lives. Hiding a button from someone unauthorised protects only the people coming through the interface: anyone calling the API directly, or guessing a document's address, still gets in. The rule that actually protects sits on the data, in the database, where it holds for the page, for the API, for a colleague's script and for any route you hadn't anticipated. It's the difference between «unlikely» and «impossible», and it costs far less decided at the start.

Nadia Maar