Self-hosted · Laravel + Inertia + React
WedFlow documentation
A premium wedding planning platform for couples, planners and their collaborators — one workspace for the guest list, checklist, budget, seating, vendors, RSVP collection, photo sharing and more. Wrapped in the Atelier editorial design system.
Highlights
- Multi-tenant couples + collaborators with a role × section permission matrix (5 roles, 13 sections, read/write per cell).
- Plan-tiered billing via Stripe Checkout (Free / Premium / Planner) with a built-in upgrade toast.
- Multi-language interface — English and French ship complete, with an in-app language switcher and admin-managed translations (add any locale from the panel).
- Editorial checklist organised by phase, with custom phase support.
- Guest list with party counts, dietary notes, side, RSVP token and bulk Save-the-Date / Invite emails.
- Public RSVP page themed per wedding (typeface, colour, button style).
- Budget with category line items, spent tracking and a remaining-budget overview.
- Seating chart + 3D floor plan powered by React Three Fiber.
- Vendor research with star ratings + side-by-side comparison (search, filter, sort) + booked-vendor contract / deposit / balance tracking.
- Inspiration board with image and video (YouTube) support.
- Run-of-show timeline shared with vendors, family and the wedding party.
- Public wedding website at
/w/{slug}and public photo gallery at/share/{slug}. - Wedding Crew — group members into custom teams.
- PDF + Excel export on every list (guests, vendors, budget, checklist, timeline, seating, crew).
- Admin panel — users, plans, languages, branding, email, storage, OAuth, Stripe, email previews.
- Light/dark theme, encrypted secrets at rest, web installer.
Requirements
- PHP 8.3+ with the standard Laravel extensions (
pdo,mbstring,xml,bcmath,fileinfo,gdorimagick). - Composer 2.x.
- Node.js 20+ and npm.
- One database: SQLite (built-in), MySQL 8+, or PostgreSQL 14+.
- A queue worker or
queue:workprocess in production. - (Optional) Stripe, Resend or SMTP, Google OAuth, S3-compatible storage.
Installation
The fastest path is the web installer. If you prefer the CLI:
# 1. Clone or extract the archive
cd wedflow
# 2. Install dependencies
composer install
npm install
# 3. Environment
cp .env.example .env
php artisan key:generate
# 4. Database
touch database/database.sqlite # if using sqlite
php artisan migrate --seed # creates an admin + a fully populated demo wedding
# 5. Build assets
npm run build # production, or `npm run dev` for development
# 6. Serve
php artisan serve # http://127.0.0.1:8000
Single-command dev loop (server + queue + logs + Vite):
composer dev
Seeded accounts (after --seed)
| Role | Password | |
|---|---|---|
| Admin | admin@wedflow.test | password |
| Couple (with full demo wedding) | couple@wedflow.test | password |
⚠ Change these immediately in production.
Web installer
Browse to /install after extracting the archive. The four-step wizard runs a
server requirements check, captures your database connection, runs migrations, and
creates your first administrator. On completion it writes storage/installed
and every install route 302s home — so it's safe to leave deployed.
rm storage/installed
Configuration
Most production settings live in the admin panel at
/admin/settings — branding, mail driver, storage driver, Google OAuth,
Stripe keys, etc. Database-backed values take precedence over .env so a
non-technical operator can manage the install from the browser.
.env is only required for low-level Laravel config (database connection,
app key, queue/cache drivers). See the annotated .env.example for the full
reference.
Admin guide
- Sign in with the seeded admin account or promote any user with:
php artisan tinker >>> \App\Models\User::where('email', 'you@example.com')->update(['is_admin' => true]); - Visit
/adminfor the dashboard (user counts, plan distribution). /admin/users— toggle admin, suspend, change plan, delete./admin/settings— six sections:- Branding — app name, logo, base URL, support email.
- Email Delivery — Resend, SMTP, or no-op (built-in test-send).
- Email previews — inline samples of Save-the-Date, Invite, RSVP confirmation, verification, password reset, collaborator invite.
- File Storage — local or S3 / R2 with a separate public URL.
- Google Sign-In — OAuth client.
- Billing (Stripe) — secret key, webhook signing secret, Premium & Planner price IDs, and the display prices shown on the public
/pricingpage.
/admin/languages— add a language, choose the default, toggle which locales appear in the switcher, and edit every translation string in a per-language editor (with progress and search). New languages start in English; untranslated strings fall back to English.
Stripe billing setup
- Create two products in Stripe with recurring prices — one per plan (Premium, Planner). Copy each price ID (
price_…). - In
/admin/settings → Billing (Stripe)paste your Secret Key, Webhook Signing Secret and the two price IDs. Save. - Add a webhook endpoint in Stripe pointing at
https://yourdomain.com/billing/webhookand subscribing tocheckout.session.completed. - Buyers upgrade from
/pricing. Limits are enforced inApp\Services\Plans.
Plan limits
| Plan | Weddings | Guests | Collaborators |
|---|---|---|---|
| Free | 1 | 50 | 2 |
| Premium | 1 | ∞ | ∞ |
| Planner | ∞ | ∞ | ∞ |
Adjust App\Services\Plans::LIMITS to change the caps.
Deployment
- Run
npm run buildand commitpublic/build— or rebuild on the server. - Set up a queue worker (
php artisan queue:work) — email and webhook side-effects use the queue. - Point your web server at
public/. A sample.htaccessships inpublic/.htaccess. - Configure a daily
php artisan schedule:runcron entry.
Project layout
app/
Http/Controllers/ Feature controllers (Wedding, Guest, Budget, …)
Http/Middleware/ EnsureWeddingSection, AdminMiddleware, …
Models/ Eloquent models, all UUID-keyed
Policies/ WeddingPolicy — section + access gates
Services/ Plans (billing), EmailService, RolePermissions
config/
wedflow.php Product metadata (version, codename)
database/
migrations/ Schema (incl. platform_settings, stripe_events)
seeders/ DemoWeddingSeeder — admin + full demo wedding
lang/
en.json / fr.json Translation strings (shared via Inertia)
resources/
js/
layouts/AppLayout.tsx Sidebar shell used by every authenticated page
pages/ Inertia pages, one per route
components/ Shared UI (UpgradeToast, ThemeToggle, …)
lib/i18n.ts useTranslator() hook
views/
app.blade.php Inertia root
install/ Web installer (Blade)
errors/ Branded 4xx/5xx pages
routes/
web.php All routes (public, auth, wedding-scoped, admin, install)
Tech stack
- Laravel 13, Inertia.js 3, Ziggy
- React 19 + TypeScript 6 (Vite 8)
- Tailwind CSS 4 (CSS-variable tokens for instant theming)
- React Three Fiber + Drei (floor plan)
- Stripe PHP SDK 20, Resend PHP 1
- jsPDF + jspdf-autotable + SheetJS (
xlsx) + qrcode (printable cards, PDF / Excel export) - Localisation: English + French complete, in-app switcher, admin-managed
Translations
The interface ships fully localised in English and French
(lang/en.json and lang/fr.json, at complete key parity). A
language switcher lives in the sidebar footer; the choice is stored per session by
App\Http\Middleware\SetLocale and works for guests and signed-in users alike.
Manage languages from the admin panel at /admin/languages —
add a locale, set the default, toggle which locales appear in the switcher, and edit every
string in a per-language editor. A new language starts as a copy of English and is translated
in place; blank strings fall back to English. (To add one by hand, drop a
lang/{locale}.json file in and it becomes available to enable.)
In components, translate through the useTranslator() hook, which reads the shared translations prop injected by HandleInertiaRequests:
import { useTranslator } from '@/lib/i18n';
const t = useTranslator();
<button>{t('common.save')}</button>
<p>{t('billing.upgrade_to', { plan: 'Premium' })}</p> // {placeholder} interpolation
Missing keys fall back to the key itself, so untranslated strings are easy to spot.
Customising the look
The Atelier design system is defined in resources/css/app.css and uses Tailwind 4 tokens.
Primary colour, accent, surfaces and font choices are all CSS variables — change them once and the
whole app re-themes. The dark mode palette lives under [data-theme="dark"]; toggle it
via the sidebar moon icon.
Security
- All state-changing routes are CSRF-protected via Inertia.
- Auth endpoints (
/login,/register,/forgot-password,/reset-password, OAuth callback, invite acceptance, RSVP submit) are rate-limited. - Stripe, SMTP, Resend, S3 and Google secrets are encrypted at rest on
platform_settingsvia Laravel'sencryptedcast. - Stripe webhooks are signature-verified and idempotent (
stripe_eventstable). - File uploads are MIME-restricted and size-capped per controller.
- Public RSVP / photo-share endpoints use unguessable tokens.
- Role × section permission matrix gates every wedding sub-resource (
WeddingPolicy::section).
Support & license
Buyers should reach support via the Envato item page. Bundle a SUPPORT.md for your own
SLA and FAQ before publishing. Released under the
Envato Regulated Marketplace License.
One licence per end-product.