Designing a two-sided marketplace: onboarding, proposals, and messaging
A case study on DevMarket: making a complex two-sided flow feel simple, from role-based onboarding through proposals, messaging, and role-aware navigation.
the brief, before the long version
Client
DevMarket, a two-sided marketplace where clients post projects and developers respond with proposals.
Problem
The product needed to make a complex two-sided flow feel simple from onboarding through messaging and project handoff.
Role
Product design, application architecture, role-based onboarding, proposal workflows, and in-product communication patterns.
Outcome
A live marketplace experience with project posting, proposals, messaging, and role-aware navigation.
A two-sided marketplace is really two products wearing one interface. DevMarket connects clients who post projects with developers who respond with proposals — which means every screen, every navigation decision, and every notification has to answer the same question twice: what does this moment look like for a client, and what does it look like for a developer?
This post walks through how I approached that on DevMarket, where I owned product design, application architecture, role-based onboarding, proposal workflows, and the in-product communication patterns. The product is live if you want to click along.
Onboarding: the fork in the road comes first
The first design decision in any marketplace is where users declare which side they are on. Delay it and every subsequent screen has to hedge, showing generic furniture that serves neither side well. So DevMarket forks immediately: you join as a client or as a developer, and from that moment the product commits to your path.
Committing early pays off because the two onboardings genuinely want different things. A client needs to get a project posted with as little ceremony as possible — the post is their activation moment. A developer needs to build enough of a profile that their future proposals carry weight. Forcing both journeys through one generic flow would make each worse to avoid building two.
The architecture followed the same fork. Role is a first-class fact in the data model, established at onboarding and enforced in authorization — not a UI flag that pages check inconsistently. Every later feature got simpler because “who can do what” had one source of truth.
Role-aware navigation, one codebase
A naive build of a two-sided product slowly becomes two applications in a trench coat, with duplicated screens drifting apart. The approach that kept DevMarket coherent: shared structure, role-aware content. Clients and developers see the same navigation shapes — a dashboard, project views, messages — but each surface reads through the lens of the viewer’s role. A project page shows a client their incoming proposals; it shows a developer the brief and their own proposal’s status.
One pattern worth stealing: name sections by what they mean to the viewer, not by the underlying table. Both roles have “Projects,” but for a client that means projects they posted, and for a developer it means projects they are pursuing. Same word, same nav slot, correct meaning for each side — and nobody has to learn a second vocabulary.
Proposals: the marketplace’s real transaction
In a services marketplace, the proposal is where value changes hands — long before money does. It deserved the most design attention of any workflow, and the effort split evenly across both sides:
- For developers, the proposal form is structured rather than a blank text box. Prompting for approach, relevant experience, and terms raises the floor on quality — which protects the asset the marketplace actually sells: the client’s experience of reading responses.
- For clients, proposals arrive as comparable cards, not a pile of cover letters. Consistent structure means deciding between five responses is scanning, not archaeology.
- Status is always legible. Submitted, seen, shortlisted, accepted, declined — a proposal is a small state machine, and both sides can always tell where things stand. Ambiguity here is where marketplaces leak trust; a developer who cannot tell if silence means “not seen yet” or “rejected” stops submitting.
Messaging: scoped to the work, not a chat app
Every marketplace needs communication, and every marketplace is tempted to build a general-purpose messenger. DevMarket’s messaging is deliberately narrower: conversations are anchored to a project and its proposals, so context travels with the thread. When a client opens a conversation, the relevant project and proposal are right there — no “which project was this about?” tax on every exchange.
Scoping messages this way also draws a clean line through the product: negotiation happens in context, decisions get reflected in proposal status, and the thread remains a record of how the engagement took shape.
The stack, briefly
DevMarket runs on Next.js and TypeScript with Supabase behind it and TanStack Query managing server state in the client. The interesting consequence of that last choice: a marketplace is a multiplayer product, where the other side’s actions change your screens. Query invalidation gave the app a disciplined way to keep proposal lists and conversations fresh without hand-rolled refresh logic scattered through the codebase.
Supabase’s row-level security did quiet, important work here too: authorization rules like “developers see only their own proposals; clients see all proposals on their own projects” are enforced in the database itself, not re-implemented per page.
What shipped, and what I would carry forward
DevMarket shipped as a live marketplace: project posting, structured proposals, scoped messaging, and role-aware navigation across both sides. Three lessons I would carry into any two-sided build:
- Make users pick a side immediately, and let the data model — not just the UI — remember the choice.
- Design the transaction artifact hardest. Whatever your marketplace’s equivalent of the proposal is, its structure and status legibility carry the product’s trust.
- Scope communication to the work. A worse chat app bound to context beats a better one floating free of it.
keep reading
More notes
Have a question or an interesting problem?
Feel free to send a note. Email is the fastest way to reach me.