QuickList is a full-featured online classifieds marketplace that lets users buy and sell almost anything — vehicles, electronics, property, jobs, services and more. This document covers the complete setup of the Admin Panel, the User Panel and the mobile application.
QuickList is a modern, scalable "Ads Buy & Sell" classifieds marketplace. Sellers post advertisements for their products or services, buyers search and browse listings by category and location, and the two sides connect through built-in real-time chat. The platform is built on secure, modern technologies:
All three clients (Admin, Web and Mobile) share a single Firebase project, so data stays consistent in real time across the whole platform.
Important — third-party services & costs: QuickList integrates several external services operated by third parties that may incur their own charges, separate from this product:
You are responsible for creating and funding these third-party accounts. Please review each provider's current pricing before going live.
QuickList has three parts — a Flutter mobile app and two Laravel web panels (the Admin Panel and the User Panel) — all powered by Google Firebase. What you need depends on which part you are setting up.
Full steps are in App Documentation → Setting up Flutter.
mod_rewrite.Full server details are in Web Documentation → Server Requirement.
The QuickList mobile application is built with Flutter (Dart). The steps below are self-contained — follow them in order to install Flutter and run the app. The official reference is docs.flutter.dev, but you should not need it to complete this guide.
Download the stable SDK from the Flutter website and extract it, or clone it with Git:
# Windows (PowerShell) — install to C:\src\flutter git clone https://github.com/flutter/flutter.git -b stable C:\src\flutter # macOS / Linux — install to ~/development/flutter git clone https://github.com/flutter/flutter.git -b stable ~/development/flutter
Add the Flutter bin folder to your PATH:
# Windows: add this line under Path in "Edit the system environment variables" C:\src\flutter\bin # macOS / Linux: add to ~/.zshrc or ~/.bashrc, then restart the terminal export PATH="$PATH:$HOME/development/flutter/bin"
sudo xcodebuild -license accept.Accept the Android SDK licences from the terminal:
flutter doctor --android-licenses
Run Flutter's built-in diagnostic — each item you intend to use should show a green tick
([✓]):
flutter doctor -v
Fix anything marked [✗] or [!] before continuing (most often a missing
Android SDK component or an unaccepted licence).
Use VS Code (install the Flutter and Dart extensions) or Android Studio (install the Flutter plugin from Settings → Plugins).
Extract the mobile app source, open the folder in your IDE, and fetch its dependencies:
cd quicklist_app flutter pub get
Start an emulator (or connect a physical device), confirm it is detected, then launch the app:
flutter devices # confirm a device/emulator is listed flutter run # build and launch QuickList
Changing the package name (also known as the bundle identifier or application ID) in a Flutter project involves a few steps:
android directory within your Flutter project.AndroidManifest.xml file located in app/src/main.package attribute in the <manifest> tag and change its
value to your desired package name.ios/Runner.xcodeproj using Xcode.pubspec.yaml in the project root and update the name field.android: package field under flutter: with your new package
name.Update MainActivity.java/kt (Android) and AppDelegate.swift (iOS) references
to the old package name, then clean and rebuild the project.
To change the launcher icon (app icon) in a Flutter project:
Android: Replace the existing ic_launcher.png files in the mipmap folders
inside android/app/src/main/res (mipmap-hdpi, mipmap-mdpi,
mipmap-xhdpi, mipmap-xxhdpi, mipmap-xxxhdpi).
iOS: Replace the AppIcon set in ios/Runner/Assets.xcassets via Xcode.
Alternatively, use the flutter_launcher_icons package to generate icons from a single
source image:
dev_dependencies: flutter_launcher_icons: "^0.13.1"
flutter pub get flutter pub run flutter_launcher_icons:main
Open android/app/src/main/AndroidManifest.xml, locate the
<application> tag, and change the android:label attribute.
Open ios/Runner/Info.plist, locate <key>CFBundleDisplayName</key>
and change the associated <string> value.
<key>CFBundleDisplayName</key> <string>QuickList</string>
To set the Google Maps API key in the mobile app for both platforms:
In android/app/src/main/AndroidManifest.xml, inside the
<application> element, set the com.google.android.geo.API_KEY
meta-data value:
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="YOUR_API_KEY_HERE"/>
In ios/Runner/AppDelegate.swift, provide the key inside
didFinishLaunchingWithOptions:
GMSServices.provideAPIKey("YOUR_API_KEY_HERE")
QuickList supports Email/Password, Phone (OTP) and Google sign-in (Apple sign-in optional). Enable the providers you need in the Firebase Console:
Connect the mobile app to the same Firebase project used by the Admin Panel and User Panel.
google-services.json (Android) and
GoogleService-Info.plist (iOS).google-services.json in android/app/.GoogleService-Info.plist in ios/Runner/ (via Xcode).Run flutter pub get and make sure Firebase is initialized on startup:
import 'package:firebase_core/firebase_core.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
Google and Phone sign-in on Android require SHA-1 / SHA-256 fingerprints registered in Firebase.
./gradlew signingReport
keytool -list -v -keystore path-to-your-keystore-file -alias your-alias-name
In the Firebase Console → Project Settings → Your Android app → Add fingerprint, paste
the SHA-1 and SHA-256 values, then re-download google-services.json.
Once the mobile app is configured, build a signed release version and publish it to the app stores.
Android — from the app's root folder, build an app bundle (recommended for the Play Store) or an APK:
flutter build appbundle --release # .aab for Google Play flutter build apk --release # .apk for direct install
iOS (macOS + Xcode required) — build the IPA:
flutter build ipa --release
The signed release build uses the keystore / signing configuration from Generate SHA-1 & SHA-256 Keys.
.aab under Production → Create new
release.This error typically arises when the google-services.json file is missing or misplaced.
google-services.json is placed in the android/app directory.android/build.gradle.flutter clean, then flutter pub get, and rebuild the project.mod_rewrite enabled.Node.js (with npm) is required for the Firebase CLI, the Firestore import/export and indexing scripts, and the admin panel's cron/utility scripts. To install it:
node and npm
and adds them to your system PATH).node -v npm -v
Then install the Firebase CLI globally (used for Firestore indexing and deploys):
npm install -g firebase-tools
Firebase is the primary backend for the Admin Panel, User Panel and Mobile App. Create one Firebase project and use its credentials across all clients.
15.1. Go to the Firebase console: https://firebase.google.com/
15.2. Click "Go to console" in the top right corner.
15.3. Click "Create a project" (or "Add project"), enter your project name and click "Continue".
15.4. Configure Google Analytics as desired, then click "Create project" and wait for it to finish provisioning.
15.5. On the project overview page, click the Web icon
(</>) to add a web app.
15.6. You can also add a web app from Settings → General → Your apps
— click the Web (</>) icon there.
15.7. Give the app a nickname and click "Register app".
15.8. Firebase then shows your web app's firebaseConfig values (API key,
auth domain, project ID, storage bucket, messaging sender ID, app ID) on the Add Firebase
SDK screen. Copy these into the .env files of both the Admin and User panels.
15.9. You can retrieve these config values again at any time from Settings → General → Your apps — select your web app and open SDK setup and configuration (the Config option).
15.10. In the left sidebar, open Firestore Database and click "Create database".
15.11. Select the edition and click "Next".
15.12. Choose the database ID and a location.
15.13. Choose the security mode and click "Create".
Named database (staging vs production): QuickList supports a named Firestore
database via the FIRESTORE_DATABASE_NAME env value. Leave it as (default)
for production. Internal/staging environments may use a separate named database (e.g.
staging). Keep the same value across the Admin and User panels.
15.14. Open Storage in the left sidebar and enable it — QuickList stores advertisement images, avatars and seller documents here.
15.15. Update your Firestore & Storage security rules so the panels can read public data and authenticated users can write their own documents. Open the Rules tab, paste the rules below, and click Publish.
These are the Firestore security rules used by QuickList. They grant the admin full
access, allow public read of catalog collections (categories, settings, blogs, etc.), keep the
settings/payment document private (readable only by your Cloud Functions
via the Admin SDK), and restrict writes so users can only modify their own records.
A complete, ready-to-deploy firestore.rules file is included in the Firebase
Rules package. Deploy it either way:
firebase deploy --only firestore:rules (see its
README).rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function isAdmin() { return request.auth != null && request.auth.token.admin == true; }
function isSelf(uid) { return request.auth != null && request.auth.uid == uid; }
function ownsField(f) {
return request.auth != null && (
(resource != null && resource.data[f] == request.auth.uid) ||
request.resource.data[f] == request.auth.uid
);
}
match /{document=**} { allow read, write: if isAdmin(); }
match /categories/{d} { allow read: if true; }
match /custom_fields/{d} { allow read: if true; }
match /languages/{d} { allow read: if true; }
match /currencies/{d} { allow read: if true; }
match /countries/{d} { allow read: if true; }
match /states/{d} { allow read: if true; }
match /cities/{d} { allow read: if true; }
match /areas/{d} { allow read: if true; }
// Public app settings, EXCEPT the payment document (gateway configs) — the
// payment doc is read only by Cloud Functions via the Admin SDK, never by clients.
match /settings/{doc} { allow read: if doc != 'payment'; }
match /settings/payment { allow read, write: if false; }
match /feature_sections/{d} { allow read: if true; }
match /slider_sections/{d} { allow read: if true; }
match /safety_tips/{d} { allow read: if true; }
match /faqs/{d} { allow read: if true; }
match /cms_pages/{d} { allow read: if true; }
match /blogs/{d} { allow read: if true; }
match /on_boarding/{d} { allow read: if true; }
match /report_reasons/{d} { allow read: if true; }
match /subscription_packages/{d} { allow read: if true; }
match /notification_templates/{d} { allow read: if true; }
match /users/{uid} {
allow read: if true; // public profile (name, photo, rating)
// Owner can write anything; any signed-in user may update ONLY the
// review aggregate fields (written when submitting a review).
allow create, delete: if isSelf(uid);
allow update: if isSelf(uid)
|| ( request.auth != null
&& request.resource.data.diff(resource.data).affectedKeys()
.hasOnly(['reviewCount', 'reviewSum']) );
match /{sub=**} {
allow read, write: if isSelf(uid);
}
}
match /advertisements/{ad} {
allow read : if true;
allow create : if ownsField('userId');
allow delete : if ownsField('userId');
// Owner can edit anything; any signed-in user may only bump the counters.
allow update : if ownsField('userId')
|| ( request.auth != null
&& request.resource.data.diff(resource.data).affectedKeys()
.hasOnly(['viewCount', 'likeCount', 'updatedAt']) );
// Per-user view records (advertisements/{ad}/views/{uid})
match /views/{uid} {
allow read, write: if request.auth != null;
}
}
match /notifications/{n} {
allow read: if ownsField('userId');
allow update: if ownsField('userId');
allow create: if request.auth != null;
}
match /seller_verifications/{d} { allow read, write: if ownsField('userId'); }
match /seller_documents/{d} { allow read, write: if ownsField('userId'); }
match /user_subscriptions/{d} { allow read, write: if ownsField('userId'); }
match /user_reports/{d} { allow read, write: if ownsField('userId'); }
match /favorite_item/{d} { allow read, write: if ownsField('userId'); }
match /review/{d} {
allow read: if true;
allow create, update: if ownsField('reviewerId');
}
match /chat/{chatId} {
// get/list: participants only; also allow the existence-check on a
// not-yet-created conversation (resource == null).
allow read: if request.auth != null &&
( resource == null
|| resource.data.buyerId == request.auth.uid
|| resource.data.sellerId == request.auth.uid );
allow create: if request.auth != null &&
( request.resource.data.buyerId == request.auth.uid
|| request.resource.data.sellerId == request.auth.uid );
allow update, delete: if request.auth != null &&
( resource.data.buyerId == request.auth.uid
|| resource.data.sellerId == request.auth.uid );
match /messages/{m} {
allow read, write: if request.auth != null;
}
}
}
}
These are the Firebase Storage security rules used by QuickList. They allow public reads of catalog images (sliders, categories, blogs, and more) and restrict uploads to the owning user with image type and size validation. Open Storage → Rules, replace the contents with the rules below, and click Publish:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
function isAdmin() { return request.auth != null && request.auth.token.admin == true; }
function isOwner(uid) { return request.auth != null && request.auth.uid == uid; }
function validImage(mb) { return request.resource.size < mb * 1024 * 1024 && request.resource.contentType.matches('image/.*'); }
// Admin has full access everywhere
match /{allPaths=**} { allow read, write: if isAdmin(); }
// Public reads
match /sliders/{allPaths=**} { allow read: if true; }
match /custom_fields/{allPaths=**} { allow read: if true; }
match /categories/{allPaths=**} { allow read: if true; }
match /subscription_packages/{allPaths=**} { allow read: if true; }
match /blogs/{allPaths=**} { allow read: if true; }
match /cms_pages/{allPaths=**} { allow read: if true; }
match /faqs/{allPaths=**} { allow read: if true; }
// Flutter user uploads
match /advertisements/{userId}/{allPaths=**} {
allow read : if request.auth != null;
allow write : if isOwner(userId) && validImage(10);
}
match /profileImage/{userId}/{allPaths=**} {
allow read : if request.auth != null;
allow write : if isOwner(userId) && validImage(5);
}
match /seller_verifications/{userId}/{allPaths=**} {
allow read : if isOwner(userId);
allow write : if isOwner(userId) && validImage(5);
}
match /verification_documents/{userId}/{allPaths=**} {
allow read, write: if isOwner(userId);
}
}
}
QuickList ships with a set of seed collections (categories, currencies, countries, settings, etc.). Import them into your Firestore database using the provided Node.js utility. To perform a Firebase Collection Import/Export:
16.1. Install Node.js from https://nodejs.org/en/download/.
16.2. Unzip the provided "Firebase Import Export Collections" package.
16.3. Configure the credentials.json file (service account). In the
Firebase Console go to Project Settings → Service accounts → Node.js, click "Generate
new private key", and save the downloaded file as credentials.json.
16.4. Open a PowerShell/terminal window in the extracted folder (hold
Ctrl+Shift, right-click → "Open PowerShell window here").
16.5. Run the import / export commands:
To import all collections:
npx -p node-firestore-import-export firestore-import -a credentials.json -b collections.json
To export all collections:
npx -p node-firestore-import-export firestore-export -a credentials.json -b collections.json
Please Note: If you use a named database (e.g. staging), make sure the
import tool targets the correct database, and always verify that your Firebase credentials in
credentials.json are correct before running these commands.
QuickList's queries (search, category filtering, sorting) require composite Firestore indexes. Deploy
them from the provided firestore.indexes.json:
17.1. Install Node.js (if not already installed) and the Firebase CLI:
npm install -g firebase-tools
17.2. Unzip the provided "Firebase Indexing" package and open a PowerShell/terminal in that folder.
17.3. Log in to Firebase:
firebase login
17.4. Initialize Firestore configuration:
firebase init
Choose Firestore: Configure security rules and index files, select Use an
existing project, and pick your QuickList project. Accept the default file names
(firestore.rules, firestore.indexes.json).
Please Note: Use the arrow keys to navigate and the space bar to select options in the CLI prompts.
17.5. Copy the provided index definitions into firestore.indexes.json,
then deploy:
firebase deploy --only firestore:indexes
QuickList ships a complete, ready-to-deploy firestore.rules file in the
Firebase Rules package. These rules grant the admin full access, allow public read of
catalog collections, keep the settings/payment document private (read
only by your Cloud Functions via the Admin SDK), and restrict each user's documents to their owner.
Deploy them:
18.1. Install Node.js (if not already) and the Firebase CLI:
npm install -g firebase-tools
18.2. Unzip the provided "Firebase Rules" package and open a PowerShell/terminal in
that folder (the one containing firebase.json).
18.3. Log in to Firebase:
firebase login
18.4. Select your QuickList project:
firebase use YOUR_PROJECT_ID
18.5. Deploy the security rules:
firebase deploy --only firestore:rules
Please Note: Keep payment secret keys only in the settings/payment
document (locked by these rules and read server-side by your Cloud Functions) or in Google Secret
Manager — never in a client-readable location. The full rules are also listed under
Create Firebase Project → Firestore Security Rules.
The Admin Panel is a Laravel 12 application. Ensure your server meets the requirements in the Server Requirement section. Upload the Admin Panel package to your server (domain or subdomain) and extract it.
The admin panel uses MySQL only for admin authentication (users, roles, permissions), sessions and jobs. Create a database and user from your server's control panel.
19.1.1. Open MySQL Databases in cPanel.
19.1.2. Enter a name and create a new database.
19.1.3. Create a new database user with a strong password.
19.1.4. Add the user to the database, grant ALL PRIVILEGES, and click "Make Changes".
19.2.1. Install dependencies and generate the application key:
composer install cp .env.example .env php artisan key:generate npm install
19.2.2. Edit the .env file and configure the database connection and your
Firebase web app credentials:
DB_DATABASE=your_database DB_USERNAME=your_db_user DB_PASSWORD=your_db_password FIREBASE_APIKEY= FIREBASE_AUTH_DOMAIN= FIREBASE_DATABASE_URL= FIREBASE_PROJECT_ID= FIREBASE_STORAGE_BUCKET= FIREBASE_MESSAAGING_SENDER_ID= FIREBASE_APP_ID= FIREBASE_MEASUREMENT_ID= # Firestore named database — '(default)' FIRESTORE_DATABASE_NAME=(default) # Node binary path used by the cron/utility scripts NODE_PATH=
To obtain these Firebase credentials, open the Firebase Console → Project Settings → General → Your apps → Web app, and copy the config values.
19.2.3. Place your Firebase service account file at
storage/app/firebase/credentials.json (Project Settings → Service accounts → Generate new
private key). This is required for server-side ID-token verification and FCM.
19.2.4. Run the database migrations and seeders to create the admin tables and default admin user:
php artisan migrate --seed
19.2.5. The admin panel is now ready. Log in with the default super-admin account and change the password immediately:
Email: admin@quicklist.com Password: 12345678
Important: For push notifications to work, upload your Firebase service-account (FCM) credentials file in the admin panel under Settings → Notification Settings, and set the Firebase Sender ID.
QuickList automatically expires advertisements that have passed their end date. This is handled by a scheduled task that must be triggered by the server's cron.
The scheduled command shells out to a Node.js script, so set NODE_BIN in your
.env to your Node executable path (e.g.
C:/Program Files/nodejs/node.exe on Windows, or /usr/bin/node on Linux).
Add a single cron entry that runs Laravel's scheduler every minute:
* * * * * cd /path-to-your-admin-panel && php artisan schedule:run >> /dev/null 2>&1
Laravel then runs the ad:expire command hourly, which flags ads past their
expiresAt as expired and clears featured flags past featuredExpiresAt.
Note: The admin panel also ships with one-off Node utility scripts in
storage/app/firebase/ (seeding countries/states, migrations, reconciliation). These
are run manually with node and a valid credentials.json; they are not
part of the recurring cron.
Subscription payments are processed through the gateways you enable under Settings →
Payment in the admin panel. Gateway credentials are stored securely in Firestore
(settings/payment); secret keys never leave the server. Each gateway has a sandbox/live
toggle.
QuickList integrates 17 payment gateways:
To enable a gateway, open its tab under Settings → Payment, enter the API keys from that provider's dashboard, choose sandbox or live mode, and save.
The gateways that use a secret key — Stripe, Razorpay, PayPal, Flutterwave, Paystack, PayMongo, Cashfree, Instamojo, Xendit, Midtrans, Paytm, PhonePe, MercadoPago, Orange, MTN MoMo and PayFast — run their secret-key
operations (creating and verifying transactions) inside Firebase Cloud Functions, so
no secret key is ever bundled in the mobile app. The app calls these functions with only non-sensitive
data (amount, currency, reference); the functions read each gateway's secret key server-side from the
settings/payment document your admin panel manages (see
Payment Gateway Setup). Deploy the provided Firebase Cloud
Functions package:
Before you deploy — enable the required Google Cloud APIs. Cloud Functions (2nd
generation) build on Cloud Build, store images in Artifact Registry,
and run on Cloud Run using your project's default compute service account. On a
new Firebase project the Compute Engine API is usually not enabled yet, so the
deploy fails for most functions with "Compute Engine API has not been used…" or
"Unknown service account" (typically only one function deploys and the rest error).
In the Google Cloud Console → APIs & Services → Enabled APIs & services,
make sure all of the following are enabled — enable any that are missing
(Compute Engine is the one most often turned off):
Or enable them all at once with the CLI, then wait 3–5 minutes for the change to propagate before deploying:
gcloud services enable compute.googleapis.com cloudbuild.googleapis.com \ artifactregistry.googleapis.com run.googleapis.com eventarc.googleapis.com \ cloudfunctions.googleapis.com --project=YOUR_PROJECT_ID
23.1. Install Node.js (if not already) and the Firebase CLI:
npm install -g firebase-tools
23.2. Unzip the provided "Firebase Cloud Functions" package and open a
PowerShell/terminal in that folder (the one containing firebase.json).
23.3. Log in to Firebase:
firebase login
23.4. Select your QuickList Firebase project:
firebase use YOUR_PROJECT_ID
23.5. Install the function dependencies:
cd functions npm install
23.6. If your Firestore database is not the (default)
one, set its name in functions/.env so it matches the value used by the panels and app:
FIRESTORE_DB=(default)
23.7. Deploy the functions:
firebase deploy --only functions
Please Note: Cloud Functions that call external payment APIs require the Firebase Blaze (pay-as-you-go) plan. Because the secret keys are read from Firestore at run time, you do not need to redeploy when an admin rotates a key under Payment Gateway Setup.
The User Panel is the buyer/seller-facing marketplace — a Laravel 12 + Bootstrap 5 application. It reads and writes marketplace data directly from Firestore using the Firebase JS SDK. Upload the package to your main domain (recommended) and extract it.
24.1. Install dependencies and generate the application key:
composer install cp .env.example .env php artisan key:generate php artisan migrate
24.2. Add the same Firebase credentials to the User Panel's
.env that you used for the Admin Panel, including the named database value:
FIREBASE_APIKEY= FIREBASE_AUTH_DOMAIN= FIREBASE_DATABASE_URL= FIREBASE_PROJECT_ID= FIREBASE_STORAGE_BUCKET= FIREBASE_MESSAAGING_SENDER_ID= FIREBASE_APP_ID= FIREBASE_MEASUREMENT_ID= FIRESTORE_DATABASE_NAME=(default)
24.3. Place the Firebase service account file at
storage/app/firebase/credentials.json — it is used to verify the Firebase ID token and
create the Laravel session (login bridge).
Notes: Use the same Firebase project and credentials for the Admin Panel, User Panel and Mobile App so all clients share one live dataset.
Google sign-in works out of the box once Google is enabled in Firebase Authentication. Make sure your
User Panel domain is listed under the Firebase Authentication Authorized domains, and
that the OAuth 2.0 client (in Google Cloud Console → APIs & Services → Credentials) includes your
domain under Authorized JavaScript origins and redirect URIs
(e.g. https://yourdomain.com/__/auth/handler).
24.4. The user panel is now ready. It requires no front-end build step (Bootstrap 5 is loaded via CDN). Access your site at your domain, for example http://yourdomain.com/.
QuickList uses Google Maps for ad location selection and display (with OpenStreetMap as a fallback).
25.1. Go to the Google Maps Platform and click Get Started.
25.2. Open the Google Cloud Platform home, then Billing and confirm your billing details are up to date (Google Maps will not work otherwise).
25.3. Go to APIs & Services → Credentials. Select an existing project or create a new one.
25.4. Click Create credentials → API key. Your new API key is displayed.
25.5. Enable the required APIs (Maps JavaScript API, Places API, Geocoding API) and restrict the key to your domains. Set this key in the admin panel under Settings → Map, and in the mobile app configuration.
Both the Admin Panel and the User Panel follow the standard Laravel 12 layout. If you plan to customize the code, the most important folders are described below.
quicklist-admin/ (or quicklist-user/) ├── app/ Application code │ ├── Console/ Artisan commands + scheduled tasks (e.g. ad-expiry) │ ├── Http/ │ │ ├── Controllers/ Request handlers (thin — data is read via Firebase) │ │ └── Middleware/ Auth & permission gates │ ├── Models/ Eloquent models (admin auth: User, Role, Permission) │ ├── Providers/ Service providers (incl. Firebase service) │ └── Services/ Firebase / Firestore helper services ├── bootstrap/ Framework bootstrap + cache ├── config/ Configuration files (app, database, firebase, …) ├── database/ │ ├── migrations/ MySQL schema (admin auth, sessions, cache, jobs) │ └── seeders/ Default admin user & base data ├── public/ Web root — index.php, css/, js/, images/ ├── resources/ │ ├── views/ Blade templates (pages, layouts, partials) │ └── lang/ Translations (e.g. en.json, ar.json) ├── routes/ │ └── web.php All web routes ├── storage/ │ └── app/firebase/ credentials.json (Firebase service account) ├── vendor/ Composer dependencies (generated — not edited) ├── .env Environment config (DB + Firebase credentials) ├── artisan Laravel command-line entry point └── composer.json PHP dependencies
Note: vendor/ and node_modules/ are generated by
composer install / npm install and should not be edited by hand. Your
configuration lives in .env; your Firebase service account lives in
storage/app/firebase/credentials.json.
This section maps the external services QuickList integrates with and where each is configured, so developers can extend or replace them. All three clients — the Flutter app, the Admin Panel and the User Panel — talk to the same Google Firebase backend.
kreait/laravel-firebase
using a service-account key; MySQL only for Laravel infrastructure.Application data lives in Firestore collections. The main ones are:
advertisements categories custom_fields users seller_verifications chat subscription_packages user_subscriptions currencies / countries / cities languages faqs / safety_tips report_reasons / user_reports blogs slider_sections feature_sections / notifications
To extend the platform, add fields or new collections in Firestore, then read/write them from the app
and — via kreait/laravel-firebase — from the Admin Panel.
User identity (phone / email / social sign-in) is handled by Firebase Authentication and shared across all clients. The Admin Panel verifies Firebase ID tokens server-side.
Uploaded media — ad images, avatars and verification documents — is stored in Firebase Storage under
the bucket set by FIREBASE_STORAGE_BUCKET.
Push notifications use Firebase Cloud Messaging. Set the Sender ID and upload the service-account JSON in Admin → Settings → Notifications; the panel then sends messages through the FCM API.
QuickList ships 17 payment gateways, each with its own settings screen under Admin → Settings → Payment:
Stripe PayPal Razorpay Paystack Flutterwave Paytm PhonePe Cashfree Instamojo Midtrans MercadoPago PayFast PayMongo Foloosi Xendit OrangePay MTN MoMo
Each gateway keeps its own credentials (API key / secret) and a sandbox toggle. To add a new gateway,
mirror an existing one: its settings view in resources/views/settings/payment/, its
controller action, and the checkout handler.
Location picking, geocoding and map display use the Google Maps API key — see Create Google Map API Key.
Transactional email is sent through standard Laravel mail — set the MAIL_* values in the
panel's .env.
.env — Firebase web config (FIREBASE_*) and mail
(MAIL_*).storage/app/firebase/credentials.json — Firebase service-account key (server-side).storage/app/firebase/ — Node.js utility scripts for Firestore import/export and
indexing (see Installing Node.js & npm).QuickList is intentionally modular. The most common ways to extend it:
Add a field or a new collection. All application data lives in Firestore (see
26.2). Create the field/collection in Firestore, read and write it from
the Flutter app, and — where the admin needs to manage it — add a controller and Blade view in the
Admin Panel (see Laravel Directory Structure) that read/write it via
kreait/laravel-firebase. Add a matching rule to firestore.rules and redeploy
(see the Firebase Rules package).
Add an admin page. Create a route in routes/web.php, a controller in
app/Http/Controllers/, a Blade view under resources/views/, and a sidebar
entry in resources/views/layouts/menu.blade.php guarded by the module's permission
(defined in PermissionsTableSeeder.php).
Add a payment gateway. Secret-key operations run server-side in Cloud Functions, never on the client. To add a gateway:
onCall function in the Cloud Functions index.js that reads the
gateway's secret from settings/payment and calls the provider's server API.resources/views/settings/payment/ in the admin panel so the
keys can be entered.Cloud Functions shape. Each function is an authenticated
firebase-functions/v2/https onCall handler that (1) validates the caller,
(2) reads config from Firestore server-side, (3) calls the provider, and (4) returns only public
tokens / URLs. Follow the same pattern for any new server-side operation.
Both panels are Laravel applications and can run on shared hosting (cPanel) or on a VPS/dedicated server. Choose the approach that matches your hosting.
Best for getting started quickly when you do not have SSH/root access.
public/ directory (or use the provided .htaccess).vendor/ folder already installed locally (run composer install
on your machine first), then upload..env file (database + Firebase credentials) and place
credentials.json in storage/app/firebase/.755) on the storage/ and
bootstrap/cache/ folders.php artisan migrate --seed — via
cPanel's Terminal or SSH. If your plan has no shell access, run the migrations from your
local machine against the remote database.Note: Ensure your host runs PHP 8.2+ with the required extensions,
and that mod_rewrite is enabled. Node.js may not be available on basic shared plans; if
the cron scripts require it, choose a plan that offers Node.js or use a VPS.
Recommended for production — you get SSH access, Composer, Node.js and full control.
composer install (and npm install for the
admin panel)..env.example to .env, set your database and Firebase credentials, and
run php artisan key:generate then php artisan migrate --seed.public/ directory.storage/ and
bootstrap/cache/.* * * * * php /path/artisan schedule:run) — see
Cron & Scheduled Tasks.php artisan config:cache and route:cache, and serve over
HTTPS.Most shared-hosting problems come down to Apache rewrite / config issues. Watch for these:
mod_rewrite is disabled or AllowOverride is not All, so
Laravel's public/.htaccess is ignored. Enable mod_rewrite and set
AllowOverride All for the site.RewriteBase to
public/.htaccess, e.g. RewriteBase /quicklist/.public/. Point
the domain to the public/ folder, or add a root .htaccess that rewrites
requests into public/. Never serve the project root — it makes .env
reachable.php artisan storage:link (or create the public/storage link manually if
symlinks are blocked on the host).755 on
storage/ and bootstrap/cache/, and confirm PHP is 8.2+.Options -MultiViews) to
.htaccess.APP_URL to your
https:// domain and force HTTPS in .htaccess.Always serve both panels over HTTPS in production. Firebase Authentication, payment gateway callbacks and secure cookies all require it, and browsers block "mixed content" on pages that are not fully secure.
Shared hosting (cPanel). Most hosts issue a free certificate automatically. Open
cPanel → Security → SSL/TLS Status (or Let's Encrypt / AutoSSL),
select your domain, and click Run AutoSSL. Once issued, the site is reachable over
https://.
VPS / dedicated server. Install a free Let's Encrypt certificate with Certbot:
# Nginx sudo apt install certbot python3-certbot-nginx sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com # Apache sudo apt install certbot python3-certbot-apache sudo certbot --apache -d yourdomain.com
Certbot installs the certificate and adds an automatic renewal timer.
Force HTTPS. Redirect all traffic to https://. On Apache, add this at the
top of the panel's public/.htaccess, just inside <IfModule mod_rewrite.c>
before Laravel's front-controller rules:
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
Then finish up:
APP_URL=https://yourdomain.com in each panel's .env.https domain so sign-in works.php artisan config:cache.Key screens (website). Browsing the marketplace — the home page, the search / listings page with filters, and an ad detail page with the seller card and chat:
Sellers post ads through a 5-step wizard:
On submit, the ad is published immediately (if auto-approve is on) or marked pending for admin review. Ad posting can be gated by an active Ad-Listing subscription, depending on admin settings.
QuickList is fully white-label — the application name, logos, favicon and brand colours can all be changed from the Admin Panel without editing any code. Branding is stored in Firebase, so a change made once is reflected across the Admin Panel, the User Panel (website) and the mobile app.
Log in to the Admin Panel and open Settings → Branding. From this screen you can update:
Click Update / Save. Your new logo and name appear immediately after the page reloads.
Still under Settings → Branding, use the Admin Panel Colour picker to set
the primary accent colour of the admin interface (buttons, active menu items, highlights). Pick a colour
or paste a hex value (for example #844AEF) and save.
The public marketplace's appearance is controlled from Settings → Web Settings:
--ql-primary theme variable.Note: Use the same Firebase project for the Admin Panel, User Panel and mobile app so that branding changes propagate everywhere. Clear your browser cache if an updated logo or colour does not appear right away.
For the Flutter mobile application, branding is set in the source code before building:
This section lists the most common errors seen while setting up QuickList with Firebase, along with their causes and fixes. Also see the FAQ for the Flutter "Missing project_info object" error.
The Firestore reads/writes are being blocked by your security rules.
FIRESTORE_DATABASE_NAME below.A Firestore composite index is missing for a search/sort query.
This almost always means the two panels point at different Firestore databases.
FIRESTORE_DATABASE_NAME is identical in both the Admin Panel and
User Panel .env files ((default) for production, or the same named database
such as staging).FIREBASE_* credentials belong to the same Firebase project.Firebase Authentication is rejecting sign-in because your domain is not whitelisted.
yourdomain.com).The sign-in provider you are using is not enabled.
storage/app/firebase/credentials.json.google-services.json /
GoogleService-Info.plist are present and match the project.The google-services.json file is missing or in the wrong place. See the detailed fix in the
FAQ.
A quick reference for the technical terms used throughout this documentation.
FIRESTORE_DATABASE_NAME — Firestore can host more than
one database per project. (default) is the standard one; a named database (e.g.
staging) is used to isolate environments. All panels must target the same name.credentials.json — a private key file that lets the
server (Laravel) act on behalf of your Firebase project (verify login tokens, send push, run
scripts). Kept in storage/app/firebase/.google-services.json / GoogleService-Info.plist — the
Firebase configuration files added to the Android and iOS mobile apps respectively.composer install)..env — the environment configuration file holding database and Firebase
credentials for each panel.php artisan migrate,
php artisan serve).To update the Admin Panel and User Panel, upload the latest source code package to the root directory of each respective panel and extract the contents.
If your project includes custom modifications, we strongly recommend performing the update using Git so your customizations are preserved while integrating the latest changes.
composer install (and npm install for the admin panel) to update
dependencies.php artisan migrate to apply any new database migrations.If you have not modified the source code:
.env and storage/app/firebase/credentials.json).composer install and php artisan migrate, then verify both panels.To update the Mobile App, replace the existing source code with the latest release and rebuild the applications.
flutter pub get to install the latest dependencies.google-services.json /
GoogleService-Info.plist and configuration.flutter pub get, test the app, then publish the updated builds to the app stores.