devsnack
Documentation

Wander AI documentation

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

$19 on CodeCanyon
On this page
01

Introduction

Wander AI is an AI-powered travel itinerary platform. The product is shipped as a monorepo with two independent sub-projects:

  • Admin panel (admin/) — a Next.js dashboard plus the mobile REST API at /api/mobile/*.
  • Mobile app (apps/) — a Flutter iOS and Android client that consumes the mobile API.

They share no code and no JS workspace manager. Each one has its own toolchain, dependencies, and .env. This guide covers both halves.

Repository structure

wander-ai/
wander-ai/
├── admin/        Next.js admin panel + /api/mobile/* REST endpoints
├── apps/         Flutter mobile client (iOS + Android)
├── docs/         This documentation
├── CLAUDE.md     Engineering notes (for Claude Code)
├── README.md     Quick-start
└── package.json  Root convenience scripts (no dependencies)

Root convenience scripts (run from repo root):

terminal
npm run dev:admin          # Next.js dev server
npm run build:admin        # Next.js production build
npm run dev:app            # flutter run (in apps/)
npm run analyze:app        # flutter analyze
npm run gen:app            # build_runner codegen
npm run build:app:android  # Android release APK
npm run build:app:ios      # iOS release build
02

Admin panel

A comprehensive administrative dashboard for managing the Wander AI platform. It provides tools for user management, trip monitoring, promotional content management, and system configuration. Built with Next.js, Shadcn UI, and Drizzle ORM.

Key features

  • Dashboard overview: real-time analytics and system status.
  • Admin users: manage admin accounts and secure access.
  • App user management New: searchable, paginated list of mobile users with per-user detail (recent trips, Concierge Chat history, likes given), one-click Comp Pro toggle, and cascade-safe delete — replaces every “open Drizzle Studio” support workflow.
  • Trip management: oversee user-generated trips, AI itineraries, and budgets.
  • Promoted destinations: manage featured locations shown in the mobile app.
  • Prompt tuner: edit the system prompts that drive both itinerary generation and the new Concierge Chat refinement (loaded from settings.system_prompt and settings.refine_system_prompt).
  • API key management: manage OpenAI / Gemini / Google Maps / OpenWeatherMap keys from the UI. Honors a IS_DEMO=true env flag — when set, real key values are never sent to the client and editing is disabled with a teal “Demo mode” banner (server-side mutations are also blocked).
  • Settings management: configure dynamic app settings directly from the UI.
  • Modern UI/UX: clean interface using Shadcn UI components and Tailwind CSS.

Tech stack

Framework
Next.js 16.3 (App Router)
Language
TypeScript 6 / React 19.3
Styling
Tailwind CSS 4.3
UI components
Shadcn UI (Radix UI)
Icons
Lucide React
Database ORM
Drizzle ORM
Database
PostgreSQL (NeonDB)
Authentication
NextAuth.js v5
State management
Zustand
Validation
Zod
AI integration
Google Generative AI / OpenAI
File storage
Vercel Blob
03

Admin setup

Prerequisites

  • Node.js 24 — declared in admin/package.json (engines.node: "24.x"). Vercel reads the same field, so local and deployed builds match. Node 22 still runs the panel, but npm install prints an engine warning.
  • npm (a package-lock.json is included)
  • PostgreSQL database (e.g., NeonDB)

Installation

  1. 01
    Open a terminal in the repo root.
  2. 02

    Install admin dependencies:

    terminal
    npm --prefix admin install
04

Admin configuration (.env)

Create a file named .env inside the admin/ directory and configure the following variables:

DATABASE_URL
PostgreSQL connection string (Postgres / NeonDB).
AUTH_SECRET
Random secret used by NextAuth for session encryption.
BLOB_READ_WRITE_TOKEN
Token for Vercel Blob storage (image uploads).
MOBILE_API_KEY
Secret key used to validate mobile API requests.
AI_PROVIDER
AI provider identifier (e.g., gemini).
JWT_SECRET
Secret for signing JSON Web Tokens issued to mobile clients.
OPENWEATHERMAP_API_KEY
Fallback OpenWeatherMap key (used if not set in admin panel).
IS_DEMO
Optional. Set to true on a public demo deployment to hide all API key values, disable the editor, and reject mutation requests server-side. Leave unset for private deployments.
admin/.env
DATABASE_URL=
AUTH_SECRET=
BLOB_READ_WRITE_TOKEN=
MOBILE_API_KEY=
AI_PROVIDER=
JWT_SECRET=
OPENWEATHERMAP_API_KEY=
# IS_DEMO=true  public demo deployments only
05

Weather API

Wander AI provides weather forecasts for trip destinations using the OpenWeatherMap One Call API 3.0.

Get an API key

  1. 01
    Create a free account at openweathermap.org.
  2. 02
    Navigate to API keys in your dashboard and copy your key.
  3. 03
    Subscribe to the One Call API 3.0 plan (free tier includes 1,000 calls/day). A basic OWM key alone will not work.
Note
New API keys may take up to 2 hours to activate on OpenWeatherMap’s servers.

Configure the key

  • Admin panel (recommended): go to API Key Management and enter your key in the OpenWeatherMap API Key field.
  • Environment variable (fallback): set OPENWEATHERMAP_API_KEY in admin/.env. Admin panel value takes priority.

How it works

  • Mobile app calls GET /api/mobile/weather?lat=...&lng=...&startDate=...&endDate=....
  • Backend proxies the request to OpenWeatherMap, keeping the API key off the device.
  • Returns up to 8 days of daily forecast (temperature, conditions, humidity) filtered to the requested range.
  • If trip dates are more than 8 days in the future, no forecast data is returned.
06

Database schema

The admin panel uses Drizzle ORM with a PostgreSQL database. Migrations live in admin/drizzle/. Manage them via npx drizzle-kit push / npx drizzle-kit generate / npx drizzle-kit studio.

admin_users
Admin accounts for panel access.Fields: id, email, password, name
users
Mobile app users, with social-provider support.Fields: id, email, password, name, provider (email/google/apple), providerId, isPro
trips
User-created trips and itineraries.Fields: id, userId, destination, featuredImage, startDate, endDate, travelerType, budget, interests, itinerary, packingList, isPublic, status
trip_messagesNew
Persisted Concierge Chat thread for the trip refinement feature. One row per user / assistant turn. Cascade-deletes with the parent trip.Fields: id, tripId, userId, role ('user' | 'assistant'), content, itinerarySnapshot (jsonb), createdAt
trip_likes
Like relationship between mobile users and public trips.Fields: id, tripId, userId, createdAt
expenses
Per-day spending entries for the in-trip Expense Tracker.Fields: id, tripId, userId, dayNumber, amount, currency, category, note, createdAt
bookings
Flights, hotels and reservations kept against a trip. Cascade-deletes with the parent trip.Fields: id, tripId, userId, type (Flight | Hotel | Train | Car | Restaurant | Activity | Other), title, confirmationNumber, location, startAt, endAt, note, createdAt
promoted_destinations
Featured destinations shown in the app.Fields: id, name, country, imageUrl, description, isActive, displayOrder
settings
Global key-value settings store.Fields: key, value
07

Running the admin

Development mode

terminal
npm run dev:admin
# or, from inside admin/
npm run dev

The app will be available at http://localhost:3000.

Production mode

terminal
npm run build:admin
npm --prefix admin start
08

Admin customization

Changing the app name

  1. 01
    Open admin/package.json and update the "name" field.
  2. 02
    Update the metadata title in admin/app/layout.tsx.

Changing colors

  1. 01
    Open admin/app/globals.css.
  2. 02
    Locate the :root block.
  3. 03
    Update the --primary HSL value (default 175 33% 31%, teal).
Tip
Also update --ring to match the primary color so focus states stay consistent.
09

Mobile app

WanderAI is an intelligent travel-planner mobile app. It leverages Generative AI and the Google Places API to create detailed, visual itineraries.

Key features

Hybrid AI engine
Combines LLMs (OpenAI / Gemini) for creativity with Google Maps for accuracy, preventing location hallucinations.
Visual itineraries
Renders trips as a vertical timeline with real photos and interactive elements — not just text.
Itinerary mapNew
Plots each day’s activities as numbered pins on an interactive map (OpenStreetMap — no API key needed). Switch days with a chip bar, auto-fit the camera to the day’s stops, and tap a pin to open its details. Reuses the lat/lng already stored on every activity.
PDF exportNew
Pro users export any saved trip as a branded, printable PDF — cover photo, bookings by date, the day-by-day itinerary with “Open in Maps” links, and the packing list — then share or print it from an in-app preview. Built on the device; no backend setup.
Concierge chatNew
Pro users open a chat sheet on any owned trip and refine the itinerary in plain English — “day 2 feels rushed”, “swap museums for street food”, “we’re vegetarian”. Changes apply in place; affected activities pulse; an Undo pill rolls back the previous version. Conversation persists per trip.
Smart onboarding
“Vibe Check” system personalizes trips based on budget, travel style, and interests.
Monetization ready
Pre-integrated AdMob banners and RevenueCat subscriptions ($4.99/mo Pro tier).
Affiliate bookingNew
Hotels (Booking.com) and Flights (Skyscanner) CTAs on every trip, plus a Find Tours & Tickets (GetYourGuide) button on each activity. Partner IDs are optional build-time values — links work unattributed until you fill them in.
Vercel AnalyticsNew
Custom events (e.g. affiliate_cta_clicked) fire fire-and-forget from the mobile app to POST /api/mobile/analytics, which forwards to Vercel Analytics. Track conversion by partner, kind, and destination in your Vercel dashboard.

Trip details and refinement

The active itinerary screen and any owned saved trip surface a Refine ✦ floating pill (next to or replacing the Save Trip FAB). Tapping it opens the Concierge Chat sheet at 75% height, hydrates prior messages from GET /api/mobile/trips/:id/messages, and lets the user converse with the same OpenAI / Gemini engine that generated the trip. Refinements are saved server-side to the new trip_messages table; previous-snapshot rollback gives an instant Undo without losing the conversation.

10

Mobile prerequisites

  • Flutter 3.44 or newer (Dart 3.12+) — required by GoRouter 18. Built and tested on Flutter 3.47 / Dart 3.13.
  • Android Studio / VS Code, with the Android SDK Platform 37 installed
  • Xcode and CocoaPods for iOS builds

Platform targets

PlatformSettingWhere to change it
Android
minSdk 24 (Android 7.0), targetSdk / compileSdk 37
apps/android/app/build.gradle.kts
iOS
Deployment target 17.0
apps/ios/Podfile and IPHONEOS_DEPLOYMENT_TARGET in the Xcode project
Note
Lowering the targets: RevenueCat 10 needs Android 6.0 (API 23) or newer, so minSdk can go down to 23. If you lower the iOS deployment target, run cd apps/ios && pod install afterwards so the pods are rebuilt against it.
11

Mobile setup

Install dependencies

terminal
cd apps
flutter pub get

Configure environment (dart-define)

The mobile app uses Flutter’s --dart-define-from-file for build-time configuration — no .env loader, no runtime asset, no async init. Values resolve to compile-time constants via String.fromEnvironment and are centralized in apps/lib/core/config/env.dart.

Copy apps/env/example.json to apps/env/dev.json (gitignored) and fill in your values:

apps/env/dev.json
{
  "WANDER_API_KEY": "your_api_key_here",
  "GOOGLE_CLIENT_ID": "your_google_client_id_here",
  "REVENUECAT_API_KEY_IOS": "appl_...",
  "REVENUECAT_API_KEY_ANDROID": "goog_...",
  "USE_MOCK_IAP": true,
  "ENABLE_ADMOB": false,
  "BOOKING_AFFILIATE_ID": "",
  "SKYSCANNER_AFFILIATE_ID": "",
  "GETYOURGUIDE_PARTNER_ID": ""
}
Warning

Important:

  • WANDER_API_KEY: must match MOBILE_API_KEY in admin/.env — the backend rejects requests without it.
  • GOOGLE_CLIENT_ID: from your Google Cloud Console (used for Google sign-in).
  • BOOKING_AFFILIATE_ID / SKYSCANNER_AFFILIATE_ID / GETYOURGUIDE_PARTNER_ID: optional. Leave empty to ship unattributed search links. When you have approved partner accounts, paste in the IDs and rebuild — no admin or backend change needed.
  • Create separate files per environment — e.g. env/dev.json, env/prod.json. Everything except example.json is gitignored.
  • Since values are compiled in, switching environments means a rebuild. Truly dynamic config (AI provider, prompt, OpenAI key) is served from the backend at /api/mobile/config, so the build-time file only holds bootstrap values.
12

RevenueCat (in-app purchases)

WanderAI uses RevenueCat for In-App Purchases and Subscriptions. You need a RevenueCat project linked to your App Store and Google Play apps.

  1. 01
  2. 02
    Create a new project for your app.
  3. 03

    Critical — configure entitlements, offerings and products

    • Create an Entitlement identifier named pro (lowercase).
    • Create an Offering (typically named default) and attach your products to it.
    • Ensure your Google Play / App Store product IDs match exactly what you enter in RevenueCat. See setup guides for iOS and Android.
  4. 04
    Get your API keys from RevenueCat (one per platform).
  5. 05

    Add them to apps/env/dev.json (or prod.json):

    apps/env/dev.json
    {
      "REVENUECAT_API_KEY_IOS": "appl_YOUR_IOS_KEY",
      "REVENUECAT_API_KEY_ANDROID": "goog_YOUR_ANDROID_KEY",
      "USE_MOCK_IAP": false
    }

Set USE_MOCK_IAP=true to test the UI without real store connections.

Note
Official guide: follow the RevenueCat Quickstart for detailed instructions.
13

PDF export

Pro users open Export as PDF from the “more actions” sheet on the itinerary or trip details screen. The preview screen offers Share and Print through the OS. Everything is rendered on the device, so there is no endpoint to deploy and no key to configure.

What’s in the file

  • Cover: trip photo, destination, dates, and travellers / budget chips.
  • Bookings grouped by date, then the day-by-day itinerary (time, place, type, description, photo, and a tappable Open in Maps link), then the packing list with checkboxes.
  • Page-numbered footers, in the app’s own font and colours. Page size is US Letter for US/CA/MX/PH locales and A4 everywhere else.

Where to customise it

pdf_export/data/trip_pdf_builder.dart
The whole layout: colours, fonts, sections, footer text. Pure Dart — no Flutter widgets — so it can be unit tested.
pdf_export/data/pdf_assets_loader.dart
Which fonts are fetched and photo timeouts.
pdf_export/ui/trip_pdf_export_screen.dart
The preview screen, the Pro gate, and analytics events.
Note
Network on first export: fonts come from Google Fonts and photos from your Blob storage, so the first export needs a connection (both are cached afterwards). A photo that can’t be fetched in time is simply left out rather than failing the export. Trips written in Japanese, Korean, Chinese or Thai pull an extra Noto font on demand, which makes that first export slower and the file larger.
14

Affiliate booking and analytics

Each trip details screen surfaces two booking CTAs (Hotels via Booking.com, Flights via Skyscanner), pre-filled with the trip’s destination and dates. Every activity bottom sheet adds a Find Tours & Tickets CTA that searches GetYourGuide by place name. The URL builders live in apps/lib/core/services/affiliate_service.dart; swap partners or tweak query params there.

Wiring partner IDs

  1. 01
    Sign up for the partner programs you want to monetize — Booking.com Affiliate Partner, Skyscanner Partners, GetYourGuide Partner.
  2. 02

    Once approved, copy your IDs into apps/env/dev.json (and prod.json):

    apps/env/prod.json
    {
      "BOOKING_AFFILIATE_ID": "1234567",
      "SKYSCANNER_AFFILIATE_ID": "your_associate_id",
      "GETYOURGUIDE_PARTNER_ID": "your_partner_id"
    }
  3. 03
    Rebuild with --dart-define-from-file=env/prod.json. Every CTA tap now carries your attribution.

Analytics and conversion tracking

CTA taps emit an affiliate_cta_clicked event with partner, kind, destination, trip_id, place_name, and activity_type properties. The mobile app POSTs events to the admin’s POST /api/mobile/analytics route, which validates the payload with Zod and forwards it to Vercel Analytics via @vercel/analytics/server (auto-tagging source: "mobile").

Find the data in your Vercel project → Analytics Events. Filter by event name and slice by partner / kind / destination to compare which booking surfaces drive the most clicks.

To track any other mobile event, call AnalyticsService.track('your_event_name', properties: { ... }) from anywhere in the Flutter code — it’s fire-and-forget and never throws.

15

Project structure

The mobile app follows a feature-first architecture for scalability.

apps/lib/
apps/lib/
├── core/               Core utilities, theme, router, shared services
├── features/           Feature-based modules
│   ├── auth/           Authentication (email, Google, Apple)
│   ├── bookings/       Flights, hotels and reservations per trip
│   ├── community/      Public trip feed
│   ├── expense/        Trip expense tracker
│   ├── explore/        Discovery / home screen
│   ├── onboarding/     Vibe-check onboarding flow
│   ├── packing/        Packing list per trip
│   ├── pdf_export/     Pro: trip PDF (builder, preview, share/print)
│   ├── refine/         Concierge Chat refinement
│   ├── settings/       App settings (theme, etc.)
│   ├── shell/          Main navigation shell
│   ├── subscription/   RevenueCat paywall
│   ├── trip/           Trip generation, itinerary, saved trips
│   └── weather/        Forecast for trip days
└── main.dart           Application entry point

Unit tests live in apps/test/, mirroring the same structure. Run them with cd apps && flutter test.

16

Mobile customization

Changing the app name

Update the name field in apps/pubspec.yaml. For the display label on the home screen, edit apps/android/app/src/main/AndroidManifest.xml and apps/ios/Runner/Info.plist.

Changing the primary color

  1. 01
    Open apps/lib/core/theme/app_theme.dart.
  2. 02
    Find the MaterialTheme class.
  3. 03
    Update primary in the lightScheme and darkScheme methods.
app_theme.dart
// Example in app_theme.dart
static ColorScheme lightScheme() {
  return const ColorScheme(
    brightness: Brightness.light,
    primary: Color(0xff356668),  // <-- change this HEX
    // ... other colors
  );
}
17

Running the mobile app

Pass your environment file with --dart-define-from-file. For convenience, add the flag to your IDE’s launch configuration (VS Code: .vscode/launch.json, Android Studio: Edit Configurations → Additional run args).

Android

terminal
cd apps
flutter run --dart-define-from-file=env/dev.json

iOS

terminal
cd apps/ios
pod install
cd ..
flutter run --dart-define-from-file=env/dev.json
Note

iOS — Swift Package Manager: some native plugins (google_mobile_ads, RevenueCat, sign_in_with_apple) are CocoaPods-only, which conflicts with Flutter’s Swift Package Manager. If pod install or the iOS build fails with an SPM/CocoaPods error, disable SPM once for your Flutter install, then reinstall pods:

terminal
flutter config --no-enable-swift-package-manager
cd apps/ios && rm -rf Pods Podfile.lock && pod install && cd ..

The remaining “plugins do not support Swift Package Manager” message is just an informational warning — the build still works through CocoaPods.

Note
iOS — keep google_mobile_ads below 9.1.0: pubspec.yaml pins ">=9.0.0 <9.1.0" on purpose. Version 9.1.0 imports a private AdMob header, so a CocoaPods build fails with “Include of non-modular header inside framework module”. If you raise it and hit that error, pin it back and run pod update Google-Mobile-Ads-SDK.

Production builds

terminal
flutter build apk       --dart-define-from-file=env/prod.json
flutter build appbundle --dart-define-from-file=env/prod.json
flutter build ios       --dart-define-from-file=env/prod.json
18

Changelog

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

v1.8.0 · Sep 2026
  • Mobile — PDF Itinerary Export (Pro): new Export as PDF action in the "more actions" sheet on both the itinerary and trip details screens. Opens a preview with built-in Share and Print. The PDF includes a cover (photo, destination, dates, travelers, budget), bookings grouped by date, a day-by-day itinerary (time, place, type, rating, description, thumbnail, "Open in Maps" link), the packing list with checkboxes, and page-numbered footers.
  • Mobile — On-brand, lightweight output: uses the app's Plus Jakarta Sans font and Warm Wanderer colors. Activity thumbnails are resampled to print resolution, so a typical trip exports in a few hundred KB. Noto fallback fonts cover accented, Cyrillic, Japanese, Korean, Chinese and Thai text, and the large CJK/Thai fonts are only downloaded when a trip uses them. Layout runs on a background isolate to keep the UI smooth.
  • Mobile — Paywall: "Export Trips as PDF" added to the Pro feature list. Free users tapping export are taken to the paywall.
  • Mobile — Dependencies: added pdf and printing. No Android manifest or iOS Info.plist changes required.
  • Mobile — Tests: first unit test suite (apps/test/) covering PDF layout, page flow, background-isolate rendering, text/file-name helpers, and community-feed parsing. Run with flutter test.
  • Mobile — Platform targets: Android minSdk 24 with targetSdk/compileSdk 37 (Android 17), and iOS deployment target raised to 17.0. Set explicitly in apps/android/app/build.gradle.kts, the Podfile, and the Xcode project.
  • Mobile — Dependency refresh: every package updated — GoRouter 18, Riverpod 3.4, Freezed 4, purchases_flutter 10 (RevenueCat), sign_in_with_apple 8, package_info_plus 10, flutter_map 8.3 and more. google_mobile_ads is held at 9.0.x on purpose: 9.1.0 fails CocoaPods iOS builds because its headers import a private AdMob header.
  • Admin — Dependency refresh: Next.js 16.3, React 19.3, Tailwind CSS 4.3, Zod 4.6, openai 7, @vercel/blob 2.8, Drizzle Kit 0.31.10, TypeScript 6. The admin now declares Node 24 in engines, which is also what Vercel will build with.
  • Fix — Community feed: feed cards showed “by Unknown”, never showed the liked state or trip length, and the traveler filter did nothing — the app read creator/travelers/isLiked while the API returns author/travelerType/isLikedByMe. The client now reads both shapes and derives trip length from the dates. No backend change or redeploy needed.
  • Fix — Trip generation screen: a failed generation (no connection, missing API key) was reported as an unhandled exception instead of the screen's retry state, and the screen could overflow on short screens, in landscape, or with large system text. It now scrolls instead.
v1.7.0 · Jul 2026
Bookings and reservations — a per-trip tracker for flights, hotels and reservations.
v1.6.0
Itinerary map view plotting a day's activities as numbered markers.
v1.5.0
Affiliate booking CTAs — hotels and flights cards on the trip details screen.
v1.4.0
Concierge chat lets Pro users refine any owned trip in natural language.
v1.3.0
Community discover feed for browsing public itineraries.
v1.2.0
Real-time weather forecast on itinerary day tabs.
v1.1.0
In-app subscriptions via RevenueCat.
v1.0.0
Initial release.
19

Support and licensing

If you need any assistance, have questions, or want to report a bug, please contact us:

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