Feastly documentation
Run it, point it at your own accounts, ship it under your own brand. Written against version 1.1.0 of the download you get on CodeCanyon.
- Version v1.1.0
- Updated Aug 2026
- Platform Flutter
- Stack Flutter · Next.js
On this page
Introduction
A resellable, multi-restaurant on-demand food-delivery platform shipped as complete source code: three Flutter apps (Customer, Rider, Restaurant) and a Next.js admin panel on one shared backend, monetized by per-order commission, with an optional AI search & recommendations layer.
Feastly is a complete, self-hostable food-delivery platform you can rebrand and launch in your own market — an UberEats / Grab / Foodpanda-style operation. It is a four-surface bundle on a single shared backend:
apps/customerapps/riderapps/restaurantapps/webThe wedge. The food-delivery category is saturated with PHP/Laravel incumbents. Feastly competes on a full 4-app ecosystem, a modern Next.js + Flutter stack, an optional dual-provider AI layer, and zero paid-service lock-in for the buyer: the realtime layer is self-hosted, transactional email is plain SMTP, and AI is fully optional.
Portable. The backend is one Next.js app you can deploy to Vercel or any Node host / VPS / container. The database is standard PostgreSQL (the docs use Neon’s free serverless tier; any Postgres works). No secret ever ships inside a mobile binary.
What’s included
- Full source for all four surfaces — three Flutter apps + the Next.js admin/API/AI/realtime backend.
- Shared Drizzle schema (24 tables) with 3 generated SQL migrations, runnable on a fresh Postgres.
- A shared Dart package (
packages/shared-dart) — theme, typed API client, and models used by all three apps. - An idempotent demo seed: 12 restaurants, 71 menu items, 11 users, 11 orders (5 completed + 6 in-flight), commission & payout ledgers, ratings, cuisines, banners and a delivery zone — so no surface ever loads empty.
- The design system (design tokens, Inter typography, the “AI Spark” gradient marker) mirrored across web and mobile.
- A branding pipeline: brand SVG/PNG masters + one-command launcher-icon regeneration.
- Optional, gracefully-degrading integrations: Stripe, Cloudflare R2, SMTP, Google Maps, OpenAI/Gemini.
- This documentation, a quick-start guide, per-surface widget tests, and a dependency license audit.
- 6 months of item support (see Support and licensing).
Features
Customer app
- Email/password onboarding (JWT-backed mobile sessions); manual address book with delivery-zone gating.
- Discovery: restaurant list, categories, cuisines, filters & sort, and AI-assisted search (degrades to full-text).
- Restaurant page with categorised menu, item detail, modifiers/add-ons, and veg/allergen tags.
- Cart & checkout: modifiers, order notes, tip, delivery vs pickup, address & payment-method select.
- Payments: Cash on Delivery (first-class) and card via Stripe (webhook-confirmed).
- Live order tracking: status timeline + rider location over the self-hosted realtime stream.
- Post-order: rate restaurant + rider, reorder, order history.
- Recommendations: “Recommended for you” / “Popular near you” (degrades to popularity).
Rider app
- Rider auth & profile; online/offline availability toggle.
- Order intake: auto-assign or accept-from-pool (admin-configurable mode).
- Full status flow: accepted → arrived → picked up → en route → delivered, with a location ping.
- Earnings: per-order breakdown plus daily/weekly totals and payout status.
Restaurant app
- Restaurant auth & profile (hours, min order).
- Menu management: categories, items, modifiers, availability toggles, images.
- Order board: incoming → accept/reject → preparing → ready-for-pickup.
- Earnings dashboard: gross, commission deducted, net owed.
- Basic analytics: orders, revenue, top items.
Admin panel (Next.js)
- Dashboard: GMV, orders, commission earned, top items.
- Restaurant management: onboard/approve, per-restaurant or global commission %, suspend.
- Rider management: onboard/approve, assignment mode, suspend.
- Order oversight: search, live status, manual reassign, refund/cancel.
- Commission engine: global default + per-restaurant override, automatic per-order calculation into an immutable ledger.
- Payout ledger: per-restaurant and per-rider amounts owed, with mark-as-paid (manual settlement in V1).
- Content: banners, categories, cuisines, delivery zones (geofence editor) at
/admin/content. - Configuration: payment / AI / maps / SMTP / push keys — all stored server-side.
- Users, roles, disputes, and ratings moderation.
Backend API (all surfaces run against it)
42 route handlers under apps/web/src/app/api, including /api/mobile/* (bootstrap config, JWT auth, addresses, zone-check), /api/restaurants, /api/search, /api/recommendations, /api/orders, /api/payments/webhook/[gateway], /api/uploads/presign, /api/realtime/* (SSE), and /api/inngest (background jobs).
Architecture
How the four surfaces connect
The three Flutter apps and the browser-based admin UI all talk to one shared Next.js backend (apps/web). Mobile apps use HTTPS REST with a JWT bearer and receive live updates over SSE; the admin UI is served by the same app using session cookies. Every third-party key stays server-side.
Flows. Mobile → backend over HTTPS REST (JWT bearer); backend → mobile live updates over SSE (order status + rider location); the admin browser ↔ backend over session cookies; backend ↔ PostgreSQL via Drizzle ORM. Payment confirmation arrives as a signature-verified webhook before an order advances past payment. All optional third-party keys are read server-side and never ship inside a mobile binary.
Monorepo layout
feastly/
├── apps/
│ ├── web/ # Next.js — admin UI + API + AI + realtime (SSE) backend
│ ├── customer/ # Flutter
│ ├── rider/ # Flutter
│ └── restaurant/ # Flutter
├── packages/
│ ├── config/ # shared TS config + Tailwind design-token preset
│ ├── db/ # Drizzle schema + migrations (PostgreSQL / Neon)
│ └── shared-dart/ # shared Dart: theme, API client, models
├── brand/ # logo SVG/PNG masters → launcher icons
├── scripts/ # set-maps-key.sh
└── .env.example # every environment variable, groupedThe JS side (apps/web, packages/config, packages/db) is a Turborepo + pnpm workspace. The three Flutter apps and packages/shared-dart are a Dart pub workspace (Melos optional).
Tech stack
jose), bcrypt password hashingDesign invariants
- All third-party keys are server-side only and admin-configurable — no secret ships in an APK/IPA.
- AI is fully optional: the app is 100% functional with AI disabled. Model IDs are env / admin-config toggled, never hardcoded.
- Money math is fail-safe (reserve → commit → refund); an order only advances past payment after a webhook-confirmed charge. COD is first-class.
- The order lifecycle is a canonical state machine; every transition emits a notification + a background event; the commission ledger entry written on completion is immutable.
- Graceful empty/offline states everywhere; single-currency config and an i18n scaffold in V1.
Prerequisites
.nvmrc / enginescorepack enableNo paid third-party service is required. Payments, AI, Maps, Push and SMTP are all optional (Optional integrations).
Local run
# 1. install JS dependencies (repo root)
pnpm install
# 2. configure environment — copy the template and set the two required values
cp .env.example .env
# DATABASE_URL = your Postgres/Neon connection string
# AUTH_SECRET = "$(openssl rand -base64 32)"
# 3. create the schema + demo data
pnpm db:migrate
pnpm db:seed
# 4. run the backend + admin panel → http://localhost:3000
pnpm --filter @feastly/web dev
# 5. resolve the Dart workspace, then run each app
flutter pub get
cd apps/customer && flutter run
cd apps/rider && flutter run
cd apps/restaurant && flutter runhttps://feastly.devsnack.dev (ApiConfig.productionBaseUrl in packages/shared-dart/lib/src/api/api_config.dart). Point them anywhere else without editing code: flutter run --dart-define=FEASTLY_API_URL=http://10.0.2.2:3000 (the Android emulator’s route to your host’s localhost).Deployment
Deploy the backend — Vercel
- 01Push the repo to GitHub, then New Project → Import in Vercel.
- 02Set Root Directory to
apps/web(the one critical setting). Framework (Next.js), build (next build) and install (pnpm install) auto-detect. Node 24.x is pinned viaengines. - 03Add environment variables (Production): required
DATABASE_URL,AUTH_SECRET,AUTH_TRUST_HOST=true,AUTH_URL=https://your-domain; plus any optional keys you use (Optional integrations). - 04Add your custom domain and keep
AUTH_URLin sync with it. - 05
Run migrations once from your machine against the production DB (Vercel does not run them):
terminalDATABASE_URL="<prod-url>" pnpm db:migrate
Deploy the backend — any Node host / VPS / container
pnpm install
pnpm --filter @feastly/web build
DATABASE_URL="…" AUTH_SECRET="…" pnpm --filter @feastly/web start # serves on :3000Set DATABASE_URL and AUTH_SECRET in the host’s environment. Run pnpm db:migrate once against the production database.
apps/web/src/lib/realtime.ts) is an in-process pub/sub — perfect on a single Node instance, but on Vercel’s serverless functions live tracking won’t propagate across instances (the tracking screen still shows status from its initial fetch and on refresh). For true realtime at scale, back publish()/subscribe() with Redis pub/sub behind the same interface, or run apps/web on a persistent Node host.Build the mobile apps
flutter build apk --dart-define=FEASTLY_API_URL=https://your-domain # Android
flutter build appbundle --dart-define=FEASTLY_API_URL=https://your-domain
flutter build ios --dart-define=FEASTLY_API_URL=https://your-domain # iOS (from macOS)Database setup
- 01
Provision Postgres
Create a free project at neon.tech (or use any Postgres) and copy the connection string. - 02
Set env
Put it in.envasDATABASE_URL(a single root.envfeeds both the web app and the DB tooling). - 03
Migrate
pnpm db:migrateapplies the 3 committed SQL migrations. (Changed the schema?pnpm db:generateregenerates SQL first.) - 04
Seed
pnpm db:seedloads the demo data. The seed is idempotent — ifcustomer@feastly.devalready exists it logs and exits cleanly, so it is safe to re-run and will not duplicate.
The schema is split into auth, catalog, orders, ledger and system groups under packages/db/src/schema:
Backups and exports
Your whole platform — restaurants, menus, orders, ledgers, users — lives in one PostgreSQL database (whatever DATABASE_URL points at). You don’t need to know PostgreSQL internals to keep it safe: a backup is a single command. The two tools below, pg_dump and pg_restore/psql, ship with PostgreSQL — install them with brew install libpq on macOS or apt install postgresql-client on Linux.
Back up the entire database to one file
pg_dump writes a complete, restorable snapshot. The compressed custom format is the safest default:
# everything → one compressed file
pg_dump "$DATABASE_URL" --format=custom --file=feastly-backup.dump
# …or a plain-text .sql file you can open and read
pg_dump "$DATABASE_URL" --format=plain --file=feastly-backup.sqlKeep that file somewhere safe (your computer, an R2/S3 bucket, a private repo). That one file is your full backup.
Restore into a fresh database
Point at an empty database ($TARGET_DATABASE_URL) and load the dump back in:
# from a custom-format .dump
pg_restore --clean --if-exists --no-owner --dbname "$TARGET_DATABASE_URL" feastly-backup.dump
# from a plain .sql file
psql "$TARGET_DATABASE_URL" --file=feastly-backup.sqlExport a single table to CSV (e.g. for a spreadsheet)
psql "$DATABASE_URL" --command "\copy (SELECT * FROM orders) TO 'orders.csv' WITH CSV HEADER"pg_dump command above still works on all of them and is the portable way to move data between providers.pnpm db:migrate && pnpm db:seed. For production, schedule the pg_dump command as a nightly cron job or GitHub Action and retain the last several dumps.Optional integrations
Every integration is optional and gracefully no-ops when its keys are blank — the app stays fully functional. Keys live in .env or, at runtime, in the admin Configuration page; the code resolves env first, then the admin store.
paymentsEnabled=false in the mobile bootstrap config.presignUpload() returns null → the presign route replies 503; uploads are disabled. Existing/seeded imagery is unaffected.null → search degrades to full-text, recommendations to popularity/proximity.sendEmail() is a clean no-op (logs a debug line, sends nothing).REDIS_URL = in-process pub/sub (correct for dev and any single instance); set it to fan events across instances (Payments, media, push and realtime).Environment reference
Every variable the platform reads, in one place. These tables mirror .env.example line-for-line — that file is the canonical template, so cp .env.example .env always gives you a complete, current file. The same key list also drives the admin Configuration page (apps/web/src/lib/config.ts), so template, docs and admin UI stay in step; when a release adds a variable it lands in all three.
Only the first two are required. Every other row can stay blank — the “If left blank” column tells you exactly what happens.
Required
openssl rand -base64 32Core URLs (set these in production)
true when running behind a proxy or on Verceltrue on Vercel.Payments and currency
USDUSD. Served to mobile via /api/mobile/config.Media — Cloudflare R2
503). Existing and seeded imagery is unaffected.Push — Firebase Cloud Messaging
AI (fully optional)
openai, gemini, or blank to disableAI_PROVIDER above.AI_PROVIDER above.AI_PROVIDER above.AI_PROVIDER above.Transactional email — SMTP
sendEmail() is a clean no-op: it logs and sends nothing. Nothing else breaks.Maps
Realtime at scale
Set at build time, not in .env
Two values belong to the mobile build rather than the server environment:
flutter build … --dart-define=FEASTLY_API_URL=https://your-domainhttps://feastly.devsnack.dev — change this to your own backend before you ship.android/local.properties + ios/Flutter/Maps.xcconfig, or scripts/set-maps-key.sh/api/mobile/config at runtime.AI setup
The AI layer (apps/web/src/lib/ai/provider.ts) is a server-side provider abstraction over OpenAI and Gemini embeddings. It powers two surfaces, both badged with the “AI Spark” marker:
- AI-assisted search (
/api/search) — embedding-based semantic ranking with a keyword fallback. - Recommendations (
/api/recommendations) — “Recommended for you” / “Popular near you” from order history, falling back to popularity/proximity on cold start.
Enable it
AI_PROVIDER="openai" # or "gemini", or "" to disable
OPENAI_API_KEY="sk-…"
OPENAI_EMBEDDING_MODEL="text-embedding-3-small"
# Gemini instead:
# AI_PROVIDER="gemini"
# GEMINI_API_KEY="…"
# GEMINI_EMBEDDING_MODEL="…"- Server-only. Keys are read from env or the admin config store and are never returned to a client or embedded in a mobile binary.
- Never hardcoded. Model IDs are configuration values, so you can swap models without a code change.
- Fully optional. Leave
AI_PROVIDERblank and every AI surface degrades gracefully — the app is 100% functional with AI off.
/api/search and /api/recommendations to cap embedding spend. This is a documented hardening step, not a default.Payments, media, push and realtime
Payments
A CardGateway interface (apps/web/src/lib/payments.ts) has two shipping implementations: a manual gateway (the default; COD is first-class, and card flows confirm via a local webhook) and a Stripe gateway (payment intents, refunds, and signature-verified webhooks). The order state machine only advances past payment on a webhook-confirmed charge. Webhooks land at /api/payments/webhook/[gateway]. The mobile apps fetch only publishable config from /api/mobile/config — the Stripe secret and webhook secret are never sent to a client.
Media (Cloudflare R2)
Image uploads use short-lived presigned PUT URLs minted at /api/uploads/presign — the client uploads directly to R2 and only ever receives the presigned URL plus the resulting public URL. With R2 unconfigured, presigning returns null and uploads are simply disabled; existing/seeded imagery is unaffected. Set the five R2_* vars to enable.
Push notifications
Order transitions write a notifications row and publish a realtime event, so every surface reflects status changes in-app immediately. Native FCM delivery is scaffolded (env slots present) but not yet wired server-side — enabling true push is a documented roadmap step, not required for the flow to work.
Background jobs (Inngest)
Inngest handles order-state jobs, notification fan-out and ledger events at /api/inngest. It runs against Inngest’s dev server locally and their cloud (or a self-hosted relay) in production.
Realtime
The self-hosted SSE layer streams order status and rider location to the customer tracking screen and the restaurant/rider/admin boards (/api/realtime/*). Out of the box it is an in-process pub/sub (apps/web/src/lib/realtime.ts) — perfect for dev and a single Node instance. On a horizontally-scaled or serverless deploy, an event published on one instance won’t reach SSE clients connected to another.
Scaling out: a Redis pub/sub adapter
Back the bus with Redis and every instance shares one channel — the publish() / subscribe() surface the rest of the app calls stays identical, so no route, job or component changes. Install the client and point REDIS_URL at any Redis (Upstash, Redis Cloud, or your own):
pnpm --filter @feastly/web add ioredis
# .env → REDIS_URL=redis://default:••••@your-host:6379Then swap the body of apps/web/src/lib/realtime.ts for this drop-in replacement — same exports, Redis-backed fan-out:
import 'server-only';
import Redis from 'ioredis';
export type RealtimeEvent = { type: 'status' | 'location' | 'hello' | 'order' | 'pool'; data: unknown };
type Listener = (event: RealtimeEvent) => void;
export const CHANNELS = {
restaurant: (id: string) => `restaurant:${id}`,
adminOrders: 'admin:orders',
riderPool: 'rider:pool',
} as const;
const BUS = 'feastly:realtime'; // one shared Redis channel
const local = new Map<string, Set<Listener>>(); // channel → this node's SSE listeners
// A connection in subscribe mode can't run other commands, so publishing needs its own.
const pub = new Redis(process.env.REDIS_URL!);
const sub = new Redis(process.env.REDIS_URL!);
// Fan every bus message out to the listeners living on THIS node.
sub.subscribe(BUS);
sub.on('message', (_channel, payload) => {
const { channel, event } = JSON.parse(payload) as { channel: string; event: RealtimeEvent };
const set = local.get(channel);
if (!set) return;
for (const listener of set) {
try { listener(event); } catch { /* a dead listener must not break the fan-out */ }
}
});
export function subscribe(channel: string, listener: Listener): () => void {
let set = local.get(channel);
if (!set) local.set(channel, (set = new Set()));
set.add(listener);
return () => {
set!.delete(listener);
if (set!.size === 0) local.delete(channel);
};
}
export function publish(channel: string, event: RealtimeEvent): void {
// Publish to Redis only; the message loops back through sub.on('message')
// above, so this node's listeners fire exactly once — same path as every other node.
pub.publish(BUS, JSON.stringify({ channel, event }));
}Why publish only to Redis? Every node — including the one that called publish() — receives the event back through the shared subscriber, so each listener fires exactly once with no special-casing of the origin. For a managed option, Upstash exposes a Redis-compatible endpoint that works from serverless functions.
Rebrand step by step
Five edits turn Feastly into your brand — roughly half an hour, no screen-by-screen work. Each step names the exact file to change, and the paths below are copy-paste ready. Steps 1–4 are the visual identity; step 5 points the apps at your backend and legal pages. Finish with the verification checklist to confirm nothing was missed.
- 01
Swap the logo and app icon
Replace the two 1024×1024 master PNGs with your own square icon art — keep the same file names and dimensions and everything downstream regenerates from them:
brand/icon-1024.png,brand/foreground-1024.png.Optional vector sources:
brand/feastly-icon.svg,brand/feastly-mark.svg. Web favicon & admin browser icon:apps/web/src/app/favicon.ico,apps/web/src/app/icon.svg,apps/web/src/app/apple-icon.png. - 02
Update the color scheme
The palette is defined once per platform from a single primary color — change it and every screen, button and badge follows. Current tokens:
TokenValueprimary#ae3200primary-container#ff5a1ftertiary#006d37error#ba1a1asurface#f8f9faWeb / admin:
packages/config/tailwind-preset.ts(the shared Tailwind design-token preset). Mobile (all three apps at once):packages/shared-dart/lib/src/theme/app_colors.dartandpackages/shared-dart/lib/src/theme/app_theme.dart(the shared Material 3 seed color). Edit the primary in these places and the whole system re-themes. - 03
Rename the apps and set your bundle IDs
Display name — each app’sandroid/app/src/main/AndroidManifest.xml(android:label) and iOSios/Runner/Info.plist(CFBundleDisplayName). Identifiers —android/app/build.gradle.kts(applicationId+namespace) and the iOSPRODUCT_BUNDLE_IDENTIFIER. The current names/IDs are in the App identity table below. - 04
Regenerate the launcher icons
Run once inside each app folder (
apps/customer,apps/rider,apps/restaurant) after swapping the masters in step 1:terminaldart run flutter_launcher_icons - 05
Point everything at your own backend and legal pages
Three text edits, all outside the visual layer — do these before you ship a build:
- Backend URL. The apps ship pointing at our demo backend. Set your own default in
packages/shared-dart/lib/src/api/api_config.dart(ApiConfig.productionBaseUrl), or override per build with--dart-define=FEASTLY_API_URL=https://your-domain. - Terms & privacy links shown in every app’s Settings → About:
_termsUrl/_privacyUrlinpackages/shared-dart/lib/src/widgets/settings_about_section.dart. Point them at your own pages — the app stores require both. - Admin browser title.
metadata.title/metadata.descriptioninapps/web/src/app/layout.tsx(currently “Feastly Admin”) — this is the browser-tab name of your admin panel.
- Backend URL. The apps ship pointing at our demo backend. Set your own default in
Verify the rebrand
Run through this once; if every row passes, no “Feastly” branding is left anywhere a user can see.
#ae3200/admin--dart-definefeastly.devsnack.devapplicationId / PRODUCT_BUNDLE_IDENTIFIERapplicationId and the iOS bundle identifier can never be changed after an app is published — a new ID means a new listing that existing users won’t receive as an update. Everything else on this page can be changed at any time.Design tokens, icons and app identity
Design tokens
The palette centers on primary orange #ae3200 (vibrant #ff5a1f) with a leaf-green accent #006d37, Inter throughout, 12px-radius buttons, 16px-radius cards, and the “AI Spark” gradient marker on AI surfaces. Web tokens live in the Tailwind preset (packages/config); mobile tokens live in packages/shared-dart.
Launcher icons
All three apps regenerate icons from shared masters in brand/ via flutter_launcher_icons:
# masters: brand/icon-1024.png (full), brand/foreground-1024.png (adaptive foreground)
# vector sources: brand/feastly-icon.svg, brand/feastly-mark.svg
dart run flutter_launcher_icons # run inside apps/customer, apps/rider, apps/restaurantTo rebrand: replace the two 1024×1024 PNGs (optionally update the SVG masters), optionally change adaptive_icon_background in each app’s pubspec.yaml, then re-run the command. The web favicon is apps/web/src/app/favicon.ico.
App identity
Change the display name in each android/app/src/main/AndroidManifest.xml (android:label) and iOS Info.plist (CFBundleDisplayName); change ids in each android/app/build.gradle.kts (applicationId + namespace) and the iOS project’s PRODUCT_BUNDLE_IDENTIFIER. Min iOS is 17.0; Android inherits the Flutter default minSdk (raise it explicitly if your Flutter toolchain’s default is below what flutter_stripe requires). If you self-host, also update the production URL and the terms/privacy links in packages/shared-dart.
CURRENCY, ISO 4217, served to mobile via /api/mobile/config). Both are structured for later expansion; the expansion itself is not built.FAQ and troubleshooting
Short answers to the questions new operators ask most — first the platform itself, then the deployment and day-two questions that come up once you go live. Full detail lives in Deployment, Database setup, Backups and exports, Optional integrations, Environment reference and Rebrand step by step.
Using the platform
https://your-domain/login (locally http://localhost:3000/login) and sign in with an admin account. On the seeded demo that is admin@feastly.dev / password123. The three mobile apps each have their own login — a customer, rider or restaurant account can’t sign in to the admin panel, and vice-versa.pnpm db:seed (Database setup) to load the demo — 12 restaurants, full menus, live and historical orders, and ledgers. In production the screens fill as real restaurants, orders and riders arrive. Every list also has a designed empty-state, so an empty screen is expected, not an error./admin/config) at runtime, or in the server .env at deploy time — the code reads env first, then the admin store. Keys are stored server-side only and are never sent to a mobile app. Leave any of them blank and that feature just stays off (Optional integrations). These are your own third-party accounts and may carry that provider’s own fees./admin/restaurants) or Riders (/admin/riders) → open the pending record → approve (or suspend). You can set a global commission % and override it per restaurant on the same screen./admin/payouts) lists amounts owed per restaurant and rider, computed from the immutable commission ledger. Settlement is manual in V1: pay them through your own bank/processor, then click mark as paid to record it.AI_PROVIDER + an OpenAI or Gemini key, AI setup). With it off, search runs as full-text and recommendations fall back to popularity — fully functional, just without the semantic ranking and the “AI Spark” badge.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY for the admin geofence editor and MAPS_API_KEY (build-injected) for mobile. Without it the geofence editor shows a notice and nothing else breaks.CURRENCY; it’s served to the mobile apps via /api/mobile/config. See Design tokens, icons and app identity.customer@feastly.dev) → accept & mark ready in the Restaurant app (owner@feastly.dev) → go online, accept and deliver in the Rider app (rider@feastly.dev) → watch the Customer tracking screen advance live and the completed order surface in Admin → Orders and Payouts. Every demo account uses password123.Deployment and operations
apps/web, not the repo root. This is a monorepo, so pointing Vercel at the root builds nothing usable. Set it under Project → Settings → General → Root Directory and redeploy. Framework detection, the build command and the install command are all automatic once that’s right (Deployment).DATABASE_URL="<prod-url>" pnpm db:migrateDo not run pnpm db:seed against production; that’s demo data (Database setup).REDIS_URL and drop in the Redis adapter (a complete, copy-paste file is in Payments, media, push and realtime), or run apps/web on a persistent Node host/VPS where a single process stays alive. Order status is still correct either way — it just refreshes on fetch rather than streaming.localhost. Use the right host for the target: Android emulator http://10.0.2.2:3000, iOS simulator http://localhost:3000, real device your machine’s LAN IP or a public URL. Pass it at run/build time:flutter run --dart-define=FEASTLY_API_URL=http://10.0.2.2:3000Release builds need the flag too, or they use the default in ApiConfig.productionBaseUrl (Rebrand step by step, step 5). Note that iOS and Android both block plain http:// to public hosts — production must be https.AUTH_URL must equal your real domain, AUTH_TRUST_HOST=true is required behind a proxy or on Vercel, and AUTH_SECRET must be set and stable — changing it signs every existing session out. If you added a custom domain later, update AUTH_URL to match and redeploy.DATABASE_URL is the pooled connection string from your provider and ends with ?sslmode=require — managed Postgres refuses plaintext connections. If your host has an IP allowlist, allow your deployment platform (Vercel’s egress IPs are dynamic, so a fully-restricted allowlist won’t work there). Any Postgres 15+ works; the docs use Neon’s free tier because it needs no allowlist..env, your brand/ masters and any files you customized should be kept. Then run pnpm install, pnpm db:migrate (new releases may add migrations) and rebuild the mobile apps. Back up first (Backups and exports) — a dump takes one command and makes any update reversible.pg_dump "$DATABASE_URL" --format=custom --file=feastly-backup.dump. That single file is your full platform state and restores with pg_restore. Step-by-step instructions, CSV export and managed-host alternatives are in Backups and exports.flutter build appbundle --dart-define=FEASTLY_API_URL=https://your-domainfor Google Play, flutter build ipa … for the App Store. Each app is submitted as its own listing. Both stores require reachable terms and privacy URLs — set them in step 5 of Rebrand step by step.Production checklist
- 01Set
DATABASE_URLand a stableAUTH_SECRET(openssl rand -base64 32) in the host environment. - 02Set
AUTH_URLto your production domain andAUTH_TRUST_HOST=true. - 03Run
pnpm db:migrateagainst the production database (do not seed it). - 04Add only the optional keys you use — Stripe, R2, SMTP, Maps, AI — all server-side. Every variable and its blank-value behavior is in the complete environment reference.
- 05If enabling AI on a public deployment, add a rate limiter in front of
/api/searchand/api/recommendations(AI setup). - 06For horizontally-scaled/serverless realtime, set
REDIS_URLand backapps/web/src/lib/realtime.tswith Redis pub/sub — drop-in adapter in Payments, media, push and realtime. - 07Schedule a backup: one
pg_dumpcommand as a nightly cron job or GitHub Action (Backups and exports). - 08Build each mobile app with
--dart-define=FEASTLY_API_URL=https://your-domainand your own signing config. - 09Rebrand: swap
brand/masters, re-runflutter_launcher_icons, set app names/ids, update terms/privacy links. - 10Set
CURRENCYto your market’s ISO 4217 code.
Changelog
Every version published so far. Updates are free for the life of the item.
Version 1.1.0 · 2026-08-03
Order details across all four apps.
- Rider order detail screen (new). Pickup and drop-off cards with one-tap Navigate and Call on each leg, the full item list with modifiers, delivery notes, and the cash-to-collect amount called out on COD orders.
- Richer rider job cards. Before accepting, a rider sees the restaurant, its address, the delivery area with distance, item count, payment method and their payout — instead of just an order number and a total.
- Restaurant order board + detail screen. Customer name, itemised order with modifiers, and the assigned rider with a Call button once one is allocated.
- Admin order oversight. Expandable pickup / drop-off / assigned-rider contact block on every order row, so support can answer “where is this going, and to whom?”.
- Customer tracking. Now shows which restaurant the order is coming from, with its address, Navigate and Call.
- Customer privacy model. A customer’s street address and phone number are visible only to the rider actually assigned to their delivery, and to the platform admin. Riders browsing the unclaimed order pool see the delivery area and distance, never the customer’s identity or exact address. Enforced in the database queries, not just hidden in the UI.
- Fixes. Delivery coordinates are now saved when a customer checks out with a saved address (previously the rider’s drop-off Navigate button had no destination); two riders can no longer accept the same order; pickup-only orders no longer appear in the rider delivery queue; and the restaurant’s address and phone now reach the customer’s tracking screen.
- Demo data. Customer phone numbers, delivery coordinates, item modifiers, and a card-paid order so both the “collect cash” and “already paid” states are visible in the seed.
- Upgrading. No database migration. Existing installations should run the one-off coordinate backfill below.
Upgrading from 1.0.0 — one-off delivery-coordinate backfill. Only relevant if you already ran 1.0.0 in production and have real orders. In 1.0.0 an order’s delivery coordinates were saved only when the customer’s device sent an explicit GPS fix, so orders placed against a saved address stored none — and the rider app’s drop-off Navigate button had nothing to open. New orders are fixed in 1.1.0; this backfills the existing ones. It is a data update, not a migration — there is nothing to add to packages/db/drizzle, and fresh installations can skip it entirely.
UPDATE orders o SET delivery_lat = a.lat, delivery_lng = a.lng
FROM addresses a
WHERE o.address_id = a.id AND o.delivery_lat IS NULL;Safe to re-run — it only touches rows that still have a NULL delivery_lat, and never overwrites a coordinate that is already set.
Version 1.0.0 · 2026-07-10
- Initial release. Four surfaces on a shared backend; order lifecycle state machine; commission & payout ledgers; COD + Stripe payments; self-hosted realtime tracking; optional AI search + recommendations; idempotent demo seed; full documentation.
Support and licensing
DevSnack — 6 months of item support is bundled per CodeCanyon standard (setup guidance, bug fixes, and answering usage questions). You supply your own hosting, domains and third-party accounts; we provide the walkthroughs.
- Support requests: post on the Comments tab of this item’s CodeCanyon page — the fastest way to reach us.
- Support tickets: open one from the Support tab on our CodeCanyon author profile.
License. Sold under the standard Envato Regular and Extended licenses. All dependencies are permissive (MIT / Apache-2.0 / BSD / ISC); no copyleft or per-seat obligation is passed to you.
What support covers
- Six months of support from purchase, extendable at checkout
- Support covers bugs in the template and questions about how it is put together
- It does not cover custom feature work, third-party API changes or store review outcomes
- The Regular licence covers one free end product. Charging users for the app itself needs the Extended licence