Foodifly
v1.0.0
Version 1.0.0 · Laravel 13 · Flutter · Next.js

Foodifly documentation

Everything you need to install, configure and launch your food delivery platform: the admin panel and API, the customer, rider and restaurant apps, the restaurant portal and the customer website.

6apps in one package
10payment gateways + cash & wallet
Anycountry, currency & timezone
Livetracking, alerts & push
Laravel 13 · PHP 8.4+Flutter customer, rider & restaurant appsNext.js portal & websiteMySQL · Pusher · Firebase
ℹ️

Read the sections in this order the first time: Requirements → Install the backend → After installing → the app or web section you need. The Known limitations section lists what is not included in this version.

Overview

Foodifly lets you run a food delivery marketplace. Customers order from restaurants, riders deliver, restaurants manage their menu and orders, and you run everything from one admin panel. All of the clients talk to the same Laravel backend (/api/v1) and receive live updates through Pusher and push notifications through Firebase.

Feature highlights

🛒

Customers

Browse and search restaurants, cart, promo codes, saved addresses (map or search), rider tip, wallet, loyalty points, live order tracking, reorder, reviews and refund requests.

🚴

Riders

Sign-up with documents and approval, online/offline and breaks, live job offers with a countdown, pickup checklist, navigation, delivery code and proof photo, earnings and payout requests.

🍽️

Restaurants

Apply online, a mobile app and a web portal with a live order board and alerts, menu and categories, opening hours and holidays, analytics, reviews, earnings, payouts and a team with roles.

🛠️

You (admin)

Approve restaurants and riders, delivery zones and distance-based fees, commission rules, coupons, banners, payouts, reports, staff roles, and platform-wide settings.

Built to work in any country

Nothing in the code is tied to one country. Currency, timezone, support contacts, delivery fees, minimum order, tip amounts, wallet limits and loyalty rules are all set in the admin panel (Settings) and picked up by every app and website automatically.

What's in the package

The download contains six applications. Each lives in its own folder inside main/:

FolderWhat it isTechnology
backend-and-admin-panelThe backend API and the admin panel (also contains the web installer and this documentation)Laravel 13, PHP 8.4, MySQL
customer-appThe customer mobile app (Android and iOS)Flutter
rider-appThe rider mobile app (Android and iOS)Flutter
restaurant-appThe restaurant mobile app for owners and staff: live orders and dish availability (Android and iOS)Flutter
restaurant-portalThe restaurant owner web portalNext.js 16, React 19
customer-websiteThe customer websiteNext.js 16, React
✅

The backend download does not contain vendor/ (the PHP packages) or public/build/ (the compiled admin styles). Marketplace rules do not allow those folders in a package, so you create them with two commands (see Install the backend, step 0). composer.json, composer.lock, package.json and package-lock.json are included for exactly that.

You do not need all six to start. The backend with its admin panel is the core. Add the clients you want to offer: for example the website and the restaurant portal only, with no mobile apps.

Requirements

Server (backend and admin panel)

RequirementMinimumRecommended
PHP8.48.4 or newer
DatabaseMySQL 8.0 or MariaDB 10.6MySQL 8.0+
Web serverApache 2.4 or Nginx 1.18Nginx + PHP-FPM
Memory / disk512 MB / 500 MB free1 GB+ / 2 GB+
HTTPSOptional for developmentRequired in production (the portal's order alerts and the apps' payment pages need it)
Background jobsCronSupervisor for the queue worker, plus cron for the scheduler

PHP extensions

ExtensionStatusWhy
pdo_mysqlRequiredDatabase
mbstring, openssl, tokenizer, xml, ctype, jsonRequiredLaravel core
fileinfoRequiredUpload validation
bcmathRequiredMoney calculations
curlRequiredPayments, push notifications, maps
intlRequiredFormatting
gd or imagickRequiredImage processing
zipRecommendedArchives
redisOptionalFaster cache and queue on busy sites
⚠️

The web installer needs PHP's exec() function enabled because it runs php artisan for you. Some shared hosts disable it; if yours does, ask them to enable it or use the command-line install.

For the web clients

  • Node.js 22 LTS (or 20.19 and newer) to build and run the restaurant portal and the customer website (Next.js 16).
  • Composer 2 and Node.js 22 LTS (or 20.19 and newer) with npm are needed once, to create vendor/ and public/build/ for the backend (step 0 of the install). Run them on the server over SSH, or on your own computer and upload the result.

Mobile app compatibility

PlatformMinimumApplies to
AndroidAndroid 7.0 (API level 24)Customer, rider and restaurant apps
iOSiOS 14Customer, rider and restaurant apps

Accounts you need (third-party services)

Foodifly depends on a few services run by other companies. You create and pay for your own accounts; nothing is shared with the author. Most have a free tier that is enough to start. See External services and outbound connections for exactly when each one is contacted.

ServiceNeeded forCost
Firebase (Google)Push notifications. The backend uses the kreait/laravel-firebase package and your project's service-account key.Cloud Messaging is free; Google sets the quotas
Google Maps PlatformMaps, address search and the delivery routeBilling account required; Google gives a monthly credit
Pusher ChannelsLive tracking and new-order alertsFree plan, paid plans for more connections
Payment gatewaysOnly the ones you switch onEach provider's own fees
Apple Developer / Google PlayPublishing the mobile appsAnnual / one-time store fees

To build the mobile apps

  • Flutter 3.41.9 (the version the apps were built and tested with).
  • Android Studio with the Android SDK, and for iOS a Mac with Xcode and an Apple Developer account.
  • A Firebase project (push notifications), a Google Maps API key (Android and iOS), and a Pusher Channels app (live updates). See the configuration sections.

Install the backend

You can install with the web installer (no command line needed) or from the command line. Both give the same result.

Step 0: Get the packages and build the admin styles (once)

In the backend folder, run these three commands:

cd backend-and-admin-panel
composer install --no-dev --optimize-autoloader
npm ci
npm run build
  • They create vendor/ (PHP packages) and public/build/ (the admin panel's CSS and JS). After npm run build you can delete node_modules/.
  • No SSH on your hosting? Run the commands on your own computer (Composer 2 and Node.js 22 LTS or 20.19+ installed), then upload the whole folder, including the new vendor/ and public/build/.
  • The web installer's first step checks both folders and tells you the command if one is missing.

Option A: Web installer

  1. Upload the files. Upload the contents of backend-and-admin-panel to your server and point the domain's document root at its public folder. Do step 0 first, so the folder you upload contains vendor/ and public/build/.
  2. Create an empty MySQL database and a user with access to it.
  3. Open https://your-domain.com/install/ and follow the steps (the last one is the finish page):
StepWhat it does
1. System requirementsChecks PHP 8.4, the extensions above, that exec() is enabled, that public/build and vendor exist, and that storage, bootstrap/cache and public are writable.
2. DatabaseHost, port, database name, user and password.
3. Application settingsApp name, URL, environment, debug switch, currency code and symbol and timezone for your market.
4. Admin accountYour name, email and password for the Super Admin. Nothing is written yet.
5. InstallWrites the .env file, generates the app key, runs the migrations, creates the default roles, permissions, settings and email templates, creates your Super Admin and then locks the installer. All of this happens in one run, so the site is never left installed without an admin.
ℹ️

The installer refuses to run again on a site that is already configured. To re-run it deliberately, delete storage/installed.lock over FTP or SSH and open the page again.

Option B: Command line

# 1. Upload the code, create the database, then (step 0 above):
composer install --no-dev --optimize-autoloader
cp .env.example .env
php artisan key:generate

# 2. Edit .env. At minimum:
#    APP_ENV=production   APP_DEBUG=false   APP_URL=https://your-domain.com
#    DB_*                 QUEUE_CONNECTION=database
#    ADMIN_EMAIL / ADMIN_PASSWORD   (quote the password if it has spaces or #)

# 3. Install, migrate, build caches (also runs npm ci && npm run build)
bash scripts/deploy.sh

# 4. Create roles, permissions, default settings and your Super Admin (once)
php artisan db:seed --class=ProductionSeeder --force

Without an ADMIN_PASSWORD, a random one is generated and printed once. Locked out or need another Super Admin later? Run php artisan foodifly:create-admin --email=you@example.com.

Then sign in at https://your-domain.com/login.

After installing

Work through this list once. The admin panel shows whether the queue worker and scheduler are running under Settings → Firebase & Push.

  1. Start the queue worker. Push notifications, emails and payouts are queued; without a worker they are saved and never sent. With Supervisor:
    sudo cp deploy/foodifly-worker.conf /etc/supervisor/conf.d/
    sudo nano /etc/supervisor/conf.d/foodifly-worker.conf   # set your path and web-server user
    sudo supervisorctl reread && sudo supervisorctl update
    sudo supervisorctl status foodifly-worker:*              # should say RUNNING
  2. Add the scheduler to cron (takes riders who closed the app offline after 30 minutes, and reports its heartbeat to the admin):
    sudo crontab -u www-data -e
    # add the line from deploy/foodifly.cron, with your path:
    * * * * * cd /var/www/foodifly && php artisan schedule:run >> /dev/null 2>&1
    On shared hosting without Supervisor, add a second cron job instead:
    * * * * * cd /home/USER/foodifly && php artisan queue:work --stop-when-empty --max-time=55 >> /dev/null 2>&1
    Notifications then go out within about a minute instead of instantly.
  3. Settings → General: check the currency, timezone, app name and logo, and fill in your support contacts, company name and address, and the Governing Law / Jurisdiction that your website's Terms of Service refer to.
  4. Settings → Firebase & Push: upload your Firebase service-account .json and press Test connection. See Push notifications.
  5. Settings → Maps & Location: add your Google Maps key and set the default map center. See Maps.
  6. Settings → Payment Gateways: enter the keys for the methods you use and switch them on. See Payment gateways.
  7. Realtime: create a Pusher Channels app and put its keys in .env. See Realtime.
  8. Create your content: a fresh install has none. Add Cuisines, Delivery Zones (and their delivery fees), Banners, and optionally Coupons. Restaurants and riders sign up themselves and you approve them.
  9. Run bash scripts/deploy.sh once more: every check at the end should show ✓.

Demo data & accounts

To try the platform with sample data (8 restaurants with menus, 3 riders, 3 customers, 3 delivery zones, two weeks of orders, 2 coupons and 3 banners placed around your map center), use a development or staging copy:

php artisan migrate --seed          # or: php artisan db:seed --class=DemoSeeder
php artisan storage:link            # serves the demo photos

The demo photos are free Unsplash images downloaded once (about 4 MB). Without internet, or with SEED_DEMO_IMAGES=false, the demo is seeded without photos. The demo seeder refuses to run when APP_ENV=production, so seeding on a live server is safe. To remove the demo data and its photos again, without touching real data:

php artisan foodifly:demo-remove
🌐

Live demo. The author runs a read-only demo of every part (nothing can be created, changed or deleted):

  • Admin panel: foodifly-admin.appiflybd.com. The sign-in page lists the demo accounts with a Use this account button.
  • Restaurant portal: foodifly-restaurant.appiflybd.com. Sign in as the restaurant below.
  • Customer website: foodifly.appiflybd.com. Sign in as the customer below.
  • Mobile apps: the customer, rider and restaurant apps use the same demo accounts (the Android demo APKs are linked on the item page).
  • Try your own build against the demo: before building an app, set API_BASE_URL=https://foodifly-admin.appiflybd.com/api/v1 in its assets/.env. You can sign in and browse right away; for maps, add your own GOOGLE_MAPS_API_KEY.

Demo accounts (all use the password password)

Customercustomer@example.com
Riderrider@example.com
Restaurantrestaurant@example.com
Admin staffmanager@example.com support@example.com
Adminadmin@foodifly.com (only on a development install seeded without an ADMIN_PASSWORD)
ℹ️

Your own admin account is the one you created in the installer (or ADMIN_EMAIL/ADMIN_PASSWORD from .env). The demo accounts above exist only after running the demo seeder.

🚨

Never leave demo accounts on a live site. Run php artisan foodifly:demo-remove before you open to the public.

Demo mode (a public preview site)

To run a live preview that anyone can try without being able to break it, turn on demo mode. Visitors can sign in to the admin panel, the apps, the website and the restaurant portal and look at everything, but nothing can be created, changed or deleted: every such request is refused with “This is a demo. Creating, changing and deleting data is turned off.” Signing in and out still works.

  1. Install a separate copy for the preview and load the demo data (above), with SEED_DEMO_DATA=true if APP_ENV=production.
  2. Turn demo mode on in .env, with the admin account you created in the installer:
    DEMO_MODE=true
    DEMO_ADMIN_EMAIL=admin@your-demo-domain.com
    DEMO_ADMIN_PASSWORD=the-password-you-set
  3. Apply it: php artisan optimize. The admin sign-in page now shows the demo accounts (your Super Admin, plus the Manager and Support staff from the demo data) with a Use this account button that fills the form, and every admin page shows a “Demo” banner.

What demo mode restricts: every request that would create, change or delete something is refused with a "demo mode" message, for the admin panel, the API, the apps, the website and the portal. Reading is unrestricted. To turn it off, set DEMO_MODE=false (or remove the three DEMO_* lines) in .env and run php artisan optimize; the sign-in page then stops showing the demo accounts and everything is editable again.

⚠️

Demo mode shows the Super Admin password on the sign-in page. Use it only on a preview copy with demo data, never on your real platform. The apps also receive demo_mode: true from /api/v1/app-config.

Admin panel: modules

Sign in at https://your-domain.com/login. What each person sees depends on their role (see Staff & roles).

ModuleWhat you do there
DashboardRevenue, orders, customers and trends for the platform's business day.
OrdersSearch and filter all orders, open an order's timeline, change its status, reassign the rider, refund, print the invoice, and export.
RestaurantsReview sign-ups and approve them, edit details, activate or deactivate, and set each restaurant's commission (including per-restaurant commission rules).
CuisinesThe cuisine categories shown on the website and apps and offered at restaurant sign-up.
RidersApprove or reject riders after reviewing their documents, suspend or activate, assign a delivery zone, record cash handed in, view the live map, and export.
CustomersView customers, their wallet and loyalty points (with adjustments), ban or unban, and export.
Delivery ZonesDefine zones and the delivery-fee tiers for each (the fee a customer pays depends on distance and zone).
Commission rulesPlatform-wide commission rules, with a preview; restaurants can override with their own rules.
CouponsPercentage, fixed-amount and free-delivery codes, with usage limits and expiry; generate many at once.
BannersThe promotional banners on the home screens; drag to reorder. A banner can have a start and an end time: it is shown only between them. Both times are in the timezone set in Settings → General (the platform timezone), not your browser's or the customer's.
OnboardingThe intro slides shown when the customer and rider apps are first opened.
NotificationsSend a push or email broadcast, edit the email templates, and see the log.
PayoutsPayout requests from riders and restaurants: approve, reject, mark complete, and export.
ReportsSales, commission, riders and customers reports for a date range (business days in your timezone), with export.
Staff & RolesCreate staff accounts and decide what each role can do.
SettingsEverything platform-wide. See the next section.

Settings reference

Admin → Settings has nine tabs. Changes apply to the website, the portal and the mobile apps without rebuilding anything.

TabWhat it controls
GeneralApp name, tagline, logo; support email, phone and hours; registered company name and address; Governing Law / Jurisdiction (named in the website's Terms of Service); currency symbol and code; default timezone; the "under…" filter steps on the website; minimum payouts for riders and restaurants; wallet top-up minimum, maximum and quick amounts; rider tip quick amounts; rider payout methods; how many seconds a rider has to accept an offer; whether a delivery needs the customer's code, how many wrong codes are allowed and the lock time; whether the website header shows "Become a Partner" after sign-in.
Payment GatewaysKeys and switches for every payment gateway and cash on delivery. See Payment gateways.
Email / SMTPMail driver, host, port, encryption, credentials, the From address and name, and the Restaurant Portal URL used in emails. Includes a test-email button.
Maps & LocationGoogle Maps key, default map center and zoom, and address search (Google Places) with an optional separate key.
Firebase & PushUpload the Firebase service-account file, test the connection, and see whether the queue worker and scheduler are running.
Commission & FeesDefault commission for new restaurants, rider commission (share of the delivery fee), minimum delivery fee, maximum delivery distance, free-delivery threshold and minimum order (checked on the subtotal).
Loyalty PointsPoints earned per unit of currency spent, the minimum to redeem, and points expiry in days.
MaintenanceSwitch the apps into maintenance mode with a message and an optional end time.
App VersionLatest and minimum Android and iOS versions for the customer app and the rider app, and a Force update switch for each.

Staff & roles

Admin access is controlled by roles. The installer creates your account as Super Admin (everything). Five roles are created for you:

RoleIntended for
Super AdminThe owner. Full access, including roles and settings.
Admin ManagerDay-to-day operations.
Finance ManagerPayouts, commission and reports.
Support AgentOrders, customers and notifications.
Content ManagerBanners, cuisines, onboarding and coupons.

Open Roles to see and change exactly which permissions each role has, to create your own roles, and to assign them. Add team members under Staff; when you create an account you can tick must change password so the person chooses their own at first sign-in.

Customer app

Folder: customer-app.

1. Configure the API address

flutter pub get
cp assets/.env.example assets/.env

Open assets/.env (a required file that is not tracked in git) and set:

KeyWhat to put
API_BASE_URLRequired. Your API address exactly, e.g. https://your-domain.com/api/v1. On a phone on your Wi-Fi use your computer's LAN IP; for the Android emulator use http://10.0.2.2:8001/api/v1. To try the app against the live demo, use https://foodifly-admin.appiflybd.com/api/v1.
GOOGLE_MAPS_API_KEYYour Maps key for the address and map screens.
PUSHER_APP_KEY, PUSHER_CLUSTERPublic Pusher key and cluster (the same values as the backend's PUSHER_APP_KEY and PUSHER_APP_CLUSTER). The app also reads them from the backend, so you can leave them blank.
DISABLE_MAP_PICKERtrue to develop without a Maps key.

Card payments need no key in the app: the publishable key comes from the payment method you set up in the admin panel.

2. Firebase (push notifications)

The package contains the demo project's Firebase files. Replace them with your own: create a Firebase project, register the Android and iOS apps with your package name, then replace android/app/google-services.json and ios/Runner/GoogleService-Info.plist. Upload the project's service account in the admin panel (Push notifications). All the address, Pusher and Firebase steps in one place: Connect the apps.

3. Your own app id, name and icon

  • Package name: change applicationId in android/app/build.gradle.kts and the iOS bundle identifier in Xcode (Runner → Signing & Capabilities). If you also rename the Android code package (namespace in the same file), move MainActivity.kt to match. Both must match the app you registered in Firebase.
  • App name: android:label in android/app/src/main/AndroidManifest.xml and CFBundleDisplayName in ios/Runner/Info.plist.
  • Icon and splash: replace assets/branding/app_icon.png, then run dart run flutter_launcher_icons and dart run flutter_native_splash:create.
  • Maps key for the native maps: the same GOOGLE_MAPS_API_KEY in assets/.env is read by the Flutter code and injected into the Android manifest and the iOS app at build time, so there is nothing to edit in the native files.

Before and after

Package name: android/app/build.gradle.kts, the iOS bundle identifier in Xcode, and the Kotlin package:

// before
namespace = "com.appifly.foodifly"
applicationId = "com.appifly.foodifly"
// after (your own id: lowercase letters, dots, no spaces)
namespace = "com.yourcompany.yourfood"
applicationId = "com.yourcompany.yourfood"

Then move android/app/src/main/kotlin/com/appifly/foodifly/MainActivity.kt to .../kotlin/com/yourcompany/yourfood/ and change its first line:

// before
package com.appifly.foodifly
// after
package com.yourcompany.yourfood

App name (shown under the icon): android/app/src/main/AndroidManifest.xml and ios/Runner/Info.plist:

<!-- before -->
android:label="Foodifly"
<key>CFBundleDisplayName</key><string>Foodifly</string>
<!-- after -->
android:label="Tasty Bites"
<key>CFBundleDisplayName</key><string>Tasty Bites</string>

API address (assets/.env, then build again):

# before
API_BASE_URL=
# after
API_BASE_URL=https://your-domain.com/api/v1

Google Maps key: only GOOGLE_MAPS_API_KEY=your-key in assets/.env. The rider and restaurant apps follow the same steps (their ids are com.appifly.deliveryman and com.appifly.foodifly_restaurant on Android, com.appifly.restaurant on iOS). Remember to register the new ids in Firebase (Connect the apps).

4. Run and release

flutter run                          # try it on a device or simulator
flutter build apk --release          # Android APK
flutter build appbundle --release    # Android, for Google Play
flutter build ipa                    # iOS, for the App Store
⚠️

iOS simulator: build with signing enabled (a plain flutter run). An unsigned build has no keychain access, so secure storage fails and every request errors without reaching your server.

To make people update, raise the minimum version under Settings → App Version and turn on Force update.

Release signing

The download contains no signing keys or Apple team. Each app has to be signed with your own key before you publish it. Do this once per app (customer, rider, restaurant).

Android. 1. Create an upload keystore (Java's keytool comes with Android Studio). Run it inside the app's android/app folder and answer the questions:

keytool -genkey -v -keystore upload-keystore.jks -keyalg RSA -keysize 2048 -validity 10000 -alias upload

2. Create android/key.properties (next to settings.gradle.kts) with the passwords you chose:

storePassword=your-keystore-password
keyPassword=your-key-password
keyAlias=upload
storeFile=upload-keystore.jks

storeFile is relative to android/app; an absolute path works too. android/app/build.gradle.kts reads this file for release builds. Without it, flutter build apk --release still works but signs with the debug key, which Google Play rejects.

3. Build with flutter build appbundle --release and upload the .aab to Google Play (turn on Play App Signing when asked).

⚠️

Keep the .jks file and key.properties private and backed up. Do not commit them to a public repository. If you lose the upload key you have to ask Google to reset it before you can publish an update.

iOS (needs a Mac, Xcode and a paid Apple Developer account):

  1. Open ios/Runner.xcworkspace in Xcode (run flutter pub get and cd ios && pod install first).
  2. Select Runner → Signing & Capabilities, tick Automatically manage signing and choose your Team. No team is set in the download.
  3. Set your own Bundle Identifier and register it in App Store Connect and Firebase. Under Signing & Capabilities, keep Push Notifications and Background Modes → Remote notifications, and add your APNs key in the Firebase console.
  4. Run flutter build ipa, then upload build/ios/ipa/*.ipa with Xcode's Organizer or the Transporter app.

In-app updates on Android

On Android, the apps can install a new version from inside the app through Google Play's in-app update feature (the in_app_update package):

  • Optional update: when you publish a newer build on Google Play, the app downloads it in the background and then shows an Update ready message with a Restart button. It never restarts by itself. It asks at most once a day, and not again for a version the person declined.
  • Urgent update: in the Play Console, set the release's in-app update priority to 4 or 5 and the app uses Google's full-screen update instead.
  • Forced update (customer and rider apps): when Force update is on and the installed version is below your minimum, the Update required button runs Google's full-screen update first and opens the store page only if that is not possible.
  • Only for Google Play installs: it does nothing for a sideloaded APK or a debug build, and nothing breaks. To try it, upload two builds with different version codes to Play's internal testing track, install the older one from Play, then publish the newer one.
  • iOS has no such feature: the apps keep opening the App Store page.

Rider app

Folder: rider-app. Set up exactly like the customer app, with these differences:

flutter pub get
cp assets/.env.example assets/.env
KeyWhat to put
API_BASE_URLRequired. Same address as the customer app.
API_TIMEOUTOptional request timeout in seconds (30 if left out).
GOOGLE_MAPS_API_KEYYour Maps key for the map and navigation screens.
PUSHER_APP_KEY, PUSHER_CLUSTEROptional: leave blank, the app reads them from the backend.
  • Firebase: replace android/app/google-services.json, ios/Runner/GoogleService-Info.plist and lib/core/firebase/firebase_options.dart with your project's. (This app keeps its Firebase options in a Dart file too; the easiest way to regenerate it is flutterfire configure.)
  • Package name: applicationId in android/app/build.gradle.kts and the iOS bundle identifier; app label in AndroidManifest.xml and Info.plist.
  • Riders: a rider registers in the app with documents, then waits for approval under Admin → Riders. Only approved, online riders receive job offers. The rider app uses location while a rider is online; review the permission wording for your store listing.

Restaurant app

Folder: restaurant-app. The kitchen on a phone or tablet: owners and staff take live orders without a computer. They sign in with the same accounts they use on the restaurant portal, and each staff role keeps its permissions. Menus, opening hours, team accounts and payouts are managed in the portal.

1. Configure the API address

flutter pub get
cp assets/.env.example assets/.env
KeyWhat to put
API_BASE_URLRequired. Same address as the customer app, e.g. https://your-domain.com/api/v1.

That is the only setting. Pusher, the currency, the app name and the Terms and Privacy links all come from the backend.

2. Live orders and push notifications

  • Live board (Pusher): nothing to set in the app. Once Pusher is set on the backend, new orders and status changes appear instantly with a sound alert and the header shows Live. Without Pusher the board refreshes every 15 seconds.
  • Push (Firebase): new orders reach the owner's phone and the phone of every active staff member who can see orders, even when the app is closed. Replace android/app/google-services.json, ios/Runner/GoogleService-Info.plist and lib/core/firebase/firebase_options.dart with your project's (flutterfire configure writes all three). If firebase_options.dart is left empty the app simply runs without push.
  • iOS: the Push Notifications capability is already set up (ios/Runner/Runner.entitlements). Upload an APNs key in Firebase, and test push on a real iPhone; the simulator does not receive a device token.

3. Your own app id, name and icon

  • Package name: applicationId in android/app/build.gradle.kts (demo: com.appifly.foodifly_restaurant) and the iOS bundle identifier in Xcode (demo: com.appifly.restaurant). iOS bundle ids cannot contain underscores. Both must match the apps you registered in Firebase.
  • App name: android:label in AndroidManifest.xml and CFBundleDisplayName in ios/Runner/Info.plist.
  • Icon and splash: replace the images in assets/branding/ (app_icon.png, app_icon_foreground.png, splash_icon.png), then run dart run flutter_launcher_icons and dart run flutter_native_splash:create. The logo on the sign-in screen is assets/images/app_icon.png.
  • Colors: lib/core/constants/app_colors.dart (main color: primaryOrange), shared with the customer app's design.

4. Run and release

flutter run
flutter build apk --release          # Android APK
flutter build appbundle --release    # Android, for Google Play
flutter build ipa                    # iOS, for the App Store

Sign in with a restaurant owner account, or a staff account the owner created under Team in the portal. The restaurant must be approved under Admin → Restaurants first.

Restaurant portal

Folder: restaurant-portal. This is how restaurant owners and their staff run their whole restaurant from a browser. For taking live orders on a phone or tablet they can also use the restaurant app.

  1. Configure. Copy .env.example to .env.local and set NEXT_PUBLIC_API_URL to your API (e.g. https://your-domain.com/api/v1). The Pusher keys are optional (read from the backend); NEXT_PUBLIC_TIMEZONE is only a fallback, as the portal uses the timezone you set in the admin panel.
  2. Install and run:
    npm ci
    npm run dev -- --port 3001        # development
  3. For production: build and serve it behind HTTPS (for example Nginx proxying to Node). It listens on port 3336:
    npm run build
    npm run pm2:start                 # keeps it running with PM2 (or: npm start)
    PM2 comes with the portal (npm run pm2:logs, pm2:restart, pm2:stop). To use another port, set PORT in .env.local for PM2. NEXT_PUBLIC_API_URL is fixed when you build, so run npm run build again after changing it.
  4. Allow it on the backend. In the backend .env add the portal's address to CORS_ALLOWED_ORIGINS and set FRONTEND_URL; set the Restaurant Portal URL under Settings → Email / SMTP so emails link to it.

Sizes and extras (dish options)

Open a dish (Menu → Edit) and use Options → Add group. A group is a question for the customer, such as Size or Extras:

  • Required means the customer must choose before adding the dish. Leave it off for optional extras.
  • Allow multiple turns the group from pick-one (sizes) into pick-any (extras).
  • Each choice has a name and an extra price added to the dish's price (0 for none).

Customers see the choices in the app and on the website, the total updates as they pick, and the order shows the chosen options to the restaurant, rider and admin. The server prices every order from the restaurant's own list, so a customer cannot change a price. A dish can have up to 10 groups.

How restaurants join

  1. A restaurant applies on the portal's Register page.
  2. You review and approve it under Admin → Restaurants.
  3. The owner signs in, builds the menu, sets opening hours and holidays, and invites staff (manager and kitchen roles) from Team. The live order board plays an alert for new orders.

The portal shows the logo and app name you set in the admin panel, and displays money and times in your currency and timezone.

Customer website

Folder: customer-website. The website lets customers browse, order, track and manage their account from a browser.

  1. Point it at your API, in one of two ways:
    • In .env (recommended when you run it with Node): copy .env.example to .env and set NEXT_PUBLIC_API_URL=https://your-domain.com/api/v1. The site then serves its settings from that value.
    • In public/config.js (also works on static hosting): set your domain and remove the // at the start of both lines:
      window.__FOODIFLY_API_HOST = 'your-domain.com';       // no http://, no /api/v1
      window.__FOODIFLY_API_PROTOCOL = 'https';
      This file is read when the page loads, so no rebuild is needed.
    With neither set, the site assumes the API is on the same host at port 8001, which is only right for local development. More in Connect the apps.
  2. Install and run:
    npm ci
    npm run dev -- --port 3002        # development
  3. Production:
    npm run build
    npm run pm2:start                 # keeps it running with PM2 on port 3002 (or: npm start)
    npm run build first pre-compiles the site's pages and switches to React's production build, so visitors' browsers do no compiling. Edit the .jsx files in public/ as usual; development (npm run dev) uses them directly. Serve it behind HTTPS and add its address to CORS_ALLOWED_ORIGINS in the backend .env.
  4. Set your details in the admin panel (Settings → General): the footer shows your support contacts, and the Terms of Service and Privacy pages use your company name, address, support contacts and governing law from there. Until the governing law is set, the Terms refer to the operator's own country without naming one.
⚠️

The Terms, Privacy and Cookie pages are a draft that describes how the platform works. Have a lawyer review them for your country before launch; the pages carry a "pending legal review" banner until you remove it (LEGAL_REVIEW_PENDING in public/legal-pages.jsx).

Connect the apps

Four things tie your backend to the apps and websites. This page is the one place that lists them all. Do them in this order, after the backend is installed.

What goes where

You needWhere you get itWhere it goes
API addressYour own domain: https://your-domain.com/api/v1Customer app, rider app, restaurant app, restaurant portal, website (one line each, below)
Pusher keysdashboard.pusher.comThe backend .env only. The apps and websites fetch the public key from the backend by themselves
Firebase service-account JSONFirebase console → Project settings → Service accountsThe admin panel: Settings → Firebase & Push
Firebase app files (google-services.json, GoogleService-Info.plist)Firebase console → Project settings → Your appsInside each Flutter app's folder

1. The API address (base URL)

The API address is your backend's APP_URL plus /api/v1. If the admin panel opens at https://food.example.com, the API address is https://food.example.com/api/v1. Test it: open https://food.example.com/api/v1/app-config in a browser. You should see text starting with {.

PartFileLine to setNeeds a rebuild?
Customer appcustomer-app/assets/.envAPI_BASE_URL=https://food.example.com/api/v1Yes: build the app again
Rider apprider-app/assets/.envAPI_BASE_URL=https://food.example.com/api/v1Yes: build the app again
Restaurant apprestaurant-app/assets/.envAPI_BASE_URL=https://food.example.com/api/v1Yes: build the app again
Restaurant portalrestaurant-portal/.env.localNEXT_PUBLIC_API_URL=https://food.example.com/api/v1Yes: run npm run build again
Customer websitecustomer-website/.env
or customer-website/public/config.js
NEXT_PUBLIC_API_URL=https://food.example.com/api/v1
or window.__FOODIFLY_API_HOST = 'food.example.com';
window.__FOODIFLY_API_PROTOCOL = 'https';
.env: build and restart. config.js: no, just save the file
  • The three Flutter files start as .env.example: copy it to assets/.env first. Never put a trailing slash after /api/v1.
  • In config.js the website wants the domain only: no https:// and no /api/v1. Add :8001-style ports only for a test server. In config.js remove the two // at the start of the lines you set.
  • Use https. Phones block plain http addresses in release builds.
  • Testing on a phone or emulator: localhost on the phone is the phone itself. Use your computer's Wi-Fi address (for example http://192.168.1.20:8001/api/v1), or http://10.0.2.2:8001/api/v1 for the Android emulator. Android allows plain http only for the hosts listed in android/app/src/main/res/xml/network_security_config.xml (localhost and the emulator), so add your computer's Wi-Fi address there as another <domain> while testing. Live builds should use https.

Let the browser apps talk to the backend

The portal and website run in a browser, so the backend must allow them. In the backend .env set:

FRONTEND_URL=https://www.example.com
CORS_ALLOWED_ORIGINS=https://www.example.com,https://portal.example.com

Use the exact addresses people type, separated by commas, with no trailing slash. Then run php artisan optimize. Mobile apps do not need this.

2. Pusher (live tracking and new-order alerts)

  1. Sign in at dashboard.pusher.com → Channels → Create app. Pick the cluster nearest your customers. Open App Keys and copy app_id, key, secret and cluster.
  2. Put them in the backend .env:
    BROADCAST_CONNECTION=pusher
    PUSHER_APP_ID=1234567
    PUSHER_APP_KEY=your-key
    PUSHER_APP_SECRET=your-secret
    PUSHER_APP_CLUSTER=eu
    Then run php artisan optimize. The secret stays on the server and is never put in an app.
  3. That is all. The customer app, rider app, restaurant app, portal and website read the public key and cluster from /api/v1/app-config. The PUSHER_APP_KEY lines in the customer and rider .env files and NEXT_PUBLIC_PUSHER_KEY in the portal are optional; leave them blank, or, if you fill them, they must equal the backend values.
  4. Check it: open /api/v1/app-config in a browser and look for "realtime". If it holds your key and cluster, Pusher is on. If it says null, the key, id or secret is missing, or BROADCAST_CONNECTION is not pusher, or you did not run php artisan optimize.
ℹ️

Use hosted Pusher for the mobile apps: the Flutter plugin cannot connect to a self-hosted server. Without Pusher everything still works, but order status and the rider's position only update when the screen is refreshed.

3. Push notifications (Firebase JSON files)

Firebase uses two different kinds of file. People mix them up, so keep them apart:

FileIt is forWhere it goes
Service-account key
a .json with "private_key" inside; name like myproject-firebase-adminsdk-xxxx.json
The backend, so it can send pushesUpload it in the admin panel: Settings → Firebase & Push. Never put it in an app or in a public folder
google-services.jsonThe Android appandroid/app/google-services.json in each Flutter app
GoogleService-Info.plistThe iOS appios/Runner/GoogleService-Info.plist in each Flutter app
firebase_options.dartRider app and restaurant app (they also set Firebase up in code)lib/core/firebase/firebase_options.dart. Regenerate it with flutterfire configure, or copy the values from your Firebase project
  1. In the Firebase console create one project. Add an Android app and an iOS app for each of the three Flutter apps (customer, rider and restaurant), using the package name and bundle id you chose for each (applicationId / bundle identifier, see Customer app). They must match exactly.
  2. For each app, download google-services.json and GoogleService-Info.plist and replace the demo files at the paths in the table. The rider and restaurant apps also need a new firebase_options.dart.
  3. Open Project settings → Service accounts → Generate new private key to download the service-account .json.
  4. In the admin panel go to Settings → Firebase & Push. In Step 1 upload that file. In Step 2 press Test connection. You want Server key uploaded and Google accepts the key to show as OK.
  5. The same page shows Queue worker and Scheduler (cron). Both must be OK: pushes are sent from the queue. See After installing.
  6. iOS only: in Firebase → Project settings → Cloud Messaging, upload an APNs authentication key from your Apple developer account. Without it iPhones receive nothing, while Android works.
  7. Build the apps again so they pick up the new files.
⚠️

The package contains the demo project's Firebase files so the apps compile. Pushes will not reach your customers until you replace them with your own and rebuild.

Check that everything is connected

  1. /api/v1/app-config opens in a browser and shows your currency.
  2. You can sign in to each app and the website against your server (not the demo one).
  3. Place a test order. The restaurant portal and the restaurant app ring and show it within a second or two: Pusher works.
  4. Change the order status in the admin panel. The customer's phone shows a notification: Firebase works.

If something does not work

SymptomLikely cause
The app says it cannot connect, or shows an error on the first screenWrong API_BASE_URL, a trailing slash, http instead of https, or you did not rebuild the app after editing .env
The website or portal shows a network or CORS error in the browser consoleIts address is missing from CORS_ALLOWED_ORIGINS, or you did not run php artisan optimize
The website still calls :8001Neither NEXT_PUBLIC_API_URL in .env nor the two lines in public/config.js are set, or the browser cached the old file (hard refresh)
The portal still uses the old addressNEXT_PUBLIC_* values are fixed at build time: run npm run build again and restart
Orders do not update liverealtime is null in /app-config (see step 2), or the Pusher cluster is wrong
Test connection failsYou uploaded google-services.json instead of the service-account key, or the Firebase Cloud Messaging API is disabled for the project
Test connection is OK but no push arrivesThe queue worker is not running, the app still has the demo Firebase files, the bundle id does not match, or (iPhone) the APNs key is missing

Environment (.env)

The installer writes the backend's .env file for you. You can edit it afterwards; after any change run php artisan optimize (the configuration is cached). Put values containing spaces or # in double quotes. The defaults below are those of .env.example.

KeyAccepted valuesDefaultWhat it does
APP_NAMETextFoodiflySite name used in emails and the installer (the admin panel name and logo are set in Settings → General).
APP_URLFull address, no trailing slashhttp://localhostThe backend's own address. The API address is this plus /api/v1.
FRONTEND_URLFull addressemptyWhere the customer website runs (used in links and CORS).
APP_ENVlocal, productionlocalUse production on a live site.
APP_DEBUGtrue, falsetrueMust be false in production: it hides error details from visitors.
APP_KEYGeneratedemptyEncryption key, created by the installer. Never change it on a live site.
DB_CONNECTIONmysqlmysqlDatabase driver (MySQL or MariaDB).
DB_HOST, DB_PORTHost name, port127.0.0.1, 3306Where the database runs.
DB_DATABASE, DB_USERNAME, DB_PASSWORDTextfoodifly_admin, root, emptyYour empty database and its user.
QUEUE_CONNECTIONdatabase, redis, syncdatabaseWhere background jobs wait (pushes, emails). database needs no extra software; use redis on a busy site (phpredis extension).
CACHE_STORE, SESSION_DRIVERdatabase, file, redisdatabaseCache and login sessions.
SESSION_LIFETIMEMinutes120How long an admin stays signed in when idle.
BROADCAST_CONNECTIONlog, pusher, reverbloglog = no live updates. Use pusher for live tracking and alerts, see Realtime.
PUSHER_APP_ID, PUSHER_APP_KEY, PUSHER_APP_SECRETFrom your Pusher appemptyAll three are required for live updates. The secret stays on the server.
PUSHER_APP_CLUSTERPusher cluster code, e.g. eu, ap2ap2The cluster you picked when creating the Pusher app.
FIREBASE_PROJECT_IDYour Firebase project idfoodifly-testYour Firebase project (the service-account file is uploaded in the admin panel).
GOOGLE_MAPS_KEYMaps API keyemptyServer-side Maps key. The value in Settings → Maps & Location takes precedence.
MAIL_MAILERsmtp, logloglog writes emails to the log instead of sending them. Use smtp live.
MAIL_HOST, MAIL_PORT, MAIL_USERNAME, MAIL_PASSWORDYour SMTP details127.0.0.1, 2525, emptySMTP server. Can also be set in Settings → Email / SMTP.
MAIL_FROM_ADDRESS, MAIL_FROM_NAMEEmail, texthello@example.com, ${APP_NAME}The sender of every email.
CORS_ALLOWED_ORIGINSComma-separated addresses, no trailing slashonly FRONTEND_URLThe website and portal addresses allowed to call the API from a browser. With APP_ENV=local, the usual localhost ports are allowed as well; to test from another device, add its address here.
ADMIN_EMAIL, ADMIN_PASSWORDEmail, passwordadmin@foodifly.com, emptyThe first Super Admin when you seed from the command line. The web installer asks for these itself.
INSTALL_CURRENCY_CODE, INSTALL_CURRENCY_SYMBOL, INSTALL_TIMEZONEe.g. EUR, €, Europe/Pariswritten by the installerUsed once for the first seed. Change them later in Settings → General.
SEED_DEMO_DATAtrue, falsefalseLoads sample restaurants and orders. Keep false on a live server.
SEED_DEMO_IMAGEStrue, falsetrueDownloads demo photos when demo data is loaded.
DEMO_MODEtrue, falsefalseTurns the site into a read-only public preview. See Demo mode.
DEMO_ADMIN_EMAIL, DEMO_ADMIN_PASSWORDEmail, passwordemptyOnly with DEMO_MODE=true: the admin account the demo sign-in page offers.
LOG_LEVELdebug, info, warning, errordebugHow much goes into storage/logs. Use warning in production.

Payment gateways

Switch gateways on under Admin → Settings → Payment Gateways. Only enabled methods appear to customers. Cash on delivery and the customer wallet need no keys.

💰

Fees are not included in the purchase price. Stripe, PayPal, Razorpay, Flutterwave, Paystack, Mercado Pago, Paymob, SSLCommerz, bKash and senangPay charge their own transaction fees, set by the provider and paid by you to the provider. The same applies to Pusher Channels (see Realtime).

⚠️

Card gateways charge in your platform currency (Settings → General). Check that your gateway account supports it. bKash operates in Bangladeshi taka only, and regional gateways (SSLCommerz, senangPay, Paystack, Paymob, Mercado Pago, Flutterwave) support only the currencies on your account; enable a gateway only if it can charge in your platform currency.

GatewayWhat you enterNotes
Stripe (cards)Publishable key, secret key, mode (test/live)The app asks the server for the publishable key, so it is not stored in the app. No webhook is needed; the Webhook Secret field is not required.
PayPalClient ID, client secret, mode; optional Webhook IDCustomers pay in the PayPal checkout page.
Razorpay (cards, UPI, wallets)Key ID and key secret (rzp_test_… keys run in test mode)India. Customers pay on a Razorpay payment page and return to /api/v1/payments/razorpay/callback.
bKashApp key, app secret, username, password, sandbox switchCallback URL: https://your-domain.com/api/v1/payments/bkash/callback
SSLCommerzStore ID, store password, sandbox/liveBangladesh. Customer pays on SSLCommerz’s page and is returned to /api/v1/payments/sslcommerz/callback.
senangPayMerchant ID, secret key, sandbox/liveMalaysia (MYR). Return URL: /api/v1/payments/senangpay/callback.
PaystackPublic key, secret keyNigeria, Ghana, South Africa, Kenya. Set the callback URL to /api/v1/payments/paystack/callback in the Paystack dashboard.
PaymobAPI address, API key, secret key, public key, integration IDsEgypt, Pakistan, UAE, Saudi Arabia, Oman. Integration IDs are the payment methods you enabled in Paymob (comma separated).
Mercado PagoPublic key, access tokenLatin America. A TEST- access token uses the sandbox.
FlutterwavePublic key, secret keyAfrica (v3 API). Customers are returned to /api/v1/payments/flutterwave/callback.
Cash on deliveryJust the on/off switchRiders record cash collected; you see it under Riders → Cash.

The platform confirms card payments by checking with the gateway when the customer returns from it, and bKash and the redirect gateways (Razorpay, SSLCommerz, senangPay, Paystack, Paymob, Mercado Pago, Flutterwave) through their callback URLs above, always by asking the gateway again before an order is marked paid. You do not need to register a webhook URL for Stripe, PayPal or Razorpay.

🚨

Start in test / sandbox mode, place a test order through each gateway you enable, and only then switch to live keys. Secret keys are stored on your server and never sent to the apps.

Push notifications (Firebase)

  1. Create a project at console.firebase.google.com.
  2. Add an Android app and an iOS app for the customer app, the rider app and the restaurant app, using the package names and bundle ids you chose. Download google-services.json and GoogleService-Info.plist into each Flutter app (see Customer app and Rider app).
  3. In Project settings → Service accounts, generate a private key (a .json file).
  4. In the admin panel open Settings → Firebase & Push, upload that file and press Test connection.
  5. Make sure the queue worker is running: pushes are sent from the queue.

For iOS you also need an APNs key uploaded in Firebase (Project settings → Cloud Messaging).

External services and outbound connections

Foodifly contains no license check, no purchase-code verification and no usage reporting. It never sends anything to the author. The only outbound connections are the ones below; apart from OpenStreetMap, they go to services you set up, and only when you enable them:

ServiceWho connectsWhenWhat is sent
Firebase Cloud MessagingYour serverWhen a push notification is sentThe device token and the notification text, to Google
Pusher ChannelsYour server and the apps/browsersLive tracking and order alertsOrder status and rider position events
Google Maps, Places and RoutesYour server and the admin pages that show a mapAddress search, delivery routes, the zone and rider mapsAddresses and coordinates, with your Maps key
Payment gateways (Stripe, PayPal, Razorpay, ...)Your server and the customer's browser or appOnly for gateways you switch onThe order amount and payment references
OpenStreetMap (map tiles and Nominatim)The visitor's browser, on the customer websiteThe live tracking map, and "use my location" on the home pageMap tile requests for the area shown, and the coordinates being turned into an address. The map shows the required "© OpenStreetMap contributors" credit. Heavy use needs your own tile provider; see the OpenStreetMap tile usage policy.
SMTP serverYour serverWhen an email is sentThe email
UnsplashYour serverOnce, when you load the optional demo dataNothing; it downloads about 4 MB of free demo photos (turn off with SEED_DEMO_IMAGES=false)
✅

The admin panel, the installer, this documentation, the customer website and the mobile apps all carry their own scripts, icons and fonts (the admin ones are in public/assets and the built files). They work offline and send no visitor data to font or CDN hosts.

Your Terms and Privacy pages should name the services you enable. See Customer website for the legal-page notes.

Maps

  1. In the Google Cloud console, create an API key and enable Maps SDK for Android, Maps SDK for iOS and, for address search, Places API (New).
  2. Put the key in Settings → Maps & Location, and in each app's assets/.env as GOOGLE_MAPS_API_KEY.
  3. Set the default latitude, longitude and zoom to your city or country; the maps and the demo data start there.
  4. Restrict the key by application id (and SHA-1 on Android) in the Google Cloud console. It is readable in any shipped app.

Without a Maps key the map screens stay blank; the apps can still be developed with DISABLE_MAP_PICKER=true (customer app).

Delivery route on the tracking map

While an order is on its way, the customer's tracking map draws the road route from the rider to the door, with the distance and time left ("5 min · 1.8 km away"), and the arrival time follows it. Before pickup it shows the planned route from the restaurant.

  1. In the same Google Cloud project, enable the Routes API.
  2. Under Settings → Maps & Location, add a key in Places API Key (or leave it blank to use the Maps key above) and keep Delivery route on the tracking map set to On. This key is used only on your server and never sent to the apps, so restrict it to the Routes and Places APIs and to your server's IP address.

The apps ask your server, not Google, and the server reuses a route for 30 seconds to keep Google usage low. With the setting off, no key, or if Google refuses the request, customers still see an estimated distance and time (marked with “~”) but no line on the map.

Email

Set your SMTP details under Settings → Email / SMTP (mail driver, host, port, encryption, username, password, From address and name) and use the test email button. Order, password-reset and notification emails are sent from the queue, so the worker must be running. The email templates can be edited under Notifications.

💰

Email costs are yours. Foodifly sends email through the SMTP server you choose. You are responsible for any costs of your email service provider (for example sending limits, monthly plans or per-email fees), and for keeping your sending domain set up so emails are delivered. The purchase price does not include an email service.

Realtime (Pusher)

Live order tracking, new-order alerts for restaurants and job offers for riders use Pusher Channels.

💰

Pusher is a paid-service dependency. It has a free plan with limits on connections and messages; a busy platform needs a paid plan. Its costs are set by Pusher, paid by you to Pusher, and are not covered by the purchase price.

  1. Create an app at dashboard.pusher.com and note its App ID, Key, Secret and Cluster.
  2. In the backend .env set BROADCAST_CONNECTION=pusher and the four PUSHER_APP_* values (all of id, key and secret are required; with any missing the app falls back to the log driver and live updates are off). Run php artisan optimize.
  3. The website, the portal and the mobile apps read the key and cluster from the backend automatically. If you do set them in an app's .env, they must match the backend's.
ℹ️

Hosted Pusher is needed for the mobile apps. A self-hosted Reverb server can serve the web portal, but the Flutter Pusher plugin can only connect to Pusher's hosted service. Channels used: private-order.{id}, private-rider.{id}, private-restaurant.{id}.

Customization

From the admin panel (no code)

  • App name, tagline and logo, currency, timezone, support details, company details: Settings → General.
  • Delivery fees, minimum order, commission, loyalty, tips, wallet limits and payout rules: Settings and Delivery Zones.
  • Home banners, cuisines and the apps' intro slides: Banners, Cuisines, Onboarding.

Colors and branding in code

WhereFile
Customer app colorscustomer-app/lib/core/constants/app_colors.dart (main color: primaryOrange)
Rider app colorsrider-app/lib/core/constants/app_colors.dart (main color: primaryGreen)
Restaurant app colorsrestaurant-app/lib/core/constants/app_colors.dart (main color: primaryOrange)
Restaurant portalrestaurant-portal/tailwind.config.ts
Admin panelbackend-and-admin-panel/tailwind.config.js, then npm run build
Website colors and logoscustomer-website/app/globals.css (CSS variables such as --primary) and the images in customer-website/public/brand/

The website, portal and apps are in English. See Known limitations.

Updating

  1. Back up your database and the .env file and storage/ folder.
  2. Upload the new files over the old ones (keep your .env and storage/).
  3. Run bash scripts/deploy.sh. It checks the server, installs packages, builds the admin assets, runs migrations (the site is in maintenance only for that step), refreshes the caches, restarts the queue workers and reports whether the worker, scheduler, Firebase key and currency are set up. It never seeds data.
  4. The mobile apps only need rebuilding if their own code changed. To make people update, raise the minimum version under Settings → App Version.

Versioning and updates policy

  • Versions are numbered major.minor.patch (for example 1.2.3). A patch fixes bugs, a minor release adds features and stays compatible with your data, a major release may need changes and says so in its notes.
  • Updates are free for every buyer on CodeCanyon: download the new version from your Downloads page. Each release lists what changed in the Changelog, including any database migrations.
  • Database changes always ship as migrations, so an update never needs you to edit tables by hand. Back up first, then follow the steps above.
  • If you changed the code yourself, keep your edits in a copy and compare them against the new version before overwriting files.
  • When you publish a new mobile app build, raise the minimum under Settings → App Version and use Force update only after the new app is live in the stores.

Go-live checklist

  • APP_ENV=production and APP_DEBUG=false.
  • HTTPS on the API, the website and the portal.
  • Demo data and demo accounts removed (php artisan foodifly:demo-remove) and a strong Super Admin password set.
  • The queue worker and the cron scheduler are running (the Firebase & Push tab shows both).
  • Gateways switched from test to live keys, and a real test order placed through each.
  • Google Maps and Firebase API keys restricted to your app ids; your own Firebase files in every app.
  • CORS_ALLOWED_ORIGINS lists only your website and portal.
  • The web installer is locked (storage/installed.lock exists). Never delete that file on a live site.
  • Database and storage/ backups scheduled.
  • Legal pages reviewed by a lawyer and your governing law set.

Troubleshooting

SymptomFix
The installer shows a 404The domain must point at the public folder, and Apache needs mod_rewrite (Nginx needs the standard Laravel try_files rule).
Installer says "exec() is disabled"Ask your host to enable it, or use the command-line install.
Installer says admin assets (public/build) are missingThe download does not include them. Run npm ci && npm run build in the backend folder, or run it on your computer and upload public/build.
Installer says vendor/ is missingRun composer install --no-dev --optimize-autoloader in the backend folder, or run it on your computer and upload vendor/.
500 error after install or updateRead storage/logs/laravel.log; then run php artisan optimize:clear. Check folder permissions on storage and bootstrap/cache.
A .env change has no effectRun php artisan optimize (the configuration is cached).
Admin panel has no stylesRun npm ci && npm run build in the backend folder (step 0), or upload public/build built on your computer.
Images or uploads do not showRun php artisan storage:link and check APP_URL.
Pushes never arriveCheck the queue worker is running, upload the service account and press Test connection, and that the app has your own Firebase files.
No live updates (tracking, new-order alerts)BROADCAST_CONNECTION=pusher with id, key, secret and cluster all set; then php artisan optimize. App and backend must use the same key and cluster.
Riders stay "online" for everThe cron scheduler line is missing.
The app shows "Connection refused" or a network errorlocalhost on a phone is the phone. Use your server's address (or your computer's LAN IP while testing) in API_BASE_URL, and run a local backend with php artisan serve --host 0.0.0.0.
Every request fails on an iOS simulatorBuild with signing (plain flutter run); an unsigned build cannot use the keychain.
App build fails: assets/.env not foundcp assets/.env.example assets/.env in that app.
Map screens are blankThe Maps key is missing, wrong, or not enabled for that platform.
The website cannot reach the APISet window.__FOODIFLY_API_HOST in public/index.html and add the site to CORS_ALLOWED_ORIGINS.
A restaurant cannot sign in to the portalIt has to be approved under Admin → Restaurants first.

FAQ

Can I use it without the mobile apps?

Yes. The admin panel, the customer website and the restaurant portal work on their own. Riders do need the rider app.

Is there a restaurant mobile app?

Yes. The restaurant app handles live orders and dish availability on a phone or tablet. Menus, opening hours, team accounts and payouts are managed in the web portal.

Can I use more than one currency?

The platform has one currency, set in Settings → General (and chosen in the installer). All apps, gateways and reports use it.

Which gateways can I use in my country?

Stripe and PayPal depend on what your own gateway account supports. The other gateways are regional: Razorpay in India, bKash and SSLCommerz in Bangladesh, senangPay in Malaysia, Paystack and Flutterwave in Africa, Paymob in the Middle East and Pakistan, Mercado Pago in Latin America. Cash on delivery and the wallet work everywhere.

Does it send SMS?

No. Notifications go out as push and email. See Known limitations.

Can I translate it?

The interface is in English. Translation is not built in; the text is in the source files.

How do I add a delivery zone and fees?

Delivery Zones → Add zone, then open the zone's Fees to set the fee tiers by distance. Assign riders to a zone from their profile.

How do I give a restaurant a different commission?

Open the restaurant and set its commission rate or add commission rules; they override the platform defaults for that restaurant.

How do I update?

ℹ️ Known limitations

So you are not surprised, here is what version 1.0.0 does not include:

  • No SMS. Notifications are delivered by push and email only; there is no SMS feature in this version.
  • The restaurant app covers the kitchen only. Building menus, opening hours, team accounts, analytics and payouts are in the web portal.
  • English only. There is no translation system; the apps, portal, website and admin panel are in English.
  • One platform currency. No per-customer currency switching.
  • Sign-in is by email and password. The backend has a Google sign-in endpoint, but none of the included apps or websites uses it.
  • Mobile Pusher needs hosted Pusher. Self-hosted Reverb works for the web portal only.
  • Regional gateways. bKash is Bangladesh-only; the other gateways work in the countries their provider supports. Test each in its sandbox before going live.

Support

ℹ️

Support is provided through the Envato comments & messaging system. Before contacting support, please:

  • Read through this documentation fully
  • Check the FAQ and Troubleshooting sections above
  • Search existing comments on the CodeCanyon item page

What's included in support

  • ✅ Answering questions about item features
  • ✅ Fixing confirmed bugs reported by the buyer
  • ✅ Assistance with item configuration

What's not included

  • ❌ Custom feature development
  • ❌ Installation service on your server
  • ❌ Third-party service support (payment gateways, Firebase, Google Maps, Pusher, hosting)
  • ❌ Assistance with items you have modified beyond the original source

Response time

Support responses are typically provided within 24–48 hours on business days.

Changelog

v1.0.0: Initial release

  • Laravel 13 backend and admin panel with web installer, roles and permissions, reports, coupons, zones and commission rules.
  • Customer, rider and restaurant Flutter apps (Android and iOS).
  • Restaurant owner web portal and customer website (Next.js).
  • Payments: Stripe, PayPal, Razorpay, bKash, SSLCommerz, senangPay, Paystack, Paymob, Mercado Pago, Flutterwave, cash on delivery and customer wallet.
  • Live order tracking and alerts (Pusher), push notifications (Firebase), loyalty points and wallet.

Credits & licenses

Foodifly is built on open-source software. All of the libraries below allow commercial use and redistribution. Their licenses are included with each package in vendor/, node_modules/ and the Flutter package cache.

Backend and admin panel

PackageLicenseUsed for
Laravel Framework 13MITBackend framework
Laravel SanctumMITAPI authentication
LivewireMITInteractive admin components
Spatie Laravel PermissionMITRoles and permissions
kreait/laravel-firebaseMITPush notifications (FCM)
Intervention ImageMITImage processing
barryvdh/laravel-dompdfMITPDF invoices
L5-SwaggerMITAPI reference page
Swagger UI 5.32.6 (bundled in public/api-docs/swagger-ui)Apache-2.0API reference page
Pusher PHP ServerMITRealtime broadcasting
Tailwind CSS, Alpine.js, Vite, AxiosMITAdmin panel styles and scripts
Chart.js 4.4.3 (bundled in public/assets)MITAdmin charts
Font Awesome Free 6.5.1 (bundled in public/assets/fontawesome)Icons CC BY 4.0, fonts SIL OFL 1.1, code MITAdmin icons

Customer, rider and restaurant apps (Flutter)

PackageLicense
FlutterBSD-3-Clause
get, dio, pretty_dio_logger, equatable, lottie, geolocator, geocoding, pusher_channels_flutter, cached_network_image, smooth_page_indicator, permission_handler, logger, flutter_dotenv, in_app_update, cupertino_iconsMIT
firebase_core, firebase_messaging, google_maps_flutter, flutter_secure_storage, shared_preferences, url_launcher, webview_flutter, flutter_local_notifications, package_info_plus, intl, shimmerBSD-3-Clause
image_pickerApache-2.0
Poppins, DM Sans and JetBrains Mono fonts (bundled in each app's assets/fonts)SIL Open Font License 1.1

The full list is in each app's pubspec.yaml; Flutter also shows every license inside the app via its license page.

Restaurant portal and customer website

PackageLicense
Next.js 16, React 18/19, pusher-js, @babel/standaloneMIT
Tailwind CSSMIT

Fonts, maps and images

  • Fonts (Inter, JetBrains Mono, Poppins, DM Sans): SIL Open Font License 1.1. Inter and JetBrains Mono are bundled with the admin panel and this documentation; the mobile apps and the website bundle their own font files.
  • Google Maps, Firebase, Pusher, Stripe, PayPal, Razorpay, bKash, SSLCommerz, senangPay, Paystack, Paymob, Mercado Pago, Flutterwave are third-party services with their own terms; the names and logos belong to their owners.
  • Demo photos are free images from Unsplash, downloaded by the demo seeder only. They are not part of the package and should be removed from a live site (php artisan foodifly:demo-remove).