A Laravel app that connects to Xero so your team can link organisations (tenants), sync bank spend and accounts-payable lines, and review expenses in the browser—with Sign in with Xero and organisation-scoped roles.
Stack: Laravel 13, PHP 8.3, Laravel Breeze (Livewire + Volt), Tailwind CSS, Vite.
- Sign in with Xero (OpenID Connect)—no local passwords for normal users.
- Connect Xero (OAuth 2) to store API tokens per organisation; tokens are encrypted at rest.
- Organisations & roles: admins connect tenants and invite reviewers by email; members only see orgs they belong to.
- Expense review UI: filter, sort, and update line-level review state synced from Xero.
- Background sync via the database queue (
SyncXeroOrganisationJob), with authorization re-checked when the job runs.
- PHP 8.3+
- Composer
- Node.js and npm (for frontend assets)
- A Xero developer app with Sign in with Xero enabled and a registered redirect URI
composer install
npm install
cp .env.example .env
php artisan key:generate
# Create database/database.sqlite if using SQLite, then:
php artisan migrate
npm run buildFor a scripted first-time setup (deps, .env, key, migrate, build): composer run setup.
Set DB_* in .env. SQLite is the default; create database/database.sqlite if it does not exist.
In the Xero developer portal, register one redirect URL used for both OpenID sign-in and accounting OAuth. It must match .env exactly (scheme, host, path):
${APP_URL}/xero/callback— same value asXERO_REDIRECT_URI(see.env.example).
| Variable | Purpose |
|---|---|
XERO_CLIENT_ID / XERO_CLIENT_SECRET |
Shared by SSO and Connect Xero |
XERO_REDIRECT_URI |
Callback for both flows |
XERO_ALLOW_OPEN_SIGNUP |
Default false: blocks self-serve sign-up unless the DB has no users yet or the user has a valid invitation. Set true only if you want a public, self-serve app. |
XERO_SSO_SCOPES |
OpenID (default openid profile email) |
XERO_SCOPES |
Accounting API scopes for sync (offline_access, transactions, contacts, settings, etc.) |
Invitations are sent from the dashboard. Configure MAIL_* in .env. For local dev, MAIL_MAILER=log writes messages to the log.
| Command | Use |
|---|---|
composer run dev |
HTTP server, queue worker, log tail, and Vite (recommended) |
php artisan serve |
App only |
npm run dev |
Vite watch mode |
Use QUEUE_CONNECTION=database (default in .env.example) so sync jobs can run; composer run dev includes queue:listen.
- Open
/or go to/loginand Sign in with Xero. - On the Dashboard, organisation admins use Connect Xero to authorize tenants. Reviewers see orgs they belong to; the Connect button appears when they admin at least one org or have no orgs yet.
- Admins use Team and invitations on the dashboard (per organisation): send invites by email, view pending, expired, and accepted invitations, resend or revoke pending/expired invites, and manage members (promote to admin, demote to reviewer, or remove from the organisation). The org must always keep at least one admin.
- Invitees use the email link and sign in with Xero using the invited email.
- Open Review expenses for an organisation (
/orgs/{public_id}/expenses). Route keys use an opaquepublic_id(ULID), not the internal numeric id.
- Sync is triggered from the app (e.g. after connection / review flows) and runs via queued jobs where configured.
SyncXeroOrganisationJobalways includes an initiating user id;handle()verifies that user is still a member of the organisation before calling Xero.- CLI:
php artisan xero:sync {public_id-or-slug}— pass--acting-user={id}with a user who belongs to that org. Optional:--from=YYYY-MM-DD,--to=YYYY-MM-DD,--queueto dispatch instead of running inline.
- Set
APP_DEBUG=false,APP_URLto your real HTTPS origin, andSESSION_SECURE_COOKIE=truebehind TLS. - If the app sits behind a reverse proxy or load balancer, configure trusted proxies so Laravel generates correct HTTPS URLs and secure cookies.
- Keep
XERO_ALLOW_OPEN_SIGNUP=falseunless you intentionally allow anyone to create an account via Xero.
Set SEED_TEST_USER=true in .env, then php artisan db:seed. This creates test@example.com (password password) for local/testing only. Normal browser sign-in still uses Xero unless you authenticate in tests with actingAs.
composer run testLaravel exposes GET /up for load balancers and uptime monitors.
This project is open source under the MIT license. Laravel is MIT licensed as well.