Setup & Installation
Everything you need to install, configure, and deploy PropertyPro — from your first pnpm install to a custom domain on production.
Upgrading from v2.x? Run the Better Auth migration before anyone tries to log in
v3.0 moves authentication from NextAuth to Better Auth, which needs a one-time database migration. Until it runs, nobody can sign in — every API call returns 503 and the sign-in page rejects even a correct administrator password with “Invalid email or password”. Passwords carry over untouched and no records are rewritten; everyone is simply signed out once. Back up first, then run it. Fresh installs can ignore this.
pnpm db:migrate-better-auth --dry-run # preview, writes nothing
pnpm db:migrate-better-auth # requiredOverview
PropertyPro is a complete property management application built with Next.js 16, React 19, and MongoDB. Version 3.0 adds a public marketing website with online rental applications. This documentation covers everything from your first install to deploying in production.
Read sectionUpgrading to v3.0
Fresh install? Skip this section entirely. Upgrading from any 2.x version? v3.0 replaces NextAuth with Better Auth and needs a one-time database migration. Until it has run nobody can sign in — not even an administrator with the right password — and the app refuses to serve requests.
Read sectionQuickstart
If you're already comfortable with Node, pnpm, and MongoDB, this is the express path. Each step is expanded later in the docs.
Read sectionRequirements
PropertyPro runs anywhere Node.js does. These versions are tested in CI — older versions may work but aren't supported.
Read sectionPropertyPro is a complete property management application built with Next.js 16, React 19, and MongoDB. Version 3.0 adds a public marketing website with online rental applications. This documentation covers everything from your first install to deploying in production.
What's in the package
Full source code, database schema, seed data, a PWA-ready dashboard, and a public marketing site with a rental application checkout.
License
One regular license per end product. Lifetime updates within the same major version.
Stack
Next.js 16, React 19 + Compiler, TypeScript, Tailwind CSS v4, MongoDB, Better Auth, Stripe.
Support
6 months of buyer support included. Renewable from your CodeCanyon account.
Upgrading from v2.x? Nobody can log in until the migration runs
v3.0 replaces NextAuth with Better Auth and needs a one-time database migration — pnpm db:migrate-better-auth. Until it runs, every API call returns 503 and sign-in refuses even a correct admin password with "Invalid email or password". See Upgrading to v3.0 before you do anything else.
Looking for end-user docs?
If you're configuring tenants, leases, and day-to-day workflows, head to the User Manual instead.
Fresh install? Skip this section entirely. Upgrading from any 2.x version? v3.0 replaces NextAuth with Better Auth and needs a one-time database migration. Until it has run nobody can sign in — not even an administrator with the right password — and the app refuses to serve requests.
- 1
Back up first
Settings → Backups → Create backup, or run mongodump. This is the entire rollback plan — do not skip it.
bashmongodump --uri="$MONGODB_URI" --archive=pre-v3-upgrade.archive --gzip - 2
Copy the v3.0 files and install
Preserve .env.local and your local ./uploads directory.
bashpnpm install - 3
Leave your environment alone
No change is required. v3.0 reads BETTER_AUTH_SECRET first and falls back to AUTH_SECRET, then NEXTAUTH_SECRET. If you switch to the new names, copy the same value across — a new secret signs everyone out.
- 4
Run the migration
Additive and safe to re-run: it copies password hashes rather than moving them, and skips anything already migrated. If it is interrupted, just run it again.
bashpnpm db:migrate-better-auth --dry-run # preview pnpm db:migrate-better-auth # apply - 5
Rebuild and verify
Sign in with an existing account and its existing password, check that Settings → Security lists your session, and confirm terminating a session signs that device out.
bashpnpm build pnpm start - 6
Run the optional migrations
Not required to boot, but each fixes something. db:migrate-email-index lets you re-create a tenant whose account was deleted; db:backfill-coordinates puts pre-3.0 properties on the new maps.
bashpnpm db:migrate-email-index pnpm db:backfill-coordinates
# 1. Preview — writes nothing
pnpm db:migrate-better-auth --dry-run
# 2. Run it
pnpm db:migrate-better-authUntil the migration runs, every API call returns 503
"This deployment is running database schema v0 but the application requires v1." That is deliberate — v3.0 refuses to serve an un-migrated database rather than answering correct passwords with "invalid credentials". Sign-in is not covered by the gate, so it reports "Invalid email or password" instead; same cause, same fix.
What the upgrade does and does not touch
Passwords are unchanged — nobody has to reset anything. Users, properties, leases, payments, and tenants are untouched. Everyone is signed out once, because pre-3.0 logins were JWT-based and have no server-side record to convert.
Customized files: one import moved
v3.0 changed 74 files mechanically. If you edited any of them, change next-auth/react to @/lib/auth-client — useSession, signIn, and signOut keep the same signatures. The old next-auth package is still installed so your build doesn't break, but it is scheduled for removal in v3.1.
Contacting support about an upgrade?
Include the output of pnpm db:migrate-better-auth --dry-run. It identifies the state of your database immediately. The bundled UPGRADE.md has the full reference, including rollback.
If you're already comfortable with Node, pnpm, and MongoDB, this is the express path. Each step is expanded later in the docs.
- 1
Install dependencies
From the unzipped folder, install all packages with pnpm.
bashcd propertypro pnpm install - 2
Copy env file
Duplicate the example env, then set MONGODB_URI and generate an auth secret. Everything else has a working default.
bashcp .env.example .env.local openssl rand -base64 32 # paste as BETTER_AUTH_SECRET - 3
Load sample data (optional)
Seeds demo properties, units, and tenants so the dashboard isn't empty. It creates no login.
bashpnpm db:seed - 4
Start the dev server
Runs on http://localhost:3000. Leave it running for the next step.
bashpnpm dev - 5
Create your first administrator
Add SETUP_SECRET to .env.local, restart, then call the one-time bootstrap endpoint. Remove the secret afterwards.
bashcurl -X POST http://localhost:3000/api/setup/create-admin \ -H "Content-Type: application/json" \ -H "x-setup-secret: $SETUP_SECRET" \ -d '{"email":"you@example.com","password":"<strong password>","firstName":"Your","lastName":"Name"}'
No default login ships with PropertyPro
As of 3.0 there is no built-in admin account and no hardcoded password. The bootstrap endpoint above is the only way to create your first administrator, and it refuses to run once one exists.
PropertyPro runs anywhere Node.js does. These versions are tested in CI — older versions may work but aren't supported.
Node.js 20.19.28+
Enforced by the package engines field. Use 20 LTS or 22 — Node 18 is not supported due to React Compiler dependencies.
pnpm 9+
Required. Install with corepack enable && corepack prepare pnpm@latest --activate.
MongoDB 6+
MongoDB Atlas is recommended. Self-hosted MongoDB works when MONGODB_URI points to your server.
File storage
Local disk by default (./uploads). Switch UPLOAD_STORAGE_PROVIDER to cloud for Cloudflare R2 — recommended on serverless hosts, where local disk does not persist.
Hosting suggestion
For most buyers, Vercel + MongoDB Atlas is the fastest path. The full deploy guide covers VPS and Docker too.
Download from CodeCanyon, unzip, and install. PropertyPro ships as a ready-to-run Next.js project — no build steps required to start dev.
- 1
Download from CodeCanyon
Sign in to CodeCanyon → Downloads → PropertyPro. Choose 'All files & documentation'.
- 2
Unzip the package
Extract the archive to a permanent location. The main folder is /propertypro.
bashunzip propertypro-v3.0.0.zip -d ~/projects/ cd ~/projects/propertypro - 3
Install dependencies
We use pnpm for fast, deterministic installs. Run this once after every download or update.
bashpnpm install - 4
Verify the install
A quick smoke test confirms everything resolved correctly.
bashnpx tsc --noEmit pnpm lint
Don't use npm or yarn
The lockfile is pnpm-only. Mixing package managers will corrupt resolutions — stick to pnpm.
All secrets live in .env.local. The included .env.example documents every key — only the core block is needed to boot. Payment gateways, SMTP, and maps are normally configured in the admin panel and stored in the database; the matching env values are used only as a fallback when the admin field is empty.
# ---- Required: core --------------------------------------------------
# MongoDB Atlas
MONGODB_URI=mongodb+srv://username:password@cluster0.kbnje.mongodb.net/propertypro
# Self-hosted alternative
# MONGODB_URI=mongodb://mongo:password@your_ip_address:27017/propertypro
# MONGODB_DB=propertypro
# Public base URL, no trailing slash
BETTER_AUTH_URL=http://localhost:3000
# Generate with: openssl rand -base64 32
BETTER_AUTH_SECRET=replace-with-a-long-random-string
# Upgrading from v2.x? The old names are still read as a fallback
# (BETTER_AUTH_SECRET -> AUTH_SECRET -> NEXTAUTH_SECRET). Keep the SAME
# value — changing the secret signs everyone out.
# NEXTAUTH_SECRET=
# ---- First admin (delete after use) ----------------------------------
# Unlocks POST /api/setup/create-admin. Generate: openssl rand -hex 32
# SETUP_SECRET=
# ---- Application -----------------------------------------------------
APP_NAME=PropertyPro
APP_URL=http://localhost:3000
NEXT_PUBLIC_APP_URL=http://localhost:3000
SUPPORT_EMAIL=support@example.com
# ---- File storage ----------------------------------------------------
# "local" (default, ./uploads) or "cloud" (Cloudflare R2)
UPLOAD_STORAGE_PROVIDER=local
# R2_ACCOUNT_ID=
# R2_ACCESS_KEY_ID=
# R2_SECRET_ACCESS_KEY=
# R2_BUCKET_NAME=
# R2_PUBLIC_URL=https://files.example.com
# NEXT_PUBLIC_R2_PUBLIC_URL=https://files.example.com
# ---- Maps (normally set in Settings -> Maps) -------------------------
# "leaflet" (OpenStreetMap, free, no key — default), "google", "disabled"
# MAPS_PROVIDER=leaflet
# Browser key: Maps JavaScript API + Places API, restrict by HTTP referrer
# NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=
# Server key for db:backfill-coordinates: Geocoding API, restrict by IP
# GOOGLE_GEOCODING_API_KEY=
# ---- Stripe (fallback for the admin panel) ---------------------------
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
# ---- Email / SMTP (fallback for the admin panel) ---------------------
# EMAIL_SERVER_HOST=smtp.gmail.com
# EMAIL_SERVER_PORT=587
# EMAIL_SERVER_USER=you@example.com
# EMAIL_SERVER_PASSWORD=your-app-password
# EMAIL_FROM=PropertyPro <noreply@example.com>
ENABLE_EMAIL_NOTIFICATIONS=true
# ---- Web Push (VAPID) ------------------------------------------------
# npx web-push generate-vapid-keys
# NEXT_PUBLIC_VAPID_PUBLIC_KEY=
# VAPID_PRIVATE_KEY=
# VAPID_SUBJECT=mailto:support@example.com
# ---- Encryption ------------------------------------------------------
# Required before storing SSNs. 32+ chars, keep stable across deploys.
# DATA_ENCRYPTION_KEY=| Key | Required | Description |
|---|---|---|
MONGODB_URI | Required | MongoDB Atlas or self-hosted connection string. In development it falls back to mongodb://localhost:27017/PropertyPro when omitted. |
MONGODB_DB | Optional | Database name override, only used to build the localhost URI when MONGODB_URI is unset. |
BETTER_AUTH_SECRET | Required | Signs sessions and tokens. Generate with openssl rand -base64 32. Falls back to AUTH_SECRET then NEXTAUTH_SECRET, so v2.x deployments keep working untouched. |
BETTER_AUTH_URL | Required | Public base URL with no trailing slash, used for callbacks and links in email. Falls back to NEXTAUTH_URL then NEXT_PUBLIC_APP_URL. |
SETUP_SECRET | Optional | Unlocks the one-time POST /api/setup/create-admin bootstrap. The endpoint is disabled while unset and refuses to run once any administrator exists. Remove it after creating your first admin. |
UPLOAD_STORAGE_PROVIDER | Optional | local (default, writes to ./uploads) or cloud (Cloudflare R2). Serverless hosts need cloud — their filesystem does not persist between requests. |
R2_* | Optional | Account ID, access key, secret, bucket, and public URL. Required only when UPLOAD_STORAGE_PROVIDER=cloud. |
MAPS_PROVIDER | Optional | leaflet (OpenStreetMap, the default, needs no key), google, or disabled. Only a fallback — once a settings document exists, Settings → Maps wins. |
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY | Optional | Browser key for the Google provider, with Maps JavaScript API and Places API enabled. It is served to the browser and cannot be kept secret, so restrict it by HTTP referrer and set a quota cap. |
GOOGLE_GEOCODING_API_KEY | Optional | Separate server-side key with the Geocoding API enabled, used only by db:backfill-coordinates. Restrict it by IP — a referrer-restricted key is rejected. |
STRIPE_SECRET_KEY / STRIPE_WEBHOOK_SECRET | Optional | Fallback for the admin payment settings. The webhook secret belongs to the specific endpoint, not your account. |
EMAIL_SERVER_* | Optional | Outbound SMTP for invites, receipts, reminders, and the new inquiry and application emails. SMTP_* names are accepted as aliases. Fallback for the admin email settings. |
NEXT_PUBLIC_DEMO_MODE | Optional | Showcase deployments only. Renders the one-click demo login panel, and each row appears only if its NEXT_PUBLIC_DEMO_*_PASSWORD is also set. Never enable on a deployment holding real data. |
VAPID_* | Optional | Required only for web push notifications. Generate with npx web-push generate-vapid-keys. |
DATA_ENCRYPTION_KEY | Optional | Required before storing SSNs or other encrypted tenant identity data. At least 32 characters, and it must stay stable across deployments. |
Upgrading from v2.x? Keep your existing secret
BETTER_AUTH_SECRET falls back to AUTH_SECRET and then NEXTAUTH_SECRET, so no env change is required. If you do switch to the new names, copy the same value across — generating a new secret signs every user out.
Generate a secret fast
Run openssl rand -base64 32 for BETTER_AUTH_SECRET, and openssl rand -hex 32 for SETUP_SECRET.
PropertyPro stores application data in MongoDB. Create a MongoDB Atlas cluster or self-hosted database, then point MONGODB_URI at it before starting the app.
- 1
Create a MongoDB database
Create a MongoDB Atlas cluster and database named propertypro, or prepare a self-hosted MongoDB instance.
bash# MongoDB Atlas example MONGODB_URI=mongodb+srv://username:password@cluster0.kbnje.mongodb.net/propertypro - 2
Allow network access
In MongoDB Atlas, add your local IP address for development and your hosting provider's outbound IPs for production.
txtAtlas → Network Access → Add IP Address - 3
Seed demo data (optional)
Loads demo properties, units, and tenants so the dashboard isn't empty. It creates no login — use the setup endpoint for that.
bashpnpm db:seed - 4
Run migrations when upgrading
Fresh installs need none of these. Upgrading from v2.x requires the Better Auth migration — the app returns 503 on every request until it has run. Preview any of them with --dry-run.
bashpnpm db:migrate-better-auth --dry-run # preview, writes nothing pnpm db:migrate-better-auth # required, v2.x -> v3.0 pnpm db:migrate-email-index # allows re-creating deleted tenants pnpm db:backfill-coordinates # puts pre-3.0 properties on the map - 5
Inspect with MongoDB Compass
Use MongoDB Compass or the Atlas Data Explorer to browse collections and verify seeded records.
txtmongodb+srv://username:password@cluster0.kbnje.mongodb.net/propertypro
Production: skip the seed
Never run pnpm db:seed against a live database — it inserts demo records intended for evaluation. Migrations are safe to re-run; they skip anything already done.
db:backfill-coordinates is a dry run by default
It lists what it would geocode, makes no API calls, and writes nothing. To apply, re-run with GEOCODE_APPLY=1 GEOCODE_CONFIRM=BACKFILL_COORDINATES and a GOOGLE_GEOCODING_API_KEY set — the script has no OpenStreetMap path, so it needs a Google key even on Leaflet installs.
Two scripts cover 99% of dev work: dev and build. The PWA service worker is disabled in dev to keep iteration fast.
- 1
Start in development
Hot-reload, fast refresh, and source maps. Defaults to port 3000.
bashpnpm dev - 2
Run a production build
Compiles, treeshakes, and runs Next.js production server. Use this to validate before deploying.
bashpnpm build pnpm start - 3
Visit the public site and the dashboard
The site root is now the public marketing homepage, not a redirect to sign-in. The dashboard lives behind /auth/signin.
txtPublic site: http://localhost:3000 Properties: http://localhost:3000/properties Dashboard: http://localhost:3000/dashboard
No account yet?
PropertyPro ships without any login. Create your first administrator with the SETUP_SECRET bootstrap covered in Quickstart and Admin & Roles.
Turn the public site off
If you only want the management dashboard, open Dashboard → Public Site and switch the master toggle off. The root URL then behaves as it did in v2.x — sign-in for guests, dashboard for signed-in users.
PropertyPro uses Cloudflare R2 for property images, tenant documents, and other uploaded files. Configure one private write path through R2 API credentials and one public read URL for displaying saved files.
- 1
Create an R2 bucket
In Cloudflare, open Storage & databases -> R2 and create a bucket for PropertyPro uploads. Use a lowercase bucket name with numbers and hyphens only, then place that exact bucket name in R2_BUCKET_NAME.
bashR2_BUCKET_NAME=your-r2-bucket-name - 2
Create a scoped R2 API token
From the R2 overview, open Manage API Tokens, create an API token with Object Read & Write permission, and scope it to the PropertyPro bucket. Copy the Access Key ID, Secret Access Key, and account ID before leaving the confirmation screen.
bashR2_ACCOUNT_ID=your-r2-account-id R2_ACCESS_KEY_ID=your-r2-access-key-id R2_SECRET_ACCESS_KEY=your-r2-secret-access-key R2_BUCKET_NAME=your-r2-bucket-name - 3
Enable public file delivery
For production, connect a custom domain such as assets.yourdomain.com to the bucket. For local testing only, you may enable the Cloudflare-managed r2.dev public development URL. Set both public URL variables to the exact origin with no trailing slash.
bash# Production custom domain R2_PUBLIC_URL=https://your-custom-domain.com NEXT_PUBLIC_R2_PUBLIC_URL=https://your-custom-domain.com # Local or staging with R2 public development URL # R2_PUBLIC_URL=https://pub-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.r2.dev # NEXT_PUBLIC_R2_PUBLIC_URL=https://pub-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.r2.dev - 4
Allow the image host in Next.js
The bundled next.config.ts already allows r2.dev and r2.cloudflarestorage.com image hosts. If you use a custom R2 domain and render uploaded images through next/image, add that hostname to images.remotePatterns, then rebuild.
next.config.tstsimages: { remotePatterns: [ { protocol: "https", hostname: "assets.yourdomain.com", port: "", pathname: "/**", search: "", }, ], } - 5
Verify uploads
Restart the app after changing environment variables, upload a property image or tenant document, then open the saved file URL. If the upload succeeds but previews fail, check the public bucket URL and image remote pattern first.
https://<ACCOUNT_ID>.r2.cloudflarestorage.comKeep write credentials server-only
Never expose R2_ACCESS_KEY_ID or R2_SECRET_ACCESS_KEY in NEXT_PUBLIC_* variables. Only NEXT_PUBLIC_R2_PUBLIC_URL should be readable by the browser.
Use custom domains in production
Cloudflare's r2.dev public development URLs are intended for non-production traffic. A custom domain gives you normal Cloudflare cache, access control, WAF, and bot-management options.
PropertyPro uses Stripe Checkout for one-time invoices and Stripe Customer Portal for tenant self-service. Both work with test keys out of the box.
- 1
Get your API keys
Stripe Dashboard → Developers → API keys. Copy the secret and publishable keys into .env.local.
- 2
Create a webhook endpoint
Point Stripe at /api/webhooks/stripe on your deployed URL. Subscribe to checkout.session.completed and invoice.payment_succeeded.
bash# Webhook URL https://yourdomain.com/api/webhooks/stripe - 3
Test webhooks locally
Use the Stripe CLI to forward events to your dev server while developing.
bashstripe listen --forward-to localhost:3000/api/webhooks/stripe - 4
Switch to live mode
When ready, swap test keys (sk_test_*, pk_test_*) for live keys (sk_live_*, pk_live_*) and re-create the webhook in live mode.
Lease reminders, payment receipts, password resets, and tenant invites all go through one SMTP transport. We've tested Resend, SendGrid, Postmark, and standard SMTP servers.
Resend (recommended)
Easiest for new buyers. Free tier covers most small portfolios. EMAIL_SERVER_HOST=smtp.resend.com, EMAIL_SERVER_USER=resend.
SendGrid
Higher limits and better deliverability for large portfolios. EMAIL_SERVER_HOST=smtp.sendgrid.net.
Postmark
Best for transactional-only email. EMAIL_SERVER_HOST=smtp.postmarkapp.com.
Gmail / generic SMTP
Works for testing but rate-limited. Not recommended for production.
Verify your sender domain
Always set up SPF, DKIM, and DMARC for EMAIL_FROM's domain — without it, emails land in spam.
Push notifications use the Web Push API with VAPID keys. Tenants and admins get instant alerts for payments, requests, and chats.
- 1
Generate VAPID keys
Run the bundled generator. Copy both keys into .env.local.
bashpnpm gen:vapid - 2
Set NEXT_PUBLIC_VAPID_PUBLIC_KEY
The client uses this public key to subscribe browsers to the push service.
- 3
Set VAPID_PRIVATE_KEY
The server uses this to sign push payloads. Never expose it to the browser.
- 4
Test from the dashboard
Sign in, allow notifications when prompted, then go to Settings → Notifications → Send test.
All brand assets live under /public and the color palette is defined as Tailwind CSS variables. No code changes required for a basic rebrand.
- 1
Replace the logo
Drop your SVG logo at /public/logo.svg. The dashboard reads from this path automatically.
- 2
Update the favicon & PWA icons
Replace /public/favicon.ico and the icons under /public/icons/ (sizes 192, 256, 384, 512).
- 3
Tweak brand colors
Edit the --brand-* tokens in app/globals.css. Tailwind picks them up across the app.
app/globals.csscss:root { --brand-50: oklch(0.97 0.02 230); --brand-500: oklch(0.62 0.18 230); --brand-700: oklch(0.45 0.18 230); } - 4
Update site metadata
Edit app/layout.tsx to change the default title, description, and OG image.
PropertyPro ships with English, Arabic, French, Spanish, and Bengali. Locales live as JSON files under /messages and are loaded with next-intl.
- 1
Edit existing strings
Open /messages/<locale>.json and edit values directly. Changes hot-reload in dev.
- 2
Add a new language
Copy /messages/en.json to /messages/<your-locale>.json and translate values. Then register it in lib/i18n.ts.
lib/i18n.tstsexport const locales = ["en", "ar", "fr", "es", "bn", "de"] as const; export const defaultLocale = "en"; - 3
RTL support
Arabic is enabled by default and switches the layout direction automatically. Add other RTL locales in lib/i18n.ts → rtlLocales.
Vercel is the fastest path to production. The whole flow takes under 10 minutes once your environment variables are ready.
- 1
Push to a GitHub repo
Create a private GitHub repo and push the source. Vercel will pull from it.
bashgit init git add . git commit -m "Initial PropertyPro setup" git remote add origin git@github.com:you/propertypro.git git push -u origin main - 2
Import the repo into Vercel
vercel.com → New Project → Import. Vercel auto-detects Next.js — no build config needed.
- 3
Add environment variables
Project Settings → Environment Variables. Paste every key from your .env.local. Set APP_URL, NEXTAUTH_URL, and AUTH_URL to your Vercel URL.
- 4
Connect MongoDB Atlas
Add Vercel's outbound access to MongoDB Atlas Network Access, then set MONGODB_URI in Vercel before deploying.
bashMONGODB_URI=mongodb+srv://username:password@cluster0.kbnje.mongodb.net/propertypro - 5
Add your custom domain
Project Settings → Domains. SSL is provisioned automatically.
If you'd rather run on a VPS, PropertyPro ships with a production-ready Dockerfile and a sample docker-compose.yml that can run MongoDB alongside the app.
- 1
Build & start the stack
From the project root, build the images and run them in the background.
bashdocker compose up -d --build - 2
Seed demo data inside the container
Load demo records only when you are setting up a test or evaluation instance.
bashdocker compose exec app pnpm db:seed - 3
Front with nginx + SSL
Use nginx (or Caddy) to terminate TLS and proxy to localhost:3000. Caddy will provision Let's Encrypt automatically.
Caddyfilenginxpropertypro.yourdomain.com { reverse_proxy localhost:3000 }
services:
app:
build: .
ports: ["3000:3000"]
env_file: .env.local
environment:
MONGODB_URI: mongodb://propertypro:change-me@mongo:27017/propertypro?authSource=admin
depends_on: [mongo]
mongo:
image: mongo:7
restart: unless-stopped
environment:
MONGO_INITDB_ROOT_USERNAME: propertypro
MONGO_INITDB_ROOT_PASSWORD: change-me
volumes:
- mongodata:/data/db
ports: ["27017:27017"]
volumes:
mongodata:Use a managed database
Self-hosting MongoDB works but you'll own backups, replication, and upgrades. MongoDB Atlas offloads that work.
Once your app is live, route a custom domain to it. Both Vercel and Caddy provision SSL certificates for free.
- 1
Add an A or CNAME record
For Vercel, add a CNAME pointing to cname.vercel-dns.com. For your VPS, point an A record at the server IP.
- 2
Verify in Vercel / Caddy
Vercel auto-verifies once DNS propagates. Caddy reissues the certificate on the next request.
- 3
Update application URLs
Switch APP_URL, NEXTAUTH_URL, and AUTH_URL to https://yourdomain.com and redeploy. These are used in emails and auth callbacks.
PropertyPro ships with no accounts at all. Create the first administrator through the one-time bootstrap endpoint, then invite your team with scoped permissions.
- 1
Create the first administrator
Add SETUP_SECRET to your environment and restart, then POST once to the bootstrap endpoint. It is disabled while the secret is unset and refuses to run once any administrator exists, so it cannot be replayed.
bashopenssl rand -hex 32 # use as SETUP_SECRET curl -X POST https://your-domain.com/api/setup/create-admin \ -H "Content-Type: application/json" \ -H "x-setup-secret: $SETUP_SECRET" \ -d '{"email":"you@example.com","password":"<strong password>","firstName":"Your","lastName":"Name"}' - 2
Remove the setup secret
Delete SETUP_SECRET from your environment and redeploy. Its job is done.
- 3
Invite your team
Settings → Team → Invite. Each invitee gets an email with a one-time setup link and chooses their own password — minimum 8 characters as of 3.0.
- 4
Assign roles
Built-in roles are Admin, Manager, and Tenant. Manager and Tenant are now editable; Admin stays locked because it is the only guaranteed holder of role_management. Create custom roles under Settings → Roles.
- 5
Grant delete rights explicitly
New in 3.0: lease_delete, tenant_delete, payment_delete, document_delete, property_delete, and user_delete are separate permissions and are never implied by the matching edit grant. The built-in Manager ships without them, so managers cannot delete until you grant them. Bulk deletes also need bulk_operations.
- 6
Audit log
Every sensitive action is recorded, including public-site edits. Review under Settings → Activity.
Never use seed:demo to create a real admin
That script exists to populate public showcase deployments at well-known email addresses. It refuses to run under NODE_ENV=production unless ALLOW_PRODUCTION_SEED is set, and it will not run at all without demo passwords in the environment.
Reserved role names
Custom roles can no longer be named after a built-in role or its aliases (super_admin, landlord, owner, property_manager, and similar). Before 3.0 such a role was created but silently granted the full built-in permission set — check any custom roles created on an older version.
Don't share admin accounts
Shared logins break audit trails. Always invite a new user — even for short-term contractors.
PropertyPro ships minor updates monthly. Your data survives every release. v3.0 is the one upgrade that needs a database migration — the bundled UPGRADE.md covers it in full, including rollback.
- 1
Back up first
Settings → Backups → Create backup, or run mongodump. On the v3.0 upgrade this is the entire rollback plan.
- 2
Download the latest build
From your CodeCanyon downloads. Compare the version against the Changelog to plan the update.
- 3
Diff and merge
Use git or your favorite diff tool to merge the new files, preserving .env.local and your local ./uploads directory.
- 4
Install and rebuild
Install dependencies and run a production build after merging each release.
bashpnpm install pnpm build - 5
v2.x → v3.0 only: run the auth migration
v3.0 replaces NextAuth with Better Auth. Passwords, properties, leases, payments, and tenants are untouched, but every user is signed out once because the old JWT logins have no server-side record to convert. The migration is additive and safe to re-run.
bashpnpm db:migrate-better-auth --dry-run # preview pnpm db:migrate-better-auth - 6
v2.x → v3.0 only: verify
Sign in with an existing account and its existing password, confirm Settings → Security lists your session, and check that terminating a session signs that device out.
- 7
Schedule database backups
Use MongoDB Atlas automated backups or set up nightly mongodump on a VPS.
bash# Cron: nightly backup at 02:00 0 2 * * * mongodump --uri="$MONGODB_URI" --archive=/backups/propertypro-$(date +\%F).archive --gzip
Customized files and the v3.0 upgrade
v3.0 changed 74 files mechanically. If you edited any of them, your copy is preserved and then out of step with the rest of the app. The usual fix is one import: next-auth/react becomes @/lib/auth-client, with useSession, signIn, and signOut keeping the same signatures. The old next-auth package is still installed so customized files don't break your build, but it is scheduled for removal in v3.1.
Rolling back v3.0
The migration copies password hashes rather than moving them, so restoring the v2.3 files works as long as nobody has changed their password since upgrading. Anyone who has must reset, or you restore the backup.
New in 3.0. Maps are a pluggable provider chosen in the app, not in a build step — the database setting wins over the environment, so switching provider needs no redeploy.
- 1
Pick a provider
Dashboard → Settings → Maps offers OpenStreetMap, Google Maps, or no maps. The same form is embedded in Public Site → Settings; both write the same setting.
- 2
OpenStreetMap — the default, no setup
Works on a fresh install with no API key, no Google Cloud account, and no credit card. Address search uses Nominatim, debounced and limited to queries of 3 characters or more to respect the OSM usage policy.
- 3
Google Maps — optional
Reveals an API key field. Enable Maps JavaScript API and Places API on the key, and restrict it by HTTP referrer. Google Maps requires a Cloud billing account.
bash# Fallback only — Settings -> Maps takes precedence MAPS_PROVIDER=google NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=your-browser-key - 4
Geocode your existing properties
Properties created before 3.0 have no coordinates and stay off the maps until you backfill them. Dry run by default: it lists what it would do, makes no API calls, and writes nothing.
bashpnpm db:backfill-coordinates # Apply for real GEOCODE_APPLY=1 GEOCODE_CONFIRM=BACKFILL_COORDINATES \ GOOGLE_GEOCODING_API_KEY=your-server-key \ pnpm db:backfill-coordinates
The browser key is public — restrict it
A Google Maps browser key is served to every visitor by design and cannot be kept secret. Restrict it by HTTP referrer to your own domains and set a daily quota cap in the Cloud console, or an unrestricted key can be lifted and billed against your account.
The backfill needs a Google key either way
It geocodes through Google only — there is no Nominatim path — so it needs a separate server-side, IP-restricted key with the Geocoding API enabled, even on installs running the free OpenStreetMap provider. It skips properties that already have coordinates, so an interrupted run can simply be re-run.
Heavy traffic? Move off the public OSM servers
The Leaflet default points at OSM's shared tile and Nominatim infrastructure, which their usage policy says is unsuitable for production traffic. A busy install should switch to its own or a commercial tile and geocoding host.
New in 3.0. PropertyPro serves a public home page, properties browser, property and unit detail pages, and a contact page from the same install — all of it switchable and editable from Dashboard → Public Site.
- 1
Switch pages on or off
A master toggle plus per-page switches for Home, Properties, Property detail, and Contact. A page is live only when both its own switch and the master switch are on, and toggles save the moment you flip them.
- 2
Edit the copy
Five editors cover Brand & navigation, Home, Properties, Contact, and FAQ — wordmark, logo, header and footer links, hero headline and background, feature cards, how-it-works steps, FAQ categories, contact methods, and per-page SEO and social share metadata.
- 3
Accept rental applications
With Property detail on, visitors can apply for a unit through the checkout: lease terms, occupants, income, and screening consent, filed as a rental application. No payment is taken. Switch that page off and both checkout URLs redirect instead of accepting applications.
- 4
Grant access to the module
The new public_site_management permission controls it. Built-in Admin has it, and existing custom admin roles holding system_settings keep access without any change.
- 5
Reset to defaults
Public Site → Module settings restores any single section, or the whole public site, to the copy the app shipped with, behind a confirmation dialog.
Switching a page off never deletes anything
Signed-out visitors are redirected to /auth/signin and signed-in users to /dashboard — both targets editable, and both must be paths inside the app. Your stored copy comes back untouched when you switch the page on again.
Changes go live immediately
The public routes render per request rather than from a build-time prerender, and every save clears the public route cache, so shared header and footer edits land on every page at once.
Image fields are paths, not uploads
The hero background, feature card, and how-it-works images are path entry with a live preview. Put the file under /public first, then reference it — the editor warns when the path does not load.
Most install problems fall into the same handful of buckets. Walk through these before opening a support ticket.
Every request returns 503 "database schema v0 but the application requires v1"
The v3.0 auth migration has not run, or did not finish. Run pnpm db:migrate-better-auth. This message is deliberate — v3.0 refuses to serve an un-migrated database rather than answering correct passwords with "invalid credentials".
"Invalid email or password" for a password you know is right
Same cause as the 503 — sign-in is not covered by the schema gate. Run pnpm db:migrate-better-auth. If the migration has run, check that BETTER_AUTH_SECRET still holds your old NEXTAUTH_SECRET value; changing it signs everyone out.
Build fails on next-auth/react
A file you customised still imports the old auth client. Change it to @/lib/auth-client — useSession, signIn, and signOut keep the same signatures and return shapes.
Properties don't appear on the map
Properties created before 3.0 have no coordinates. Run pnpm db:backfill-coordinates with GEOCODE_APPLY=1 and a GOOGLE_GEOCODING_API_KEY. New properties get coordinates from the address autocomplete on the property form.
Managers can no longer delete leases or tenants
Expected in 3.0 — deleting now needs lease_delete, tenant_delete, payment_delete, document_delete, or user_delete, which no longer ride along with the edit grant. Edit the built-in Manager role or create a custom role to grant them.
MONGODB_URI connection refused
Check the username, password, database name, and Atlas Network Access allowlist. For self-hosted MongoDB, confirm the authSource value.
Inquiry and application emails never arrive
Everything email-shaped depends on SMTP under Settings → Email. Without it inquiries and applications are still stored and still badge the sidebar, but acknowledgements and staff alerts are not sent and dashboard replies are filed with an "Email failed" badge.
Stripe webhook signature invalid
Make sure STRIPE_WEBHOOK_SECRET matches the secret of the specific endpoint, not your account secret.
Emails landing in spam
Set up SPF, DKIM, and DMARC for your sender domain. Resend & SendGrid have step-by-step UIs for this.
Push notifications not arriving
iOS Safari requires the PWA to be installed via Add to Home Screen first. Web push is unavailable in Incognito mode.
pnpm install fails
Delete node_modules and pnpm-lock.yaml, run corepack enable, then pnpm install fresh.
File uploads silently fail
Check R2_* variables, NEXT_PUBLIC_R2_PUBLIC_URL, upload limits, and whether your Cloudflare R2 bucket allows public reads from the configured URL.