Open source short-term rental management for hosts who want a real system.
OpenSTR is a self-hosted platform that replaces the chaos of WhatsApp messages, spreadsheets, and Google Forms with a proper cleaning management system — scheduling, checklists, photo evidence, supply tracking, and monitoring, all in one place.
Built for Airbnb hosts. Works for any short-term rental platform.
- Automatic schedule — imports your Airbnb booking calendar via iCal so cleaners always know what's coming
- Calendar dashboard — see upcoming stays, past cleans, assigned cleaners, and guest info at a glance
- Structured checklists — room-by-room task lists with drag-and-drop reorder
- Photo evidence — mandatory before/after photos per room for every clean
- Multi-property — manage your STR, your home, and future properties from one dashboard
- Multi-cleaner — assign cleaners per property, track performance, compare against your standards.
- Supply alerts — cleaners flag low stock inline; you see it on the dashboard
- Local hosting and wifi — your cleaner sees their schedule and can claim cleans from their phone from anywhere, but the cleaning checklists and photos only activate when on your local wifi (optional)
OpenSTR is a monorepo with four apps:
openstr/
├── api/ # Node.js + Express REST API (TypeScript)
├── admin/ # React + Vite admin panel (TypeScript)
├── mobile_flutter/ # Flutter cleaner app (iOS, Android, Web)
└── shared/ # Shared TypeScript types
| Service | Tech | Port |
|---|---|---|
| API | Express + PostgreSQL | 3000 |
| Admin | React + Vite | 5173 |
| Mobile | Flutter (Web served at /app/) | — |
| Database | PostgreSQL 16 | 5432 |
- Node.js 20+
- Docker (for PostgreSQL)
- Your Airbnb iCal URL (from Calendar → Export in your Airbnb host dashboard)
git clone https://github.com/lkilpatrick/OpenSTR.git
cd OpenSTR
npm installdocker run -d --name openstr-postgres \
-e POSTGRES_DB=openstr \
-e POSTGRES_USER=openstr \
-e POSTGRES_PASSWORD=openstr \
-p 5432:5432 \
postgres:16-alpinecp api/.env.example api/.env
# Edit api/.env — defaults work with the Docker command aboveDefault .env values:
DATABASE_URL=postgresql://openstr:openstr@localhost:5432/openstr
JWT_SECRET=dev-jwt-secret-change-in-production
JWT_REFRESH_SECRET=dev-refresh-secret-change-in-productioncd api
npx node-pg-migrate up --database-url-var DATABASE_URL
npm run db:seed:demo # Generic demo data (open source)
# — OR —
npm run db:seed:full # Your personal property data (private fork)Demo seed credentials:
- Admin user:
admin@demo.openstr.dev/admin123 - Cleaner user:
cleaner@demo.openstr.dev/cleaner123 - Property: Beach House (STR) with rooms, tasks, reservations, and sessions
# From the repo root
npm run dev:api # API on http://localhost:3000
npm run dev:admin # Admin on http://localhost:5173cd mobile_flutter
flutter run -d chrome --dart-define=API_URL=http://localhost:3000
# or on a physical device:
flutter run --dart-define=API_URL=http://YOUR_LAN_IP:3000OpenSTR is designed to self-host on a Linux server using Docker.
- Linux server (Ubuntu 22.04+ recommended)
- Docker + Docker Compose v2
- A domain name pointing to your server (for SSL)
- Port 80 and 443 open
git clone https://github.com/lkilpatrick/OpenSTR.git
cd OpenSTR
cp .env.production.example .env.production
nano .env.production # Fill in DOMAIN, passwords, secretsGenerate secrets:
openssl rand -base64 32 # Use for JWT_SECRET
openssl rand -base64 32 # Use for JWT_REFRESH_SECRET
openssl rand -base64 32 # Use for BETTER_AUTH_SECRETchmod +x deploy.sh
./deploy.sh # Build, migrate, start everythingThis will:
- Build the API, admin panel, and mobile web app as Docker containers
- Start PostgreSQL, the API, and nginx with SSL
- Run database migrations
- Generate a self-signed cert if no SSL certs exist yet
./deploy.sh --ssl # Automated Let's Encrypt certificate./deploy.sh --seed-demo # Generic demo data| Service | URL |
|---|---|
| Admin panel | https://yourdomain.com |
| Mobile web app | https://yourdomain.com/app/ |
| API | https://yourdomain.com/api/ |
./deploy.sh --update # Pull latest, rebuild, migrate, restart
./deploy.sh --logs # Tail container logs
./deploy.sh --stop # Stop all servicesThe mobile app detects whether the cleaner is on your local WiFi or accessing remotely:
- Remote access: Can view schedule, accept assignments, view history
- Local WiFi only: Can start and continue cleaning sessions
This is enforced by nginx's geo module which tags requests with an X-Is-Local header based on the client's IP range. The mobile app calls /network-check to detect its status.
If you use a Cloudflare Tunnel instead of opening ports directly, set DOMAIN in .env.production to your public tunnel hostname (e.g. app.yourdomain.com). The nginx config includes a /auth/ location that rewrites to /api/auth/ so the mobile app's auth calls work correctly through the tunnel.
DOMAIN=app.yourdomain.com
BETTER_AUTH_URL=https://app.yourdomain.com
ALLOWED_ORIGINS=https://app.yourdomain.com,https://YOUR_TAILSCALE_IP- Go to your Airbnb Host Dashboard → Calendar → Export Calendar
- Copy the iCal URL
- In the admin panel, update your property's iCal URL via the API:
PATCH /properties/:id { "ical_url": "https://www.airbnb.com/calendar/ical/..." } - Trigger a sync:
POST /ical/sync/:propertyId
The sync extracts: check-in/out dates, guest names, phone numbers (last 4 digits), number of guests, reservation URLs, and blocked dates.
OpenSTR can receive webhooks from Home Assistant when your smart lock opens, automatically triggering a clean session.
See docs/home-assistant-setup.md for setup instructions.
| Endpoint | Description |
|---|---|
POST /auth/sign-in/email |
Sign in (email + password) |
GET /properties |
List properties |
GET /properties/:id/rooms |
Rooms for a property |
GET /properties/:id/rooms/:roomId/tasks |
Tasks for a room |
GET /sessions |
List clean sessions |
GET /sessions/upcoming-cleans |
Upcoming cleans for cleaner (includes rejected) |
POST /sessions/claim |
Cleaner claims an unassigned clean |
POST /sessions/:id/accept |
Accept a pending session |
PATCH /sessions/:id/status |
Transition session status |
POST /sessions/schedule |
Schedule a clean (owner/admin) |
GET /ical/reservations/:id |
Reservations from iCal |
POST /ical/sync/:id |
Trigger iCal sync |
GET /admin/cleaners |
Cleaner performance |
GET /admin/superhost/current |
Superhost metrics |
GET /issues |
Property issues |
GET /messages |
Guest messages |
POST /photos/:roomCleanId |
Upload before/after/issue photo |
GET /photos/:roomCleanId |
List photos for a room clean |
POST /photos/:roomCleanId/tasks/:taskId/complete |
Mark task complete |
DELETE /photos/:roomCleanId/tasks/:taskId/complete |
Uncheck a task |
All endpoints require bearer token auth (Authorization: Bearer <token>). Auth is handled by better-auth mounted at /api/auth/* (admin) and /auth/* (mobile).
Built with Flutter for iOS, Android, and Web.
For cleaners: Schedule view, accept/claim assignments, room checklists, photo capture, supply alerts, cleaning history with detail drill-down.
Features:
- Network-aware: Detects local WiFi vs remote access — schedule viewing works anywhere, but starting a clean requires being on-site
- 3-tab navigation: Schedule, History, Profile
- Photo evidence: Before/after photos with metadata and expandable viewer
- Real-time status: Pull-to-refresh with auto-refresh after session submission
Development:
cd mobile_flutter
flutter run --dart-define=API_URL=http://localhost:3000Production: The Flutter web build is automatically included in docker-compose.prod.yml and served at /app/.
- Core cleaning session workflow
- iCal schedule integration with guest data extraction
- Multi-property + multi-cleaner support
- Superhost KPI tracking
- Home Assistant / smart lock integration
- Calendar dashboard with stays and cleans
- Room management (add, rename, reorder, archive)
- Flutter mobile app (Schedule, History, Profile)
- Photo evidence with before/after viewer
- Network-aware WiFi restriction for cleaning actions
- Docker production deployment with SSL
- Cleaner pay tracking
- Rejection/redo workflow — owner rejects with reason, cleaner sees it on Schedule tab and can redo
- Cloudflare Tunnel support
- Auto-seed room checklists from cleaning standards on room creation
- VRBO / Booking.com iCal support
- Direct booking calendar (no OTA)
- Native iOS / Android app builds
- Zapier / webhook integrations
- Multi-language support
- Rejection/redo flow — When an owner rejects a submitted clean, the session reappears on the cleaner's Schedule tab with the rejection reason and a "Redo Clean" button. Tapping it transitions the session back to
in_progressand opens the room checklist. The same button is also available in the History detail screen. - Task check/uncheck — Cleaners can now tap a completed task again to uncheck it during an active session.
- Photo upload fix — Fixed a bug where
Content-Type: application/jsonwas being sent globally on all requests including multipart photo uploads, preventing the before photo from advancing to the checklist. - nginx
/photos/routing fix — The/photos/API routes (upload, task completion) were being intercepted by nginx's static file server instead of being proxied to the API. Fixed by addingphotosto the nginx proxy regex. - Cloudflare Tunnel login — Fixed mobile app login through Cloudflare tunnels by adding a
/auth/nginx location that rewrites to/api/auth/(better-auth's configured base path). - Property slug field — Added
slugto allGET /propertiesresponses; fixed empty-string coercion tonullin POST and PATCH handlers. - Delete property — Added Delete Property button with confirmation to the admin panel.
- Auto-seed tasks — When a new room is created via
POST /properties/:id/rooms, tasks are automatically seeded from the property's cleaning standard. - Session state machine — Added
rejected → in_progresstransition so cleaners can redo rejected sessions. - Upload timeouts — Increased Dio connect/receive timeouts to 30s/60s for reliable photo uploads on mobile.
OpenSTR welcomes contributions from hosts, developers, and STR industry folks.
See CONTRIBUTING.md to get started.
OpenSTR is licensed under the GNU General Public License v3 (GPL-3.0).
You are free to use, modify, and distribute OpenSTR — including for your own STR business. If you distribute a modified version, it must remain open source under the same license.
See LICENSE for full terms.
Created by lkilpatrick in 2026.
Built on: Express, React, React Native, PostgreSQL, Expo, and many other excellent open source projects. See ATTRIBUTION.md for the full list.
- Bug reports — open an issue
- Feature requests — open an issue with the
enhancementlabel - Questions — GitHub Discussions