DEV Community

Cover image for Shipping a marketplace MVP in FlutterFlow: 12-week build
SapotaCorp
SapotaCorp

Posted on • Originally published at sapotacorp.vn

Shipping a marketplace MVP in FlutterFlow: 12-week build

A founder approached us last quarter with a tight constraint: a two-sided marketplace MVP with three role-based dashboards (learner, instructor, school admin), Stripe Connect for split payouts to both independent providers and partner schools, white-label theming for those partner brands, and a twelve-week timeline before the first round of investor demos.

The product was a UK-based booking platform connecting learners with DVSA-registered driving instructors. The team had three engineers, no FlutterFlow experience, and a budget that did not stretch to a full native build. The choice was effectively FlutterFlow or nothing shippable in twelve weeks.

We took the project, shipped on time, and learned a lot about where FlutterFlow earns its complexity and where it pushes back. The marketplace pattern in this case study generalizes well: two-sided platforms with provider affiliations, multi-tenant theming, and split payouts share the same architectural challenges regardless of the specific service domain. We have held back the client name per agreement but kept the technical and business details intact.

The product in brief

The complexity was not in the basic booking flow; it was in the ecosystem around it:

  • Three distinct user roles (learner, instructor, school admin), each with different navigation, permissions, and dashboards
  • Postcode-based geographic matching: learners only see instructors whose teaching radius covers the learner's selected lesson location
  • Under-18 learner handling: parent / guardian phone number captured, SMS notifications routed to the guardian for booking confirmations
  • Stripe Connect with two payout paths: independent instructors get paid directly to their own Stripe account; school-affiliated instructors route payment through the school's Stripe Connect account, with the school handling instructor payroll outside the platform
  • White-label theming so partner driving schools can offer the platform under their own brand
  • Skill progression tracker with milestones (parking, motorway, manoeuvres) so learners and instructors can see what is left to cover before test
  • A "Zone Demand Insights" module surfacing postcode-level coverage gaps to school admins (where learner searches happened in the school's priority zone but no affiliated instructor had availability)

This is the kind of product spec that, three years ago, would have started a six-month React Native build. With FlutterFlow plus a Supabase backend and a few custom code blocks, we shipped a working v1 in twelve weeks.

The data model that made it work

Before any FlutterFlow screen got built, we modeled the backend. Three entities anchored the platform:

Instructor Profile. The core record persisting permanently in the platform database. Personal details, DVSA registration number, qualifications, availability calendar, booking history, learner ratings, Stripe account ID for independent instructors, profile status. Owned by the instructor in the GDPR sense, controlled by the platform in the operational sense. This record persists regardless of which school the instructor is affiliated with at any given time.

School Affiliation Record. A relationship record linking an instructor to a school. Fields: instructor_id, school_id, start_date, end_date (null if active), status, payout_routing_flag. Created when a school adds an instructor. When the relationship ends, status is set to terminated rather than deleted. Historical affiliation records are retained for audit and analytics.

School Payout Configuration. A school-level record with the school's Stripe Connect account ID, the platform fee rate (5% flat across all instructor bands), and the payout schedule.

The payment flow at booking time: check the instructor's affiliation status. Affiliated instructor: payment collected by the platform, platform fee deducted, net amount remitted to the school's Stripe Connect account. Independent instructor: payment collected by the platform, commission deducted per the instructor's subscription tier, net amount remitted to the instructor's Stripe account directly.

The school's internal arrangement with the instructor (how the school pays the instructor from the net amount received) is outside the platform's scope. The platform does not process instructor payroll for school-affiliated bookings.

This split was non-negotiable for the business model and dictated the entire payment architecture.

What FlutterFlow did genuinely well

Visual development for standard CRUD flows. Authentication, profile editing, list views, detail pages, search, filters. These are the bulk of any consumer app, and FlutterFlow's drag-and-drop is faster than hand-coding equivalent React Native or Flutter screens. We estimated about a 2x to 3x speedup on this 60% of the app.

Tight Supabase integration. Auth flows, database queries with row-level security, real-time subscriptions, storage uploads. The Supabase actions library handled most of what we needed without writing custom code. The pieces that did need custom code (a few complex queries, the postcode radius filter, the Stripe Connect split payout webhook) plugged in cleanly via FlutterFlow's custom action interface.

Theming as a first-class concept. White-label theming was the spec requirement we worried about most going in. FlutterFlow's theme variables turned out to be exactly the right primitive. We exposed brand color, logo, typography, and a school_id variable tied to the tenant's record. Each white-label instance loads its theme on app start. Same APK, different brand. This was one feature where FlutterFlow saved weeks compared to building it ourselves.

Pixel-perfect design implementation. The designers handed us Figma, we matched it within a few percent on the first pass, and the back-and-forth on visual fidelity was minimal. FlutterFlow's design fidelity is significantly better than React Native's default behavior. Spacing, typography, gradients, and animations rendered close to design without the usual "why does this look different on Android" debugging.

Where FlutterFlow pushed back

Stripe Connect with two payout paths was painful. The Stripe SDK integration in FlutterFlow handled basic payment intents fine, but split payouts (the heart of our marketplace logic) required custom backend functions. We ended up running the entire Connect logic in Supabase Edge Functions: detect affiliation status from the instructor record, compute the platform fee, route to either the instructor's Stripe account or the school's Stripe Connect account, log the payout for the dashboard.

The Stripe integration in FlutterFlow is misleadingly named: it covers the consumer-side payment flow well, not the marketplace payout flow. Plan accordingly.

The postcode radius filter needed custom Dart. Each instructor has a teaching radius (typically 10-25 miles from a home postcode). Each lesson booking has a learner-selected postcode. We needed to find instructors whose radius covered the booking postcode. PostGIS in Supabase handled the spatial query, but FlutterFlow's standard query builder does not generate PostGIS calls. We wrapped the query in a Supabase Edge Function and called it from a custom action.

Custom widgets needed for the skill tracker. The skill progression visualization (parking, motorway, manoeuvres each with a circular progress indicator, color-coded by completion) did not exist in FlutterFlow's component library. We dropped to writing a custom widget in Dart. This works but pulls you out of the visual development flow. The line is roughly: if you can describe it in standard mobile UI primitives, FlutterFlow handles it. If it needs custom canvas drawing or platform-specific behavior, you write Dart.

State management at scale. FlutterFlow's app-state and page-state primitives are fine for simple flows. By the time we had three roles, multi-tenant theming, persistent filter state, real-time booking updates, and the demand insights live data, we had outgrown what app-state could manage cleanly. We ended up storing more state in Supabase and treating the app as a thin client over the backend. This is probably the right architectural choice anyway, but it took some experimentation to land on it.

Performance tuning is shallow. When the booking list page started lagging on lower-end Android devices (around 200 items rendered in a list), there was no profiler equivalent to React Native's Flipper or Flutter's DevTools. We solved it through pagination and lazy image loading, both of which FlutterFlow supports, but the diagnostic process was guesswork.

The Demand Insights module: a self-contained build worth describing

The school admin dashboard has a "Zone Demand Insights" module that surfaces a specific signal: how many learner searches happened in the school's priority postcode zone when no affiliated instructor had availability. This is the platform telling the school "here is where demand exists that you are not capturing."

The build was instructive because it touched almost every layer of the stack:

Data capture. Every learner search event is logged with a timestamp, a postcode, the school's priority zone match, and a list of affiliated instructors checked for availability. If no instructor was available, the event is flagged as an "uncaptured demand" event. The schema captured this from launch; retrofitting it later would have been impossible.

Display threshold. The module only appears on the dashboard if there are 10 or more uncaptured searches in the selected period (7, 30, or 90 days). Below the threshold, the module either hides or displays "Your zone coverage is strong." The product principle: do not show schools their gaps until the gap is large enough to matter, otherwise the dashboard becomes anxiety-inducing.

Visual breakdown. Below the headline number, a heatmap shows uncaptured demand by day of week and time of day. The school can see "Tuesday late afternoon is consistently uncovered" without the platform telling them what to do about it.

Suggested insight. A single line of contextual copy: "Expanding coverage during Tuesday late afternoons could help capture this demand." The language is non-directive. The platform identifies the gap. The school decides the response.

What the school never sees. Which competitor instructor or school fulfilled the booking instead. The learner's identity. Any ranking against other schools. These are deliberate omissions that protect learner privacy and avoid platform-induced rivalry between schools.

This module is one place where the difference between "we shipped a marketplace MVP" and "we shipped a marketplace MVP that respects the operating principles of the people using it" shows up. The technical build was straightforward. The product judgment was where the time went.

The architectural decisions that mattered

Three decisions made a disproportionate difference to the project's success.

Backend-first, FlutterFlow-second. We designed the Supabase schema, RLS policies, and Edge Functions before building any FlutterFlow screens. The data model was the contract, the app was the consumer. This avoided the pattern we have seen on other FlutterFlow projects where teams build screens first and then realize the data model cannot support them.

Custom actions as the escape valve. FlutterFlow's custom action interface lets you drop into Dart for any logic that does not fit visual development. We used this for: complex query composition, the postcode radius filter, Stripe Connect webhook handling, and the skill tracker widget. Treating custom actions as a normal part of the stack (rather than a last resort) kept the velocity steady when we hit the limits.

Separate environments, automated deploys. FlutterFlow supports environment variables and branch-based deployments. We set up dev, staging, and production environments with separate Supabase projects and Stripe accounts. The deploy was a single click from FlutterFlow's UI. This sounds basic, but on a project where the founder wanted to show stable demos to investors while we shipped daily to dev, the environment separation was non-negotiable.

What twelve weeks actually looked like

A rough breakdown of where the time went:

  • Weeks 1-2: backend schema (instructor profile, affiliation, payout config, booking, search event), RLS policies, Stripe Connect account setup, FlutterFlow project scaffolding, design system extraction from Figma
  • Weeks 3-5: authentication with role routing, profile screens for all three roles, instructor browse and detail pages
  • Weeks 6-8: booking flow with postcode geographic matching, Stripe Connect with split payouts, parent/guardian SMS for under-18 learners
  • Weeks 9-10: school admin dashboard, multi-tenant theming, demand insights module with the heatmap visualization
  • Week 11: bug fixes, polish, store submission preparation (App Store privacy manifest, Google Play data safety form)
  • Week 12: store submissions, last-mile fixes, soft launch to invite-only schools and instructors

Roughly 70% of the build time was on FlutterFlow visual development. About 20% was on Supabase and Edge Functions. The remaining 10% was custom Dart for the skill tracker, the postcode filter, and the Stripe Connect routing.

Why 12 weeks (and not 4)

A common reaction to "12-week FlutterFlow MVP" is "FlutterFlow is supposed to be fast, why so long?" Two things are worth addressing directly.

FlutterFlow speed depends entirely on scope. A simple 5-screen single-role booking app is realistically 2-3 weeks on FlutterFlow. The 12 weeks here cover three distinct user roles, Stripe Connect with two payout paths, multi-tenant white-label theming, postcode-based geographic matching with custom Dart, two custom canvas widgets (skill tracker and demand heatmap), parent/guardian SMS routing, and a school admin dashboard with its own data visualization layer. That scope is not 4-week scope on any framework.

The comparison baseline matters more than the absolute number. The same project in React Native, with the same 3-engineer team, would have realistically taken 24-30 weeks. The same project in Flutter native would have been 18-22 weeks. The same project as a fully native iOS / Android pair would have been 36-40 weeks. FlutterFlow saved 12-18 weeks compared to React Native, not "made it instant."

The honest framing of FlutterFlow's value proposition is "50-60% time savings on mid-complex projects" rather than "ship anything in two weeks." If your scope is closer to "simple app with payment", FlutterFlow gets you to 3-4 weeks. If your scope is closer to "enterprise marketplace with multi-tenant, dual payouts, and custom widgets", FlutterFlow gets you to 12-16 weeks. Pick the framework based on the scope reality, not the marketing speed promise.

The teams that ship 4-week FlutterFlow apps and then claim "all FlutterFlow apps ship in 4 weeks" are usually shipping much smaller scope. The teams that ship 24-week React Native apps and then claim "FlutterFlow could not handle this" are usually picking the wrong tool for projects FlutterFlow could have shipped in 12 weeks. Neither claim is honest about the trade-off.

When we would do this again

FlutterFlow earned its place on this project because:

  • The MVP needed to ship in twelve weeks with three engineers
  • About 80% of the screens were standard mobile UI patterns (auth, lists, details, forms)
  • The backend was decoupled (Supabase, not embedded in the app)
  • The team accepted that custom Dart would be needed for the bespoke 20%

Where we would not pick FlutterFlow:

  • Apps with heavy real-time interactivity (games, live drawing, complex animations)
  • Products where the visual design is itself a differentiator and pixel-perfect cannot be approximate
  • Long-term codebases where the team needs to retain engineers comfortable with the framework five years out
  • Projects where the team has a strong React Native or Flutter background already; the framework switch costs more than the visual development saves

The pattern that surprised us

The biggest unexpected benefit was how much FlutterFlow accelerated the design-to-development feedback loop. The founder could open the FlutterFlow editor, see the actual app, and request changes that we could implement in minutes rather than days. This shortened the "what do we actually want" cycle significantly. By week six, the founder was sketching screen changes directly in FlutterFlow and we were turning them into shippable features the same afternoon.

This is a workflow that does not exist with React Native or native development. It is a meaningful product velocity advantage when the team can use it.

If you are evaluating FlutterFlow

If your team is weighing FlutterFlow for a marketplace, MVP, or content app, the right question is not "is FlutterFlow good enough." It is "is your project's complexity within FlutterFlow's natural strengths, and does the team have a fallback to Dart for the parts where it pushes back."

Sapota offers a one-week FlutterFlow viability assessment that takes your product spec, identifies which screens FlutterFlow handles natively versus which need custom Dart, projects the timeline, and surfaces the risks before you commit. We have shipped multiple FlutterFlow apps for B2C marketplaces, internal tools, and consumer products.

Reach out via the mobile app development page with a description of what you are building and what your timeline looks like. The first conversation usually surfaces whether FlutterFlow is the right fit within thirty minutes.

Top comments (0)