Dashly
Food, grocery and pharmacy delivery for your town: a customer app and a rider app (Flutter, iOS and Android), and one Next.js server that is the website, the vendor panel, the admin panel and the API. You add your own keys; everything else is ready.
1. Welcome
Thank you for buying Dashly. This guide is written for beginners: follow the chapters in order and you will have the server, the website, the vendor panel and both apps running under your own name and keys.
What is in the download
| Folder | What it is |
|---|---|
dashly_customer_flutter/ | The customer app. People browse stores near them, order, pay, track the rider and chat. Also runs as a single-restaurant app with dine-in and reservations (chapter 11). |
dashly_rider_flutter/ | The rider app. Riders apply, go online, accept delivery offers, pick up, hand over with a code or a photo, and see their earnings. |
dashly_flutter_core/ | Code both apps share: the API client, sign-in, theme, models, widgets and common screens. |
dashly_web_nextjs/ | One Next.js app that is the website, the vendor panel (/vendor), the admin panel (/admin), the API both apps use (/api/v1) and the background worker (pnpm worker). |
deploy/ | The Docker stack: website/API + worker + PostgreSQL database + MinIO file storage, started with one command. |
tools/rename.mjs | Renames both apps and their package ids in one command. |
docs/ | This documentation (HTML and PDF). |
How the parts fit
Both apps and the website talk to the same server. All data (stores, menus, orders, wallets, riders) lives in your PostgreSQL database. Photos, rider documents and delivery proofs live in S3-compatible storage (MinIO in the Docker stack, or Amazon S3 / Cloudflare R2). Firebase is used only for sign-in and push notifications.
The flow of an order: a customer orders → the store accepts on the vendor panel → the worker offers the delivery to the nearest online riders in that zone → the first rider to accept picks it up → the customer follows the rider on the map and gives the 4-digit code at the door.
2. Requirements
To run the server
- A Linux server (VPS) with at least 2 CPU cores, 4 GB RAM and 30 GB disk. Ubuntu 24.04 is used in this guide.
- Docker with the Compose plugin (install guide).
- A domain name, for example
your-domain.com, with a DNS A record pointing at the server.
To build the mobile apps
- Flutter 3.44 or newer (install), Android Studio for Android, a Mac with Xcode 26 for iOS.
- Node.js 22 or newer for the rename tool, and pnpm 11 (
npm i -g pnpm) if you run the website without Docker.
Accounts you will create (all have free tiers)
- Firebase (required: sign-in and push).
- Optional, when you want them: Stripe (card payments), Google Cloud (address search with the Geocoding API), a map tile provider such as MapTiler or Stadia Maps, and any SMTP e-mail provider.
3. Quick start (Docker)
This gets the whole server running on your VPS in about 15 minutes. You need the Firebase keys from chapter 4 for sign-in; you can do this chapter first and add them after.
- Copy the kit to the server, for example with
scp dashly-1.0.0.zip root@YOUR_SERVER_IP:, then on the server:apt install -y unzip unzip dashly-1.0.0.zip cd dashly/deploy cp .env.example .env nano .env - In
.env, fill at least these values:Key What to put POSTGRES_PASSWORDA long random password, letters and digits only (run openssl rand -hex 24to make one).S3_SECRET_ACCESS_KEYAnother random value for the built-in MinIO storage (at least 8 characters). APP_URLYour website address, e.g. https://your-domain.com(orhttp://YOUR_SERVER_IP:3000for a first test).NEXT_PUBLIC_FIREBASE_*,FIREBASE_*From chapter 4. - Start everything:
The first build takes 5–10 minutes. On every start the database tables are created or updated and the base data (Food, Grocery and Pharmacy modules with their categories) is loaded.docker compose up -d --build - Open
APP_URLin your browser. The install wizard opens: create your admin account, name the business, pick the currency and load the sample stores if you want them (chapter 7). - Check it:
APP_URL/api/v1/healthshows{"ok":true,…}. Then put it on your domain with HTTPS (chapter 5).
.env? Run docker compose up -d again. The Firebase web values are read when the page loads, so no rebuild is needed for them.4. Firebase setup
Firebase handles sign-in (Google, Apple, phone, guest, and e-mail for staff) and push notifications. Your data stays in your own database.
- Go to the Firebase console → Add project. Give it your app name.
- Build → Authentication → Get started → Sign-in method. Turn on: Anonymous (guest mode), Google, Phone, Email/Password (vendors and admins) and Apple (see chapter 16).
- Authentication → Settings → Authorized domains: add
your-domain.com. - Project settings (gear) → General → Your apps → Add app → Web (name it "Website"). Copy the values from the
firebaseConfigshown intodeploy/.env:NEXT_PUBLIC_FIREBASE_API_KEY="…apiKey…" NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN="your-project.firebaseapp.com" NEXT_PUBLIC_FIREBASE_PROJECT_ID="your-project" NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET="your-project.firebasestorage.app" NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID="…" NEXT_PUBLIC_FIREBASE_APP_ID="1:…:web:…" - Project settings → Service accounts → Generate new private key. A JSON file downloads. Copy three values from it into
deploy/.env:
Keep theFIREBASE_PROJECT_ID="your-project" FIREBASE_CLIENT_EMAIL="firebase-adminsdk-xxxx@your-project.iam.gserviceaccount.com" FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nMIIE…\n-----END PRIVATE KEY-----\n"\nas they are in the file. Keep this file secret. - Register the two mobile apps: Project settings → Your apps → Add app → Android twice (package names, e.g.
com.yourcompany.dashlyandcom.yourcompany.dashly.rider) and iOS twice with the same ids. Rename the apps first if you want your own ids (chapter 17). Or use the FlutterFire CLI, which registers them for you:dart pub global activate flutterfire_cli cd dashly_customer_flutter flutterfire configure --project your-project --platforms android,ios \ --android-package-name com.yourcompany.dashly --ios-bundle-id com.yourcompany.dashly - Copy each app's values into its
.env(dashly_customer_flutter/.envanddashly_rider_flutter/.env):FIREBASE_PROJECT_ID,FIREBASE_MESSAGING_SENDER_ID,FIREBASE_STORAGE_BUCKET,FIREBASE_ANDROID_API_KEY,FIREBASE_ANDROID_APP_ID,FIREBASE_IOS_API_KEY,FIREBASE_IOS_APP_ID,FIREBASE_IOS_CLIENT_ID,FIREBASE_IOS_BUNDLE_ID. You find them in each app'sgoogle-services.json/GoogleService-Info.plist(download them from Project settings), or on the app's card in Project settings. The apps read these values from.env, so you do not need to put the downloaded files in the project. - Google sign-in on Android needs
GOOGLE_SERVER_CLIENT_IDin each app's.env: Authentication → Sign-in method → Google → Web SDK configuration → Web client ID. - iOS sign-in callbacks: in each app, copy
ios/Flutter/Firebase.xcconfig.exampletoios/Flutter/Firebase.xcconfigand setGOOGLE_REVERSED_CLIENT_ID(theREVERSED_CLIENT_IDinGoogleService-Info.plist) andFIREBASE_ENCODED_APP_ID(your iOS app id with:replaced by-andapp-in front). - Android Google and phone sign-in need your signing key fingerprints: run
cd android && ./gradlew signingReportin each app and add the SHA-1 and SHA-256 under Project settings → Your apps → Android → Add fingerprint. Add the fingerprints of your upload key and of Google Play's app signing key too when you publish.
5. Put it online (VPS, HTTPS)
The quickest way: the stack has a built-in Caddy web server that gets a free HTTPS certificate for your domain.
- Point your domain's DNS A record at the server and wait until it resolves.
- In
deploy/.envsetDOMAIN=your-domain.comandAPP_URL=https://your-domain.com. - Open ports 80 and 443 in the firewall (
ufw allow 80,443/tcp) and start the stack with the https profile:docker compose --profile https up -d - Add
your-domain.comto Firebase → Authentication → Settings → Authorized domains, if you have not yet.
Already run nginx, Traefik or another proxy? Skip the profile and forward your domain to 127.0.0.1:3000 (the WEB_PORT).
Backups
Back up the database every day, for example with a cron job:
docker compose exec -T postgres pg_dump -U dashly dashly | gzip > /root/backups/dashly-$(date +%F).sql.gz
Also back up the minio-data volume (photos and documents), or use a cloud bucket (next chapter).
6. Other hosting
Website on Vercel, database on a managed PostgreSQL
- Create a PostgreSQL database (Neon, Supabase, Railway…) and copy its connection string.
- Import
dashly_web_nextjs/into Vercel. Add every key fromdashly_web_nextjs/.env.exampleas an environment variable, withDATABASE_URLset to your database and the fiveS3_*keys set to a cloud bucket (Vercel has no disk for uploads). - On your computer, create the tables once:
cd dashly_web_nextjs && pnpm install && pnpm prisma:migrate:deploy && pnpm prisma:seed -- --base(withDATABASE_URLin.env). - The background worker does not run on Vercel. Run
pnpm workeron any small server or service that keeps a process running (Railway, Render, a VPS) with the same environment variables. Without the worker, rider offers are not re-sent and late orders are not auto-rejected.
Cloud storage instead of MinIO
Create a private bucket on Amazon S3 or Cloudflare R2 and set S3_ENDPOINT (empty for AWS, your R2 endpoint for R2), S3_REGION, S3_BUCKET, S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY. Files are served through the API, so the bucket stays private.
Without Docker
Install Node.js 22 and pnpm 11, then in dashly_web_nextjs/: cp .env.example .env, fill it in, and run pnpm install, pnpm prisma:migrate:deploy, pnpm prisma:seed -- --base, pnpm build, then keep pnpm start and pnpm worker running (for example with pm2).
7. Install wizard and sample data
The first time you open the website, the install wizard (/install) asks for:
- Your admin account (name, e-mail and password). This is the super admin; you add more staff later in Admin → Staff.
- The business: app name, currency, distance unit, time zone, and what you run — a multi-store marketplace or a single restaurant (chapter 11).
- The look and the sample data: the brand colour, and Load the sample stores and menus for 16 sample stores in three zones (around Austin, Texas — move or redraw them in Admin → Zones) with menus, riders, customers, reviews and three weeks of orders, so every screen has content. Leave it off for a clean start.
The wizard closes for good once a super admin exists. Sign in later at /admin.
8. Setup check
Two ways to see which keys are missing and whether each service answers:
- Admin → Setup: every key from every
.env.example(server, both apps, Docker) marked set or missing, and a live test of the database, storage, Firebase, push, Stripe and e-mail. Values are never shown. - Command line: in
dashly_web_nextjs/runpnpm run doctor(with Docker:docker compose run --rm migrate pnpm run doctor).
9. Run the mobile apps
- In each app folder, copy the settings file and fill it in:
Setcd dashly_customer_flutter cp .env.example .envAPI_BASE_URLto your server (https://your-domain.com; for a server on your computer usehttp://10.0.2.2:3000in the Android emulator) and the Firebase values from chapter 4. - Get the packages and run:
flutter pub get flutter run - Do the same in
dashly_rider_flutter/.
Until .env has the server and Firebase values, the apps open a "Finish the setup" screen that names what is missing.
Build for release
flutter build appbundle # Android, for Google Play
flutter build ipa # iOS, for the App Store (on a Mac)
10. Run your business
| Task | Where |
|---|---|
| Draw delivery zones and set the base fee | Admin → Zones. Customers outside every zone see "We don't deliver here yet". |
| Add stores | Admin → Stores, or let owners apply at /become-a-vendor and approve them in Admin → Stores. |
| Menus, stock, opening hours, promotions | The store owner signs in at /vendor. |
| Accept and prepare orders | Vendor → Orders (a live board that refreshes every few seconds). |
| Approve riders | Riders apply in the rider app; review documents and approve in Admin → Riders. |
| Dispatch | Automatic: when a store accepts, the nearest online riders in the zone get an offer (offer time and riders per offer: Admin → Settings → Delivery → Riders). To do it by hand, open the order in Admin → Orders and assign a rider. |
| Commission and fees | Admin → Commissions (per module or per store) and Admin → Settings → Delivery (customer fees and rider pay). |
| Pay stores and riders | Admin → Payouts lists what each store and rider has earned. Pay them by bank transfer, then mark the payout paid. Cash a rider hands in is recorded in Admin → Riders → the rider → Record a cash deposit. |
| Coupons, banners, push campaigns | Admin → Coupons, Banners, Campaigns. |
| Support | Customers chat from the app's Help screen; answer in Admin → Tickets. |
11. Single-restaurant mode
Sell the same apps to one restaurant brand: Admin → Settings → General → Mode → Single restaurant, and set Single-restaurant store slug to that store's slug (shown in Admin → Stores). The customer app then opens straight on that restaurant's menu, with a Dine-in tab and Reservations.
- Dine-in: the restaurant adds tables in Vendor → Tables and prints each table's QR code. Guests scan it with the app (or the phone camera, which opens
/t/…on the website), order in rounds, call staff, and pay the bill in full or split it, with a tip. - Reservations: guests book a time and party size; full slots offer the waitlist. The restaurant confirms in Vendor → Reservations.
12. Payments (Stripe)
Card payments use Stripe Checkout (cards, Apple Pay, Google Pay) for orders, wallet top-ups and dine-in bills, in the apps and on the website. Cash on delivery and wallet always work.
- In the Stripe dashboard copy the Secret key into
STRIPE_SECRET_KEY. - Developers → Webhooks → Add endpoint: URL
https://your-domain.com/api/v1/webhooks/stripe, eventscheckout.session.completedandcheckout.session.async_payment_succeeded. Copy the signing secret intoSTRIPE_WEBHOOK_SECRET. - Restart:
docker compose up -d. The checkout now shows Card instead of Card (demo).
13. Push notifications
Order updates, rider offers, chat messages and admin campaigns are sent with Firebase Cloud Messaging through your FIREBASE_* service account — nothing else to set for Android. For iOS, upload an APNs key: Apple Developer → Keys → + (Apple Push Notifications service), then Firebase → Project settings → Cloud Messaging → Apple app configuration → Upload. In Xcode, add the Push Notifications and Background Modes → Remote notifications capabilities to both apps.
Every user also has an in-app notification inbox, and can switch message and promotion notifications off in the app's settings.
14. Maps and addresses
- Map tiles: OpenStreetMap's public tiles by default — fine to start, but their usage policy asks heavy users to use a provider. Set
MAP_TILE_URL(e.g. MapTiler:https://api.maptiler.com/maps/streets-v2/{z}/{x}/{y}.png?key=YOUR_KEY) andMAP_ATTRIBUTION. Both apps and the website read it from the server. - Address search: with
GOOGLE_MAPS_API_KEY(Geocoding API enabled) the server uses Google; without it, OpenStreetMap's Nominatim (rate-limited, results cached). - Distances and ETAs are worked out from the store's prep time and the straight-line distance with a road factor, so no paid routing API is needed. The rider's Navigate button opens Google Maps or Apple Maps.
15. Email
Set SMTP_HOST, SMTP_PORT, SMTP_USER and SMTP_PASS (any provider: Amazon SES, Brevo, Mailgun, Postmark, your host's mail server). Use port 587, or 465 with SMTP_SECURE=true. The sender address and name are in Admin → Settings → Email. Until SMTP is set, e-mails are printed to the web container's log (docker compose logs web).
16. Sign-in methods
| Who | Methods |
|---|---|
| Customer app | Google, Apple (iOS), phone number (SMS code), and guest (browse and order; link a phone later). |
| Rider app | Phone number and Google. |
| Website | Google, phone number, e-mail and password, and guest. |
| Vendor panel and admin | E-mail and password. |
Apple sign-in (required by Apple when an iOS app offers Google sign-in): in the Apple Developer account, enable Sign in with Apple for the customer app's id, add the capability in Xcode, and turn on Apple in Firebase Authentication.
17. Rename the app and package id
From the folder that holds the apps (requires Node.js):
node tools/rename.mjs --id com.yourcompany.foodly --name "Foodly"
This sets the app names (under the icon on Android and iOS) and the Android package / iOS bundle ids of both apps (the rider app gets com.yourcompany.foodly.rider and "Foodly Rider"; change them with --rider-id and --rider-name). It also moves Android's MainActivity to the new package. Then register the new ids in Firebase (chapter 4).
The name customers see inside the apps and on the website comes from Admin → Settings → General → App name, so you can change it any time without a new build.
18. Logo, icon, colours and fonts
- Logo and colours: Admin → Settings → General → Logo, Primary colour and Accent colour. The website changes at once; the apps load them when they start.
- App icon: replace the icons with your own 1024×1024 PNG. The easiest way is flutter_launcher_icons: add it under
dev_dependencies, addflutter_launcher_icons: {android: true, ios: true, image_path: "assets/icon.png"}topubspec.yaml, and rundart run flutter_launcher_iconsin each app. The in-app logo isassets/images/logo.png. - Website icon: replace
dashly_web_nextjs/src/app/icon.pngandapple-icon.png. - Colours in code (for a deeper rebrand): the apps' colour tokens are in
dashly_flutter_core/lib/src/theme/tokens.dart, the website's indashly_web_nextjs/src/app/globals.css. - Font: the apps use Nunito from
dashly_flutter_core/assets/fonts/(declared in itspubspec.yaml); the website loads Nunito insrc/app/layout.tsxwithnext/font/google. Swap the files and the family name to change it.
19. Basic edits
| I want to change… | Where |
|---|---|
| Terms and privacy policy | Admin → Settings → General → Legal pages (shown in both apps and on the website). |
| Currency, taxes, service and small-order fees | Admin → Settings → General, Taxes and Delivery; per module in Admin → Modules. |
| Home banners and featured stores | Admin → Banners; Admin → Stores → a store → Featured. |
| Which modules show (Food / Grocery / Pharmacy) | Admin → Modules. |
| Help centre questions | Admin → Settings → General → Help centre FAQ. |
| Support phone and e-mail | Admin → Settings → General. |
| Force an app update, maintenance message | Admin → Settings → General → Apps (minimum app version, update prompt, maintenance mode). |
| Texts inside the apps | The screens are in lib/pages/ of each app; search for the text and edit it. |
| App download links on the website | Admin → Settings → General → Apps (App Store, Google Play and APK links). |
20. Every key, explained
Each folder has a .env.example that lists its keys with comments. This table lists all of them: web = dashly_web_nextjs/.env, Docker = deploy/.env, customer / rider = each app's .env.
| Key | Required | Where | What it does |
|---|---|---|---|
| Database | |||
DATABASE_URL | Yes | web | PostgreSQL connection string |
POSTGRES_DB | Yes | Docker | Database the Docker stack creates |
POSTGRES_USER | Yes | Docker | Database user for the Docker stack |
POSTGRES_PASSWORD | Yes | Docker | Database password for the Docker stack |
| App | |||
APP_URL | Yes | web, Docker | Public URL of the website/API, e.g. https://dashly.example.com |
NEXT_PUBLIC_APP_NAME | — | web, Docker | Name shown before the admin sets one |
WEB_PORT | — | Docker | Host port the web container listens on |
DOMAIN | — | Docker | Your domain for automatic HTTPS (docker compose --profile https) |
CORS_ORIGINS | — | web, Docker | Browser apps allowed to call /api/v1 (Flutter web builds) |
DEMO_MODE | — | web, Docker | "true" only for a public demo: one-click demo sign-in |
| Firebase sign-in | |||
NEXT_PUBLIC_FIREBASE_API_KEY | Yes | web, Docker | Firebase web app config |
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN | Yes | web, Docker | Firebase web app config |
NEXT_PUBLIC_FIREBASE_PROJECT_ID | Yes | web, Docker | Firebase web app config |
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET | — | web, Docker | Firebase web app config |
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID | — | web, Docker | Firebase web app config |
NEXT_PUBLIC_FIREBASE_APP_ID | Yes | web, Docker | Firebase web app config |
| Firebase Admin + push | |||
FIREBASE_PROJECT_ID | Yes | web, Docker | Firebase project id |
FIREBASE_CLIENT_EMAIL | Yes | web, Docker | Service account e-mail |
FIREBASE_PRIVATE_KEY | Yes | web, Docker | Service account private key |
| Payments | |||
STRIPE_SECRET_KEY | — | web, Docker | Stripe secret key — card payments (empty = demo card) |
STRIPE_WEBHOOK_SECRET | — | web, Docker | Stripe webhook signing secret (/api/v1/webhooks/stripe) |
DEMO_PAYMENTS | — | web, Docker | "false" hides the demo card when Stripe is not set |
| Maps | |||
GOOGLE_MAPS_API_KEY | — | web, Docker | Google Geocoding for address search (empty = OpenStreetMap) |
MAP_TILE_URL | — | web, Docker | Map tiles URL template (MapTiler, Stadia, Mapbox…) |
MAP_ATTRIBUTION | — | web, Docker | Credit line your tile provider requires |
| Storage | |||
S3_ENDPOINT | — | web, Docker | S3-compatible endpoint (MinIO, R2); empty for AWS |
S3_REGION | — | web, Docker | Bucket region |
S3_BUCKET | — | web, Docker | Bucket for photos and documents (empty = local uploads folder) |
S3_ACCESS_KEY_ID | — | web, Docker | Storage access key |
S3_SECRET_ACCESS_KEY | — | web, Docker | Storage secret key |
SMTP_HOST | — | web, Docker | SMTP server; empty = emails are printed to the log |
SMTP_PORT | — | web, Docker | 587 (STARTTLS) or 465 (TLS) |
SMTP_USER | — | web, Docker | SMTP user |
SMTP_PASS | — | web, Docker | SMTP password |
SMTP_SECURE | — | web, Docker | "true" for port 465 (TLS); empty for 587 |
| Storage | |||
UPLOAD_DIR | — | web | Folder for uploads when no bucket is set (default ./uploads) |
| Mobile apps | |||
API_BASE_URL | Yes | customer, rider | Your server; the app calls <url>/api/v1 |
APP_NAME | — | customer, rider | App name in the UI |
FIREBASE_PROJECT_ID | Yes | customer, rider | Firebase project id |
FIREBASE_MESSAGING_SENDER_ID | Yes | customer, rider | Firebase config |
FIREBASE_STORAGE_BUCKET | — | customer, rider | Firebase config |
FIREBASE_ANDROID_API_KEY | Yes | customer, rider | Firebase Android config |
FIREBASE_ANDROID_APP_ID | Yes | customer, rider | Firebase Android config |
FIREBASE_IOS_API_KEY | Yes | customer, rider | Firebase iOS config |
FIREBASE_IOS_APP_ID | Yes | customer, rider | Firebase iOS config |
FIREBASE_IOS_CLIENT_ID | — | customer, rider | Google sign-in on iOS |
FIREBASE_IOS_BUNDLE_ID | — | customer, rider | iOS bundle id |
GOOGLE_SERVER_CLIENT_ID | — | customer, rider | Web OAuth client id — Google sign-in on Android |
SUPPORT_EMAIL | — | customer, rider | Fallback support e-mail until the server config loads |
DEMO_SIGN_IN | — | customer, rider | "true" shows one-tap demo sign-in (your public demo only) |
21. File structure
Mobile apps
dashly_customer_flutter/lib/
main.dart starts Firebase and the app
app.dart theme, push handling
router.dart every screen's route and the start-up gates (setup → onboarding → sign-in)
pages/ one folder per area: launch, home, search, store, cart, orders, wallet, profile,
address, single (dine-in, reservations)
providers/ app state with Riverpod (bag, place, data loaded from the API)
widgets/, utils/ app-only widgets and helpers
dashly_rider_flutter/lib/
pages/ launch (sign-in, apply), home (online, offers), job (pick-up, hand-over),
deliveries, earnings, profile
providers/ rider state (profile, current job, earnings)
dashly_flutter_core/lib/src/
api/ the HTTP client for /api/v1
auth/ Firebase sign-in (Google, Apple, phone, guest)
config/ .env reading and Firebase options
models/ Store, MenuItem, Order, Config…
theme/ colour tokens, text styles, light and dark themes
widgets/ buttons, cards, map, sheets, states, chat
pages/ screens both apps share (splash, setup needed, offline, phone sign-in, legal)
Long lists use lazy .builder lists; state flows through Riverpod providers.
Server (dashly_web_nextjs/src)
app/(site)/ the public website (home, stores, store page, checkout, tracking, account)
app/vendor/ the vendor panel
app/admin/ the admin panel
app/install/ the first-run wizard
app/api/v1/ one route that hands every API call to lib/server/router.ts
lib/server/ business logic: handlers/ (the API routes), orders, pricing, dispatch, wallet,
payments (Stripe), notify (push), email, storage, geo, settings, setup-check
lib/client/ browser code: Firebase sign-in, API calls
components/ site/, panel/ (vendor and admin), ui/ (shared building blocks)
database/ Prisma schema, migrations, seed (sample data)
worker/ background jobs (pnpm worker)
scripts/doctor.ts pnpm run doctor
tests/ API tests (pnpm test)
Outside src, e2e/ holds browser smoke tests for a running server:
E2E_BASE_URL=https://your-domain.com pnpm e2e (run pnpm exec playwright install chromium once first).
22. Publish to the stores
You publish both apps under your own developer accounts. Rename them first (chapter 17).
Google Play
- Create an upload key:
keytool -genkey -v -keystore ~/upload-keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias upload. - Create
android/key.propertieswithstorePassword,keyPassword,keyAlias=uploadandstoreFile, and add asigningConfigs.releaseblock toandroid/app/build.gradle.ktsas in Flutter's guide. - Set the version in
pubspec.yaml(version: 1.0.0+1), runflutter build appbundle, and uploadbuild/app/outputs/bundle/release/app-release.aabin the Play Console. - Fill the store listing, the data safety form (the apps collect name, phone, e-mail, location while ordering or delivering, and photos the user uploads) and the content rating. The rider app shares location only while online and in the foreground — say so in the listing.
- Add the Play app signing key's SHA-1 and SHA-256 to Firebase (chapter 4).
App Store
- In Apple Developer, create the app ids with Push Notifications (and Sign in with Apple for the customer app).
- Open
ios/Runner.xcworkspacein Xcode, choose your team under Signing & Capabilities, add the capabilities from chapters 13 and 16. flutter build ipa, then upload with Xcode's Organizer or Transporter, and submit in App Store Connect. Give the reviewer a test phone number and code (chapter 4 tip) and a sample store.
23. Updating
When a new version comes out, read its changelog, back up your database, and copy the new files over your copy (keep your .env files and your edits — a tool like git makes merging easy). Then docker compose up -d --build; migrations run on start. For the apps, run flutter pub get and build again.
24. FAQ and troubleshooting
The app shows "Finish the setup"
Its .env is missing API_BASE_URL or Firebase values. Fill them in and restart the app (a hot reload does not re-read .env).
The app shows "Can't reach the server"
Open API_BASE_URL/api/v1/health in the phone's browser. On the Android emulator, localhost is the emulator itself — use http://10.0.2.2:3000. Android blocks plain http:// to other hosts; use HTTPS.
Sign-in fails on the website
Add your domain under Firebase → Authentication → Settings → Authorized domains, and check the NEXT_PUBLIC_FIREBASE_* values in Admin → Setup.
Google sign-in fails on Android
Add the SHA-1 and SHA-256 of the key that signed the build (debug, upload and Play signing) to the Android app in Firebase, and set GOOGLE_SERVER_CLIENT_ID.
"We don't deliver here yet" for every address
The address is outside every active zone. Draw a zone around your town in Admin → Zones.
A store shows "Closed"
Its opening hours (Vendor → Settings) don't include the current time. Customers can still schedule an order for later.
Riders get no offers
The rider must be approved, online, inside the order's zone and under the cash limit, and the worker must be running (docker compose ps shows worker up).
Payments say "demo"
STRIPE_SECRET_KEY is empty. See chapter 12. Set DEMO_PAYMENTS=false to hide the demo card.
Photos do not upload
Run the setup check: the storage test shows the problem (wrong S3_* keys, or the bucket does not exist).
I changed .env and nothing happened
Server: docker compose up -d recreates the containers with the new values. Apps: stop and start the app again.
Where are the logs?
docker compose logs -f web and docker compose logs -f worker.
25. Credits
Dashly is built on these open-source projects, each under its own licence:
Mobile apps
Flutter, flutter_riverpod, go_router, firebase_core, firebase_auth, firebase_messaging, google_sign_in, sign_in_with_apple, flutter_map, geolocator, mobile_scanner, image_picker, http, flutter_dotenv, shared_preferences, share_plus, url_launcher, package_info_plus, intl, lucide_icons_flutter.
Website, panels and API
Next.js, React, Prisma, PostgreSQL, Tailwind CSS, shadcn/ui, Base UI, Lucide, Leaflet and React Leaflet, Firebase JS SDK and Firebase Admin, Stripe Node, AWS SDK for JavaScript, Nodemailer, Zod, Sonner, node-qrcode, MinIO, Caddy.
Fonts, maps and content
Nunito (SIL Open Font License 1.1, licence file in dashly_flutter_core/assets/fonts/OFL.txt). Map data © OpenStreetMap contributors. The sample stores, menus, people and texts are original and fictional.
26. Changelog
1.0.0 — 2026-10-06
First release. The full list is in CHANGELOG.md.
27. Support
Use the Support tab on the item's CodeCanyon page. Please include your purchase code, what you did, what you expected and what happened, and the output of pnpm run doctor or a screenshot of Admin → Setup.
Support covers questions about the item's features and fixing bugs in the item. Custom changes and installation on your server are not part of item support.