devsnack

What It Costs to Run a Multi-Vendor Delivery App: Firebase and Maps, Line by Line

Current Firestore, Realtime Database and Google Maps pricing applied to a thousand orders a month. The same app bills $21 or $1,770, and one await decides which.

DevSnack12 Aug 2026 · 12 min

Two answers circulate whenever someone asks what a delivery app costs to run. One says Firebase is effectively free until you are large. The other says the Maps bill will eat you alive. Both are correct, and which one you get is decided in about four places in your code.

Take a concrete app: 1,000 orders a month, twenty vendors, ten riders, thirty minutes from assignment to doorstep, Firestore in a North American multi-region. At that load the database costs nothing and the maps cost either $1,750 a month or nothing at all. The bill is set by how you stream the rider's location, not by how many orders you take.

The fixed costs are the boring part

Before any usage-based billing there is a floor, and it is smaller than people expect:

Google Play, one-time registration        $25
Apple Developer Program, per year         $99
Domain, per year                          $14
Vercel Pro, per month                     $20

First year                               $278
Every year after                         $253   (~$21 / month)

The line that catches people is Vercel. The admin panel deploys to the free Hobby plan without complaint, but the fair-use terms restrict Hobby to non-commercial, personal use only, and a vendor dashboard for a marketplace taking real money is not that. Pro is $20 per user per month. A $6 VPS running the same Next.js build is a perfectly legitimate alternative — just make the choice deliberately, rather than finding out when someone reads your terms.

Firestore is not where the money goes

An order touches the database far less than the fear suggests. Browsing dominates the reads, and the order lifecycle itself is a rounding error:

                          per order    × 1,000    free / month
catalogue browsing              330      330,000
rider location listeners      1,080    1,080,000
                              -----    ---------
reads                         1,410    1,410,000     1,500,000  →  $0

order lifecycle                   8        8,000
rider location writes           360      360,000
                              -----    ---------
writes                          368      368,000       600,000  →  $0

The whole month fits inside the free tier. That holds even though the rider pings every five seconds for half an hour and three parties — customer, vendor, dispatcher — are watching. Firestore charges one read per document each time it changes, so three listeners on one moving document means three reads per ping, and it is still free at this size.

Two caveats worth internalising. The free tier is daily, not monthly. Fifty thousand reads a day does not bank; a quiet Tuesday buys you nothing on a busy Friday, so a month that averages comfortably under the cap can still bill on its peaks. The amounts are pennies, but the surprise is the point.

Your database location doubles the bill or halves it. A multi-region like nam5 costs $0.06 per 100,000 reads and $0.18 per 100,000 writes; the regional us-central1 is exactly half that. At 10,000 orders a month it is the difference between roughly $13 and $6.50 — immaterial today, but the location is chosen when you create the database and cannot be changed afterwards. Choose regional unless you can articulate why you need the extra durability.

Maps is where the money goes

In March 2025 Google replaced the $200 monthly credit with per-SKU free allowances. Most cost breakdowns written before then are now wrong in a way that flatters Google: instead of one pooled credit you get 10,000 free calls per Essentials SKU per month, and the SKUs a delivery app leans on are not cheap. Dynamic map loads are $7 per 1,000. Route computation, directions and geocoding are $5 per 1,000.

Now here is the pattern that appears in almost every tracking screen built from a tutorial:

lib/tracking/rider_route.dart
riderLocation.listen((position) async {
  // One billable Routes call. Every five seconds. Per order.
  final route = await routes.computeRoute(
    origin: position,
    destination: order.dropOff,
  );
  setState(() => _polyline = decode(route.encodedPolyline));
});

Thirty minutes of five-second pings is 360 routing calls for one delivery. A thousand deliveries is 360,000 calls; subtract the 10,000 free and multiply by $5 per 1,000 and the map on that one screen costs $1,750 a month. The database serving the same orders cost nothing. Nobody notices during development, because ten test orders sit inside the free tier and look fine.

Compute the route once, move the marker yourself

The rider does not need a new route every five seconds. They need one route, and a marker that moves along it. Fetch the polyline when the rider accepts the job, then snap each incoming position onto the line you already hold — and only pay again when they have genuinely left it.

lib/tracking/rider_route.dart
final route = await routes.computeRoute(
  origin: rider.position,
  destination: order.dropOff,
);
_polyline = decode(route.encodedPolyline);

riderLocation.listen((position) {
  // Local geometry, no network call, no charge.
  final snapped = _polyline.nearestPointTo(position);
  setState(() => _marker = snapped);

  // Wrong turn, not GPS jitter. 80 m is a reasonable threshold.
  if (snapped.distanceTo(position) > 80) {
    _refetchRoute(from: position);
  }
});

That is three routing calls per delivery instead of 360 — one at assignment and a couple of genuine reroutes. Three thousand calls a month sits inside the free allowance, so the same feature, with the same smoothness on screen, costs nothing. The customer cannot tell the difference, because interpolating along a known polyline looks better than a line that redraws itself every five seconds.

Address entry has the same shape. Autocomplete billed per request costs $2.83 per 1,000 and a debounced field still fires several requests per address; wrap the keystrokes and the final Place Details call in a sessionToken and the typing is billed as one session instead. At 1,000 orders either approach is free. At 10,000 the careless version is about $200 a month for exactly the same autocomplete.

Location pings belong in Realtime Database

Firestore bills per operation, and operations multiply by the number of people watching. Realtime Database bills $5 per GB stored and $1 per GB downloaded with no per-operation charge at all, and a location ping is about 120 bytes. Put the firehose there and leave the orders in Firestore:

lib/tracking/location_stream.dart
FirebaseDatabase.instance
    .ref('riders/${rider.id}/position')
    .set({
  'lat': p.latitude,
  'lng': p.longitude,
  'ts': ServerValue.timestamp,
});

Be honest about the size of this one: at 1,000 orders both databases are free, and at 10,000 the split saves perhaps $13 a month. The reason to do it is not today's invoice but the shape of the curve. Firestore multiplies by listeners, so the day you add an operations map watching all ten riders at once, the cost of that screen is ten riders times every ping times everyone looking at it. Bandwidth billing does not care how many people are subscribed.

Set the budget alert before you launch

The same app, at the same thousand orders, is either about $21 a month or about $1,770. Nothing about the order volume changed — the difference is one await inside a location listener. Usage-based billing punishes the loop you did not think about, and it does so silently, in arrears, after the month has already been spent.

So before the first real order: put a budget alert on the Google Cloud project at a number that would genuinely alarm you, and set a quota cap on every Maps SKU you use. Then open your tracking screen, watch the network tab for sixty seconds, and count the requests. If that number grows when the rider moves and nothing else changed, you have found your bill.

GreenCart — $19

Multi-vendor grocery marketplace with live delivery tracking and a rider app, built on the pattern above — one route fetch per assignment, not one per location ping.

See the live demo