Expense Manager is a self-hosted, multi-family finance tracker built with plain PHP, MariaDB/MySQL and Bootstrap 5. It tracks spending, income, cards, bank balances, budgets and net worth, and includes Islamic-finance modules (Zakath, Sadaqa, interest purification) and UAE open banking.
- Expenses & income with multi-row entry, AED/INR/USD/EUR/GBP with live exchange rates, tags, subscriptions and cashback tracking.
- Saved spends — store names you record often (e.g. "Carrefour" → Grocery); typing one on Add/Edit Expense suggests it and fills in the category. Frequent past expenses are suggested for saving.
- Bank balances per account, with debit-card spending, income and card payments moving the right balance automatically (and reversed on edit/delete).
- Open banking (Lean) — link UAE bank accounts, sync balances daily and review bank transactions before importing them as expenses or income.
- Budgets per category with a 50/30/20 needs/wants view and email alerts when a category reaches a warning threshold (default 80%) or goes over (default 100%).
- Credit-card cycles — statement and due dates, amount still due, utilization, expected vs recorded cashback, and due-date reminder emails.
- Family split — split expenses between family members, see who owes whom (simplified to the fewest transfers) and record settlements.
- Goals, net worth, subscriptions, reminders, calendar, lending tracker, travel planner, advanced reports and print-ready statements.
- Zakath — live gold/silver prices for the nisab (gold or silver basis, configurable weights), hawl (lunar year) due-date tracking with Hijri dates, assets pulled from bank balances, net worth and money lent, and reminder emails 30 days, 7 days and on the due date.
- Sadaqa tracker with categories, and an interest tracker for purification.
- Multi-tenant: every family's data is isolated; family admins manage members (edit access, reset passwords, revoke access).
- Accounts: email password reset (single-use, 60-minute hashed tokens), change password from My Profile, login rate limiting, new-device login alerts, and sessions that end when the password changes.
- Hardening: CSRF tokens on every form, a strict nonce-based Content-Security-Policy (no inline JavaScript), output escaping, tenant checks on every record, audit log, and an
.htaccessthat blocks dumps, logs and internal folders.
- PHP 8.0+ with
pdo_mysql,curl,mbstring,json(intloptional, for Hijri dates) - MariaDB 10.3+ (or MySQL 8.0+)
- Apache or LiteSpeed with
.htaccesssupport - A working PHP
mail()(used for password reset and notifications) - A daily cron job (see below)
- Get the code and upload it to your web root (or a sub-folder such as
/expenses/):git clone https://github.com/basimibrahim-git/Expense-Manager.git
- Create an empty database and a database user.
- Run the installer at
https://your-domain/install/install.php. It writes.env, creates the schema (including every file inmigrations/) and your first family admin account. - Delete the
install/folder once you are signed in (it also refuses to run again while.envexists). - Finish
.env(see Configuration), especiallyAPP_URLand the mail settings. - Add the cron job (see Scheduled jobs).
- Back up the database.
- Run every new file in
migrations/that you have not run yet, oldest first, in phpMyAdmin (SQL tab) or themysqlclient. They are idempotent, so re-running one is safe. - Upload all application files (a partial upload can leave pages calling helpers that are not on the server yet).
.envis never part of the code. - Add any new keys from
.env.exampleto your.env.
Copy .env.example to .env. Unquoted values may have a trailing # comment.
| Key | Purpose |
|---|---|
DB_HOST, DB_NAME, DB_USER, DB_PASS |
Database connection |
APP_ENV |
production forces HTTPS |
APP_URL |
Public URL of the app root (include the sub-folder, if any). Used in password-reset and notification links |
APP_TIMEZONE |
Defaults to Asia/Dubai |
APP_VERSION |
Bump to bust CSS/JS caches |
MAIL_FROM, MAIL_FROM_NAME |
Sender for all emails |
MAIL_RECIPIENTS |
Fallback recipients for reminder emails when a family has no members with email |
CRON_SECRET |
Required to trigger the cron over HTTP |
LEAN_CLIENT_ID, LEAN_CLIENT_SECRET, LEAN_APP_TOKEN, LEAN_ENV, LEAN_WEBHOOK_SECRET, LEAN_API_BASE |
Open banking (optional — disabled until the client id and secret are set) |
ZAKATH_METALS_PROVIDER, ZAKATH_METALS_API_KEY |
Gold/silver price source: gold-api (default, keyless), metalpriceapi (needs a key) or none (manual prices) |
One daily job runs reminders, the monthly digest, budget alerts, card due-date reminders, Zakath reminders and the open-banking sync (cron/jobs/*.php):
0 9 * * * php /path/to/app/cron/reminder_emails.php >> /dev/null 2>&1If your host cannot run PHP from cron, call https://your-domain/cron/reminder_emails.php?key=<CRON_SECRET> instead.
- Create an application in the Lean dashboard (sandbox first) and put its credentials in
.env. - Redirect URL:
https://your-domain/lean_return.php. Allowed web origin:https://your-domain. - Webhook URL:
https://your-domain/lean_webhook.php, subscribed toentity.created,entity.reconnected,entity.data.refresh.updatedandconsent.status.updated; copy the signing secret intoLEAN_WEBHOOK_SECRET. - In the app: Open Banking → Connect bank, then link each Lean account to a bank. Synced balances are recorded as balance snapshots; transactions wait in Bank Transactions until you import or ignore them (imports never move the balance a second time).
| Path | Contents |
|---|---|
*.php (root) |
Pages and their *_actions.php form handlers |
src/Core/Bootstrap.php |
Loads config.php, enforces login on every non-public page |
src/Helpers/ |
Shared logic: Html (escaping), Flash (messages), Categories, ExpensePresets, BalanceHelper, ExchangeRateHelper, Notifier, BudgetAlertHelper, CardCycleHelper, SplitHelper, ZakathHelper, LeanClient/LeanSync, SecurityHelper, PasswordResetHelper, Layout |
includes/ |
Layout templates (header, sidebar, footer, public-page layout) |
assets/js/app.js |
CSP-safe event wiring (data-onclick, data-confirm, …) — the app uses no inline JavaScript handlers |
cron/ |
Daily cron entry point and its jobs (cron/jobs/) |
migrations/ |
Dated, idempotent SQL upgrades |
install/ |
Web installer |
tests/ |
CLI checks for date and split maths: php tests/card_cycle_dates_test.php, php tests/split_simplify_test.php |
- Never commit
.envor database dumps; both are in.gitignore, and the bundled.htaccessrefuses to serve*.sql,*.log,*.mdand thesrc/,includes/,migrations/,logs/,tests/folders. - Errors are logged to
logs/php_errors.log(not web-accessible). - Report vulnerabilities as described in SECURITY.md.
MIT — see the badge above.