GreenCart documentation
Run it, point it at your own accounts, ship it under your own brand. Written against version 1.0.0 of the download you get on CodeCanyon.
- Version v1.0.0
- Updated Jun 2026
- Platform Flutter
- Stack Flutter · Next.js
On this page
Overview
A multi-vendor organic & local grocery delivery marketplace — three Flutter apps (customer + rider + vendor) plus one Next.js project that is the public storefront, the /vendor portal, the /admin panel, and the sole backend.
Live demo: https://greencart.devsnack.dev
GreenCart Market is a multi-vendor organic & local grocery delivery marketplace, sold as a 6-surface bundle:
- Customer Flutter app — shoppers browse vendors, build a multi-vendor cart, check out, and track deliveries.
- Rider Flutter app — delivery riders accept jobs, navigate, and capture proof of delivery.
- Vendor Flutter app — vendors run the store from a phone: incoming orders and status transitions, stock, earnings, reviews.
- One Next.js project — the public SSR storefront at
/, the vendor portal at/vendor, the super-admin panel at/admin, and the only backend (every/api/*route). No second server exists.
It is a vertical marketplace for organic / local / farm-to-table grocers, built on a modern TypeScript stack (Next.js 16 + PostgreSQL + Drizzle ORM) rather than the PHP/Laravel platforms that dominate this category. Two differentiators the incumbents lack:
- Subscription produce boxes — recurring weekly/biweekly grocery boxes with skip/pause.
- Product provenance / sourcing — every product carries a farm/origin “provenance card.”
Portable by design: deploy to Vercel + Neon (the easy path) or to any Node host / VPS + any Postgres (the portable path). The only platform-specific dependency is Vercel Cron, which degrades to any scheduler hitting the CRON_SECRET-guarded routes.
PRD.md (schema, API, screens), the build contract in CLAUDE.md, the design system in DESIGN.md, and the production deploy walkthrough in docs/DEPLOY_VERCEL.md. This document does not duplicate them — it points to them.What’s included
- Full source for all four apps — three Flutter apps (
customer_app/,rider_app/,vendor_app/) + one Next.js project/backend (web/). - Complete Drizzle schema + SQL migrations (
web/src/db/). - The “Modern Agrarian” design system — Tailwind v4 CSS-first theme + matching Flutter
ThemeData. - Branding pipeline — a single SVG mark in
brand/regenerates every web favicon/icon/OG image and both Flutter launcher icons. - Buyer docs (this file), a 20-minute quick-start, and the Vercel + Neon deploy guide.
- Vitest backend integration suites + Flutter unit/widget tests.
- 6 months Item Support (per the Envato Item Support Policy) and free future updates.
Features
Customer Flutter app
/track for rider position and status.Rider Flutter app
Vendor Flutter app
Uses no Maps or payment SDKs — API_BASE_URL is the only dart-define it needs. Tenant-isolated server-side: a vendor can only ever read or write its own rows. Coupons and subscription boxes remain web-only in /vendor.
Storefront (web, /)
SSR/SEO marketplace — home, vendors directory + indexable vendor storefronts (/vendors/[slug]), shop/catalog, product detail (provenance + JSON-LD), search, cart, checkout split, subscription boxes, account + my orders (live tracking) + manage subscription, auth, sitemap/robots/OG.
Vendor portal (web, /vendor)
Onboarding/apply, dashboard (KPIs, payout balance, low-stock), products CRUD (+ R2 image upload, provenance, SEO), this vendor’s sub-orders + status transitions, subscriptions, vendor coupons, payouts (earnings/commission/history), store settings, reviews, notifications. Tenant-isolated — a vendor only ever sees its own rows.
Admin panel (web, /admin)
KPI dashboard (GMV, commission, charts); Vendors (approve/suspend/set commission); Orders (all + filters + real Stripe/PayPal refunds); Riders (CRUD + assign); Customers; Payouts (generate per period + mark paid); Categories; Coupons (platform + vendor); Subscriptions; Reviews (moderation); Settings (appSettings); Reports (sales, commission, GMV/commission trend chart, top vendors/products, churn, CSV export).
Backend (Next.js API)
REST under /api/* — auth, catalog/discovery, server cart, checkout (quote/intent/orders split + commission ledger), orders + /track, subscriptions, vendor/*, admin/*, rider/*, uploads/sign (R2), devices/fcm-token, public settings, the vendor/* REST surface the vendor app consumes (15 endpoints — profile, dashboard, orders + status, products + stock/active, categories, payouts, reviews, notifications, settings), webhooks/{stripe,paypal}, ai/{recipes,seasonal}, and two Vercel Cron jobs — cron/subscriptions (generate box orders + advance cycles) and cron/payout-run (close vendor payout periods). Both are CRON_SECRET-guarded and idempotent.
Architecture
green-cart/
customer_app/ Flutter customer (Riverpod 3 + GoRouter + Dio + freezed)
rider_app/ Flutter rider (same stack)
vendor_app/ Flutter vendor (same stack, no Maps/payment SDKs)
web/ Next.js 16 — storefront + /vendor + /admin + the backend
src/app/(storefront)/ public SSR marketplace at /
src/app/vendor/ vendor portal
src/app/admin/ super-admin panel
src/app/api/ the backend (all routes)
src/db/ Drizzle schema + migrations
src/lib/ auth (JWT), payments, r2, ai, cron helpers
brand/ SVG logo sources (single source of truth)
docs/ DEPLOY_VERCEL.md, codecanyon/requireUser(roles)Invariants (do not violate)
- Single backend, single DB. Only the Next.js app touches Postgres. No client ever gets DB credentials.
- Mobile apps are thin JWT clients. Flutter calls typed HTTP endpoints for everything — no business logic or money math on the device.
- Multi-vendor cart → order split. Checkout always splits one cart into one
order+ NsubOrders(one per vendor), each independently fulfilled, tracked, settled. - Commission ledger + payouts. The platform collects the whole payment; each sub-order records commission fields and
ledgerEntriesaccrue to a vendor payout balance. - Real-time via polling through the backend — never client→DB listeners (SSE is the documented upgrade path).
- Config in the DB — a single
appSettingsrow (theme, commission, feature flags, currency, tax). - Firebase is push-only — no Firestore, no Firebase Auth, no Cloud Functions.
Quick setup
The fastest path from the downloaded zip to all three surfaces running locally (≈20 minutes). The apps run fully on COD with just API_BASE_URL set — payment/maps/AI keys are optional.
Prerequisites
Node 20+, Flutter 3.x (Dart 3), Xcode and/or Android Studio for a simulator/emulator, and a Postgres URL (Neon’s free tier works).
- 01
Bootstrap the backend
terminalcd web && npm install cp .env.example .env # fill DATABASE_URL, DATABASE_URL_UNPOOLED, # JWT_ACCESS_SECRET, JWT_REFRESH_SECRET, CRON_SECRET npm run db:migrate npm run dev # → http://localhost:3000Register a user in the browser, then in a second terminal run
npm run db:studioand set that user’sroletoadmin— reload/adminand the panel unlocks. - 02
Run the Flutter apps
terminalcd ../customer_app && flutter pub get cp dart_defines.example.json dart_defines.json # set API_BASE_URL flutter run --dart-define-from-file=dart_defines.json # same three steps in ../rider_app and ../vendor_appUse
http://10.0.2.2:3000asAPI_BASE_URLon the Android emulator andhttp://localhost:3000on the iOS simulator (no trailing/api). For the rider flow, create a rider user and set itsroletoriderindb:studio, then assign it a sub-order from/admin.
Build commands
npm run build # Next.js production build (web/)
flutter build appbundle --release --dart-define-from-file=dart_defines.json # Android
flutter build ipa --release --dart-define-from-file=dart_defines.json # iOSFor a production deployment (Vercel + Neon, env checklist, migrations, crons, webhooks, smoke test), see docs/DEPLOY_VERCEL.md.
README.md yet. Before listing, add one (or rely on this docs/codecanyon/ set) so buyers landing in the repo root find the setup path immediately.Database setup
- 01Create a Postgres database. Neon (free tier) is recommended; any Postgres works.
- 02Set
DATABASE_URL(the pooled connection) andDATABASE_URL_UNPOOLED(the direct connection, used by migrations) inweb/.env. - 03
Run migrations against the unpooled URL:
terminalcd web DATABASE_URL="<neon-unpooled-url>" npm run db:migrate
Open the GUI any time with npm run db:studio.
db:studio set that user’s role to admin (or vendor / rider) to unlock the corresponding panel.Optional integrations
Every integration gracefully no-ops when unconfigured — payment intents return 503, R2 presign checks isConfigured(), AI falls back to a safe mock, FCM just stores tokens. COD works with zero payment configuration.
To extend the platform with a new optional integration, mirror the existing probe pattern: a server-side isConfigured() check that returns a graceful fallback (a 503 or a mock) when the env var is unset, so a fresh install never crashes on a missing key.
AI provider setup
Two endpoints power the AI features: /api/ai/recipes (recipe ideas from cart contents) and /api/ai/seasonal (seasonal produce picks). Both call web/src/lib/ai/ server-side only.
- Set
OPENAI_API_KEYorGEMINI_API_KEYin the server env — never in a Flutter build. - Both routes are daily rate-limited per user via a Postgres counter, so a buyer can’t run up a bill before noticing.
- When no key is set, the endpoints return a safe mock so the screens still render.
- Toggle and cap via
appSettings—featureAiEnabledandaiDailyCapPerUser.
OPENAI_API_KEY / GEMINI_API_KEY under customer_app/ or rider_app/ returns zero matches — keep it that way.Payments setup
Stripe
- 01Create a Stripe account; get
STRIPE_SECRET_KEY+NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY. - 02Dashboard → Webhooks → add
https://<deploy>/api/webhooks/stripelistening topayment_intent.succeeded(+charge.succeeded); paste the signing secret asSTRIPE_WEBHOOK_SECRET. - 03The customer app uses
flutter_stripePaymentSheet on the returnedclientSecret. IfSTRIPE_PUBLISHABLE_KEYisn’t passed as a dart-define, the app hides the Card option (COD + PayPal still work).
PayPal
- 01Set
PAYPAL_CLIENT_ID+PAYPAL_SECRET+PAYPAL_ENV(sandbox|live). - 02Set
PAYPAL_RETURN_URL/PAYPAL_CANCEL_URL. The customer app’s--dart-define=PAYPAL_RETURN_URLmust match the server value — the webview detects completion by watching for that URL (there is no deep-link scheme). - 03Webhook at
/api/webhooks/paypal; the authoritative paid signal is the synchronous capture insidePOST /api/orders.
Cash on delivery
Works out of the box with zero config. The rider collects cash and taps “cash collected” to flip the order to paid.
cron/payout-run + admin “mark paid.”Push notifications and live tracking
Push notifications (FCM)
- 01Create a Firebase project, add an iOS and an Android app, and run
flutterfire configurein each Flutter app. The native config files are gitignored, and Firebase init is wrapped in try/catch so a missing config is a soft no-op. - 02Paste the full service-account JSON (single line) into
FCM_SERVICE_ACCOUNTin the server env. - 03iOS: in Xcode enable Push Notifications + Background Modes.
/api/devices/fcm-token); server-side push send is on the roadmap. In-app notifications are fully wired. Stated plainly so you don’t open a ticket asking where push sends are.Live tracking (polling)
Order status and rider location update via short-interval polling through the backend — GET /api/orders/[id]/track, fed by the rider app’s throttled POST /api/rider/location ping. There is no Pusher and no client→DB listener; an SSE upgrade is the documented path if you need lower latency.
Google Maps key setup
Enable Maps SDK for Android, Maps SDK for iOS, and Directions API (the rider app calls Directions directly). Keys go into both the native config and the dart-define:
customer_app/ios/Flutter/Secrets.xcconfig + rider_app/ios/Flutter/Secrets.xcconfig (gitignored; copy the .example) → substituted into Info.plist GMSApiKeyGOOGLE_MAPS_ANDROID_KEY in */android/local.properties (or an env var) → manifest placeholderGOOGLE_MAPS_ANDROID_KEY / GOOGLE_MAPS_IOS_KEY in each dart_defines.jsonBranding and customization
Source of truth — brand/*.svg
Edit mark.svg, icon-customer.svg, icon-rider.svg, the foreground SVGs, and og.svg, then regenerate:
cd web && npm run gen:brand # web favicon/icons/OG + Flutter master PNGs
cd ../customer_app && dart run flutter_launcher_icons
cd ../rider_app && dart run flutter_launcher_icons
cd ../vendor_app && dart run flutter_launcher_iconsTheme tokens
- Web —
web/src/app/globals.css(CSS custom properties, Tailwind v4). - Flutter —
customer_app/lib/core/theme/,rider_app/lib/core/theme/+vendor_app/lib/core/theme/. - Runtime white-label —
appSettings.themePrimaryHex/themeAccentHex.
App name and ids
Edit each pubspec.yaml, android/app/build.gradle.kts (applicationId), AndroidManifest.xml (android:label), and ios/Runner/Info.plist (CFBundleDisplayName). Defaults:
App signing (Android and iOS)
Store builds must be signed with your own credentials. The package ships with Flutter’s default debug signing (android/app/build.gradle.kts uses signingConfigs.getByName("debug")), so set up release signing once per app before your first upload.
Android — upload keystore
- 01
Generate a keystore (back it up — you cannot update the app on Play without it):
terminalkeytool -genkey -v -keystore ~/upload-keystore.jks \ -keyalg RSA -keysize 2048 -validity 10000 -alias upload - 02
Create
customer_app/android/key.properties(and the rider equivalent). It is already gitignored (key.properties,*.jks,*.keystore) — never commit it:key.propertiesstorePassword=... keyPassword=... keyAlias=upload storeFile=/absolute/path/to/upload-keystore.jks - 03Wire it into
android/app/build.gradle.kts: loadkey.properties, add asigningConfigs { create("release") { ... } }block, and pointbuildTypes { release { signingConfig = signingConfigs.getByName("release") } }at it (replacing the currentdebugreference). - 04
Build the signed bundle and upload
build/app/outputs/bundle/release/app-release.aabto the Play Console:terminalflutter build appbundle --release --dart-define-from-file=dart_defines.json
iOS — certificates and provisioning
- 01Enroll in the Apple Developer Program.
- 02In App Store Connect, register each App ID / bundle id (
market.greencart.customerApp,market.greencart.riderApp) and create the app records. - 03Open
ios/Runner.xcworkspacein Xcode → Signing & Capabilities → pick your Team and enable Automatically manage signing (Xcode provisions the signing certificate + profile). Add Push Notifications + Background Modes here if you use FCM. - 04
Archive and upload via Xcode → Organizer (or Transporter):
terminalflutter build ipa --release --dart-define-from-file=dart_defines.json
key.properties, and any iOS .p12 / .mobileprovision files must never be committed. Store them in a password manager — losing the Android upload key means you can no longer update that app.Production checklist
- 01Branding: edit
brand/*.svg→npm run gen:brand→dart run flutter_launcher_iconsin each Flutter app. - 02Theme tokens:
web/src/app/globals.css+ the three Flutter apps’lib/core/theme/. - 03App names + ids in the Flutter pubspecs + native config.
- 04Provision Postgres (Neon recommended).
- 05
cd web && npm install, thennpm run db:migrateagainst the unpooled URL. No seed step — create your first account via the app, promote to admin indb:studio. - 06Set required env (
DATABASE_URL,DATABASE_URL_UNPOOLED,JWT_ACCESS_SECRET,JWT_REFRESH_SECRET,CRON_SECRET) + any optional integrations. - 07Vercel → import repo → Root Directory =
web→ add env → deploy. The two cron jobs inweb/vercel.jsonrun automatically (CRON_SECRET). - 08Stripe Dashboard → add webhook → paste
STRIPE_WEBHOOK_SECRET. PayPal → setPAYPAL_*+ return/cancel URLs. - 09R2 → bucket + token →
R2_*+NEXT_PUBLIC_R2_PUBLIC_BASE_URL(must be build-available fornext/image). - 10Firebase → service-account JSON →
FCM_SERVICE_ACCOUNT;flutterfire configurein each app. iOS: enable Push + Background Modes in Xcode. - 11Maps key →
Secrets.xcconfig(iOS) +local.properties(Android) +dart_defines.jsonfor each app. - 12Set up release signing for each app — Android upload keystore +
key.properties, iOS Team + provisioning (see App signing). - 13Build Flutter releases with
--dart-define-from-file=dart_defines.json: iOSflutter build ipa --release(upload via Xcode Organizer); Androidflutter build appbundle --release(Play Console). - 14Promote your admin: in
db:studio, set the user row’srole = 'admin'.
Changelog
Every version published so far. Updates are free for the life of the item.
Version 1.1.0 · 2026-08-12
- New: vendor Flutter app (
vendor_app/) — dashboard with a 14-day sales sparkline, orders with server-driven status transitions, products with inline stock/publish toggles and image upload, earnings + payout ledger, reviews, notifications, store settings, and an application-pending state. - Backed by a new
/api/vendor/*REST surface (15 endpoints). - Hardened the vendor tenant-isolation and error contract: scoped writes now refuse instead of silently no-opping, and validation / ownership / server faults map to distinct status codes.
- Backend suite 157 → 202 tests.
Version 1.0.0 · 2026-06-16
- Initial release — customer app, rider app, storefront + vendor portal + admin panel + backend; multi-vendor order split + commission ledger + payouts; subscription boxes; provenance; Stripe + PayPal + COD with real refunds; 2 AI features; live tracking via polling.
Support and licensing
6 months of Item Support is bundled per the CodeCanyon standard. You supply your own Postgres / Stripe / PayPal / Firebase / R2 accounts — we provide the setup walkthroughs, not the accounts or the hosting.
Live demo: greencart.devsnack.dev
Sold under the Envato Regular & Extended License.
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