devsnack
Documentation

ClassiMarket documentation

Run it, point it at your own accounts, ship it under your own brand. Written against version 1.1.0 of the download you get on CodeCanyon.

$18 on CodeCanyon
  • Version v1.1.0
  • Updated 2026
  • Platform Flutter
  • Stack Flutter · Next.js
On this page
01

Introduction

Welcome to ClassiMarket — a production-ready classified marketplace starter kit.

ClassiMarket gives you everything you need to launch a Craigslist- or OLX-style marketplace. The package includes three interconnected projects that work together out of the box:

Flutter app
Native Android & iOS app with listings, chat, search, map, subscriptions, and AdMob ads.
Next.js web
Public marketplace website, seller dashboard, full REST API, and server-side rendering.
Admin panel
Approve listings, manage users, configure subscription plans, CMS, and broadcast notifications.
Shared types
Single source-of-truth API contract shared between the web and Flutter app.
02

Requirements

Install these tools before you begin.

ToolMinimum versionNotes
Node.js
18.x or later
Required for the Next.js web app
npm
9.x or later
Bundled with Node.js
Flutter SDK
3.x
Dart SDK
3.x
Bundled with Flutter
PostgreSQL
14+
Or use Railway / Supabase / Neon (hosted)
Android Studio
Latest stable
Required for Android builds
Xcode
15+
Required for iOS builds (macOS only)
Git
Any
For cloning the source code
Note
Verify your Flutter installation. Run flutter doctor in your terminal to check for missing dependencies before proceeding.
03

Project structure

After extracting the ZIP, you will find this top-level structure.

classi-flutter/
classi-flutter/
├── shared/                 ← API contract & shared types (source of truth)
│   ├── api-contract.yaml       ← OpenAPI endpoint definitions
│   ├── types/
│   │   ├── api.ts              ← TypeScript interfaces (used by Next.js)
│   │   └── api.dart            ← Dart models (used by Flutter)
│   └── constants/
│       ├── endpoints.ts
│       └── endpoints.dart

├── web/                    ← Next.js 16 — API + Web + Admin
│   ├── app/api/            ← 50+ REST API endpoints
│   ├── app/(public)/       ← Public marketplace pages
│   ├── app/dashboard/      ← Seller dashboard
│   ├── app/admin/          ← Admin panel (11 sections)
│   ├── prisma/
│   │   ├── schema.prisma       ← Database schema
│   │   └── seed.ts             ← Seeds admin user + demo data
│   └── .env.local              ← You must create this file

├── flutter_app/            ← Flutter mobile app (Android + iOS)
│   ├── lib/core/           ← API client, config, services
│   ├── lib/data/           ← Models & repositories
│   ├── lib/presentation/   ← Screens, widgets, Riverpod providers
│   ├── android/            ← Android native config
│   └── ios/                ← iOS native config

└── docs/                   ← This documentation
04

Web and API setup

Set up the Next.js backend and run database migrations.

  1. 01

    Install dependencies

    terminal
    # Navigate to the web directory
    cd web
    npm install
  2. 02

    Create environment file

    Copy the example file and fill in your values (see Environment variables for all variables).

    terminal
    cp .env.example .env.local
    Warning
    Required: set DATABASE_URL. At minimum you must set DATABASE_URL before running migrations. Example: postgresql://user:password@localhost:5432/classimarket
  3. 03

    Start a PostgreSQL database

    You can use a local Docker container or a hosted service like Railway, Supabase, or Neon.

    Option A — Docker (local)

    terminal
    docker run -d \
      --name classimarket-db \
      -e POSTGRES_DB=classimarket \
      -e POSTGRES_USER=user \
      -e POSTGRES_PASSWORD=password \
      -p 5432:5432 \
      postgres:16

    Option B — Railway / Supabase / Neon

    Create a free PostgreSQL database and copy the connection string into DATABASE_URL.

  4. 04

    Run migrations & seed

    terminal
    # Create all database tables
    npx prisma migrate dev --name init
    
    # You must supply admin credentials — there are no defaults.
    export SEED_ADMIN_EMAIL="you@yourdomain.com"
    export SEED_ADMIN_PASSWORD="<a strong password you choose>"
    
    # Seed admin user and sample categories
    npx prisma db seed
    Warning
    Admin credentials are not bundled. The seed script requires SEED_ADMIN_EMAIL and SEED_ADMIN_PASSWORD to be set in the environment and will exit with an error if either is missing. The password you choose is hashed with bcrypt before storage and is never logged or printed. Pick a strong password and store it in your password manager — to reset it later you’ll need to update the admin row’s password column directly (it’s a bcrypt hash, not plaintext).
    Warning

    The seed script is for development / demo use only. web/prisma/seed.ts also creates five demo seller accounts (alice / bob / carol / david / emma @example.com) and a full set of demo listings, reviews, and conversations to make the marketplace feel populated during local development. Never run this script against a production database.

    If you need to run it in any non-throwaway environment, do one of the following first:

    • Delete the demo-seller blocks (sellers 1–5) and every section that depends on them (listings, reviews, conversations) from seed.ts, or
    • Leave SEED_DEMO_PASSWORD unset — the script will generate a random, unrecoverable password for each demo account so the rows exist but nobody can log in as them.

    For a true production deployment, the recommendation is to migrate (prisma migrate deploy), then seed only the admin row by running a trimmed seed script.

  5. 05

    Start the development server

    terminal
    npm run dev
    # Server starts at http://localhost:3000
    # Admin panel at http://localhost:3000/admin
05

Flutter app setup

Configure and run the mobile app on Android or iOS.

  1. 01

    Set the API base URL

    The base URL is read from the API_BASE_URL Dart define — you don’t need to edit app_config.dart. The default (http://10.0.2.2:3000/api) targets a local Next.js server from the Android emulator, so flutter run works out of the box for local dev.

    terminal
    # Real device on the same Wi-Fi
    flutter run --dart-define=API_BASE_URL=http://192.168.1.42:3000/api
    
    # Production build pointing at your deployed API
    flutter build apk --release --dart-define=API_BASE_URL=https://yoursite.com/api

    See Build-time configuration (Dart defines) below for the full list of defines (environment, ad units, Maps key, Stripe key).

  2. 02

    Replace the bundled Firebase project with your own

    The source ships flutter_app/lib/firebase_options.dart, google-services.json, and GoogleService-Info.plist with placeholder values only (e.g. YOUR_PROJECT_ID). You must connect your own Firebase project before building — running flutterfire configure overwrites all three files with your project’s real values so push notifications, FCM tokens, and other Firebase services flow into your project.

    Warning
    Required before any release build. This is mandatory for publishing. The bundled config is for local compilation only.

    ① Install the FlutterFire CLI (one-time)

    terminal
    dart pub global activate flutterfire_cli

    ② Create / sign in to your Firebase project

    Go to console.firebase.google.com, create a new project, and enable Cloud Messaging.

    ③ Wire your Firebase project into the app

    terminal
    cd flutter_app
    flutterfire configure \
      --project=<your-firebase-project-id> \
      --platforms=android,ios \
      --android-package-name=<your.android.package> \
      --ios-bundle-id=<your.ios.bundle.id> \
      --out=lib/firebase_options.dart \
      --yes

    This overwrites lib/firebase_options.dart with values from your Firebase project and downloads the matching google-services.json (Android) and GoogleService-Info.plist (iOS) into the right directories.

    ④ Verify the replacement

    terminal
    grep YOUR_PROJECT_ID lib/firebase_options.dart   # should return nothing after you reconfigure
    grep projectId lib/firebase_options.dart         # should show YOUR project id
  3. 03

    Add Google Maps API key

    Android

    flutter_app/android/local.properties
    GOOGLE_MAPS_API_KEY=your_key_here

    iOS

    flutter_app/ios/Runner/Info.plist
    <key>GOOGLE_MAPS_API_KEY</key>
    <string>your_key_here</string>
  4. 04

    Install Flutter dependencies

    terminal
    cd flutter_app
    flutter pub get
  5. 05

    Generate serialization code

    terminal
    dart run build_runner build --delete-conflicting-outputs
  6. 06

    Run the app

    terminal
    flutter run                   # default connected device
    flutter run -d emulator-5554  # specific Android emulator
    flutter run -d iPhone         # iOS simulator
06

Release signing

Android requires every release build to be signed with your own keystore before you can publish to the Play Store. Debug builds use a pre-generated debug key and do not require this step.

  1. 01

    Generate an upload keystore

    terminal
    keytool -genkey -v -keystore ~/upload-keystore.jks \
      -keyalg RSA -keysize 2048 -validity 10000 \
      -alias upload
  2. 02

    Create flutter_app/android/key.properties

    flutter_app/android/key.properties
    storePassword=your-keystore-password
    keyPassword=your-key-password
    keyAlias=upload
    storeFile=/Users/you/upload-keystore.jks
  3. 03

    That’s it — the Gradle config is already wired up

    No Gradle edits are required. flutter_app/android/app/build.gradle.kts already loads key.properties and applies a release signingConfig (with minifyEnabled, shrinkResources, and ProGuard rules enabled). For reference, this is the relevant Kotlin DSL already in the file:

    flutter_app/android/app/build.gradle.kts
    val keystoreProperties = Properties()
    val keystorePropertiesFile = rootProject.file("key.properties")
    val hasReleaseKeystore = keystorePropertiesFile.exists()
    if (hasReleaseKeystore) {
        keystoreProperties.load(FileInputStream(keystorePropertiesFile))
    }
    // inside android { } :
    signingConfigs {
        if (hasReleaseKeystore) {
            create("release") {
                keyAlias = keystoreProperties["keyAlias"] as String?
                keyPassword = keystoreProperties["keyPassword"] as String?
                storeFile = (keystoreProperties["storeFile"] as String?)?.let { file(it) }
                storePassword = keystoreProperties["storePassword"] as String?
            }
        }
    }
    buildTypes {
        release {
            signingConfig = if (hasReleaseKeystore)
                signingConfigs.getByName("release") else signingConfigs.getByName("debug")
            isMinifyEnabled = true
            isShrinkResources = true
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
        }
    }

    When key.properties is absent the release build falls back to debug signing so the demo still runs — but you must add your own keystore before publishing.

Warning
Protect your keystore. Never commit key.properties or the .jks file to Git — add both to .gitignore. Keep offline backups in at least two secure locations. Losing your upload keystore means you can never update your app on the Play Store again (unless you’ve enrolled in Play App Signing, which lets Google reset your upload key).
Note
iOS signing. iOS signing is handled entirely in Xcode. Open flutter_app/ios/Runner.xcworkspace, select the Runner target → Signing & Capabilities tab, and pick your Apple Developer team. Xcode auto-manages provisioning profiles for Debug and Release. For TestFlight / App Store distribution you need a paid Apple Developer Program account.
Note

Building for release. Android: flutter build apk --release or flutter build appbundle --release (requires the signing setup above)

iOS: flutter build ipa --release (requires Apple Developer account)

07

Build-time configuration (Dart defines)

The Flutter app is configured entirely through --dart-define flags so you never need to edit source code to switch environments. Defaults are dev-safe (test ads, dev environment, local API). For a production release, set them all explicitly:

terminal
flutter build apk --release \
  --dart-define=APP_ENV=production \
  --dart-define=API_BASE_URL=https://your-domain.com/api \
  --dart-define=USE_TEST_ADS=false \
  --dart-define=ADMOB_BANNER_ANDROID=ca-app-pub-XXX/YYY \
  --dart-define=ADMOB_BANNER_IOS=ca-app-pub-XXX/YYY \
  --dart-define=ADMOB_INTERSTITIAL_ANDROID=ca-app-pub-XXX/YYY \
  --dart-define=ADMOB_INTERSTITIAL_IOS=ca-app-pub-XXX/YYY \
  --dart-define=GOOGLE_MAPS_API_KEY=AIza... \
  --dart-define=STRIPE_PUBLISHABLE_KEY=pk_live_...
DefineDefaultEffect
APP_ENV
development
Selects environment in AppEnvironment.current. Accepted: development, staging, production.
API_BASE_URL
http://10.0.2.2:3000/api
REST API base. The default targets the Android emulator’s host loopback. Override for any other build.
USE_TEST_ADS
true
When true, AdMob serves Google’s public test ads regardless of the IDs below. Set false for production.
ADMOB_BANNER_ANDROID / ADMOB_BANNER_IOS
empty
Real banner ad unit IDs. If USE_TEST_ADS=false but a value is empty, the app falls back to test IDs (never bills the wrong account).
ADMOB_INTERSTITIAL_ANDROID / ADMOB_INTERSTITIAL_IOS
empty
Real interstitial ad unit IDs. Same fallback rule as banners.
08

Environment variables

Create web/.env.local with the following variables.

Database & auth

VariableDescriptionRequiredExample
DATABASE_URL
PostgreSQL connection string
Required
postgresql://user:pass@host/db
JWT_SECRET
Secret for signing JWTs (Flutter auth)
Required
Any 32+ char random string
JWT_SECRET
Secret used to sign JWT access & refresh tokens
Required
Any 32+ char random string
NEXT_PUBLIC_BASE_URL
Public URL of your web app (used as the default Socket.IO CORS origin and for sitemap/robots)
Required
https://yoursite.com
SOCKET_CORS_ORIGINS
Comma-separated allow-list of origins for the Socket.IO server. If unset, falls back to NEXT_PUBLIC_BASE_URL; if both are unset, all cross-origin WebSocket connections are rejected.
Optional
https://yoursite.com,https://admin.yoursite.com
SEED_ADMIN_EMAIL
Email for the admin user created by prisma db seed. The seed script exits with an error if this is missing — there is no default.
Required(seed only)
you@yourdomain.com
SEED_ADMIN_PASSWORD
Initial admin password. Hashed with bcrypt before storage and never logged. Choose a strong value — there is no default.
Required(seed only)
(your choice)

File storage (AWS S3 or Cloudflare R2)

VariableDescriptionRequired
AWS_ACCESS_KEY_ID
S3 / R2 access key
Required
AWS_SECRET_ACCESS_KEY
S3 / R2 secret key
Required
AWS_S3_BUCKET
Bucket name for uploads
Required
AWS_REGION
Bucket region (e.g. ap-southeast-1)
Required
AWS_ENDPOINT
Custom endpoint (for R2/MinIO)
Optional

Firebase (push notifications)

VariableDescriptionRequired
FIREBASE_PROJECT_ID
Firebase project ID
Required
FIREBASE_CLIENT_EMAIL
Service account email
Required
FIREBASE_PRIVATE_KEY
Service account private key
Required

Payments

VariableDescriptionRequired
STRIPE_SECRET_KEY
Stripe secret key (sk_live_…)
Required
STRIPE_WEBHOOK_SECRET
Stripe webhook signing secret
Required

Google OAuth & Maps

VariableDescriptionRequired
GOOGLE_CLIENT_ID
Google OAuth 2.0 client ID
Optional
GOOGLE_CLIENT_SECRET
Google OAuth 2.0 client secret
Optional
GOOGLE_MAPS_API_KEY
Maps JavaScript API key
Optional

Email (SMTP)

VariableDescriptionRequired
SMTP_HOST
SMTP server hostname
Required
SMTP_PORT
SMTP port (usually 587 or 465)
Required
SMTP_USER
SMTP username / email address
Required
SMTP_PASS
SMTP password or app password
Required
SMTP_FROM
Sender email address
Required
Warning
Never commit .env.local to git. The .env.local file is already in .gitignore. Never expose your secret keys in a public repository.
09

Third-party services

Set up external services required by the app.

Warning
Pricing & account ownership. The services listed below are optional third-party providers operated by their respective companies. They are not bundled with this item and are not included in the purchase price. You must register your own accounts, obtain your own API keys, and accept each provider’s terms of service. Any usage fees (AWS S3 / R2 storage & bandwidth, Google Maps Platform requests, Stripe transaction fees, Firebase quotas, AdMob revenue-share, SMTP sending costs) are your responsibility.

Firebase (push notifications & auth)

  1. 01
    Go to console.firebase.google.com and create a new project.
  2. 02
    Enable Cloud Messaging in the Firebase console.
  3. 03
    Register your Android app with package name com.devsnack.classiflutter (or your custom ID). Download google-services.json.
  4. 04
    Register your iOS app with your Bundle ID. Download GoogleService-Info.plist.
  5. 05
    Go to Project Settings → Service Accounts, generate a new private key, and use the values for FIREBASE_PROJECT_ID, FIREBASE_CLIENT_EMAIL, and FIREBASE_PRIVATE_KEY.

Google Maps

  1. 01
    Open console.cloud.google.com and create or select a project.
  2. 02
    Enable these APIs: Maps SDK for Android, Maps SDK for iOS, Geocoding API.
  3. 03
    Create an API key under Credentials and restrict it to your app’s package/bundle ID.
  4. 04
    Add the key to android/local.properties and ios/Runner/Info.plist.

Stripe

  1. 01
    Create an account at stripe.com.
  2. 02
    From the dashboard, copy your Secret Key (sk_live_…) and Publishable Key (pk_live_…).
  3. 03
    In Webhooks → Add endpoint, set your URL to https://yoursite.com/api/payments/webhook and subscribe to checkout.session.completed and customer.subscription.* events. Copy the signing secret.
  4. 04
    Add the publishable key to the Flutter app via --dart-define=STRIPE_PUBLISHABLE_KEY=pk_live_… in your build command.

AWS S3 / Cloudflare R2 (image storage)

  1. 01
    Create an S3 bucket (or Cloudflare R2 bucket). Enable public read access or use presigned URLs.
  2. 02
    Create an IAM user with s3:PutObject, s3:DeleteObject, and s3:GetObject permissions on your bucket.
  3. 03
    Copy the Access Key ID and Secret to your .env.local.
  4. 04
    For Cloudflare R2, set AWS_ENDPOINT to your R2 endpoint URL.

Google AdMob (in-app ads)

  1. 01
    Create an AdMob account at admob.google.com.
  2. 02
    Add your app and note the App ID.
  3. 03
    Create ad units: one Banner and one Interstitial per platform (Android + iOS).
  4. 04
    Add the App ID to android/app/src/main/AndroidManifest.xml as com.google.android.gms.ads.APPLICATION_ID meta-data, and to ios/Runner/Info.plist under GADApplicationIdentifier.
  5. 05
    Supply the four ad unit IDs at build time via --dart-define (ADMOB_BANNER_ANDROID, ADMOB_BANNER_IOS, ADMOB_INTERSTITIAL_ANDROID, ADMOB_INTERSTITIAL_IOS) and set USE_TEST_ADS=false. See Build-time configuration for the full command.
10

App configuration

Rebrand the app with your own name, colors, and package ID.

Change the app name

flutter_app/pubspec.yaml
name: field
flutter_app/android/app/src/main/AndroidManifest.xml
android:label="…"
flutter_app/ios/Runner/Info.plist
CFBundleDisplayName

Change the package / bundle ID

The default Android package is com.devsnack.classiflutter and the default iOS bundle ID is com.devsnack.classiflutter. You must change both before publishing to the Play Store or App Store — the stores will reject duplicate identifiers.

Android — change the package name

  1. 01
    Edit flutter_app/android/app/build.gradle.kts defaultConfig { applicationId = "com.yourcompany.yourapp" } and the same value in namespace = "...".
  2. 02
    Rename the folder flutter_app/android/app/src/main/kotlin/com/devsnack/classiflutter to match your new package path (e.g. com/yourcompany/yourapp).
  3. 03
    Update the package declaration at the top of MainActivity.kt to match.
  4. 04
    Update flutter_app/android/app/src/main/AndroidManifest.xml if it contains a package="…" attribute.
  5. 05
    Re-run flutterfire configure with the new --android-package-name so google-services.json matches.

iOS — change the bundle identifier

  1. 01
    Open flutter_app/ios/Runner.xcworkspace in Xcode.
  2. 02
    Select the Runner project → Runner target → GeneralBundle Identifier. Set it to com.yourcompany.yourapp.
  3. 03
    Switch to the Signing & Capabilities tab, pick your Team, and let Xcode regenerate the provisioning profile. (For TestFlight / App Store distribution you need a paid Apple Developer Program account; for personal device testing a free Apple ID works.)
  4. 04
    If you have widgets / share extensions, update their bundle IDs too (must share the prefix of the main bundle ID).
  5. 05
    Re-run flutterfire configure with the new --ios-bundle-id so GoogleService-Info.plist matches.
Note
After changing the IDs. Re-run flutter clean && flutter pub get, then re-generate Firebase config (flutterfire configure) and AdMob registrations so all third-party services know about the new identifier.

Change primary color

The brand color is defined in one place — update it and all screens adapt automatically.

flutter_app/lib/core/constants/app_colors.dart
class AppColors {
  static const Color seed = Color(0xFF004357);  // ← change this
  static const Color primary = Color(0xFF004357);  // ← and this
}

Change the app icon

  1. 01
    Replace flutter_app/assets/icon/app_icon.png with your 1024×1024 icon.
  2. 02
    Replace flutter_app/assets/icon/app_icon_fg.png with the foreground layer (transparent background) for Android adaptive icons.
  3. 03
    Run: dart run flutter_launcher_icons

Change API base URL

flutter_app/lib/core/config/app_config.dart
defaultValue: 'https://yoursite.com/api'
11

Admin panel

Manage your marketplace from the built-in admin dashboard.

Note

First admin login. URL: http://localhost:3000/admin

The admin email and temporary password are printed by the Prisma seed script (Web and API setup → step 4). Copy them from the terminal output and change the password immediately in Admin → Settings after signing in.

Panel sections

Users

  • View, search, filter users
  • Ban / unban accounts
  • Change user roles
  • View user listings & reviews

Listings

  • Approve / reject pending listings
  • Feature / unfeature listings
  • Delete listings
  • View listing reports

Categories

  • Create / edit / delete categories
  • Create, edit, and reorder categories
  • Set category icons and order

Subscriptions

  • Create & manage plans
  • Set listing & featured quotas
  • View subscriber list

Reports

  • Review user/listing reports
  • Mark as resolved or dismissed
  • Take action on reported content

Payments

  • View all transactions
  • Subscription payments
  • Featured listing payments

Notifications

  • Send broadcast push to all users
  • Send to specific user groups

CMS & blog

  • Edit Terms, Privacy, About pages
  • Create & publish blog posts
  • Manage static pages
12

Deployment

Deploy the Next.js web app to production.

Railway (recommended)

Railway can host both the app and the database with zero configuration.

  1. 01
    Push your code to a GitHub repository.
  2. 02
    Go to railway.app, create a new project, and link your GitHub repo.
  3. 03
    Add a PostgreSQL plugin — Railway will auto-set DATABASE_URL.
  4. 04
    Add all other environment variables in the Railway dashboard under Variables.
  5. 05
    The railway.toml in the project root handles build & start commands automatically.
Note
Run migrations on deploy. Add this to your Railway start command: npx prisma migrate deploy && node server.js

Vercel

terminal
# Install Vercel CLI
npm i -g vercel

# Deploy from the web/ directory
cd web && vercel --prod
Warning
Vercel serverless limitation. Vercel does not support persistent Socket.io connections. Chat real-time features require a separate server deployment (Railway, Render, Fly.io, or a VPS).

Docker

terminal
# Build the image
docker build -t classimarket .

# Run with environment file
docker run -p 3000:3000 --env-file web/.env.local classimarket

VPS / self-hosted

terminal
cd web
npm run build
npm start         # starts on port 3000
# Use Nginx as reverse proxy for SSL/domain
13

Security headers and production signing

HTTP security headers

A baseline set of security headers is configured in web/next.config.ts and applied to every response out of the box:

HeaderValuePurpose
X-Frame-Options
SAMEORIGIN
Blocks the site from being embedded in third-party iframes (clickjacking).
X-Content-Type-Options
nosniff
Prevents MIME-type sniffing.
Referrer-Policy
strict-origin-when-cross-origin
Limits referrer leakage to other origins.
Permissions-Policy
camera=(), microphone=(), geolocation=(self)
Denies camera/mic by default; allows geolocation only on same-origin.
Strict-Transport-Security
max-age=63072000; includeSubDomains; preload
Forces HTTPS for two years on this host and all subdomains.
Content-Security-Policy
(see next.config.ts)
Restricts script / style / image / connect sources. Includes Stripe, Google Maps, and Google Fonts by default.
Note
Tighten the CSP for your deployment. The default CSP is intentionally permissive so the app runs without extra config. For a hardened production deployment, edit the Content-Security-Policy entry in web/next.config.ts to remove 'unsafe-inline' / 'unsafe-eval' (you may need to migrate inline scripts to nonce-based ones) and replace the broad https: in connect-src / img-src with the specific hostnames you actually use.

Production app signing (Flutter mobile app)

Before uploading your Flutter app to the Play Store or App Store, complete the signing setup covered in Release signing. Key points for production:

  • Android: generate a dedicated upload keystore with keytool and reference it from flutter_app/android/key.properties (copy from key.properties.example). The signingConfigs.release block in build.gradle.kts is already wired up — no Gradle edits needed. Build with flutter build appbundle --release and upload the resulting .aab.
  • Back up the keystore to at least two offline locations. Losing it locks you out of future Play Store updates unless you enrolled in Play App Signing (recommended — Google stores the signing key and lets you reset the upload key).
  • iOS: use Xcode’s automatic signing tied to your Apple Developer team. Archive with Product → Archive, then distribute via Organizer to TestFlight or the App Store.
  • Never commit key.properties, .jks files, or .p12 / .p8 certificates to Git.
14

Push notifications

Enable Firebase Cloud Messaging (FCM) for push notifications on Android and iOS.

  1. 01

    Create a Firebase project

    Go to console.firebase.google.com → New Project. Enable Google Analytics if desired.
  2. 02

    Register Android app

    Add Android app with package name com.devsnack.classiflutter. Download google-services.json and place it at flutter_app/android/app/google-services.json.
  3. 03

    Register iOS app

    Add iOS app with your Bundle ID. Download GoogleService-Info.plist and place it at flutter_app/ios/Runner/GoogleService-Info.plist. In Xcode, also enable the Push Notifications capability.
  4. 04

    Add server credentials to .env.local

    In Firebase Console → Project Settings → Service Accounts → Generate new private key. Copy the JSON values:

    web/.env.local
    FIREBASE_PROJECT_ID=your-project-id
    FIREBASE_CLIENT_EMAIL=firebase-adminsdk-xxx@your-project.iam.gserviceaccount.com
    FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n…\n-----END PRIVATE KEY-----\n"
  5. 05

    Test notifications

    Log in to the Flutter app, then go to Admin → Notifications → Broadcast and send a test push. The device should receive it within seconds.
Note
Automatic notification triggers. Notifications are automatically sent when: a listing is approved/rejected, a new chat message arrives, or an admin broadcasts to all users.
15

Features overview

Everything included in ClassiMarket.

Marketplace

  • Infinite scroll listing feed
  • Multi-level category hierarchy
  • Per-category custom fields
  • Full-text search with filters
  • Price types: Fixed, Negotiable, Free, Contact
  • Item condition tracking
  • Featured / promoted listings
  • Nearby listings map (Google Maps)
  • Listing view counter
  • Listing expiry & sold status
  • Up to 10 images per listing
  • Image compression on upload
  • Report listing
  • Share listing

Users & auth

  • Email / password registration
  • Google Sign-In
  • Sign in with Apple (iOS)
  • Email verification
  • Password reset via email
  • JWT bearer (mobile) + httpOnly cookie (web)
  • Role-based access (User / Seller / Admin)
  • Edit profile (name, avatar, bio, location)
  • Public seller profile page
  • Account deletion

Chat

  • Real-time messaging (Socket.io)
  • Conversation list with last message
  • Image sharing in chat
  • Read receipts
  • Unread message badge
  • Block user
  • Push notification on new message

Offers

  • Offers on Negotiable listings only
  • Accept / decline / counter / withdraw
  • Unlimited counter-offer rounds
  • One live offer per conversation
  • Interactive offer cards inline in chat
  • Live status updates via Socket.io
  • Optional per-offer expiry
  • Push + in-app notification per event
  • Seller offer management in the dashboard

Monetization

  • Subscription plans (Free / Basic / Premium)
  • Per-plan listing & featured quotas
  • Feature listing boost (one-time)
  • Stripe payments
  • Google AdMob (banner + interstitial)
  • Ads only for free-plan users

Notifications

  • Firebase Cloud Messaging (FCM)
  • In-app notification center
  • Mark all as read
  • Swipe to delete
  • Transactional emails (welcome, reset)
  • Listing approval / rejection emails
  • Admin broadcast push

Reviews & bookmarks

  • 1–5 star seller ratings
  • Written reviews with moderation
  • Average rating on seller profile
  • Save / bookmark listings
  • Bookmarks with infinite scroll

Admin panel

  • Dashboard with metrics
  • User management & banning
  • Listing approval workflow
  • Category management
  • Subscription plan management
  • Report moderation queue
  • Payment & transaction history
  • Broadcast notifications
  • CMS (Terms, Privacy, Blog)
  • Platform settings

Web platform

  • SEO-optimized listing pages (SSR)
  • OpenGraph meta tags
  • Sitemap & robots.txt
  • Seller dashboard
  • Category pages (SSG)
  • Light & dark mode
  • Responsive design
16

Customization

Make ClassiMarket your own.

App icon

  1. 01
    Create a 1024×1024 PNG icon with no rounded corners — the OS applies the mask.
  2. 02
    Replace flutter_app/assets/icon/app_icon.png (flat icon with background).
  3. 03
    Replace flutter_app/assets/icon/app_icon_fg.png (foreground layer, transparent background, for Android adaptive icon).
  4. 04

    Run from the flutter_app/ directory:

    terminal
    dart run flutter_launcher_icons

    This generates all required sizes for Android and iOS automatically.

Splash screen

Edit flutter_app/pubspec.yaml under the flutter_native_splash section, then run dart run flutter_native_splash:create.

Colors & theme

All colors are centralized. Change the seed color in flutter_app/lib/core/constants/app_colors.dart and the Material 3 system generates the full palette automatically:

app_colors.dart
static const Color seed    = Color(0xFF004357);  // ← your brand color
static const Color primary = Color(0xFF004357);  // ← same value

Fonts

Add a Google Font to pubspec.yaml under fonts:, then update flutter_app/lib/app/app_theme.dart to use it in the ThemeData.

Custom fields per category

Custom fields are defined in the database seed at web/prisma/seed.ts. Each field has a type (Text, Number, Select, Multi-select, Boolean, or Date) and is attached to a category; once seeded, fields appear automatically on the listing create/edit forms in both the web app and the Flutter app. To add or change fields, edit seed.ts and re-run npx prisma db seed, or insert rows into the CustomField table directly.

Subscription plans

Configure plans (name, price, listing quota, featured quota) from Admin → Subscriptions. Update corresponding Stripe products to match pricing.

Web branding (Next.js)

Update web/app/layout.tsx for site name and meta, web/globals.css for Tailwind color variables, and web/public/ for logos and favicon.

17

Troubleshooting

Fixes for the most common issues you may hit during setup, development, and deployment.

Flutter — stale build artifacts

If the Flutter app fails to compile after pulling updates, dependency changes, or switching branches, clear the build cache:

terminal
cd flutter_app
flutter clean
flutter pub get
dart run build_runner build --delete-conflicting-outputs

iOS — CocoaPods install fails

CocoaPods issues (“Unable to find a specification”, podspec version conflicts, corrupted pod cache) usually clear after a full reset:

terminal
cd flutter_app/ios
pod deintegrate
pod cache clean --all
rm -rf Pods Podfile.lock
pod install --repo-update

If CocoaPods itself is out of date: sudo gem install cocoapods. On Apple Silicon, run under Rosetta if you see arch errors: arch -x86_64 pod install.

build_runner conflicts

Generated *.g.dart and *.freezed.dart files can go out of sync after model changes. Force a clean regen:

terminal
dart run build_runner clean
dart run build_runner build --delete-conflicting-outputs

Prisma — client out of sync with schema

If your Next.js app throws “The table does not exist” or “Unknown argument” errors after a schema change:

terminal
cd web
npx prisma generate           # regenerate the client
npx prisma migrate dev        # apply pending migrations

Prisma — migration drift

If you see “Drift detected: Your database schema is not in sync with your migration history”, reset the local database (wipes all local data):

terminal
npx prisma migrate reset
npx prisma db seed
Warning
Never run migrate reset against production. It drops every table. Use it only on local / development databases.

PostgreSQL — connection refused

Common causes and fixes for ECONNREFUSED 127.0.0.1:5432:

  • The Postgres container isn’t running — check docker ps, then docker start classimarket-db.
  • Port 5432 is already in use by a local Postgres install — stop it (brew services stop postgresql) or change the Docker port mapping.
  • Wrong credentials or DB name in DATABASE_URL — it must match the POSTGRES_USER, POSTGRES_PASSWORD, and POSTGRES_DB you passed to Docker.
  • Hosted DB (Railway / Supabase / Neon) requires SSL — append ?sslmode=require to the connection string.

Flutter — can’t reach the API

  • Android emulator: use http://10.0.2.2:3000/api localhost refers to the emulator itself, not the host machine.
  • iOS simulator: http://localhost:3000/api works because the simulator shares the host network.
  • Physical device: phone and dev machine must be on the same Wi-Fi. Use your machine’s LAN IP (e.g. http://192.168.1.42:3000/api). On macOS, allow incoming connections to Node in System Settings → Network → Firewall.
  • HTTPS only on iOS release builds — plain HTTP is blocked by App Transport Security. Deploy behind HTTPS for production builds.

Google Maps — grey / blank map

A grey tile almost always means the Maps SDK is not authorised for your app:

  • Enable Maps SDK for Android and Maps SDK for iOS in Google Cloud Console → APIs & Services.
  • Enable billing on the Google Cloud project — Maps requires a billing account even for the free tier.
  • Remove overly strict key restrictions while debugging, then re-add them once you confirm maps render.
  • Ensure the key is set in flutter_app/android/local.properties and inside <dict> of flutter_app/ios/Runner/Info.plist.

Firebase — push notifications not arriving

  • google-services.json and GoogleService-Info.plist must be placed in the correct folders and the app’s package / bundle ID must exactly match the Firebase app registration.
  • iOS only: upload an APNs authentication key in Firebase Console → Project Settings → Cloud Messaging.
  • Backend FIREBASE_PRIVATE_KEY env var must keep the literal \n newline escapes — copy it from the service-account JSON as-is and wrap it in double quotes.
  • Test with a real device — the iOS Simulator does not receive remote push notifications.

Android release build is unsigned / rejected by Play Console

Make sure you completed Release signing (sign your Android release build). Also verify:

  • The storeFile path in key.properties is absolute and readable.
  • You’re building in release mode: flutter build appbundle --release.
  • For Play Store upload, prefer .aab (App Bundle) over .apk.

Node / npm — mismatched versions

The web app requires Node 18+. If npm install fails with engine warnings or unexpected syntax errors, check node -v and upgrade via nvm: nvm install 20 && nvm use 20.

18

Changelog

Every version published so far. Updates are free for the life of the item.

v1.1.0 · 2026
Offers and price negotiation with counter-offers, expiry and push notifications. Upgraded to Next.js 16, React 19, Prisma 7 and Tailwind CSS v4.
v1.0.0 · 2025
Initial release — Flutter app, Next.js web, admin panel, real-time chat, subscriptions, FCM push, Google Maps, AdMob and Stripe.

Version 1.1.0 · 2026

  • Offers & price negotiation (offer / counter / accept / decline / withdraw, live in chat).
  • Stack upgraded to Next.js 16, React 19, Prisma 7, Tailwind v4; Flutter dependencies refreshed.

Version 1.0.0 · 2025

  • Initial release — full feature set
19

Support and licensing

We’re here to help you get ClassiMarket running smoothly. We respond to all support requests within 1–2 business days (Monday–Friday).

Note
Before contacting support. Please include your CodeCanyon purchase code, a description of the issue, and any error messages or screenshots. This helps us resolve your issue faster.

Enjoying ClassiMarket? A 5-star review on CodeCanyon means the world to us and helps other buyers find the product. Rate on CodeCanyon

What support covers

  • Six months of support from purchase, extendable at checkout
  • Support covers bugs in the template and questions about how it is put together
  • It does not cover custom feature work, third-party API changes or store review outcomes
  • The Regular licence covers one free end product. Charging users for the app itself needs the Extended licence