Track your time, know what you're owed, and get paid. MelaFreelance is a self-hosted time-tracking app for freelancers. You keep a tidy list of clients, the projects you do for them, and the tasks you work on — the app times your work, computes what each project earns, and tracks who has paid you.
It runs as your own private app (single-user by default), built on Laravel 12 + Inertia 2 + Vue 3 + TypeScript + Tailwind 4. There's no account to sign up for and no data leaving your server — you host it, you own it.
Everything hangs off a simple hierarchy that matches how freelance work actually flows:
- Clients — the people or companies you work for.
- Projects — a piece of work for a client. Each project sets how it bills (see below).
- Tasks — the individual things you do on a project. Tasks are what you put the timer on and where your hours accumulate.
Each client carries their contact and VAT details and rolls up their total earnings — including a non-billable client like Myself for personal work.
You don't set a "project type"; the app figures out how a project bills from what you fill in:
| You set… | Billing | What you're owed |
|---|---|---|
| An agreed fee | Fixed-price | The fee, flat. Your tracked hours are shown for reference (and an effective hourly rate), but never change what's owed. |
| An hourly rate (no fee) | Hourly | Your tracked time × the rate. |
| Neither (rate left at 0) | Non-billable | Nothing. For personal work you still want to time — the money UI drops away and hours become the headline. |
Because it's derived, you can start a project hourly and later agree a fixed fee just by filling the fee in — no migration, no re-typing. Each project shows its mode, tracked time, earnings and payment status at a glance.
A running timer bar lives in the app header on every page, not buried in one screen — so you can start a task, navigate anywhere, and still see the clock ticking.
- One timer at a time. Starting a task automatically stops whatever else was running and banks its time. As a freelancer you work one thing at a time, and this guarantees the same minute is never billed to two tasks.
- It remembers what you were on. When you stop, the bar stays put showing the last task you worked — with a Resume button and a link straight to that task — until you dismiss it. Change pages all you like; you won't lose your place.
Click any task — from the list, or straight from the timer bar — to open its own page. It's the task's detail view and its editor in one, so everything you need is in a single place:
- Edit inline. Give it a short, scannable title and a full multi-line description, and fine-tune the tracked time, then Save without leaving the page.
- Time and money at a glance. Total time, the live timer, and what the task has earned sit together, with Start/Stop and Complete right there.
- Attach what you actually work with. Drag in screenshots, PDFs or HTML exports, paste a screenshot straight from your clipboard, or add links — a Figma file, a staging URL, shared notes. Files and links live in one gallery: images preview as thumbnails, documents open in a click, and anything can be removed with a confirm.
Your files are stored privately and streamed only to you through the app — nothing is dropped in a public folder or handed a shareable URL. Remove an attachment (or the whole task) and its file is cleaned up with it.
"Paid" isn't a yes/no flag. You record payments against a project as they arrive — amount, date, and an optional note — and the app derives the rest:
- Paid so far — the sum of recorded payments.
- Outstanding — what's owed minus what's been paid.
- Fully paid — reached automatically once payments cover what's owed.
This handles deposits, staged/milestone payments, and part-payments naturally: one project can have many payment rows.
Projects and tasks can be marked complete independently of payment — done is done, whether or not the money's in yet. Two sensible rules are built in:
- Completing a task that's still timing stops the timer first (your seconds are banked, nothing is lost).
- A fully-paid project can't be re-opened — once it's delivered and paid, it's closed for good.
The landing page is your at-a-glance overview:
- Outstanding — total money still owed to you across all projects.
- Received this month vs. all-time — because payments carry a date, the money side can be windowed.
- Hours tracked — all-time.
- Awaiting payment — projects you've completed but haven't been fully paid for.
- Recent payments — your latest income, at a glance.
The dashboard shows your all-time totals; the Report page answers the other question — when did I do the work. Every timed session is logged with its own date, so you can pick a period and see it broken down:
- This month, last month, this year, or all time — one click to switch.
- Day by day — how many hours (and how much they're worth) you put in on each day of the period.
- By project — the same hours split across the projects you worked on, so you can see where your time actually went.
Each session is valued at the rate that was in effect when you worked it, so raising a project's rate later never quietly rewrites what past work was worth. This is also the clean, dated record a future invoicing tool would read.
Deleting a client, project, or task doesn't destroy it — it moves to Trash, and its whole subtree goes with it (a trashed client hides its projects and tasks and drops out of your totals). Restore it and everything comes back exactly as it was. Only a permanent delete from Trash wipes it for good.
The Settings page lets you update your profile (name, email), change your password, and switch between light, dark, and system appearance themes. Dark mode is fully supported throughout the interface.
MelaFreelance is meant to be cloned and run on your own machine or server. Two ways to run it — pick one.
Best if you have Docker and want the same MySQL setup used in production.
git clone https://github.com/melasistema/melafreelance.git
cd melafreelance
cp .env.example .env
composer install # Sail needs vendor/ to exist
./vendor/bin/sail up -d # start the containers
./vendor/bin/sail npm install
./vendor/bin/sail artisan key:generate
./vendor/bin/sail artisan migrate
./vendor/bin/sail npm run devThe app runs at http://localhost.
Best for trying it out quickly — the default .env uses a file-based SQLite
database, so there's nothing else to install.
git clone https://github.com/melasistema/melafreelance.git
cd melafreelance
cp .env.example .env
composer install
npm install
php artisan key:generate
php artisan migrate
composer dev # serves the app and the frontend togetherRequires PHP 8.4 and Node 22+.
MelaFreelance ships single-user: public sign-up is closed and you create the one owner account yourself from the command line.
php artisan app:create-user
# …or scripted / non-interactive:
php artisan app:create-user --name="Jane Doe" --email=jane@example.com --password='a-strong-password'The account is created already verified, so you can log in immediately without setting up a mail server. Then open the app and go to Clients to start.
Want to self-host for several people and let them register? Set this in .env:
REGISTRATION_ENABLED=trueThat opens the sign-up page and its "Sign up" link. Everyone's data stays private to their own account. Leave it unset to keep the app single-user.
The whole interface and all money/number formatting are driven by config — a
self-hoster switches language and currency in .env, without touching any code.
APP_LOCALE=it # 'en' (default) or 'it'
APP_FALLBACK_LOCALE=en # used for any string missing in the active languageEnglish and Italian ship complete — every screen, menu, and the full login flow. A string missing in one language automatically falls back to the other, so you never see a broken label.
Adding your own language: copy lang/en/ to lang/{code}/ (e.g.
lang/fr/), translate the values, and set APP_LOCALE={code}. Anything you
leave untranslated falls back to English.
Independent of the interface language:
MONEY_CURRENCY=EUR # ISO 4217 currency code (default: EUR)
MONEY_LOCALE=it-IT # locale used to format amounts & numbers (default: it-IT)After changing any of these, refresh the cached config:
php artisan config:clearThere is no cloud, no telemetry, and no shared database — MelaFreelance runs entirely on the server you put it on, and your clients, hours, and earnings never leave it. Back up your database the way you back up anything else you care about.
A few things self-hosters and contributors may find useful:
- Security headers — the app ships with hardened response headers out of the box: Content-Security-Policy (with script nonce), HSTS, X-Frame-Options DENY, X-Content-Type-Options, and a strict Referrer-Policy.
- Server-side rendering — a full SSR entry point (
ssr.ts) is included for faster first paint and better SEO. - CI/CD — the repo includes GitHub Actions workflows: a CI pipeline (lint, type-check, Pest tests with coverage floor, Playwright E2E) and a Deploy pipeline that ships the exact tested build artifact to production.
- Development —
composer devstarts the PHP server, queue listener, log tail, and Vite dev server together. The test suite runs on in-memory SQLite for speed.
MelaFreelance is open source under the MIT License — free to use, modify, and self-host. See LICENSE for the full text.
© 2026 Luca Visciola (Melasistema)





