PHPackages                             academe/laravel-journal - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. academe/laravel-journal

ActiveLibrary

academe/laravel-journal
=======================

Accounting journals and double-entry bookkeeping for Eloquent models

1.4.0(1mo ago)091↑275%1MITPHPPHP ^8.2CI passing

Since Jul 14Pushed 1mo agoCompare

[ Source](https://github.com/academe/laravel-journal)[ Packagist](https://packagist.org/packages/academe/laravel-journal)[ RSS](/packages/academe-laravel-journal/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (6)Dependencies (14)Versions (7)Used By (0)

Laravel Journal
===============

[](#laravel-journal)

[![Latest Version on Packagist](https://camo.githubusercontent.com/77076b35dad3dc70c6b6e704f272605a304009440a1b2af7c1204db616b6f70b/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f61636164656d652f6c61726176656c2d6a6f75726e616c2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/academe/laravel-journal)[![CI](https://camo.githubusercontent.com/8214f12133f6e1c59c86681210d6ee5b0735d7c95477e22c2a84bea99240de4e/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f61636164656d652f6c61726176656c2d6c65646765722f63692e796d6c3f6272616e63683d6d61696e266c6162656c3d4349267374796c653d666c61742d737175617265)](https://github.com/academe/laravel-ledger/actions/workflows/ci.yml)[![Total Downloads](https://camo.githubusercontent.com/5a04cb784b6a55dc1a8f7de344d6858e21da0e1b182435718fe2a50d74ac0b40/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f61636164656d652f6c61726176656c2d6a6f75726e616c2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/academe/laravel-journal)[![License](https://camo.githubusercontent.com/62c307e16b6fdf7541274da1c01a52b3e3e951319328ca4a429cda3245ffa2d0/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f61636164656d652f6c61726176656c2d6a6f75726e616c2e7376673f7374796c653d666c61742d737175617265)](LICENSE.txt)

Accounting journals and double-entry bookkeeping for Eloquent models.

Give any Eloquent model its own accounting journal, post credits and debits to it in [moneyphp/money](https://github.com/moneyphp/money) amounts, read back running balances, and — when you need it — enforce proper double-entry bookkeeping across journals grouped into ledgers.

This package is a modernised, journal-centric conversion of [consilience/accounting](https://github.com/consilience/accounting), itself a fork of the original [scottlaurent/accounting](https://github.com/scottlaurent/accounting)package. If you're upgrading from either of those, see [UPGRADE.md](UPGRADE.md).

Why this package
----------------

[](#why-this-package)

- **Journal-first, not chart-of-accounts-first.** Attach a journal to any Eloquent model with one trait and start posting. There is no world model to adopt — no mandatory chart of accounts, entities, or fiscal calendar. Ledgers, enforced double entry, and period locking layer on only when you need them (see [the three scenarios](docs/ledgers.md)).
- **`moneyphp/money` as the public API.** Amounts go in and come out as `Money` value objects; storage is integer minor units. No floats and no decimal strings in your application code, and posting the wrong currency to a journal fails loudly rather than corrupting a balance.
- **Checkpoints: fast balances and closed periods in one mechanism.** A checkpoint stores a journal's cumulative totals through a date and locks the period behind it. Balance queries start from the nearest checkpoint and scan only what's posted since — a journal with ten years of history answers as fast as one with ten days — and the entries behind a checkpoint can no longer be edited or deleted.
- **Scales with your rigour.** The same tables serve a single wallet's running balance, manual double entry between journals, and ledger-enforced double entry across the full accounting equation — adopt each level as your application grows into it.

Structure at a glance
---------------------

[](#structure-at-a-glance)

 ```
erDiagram
    OWNER_MODEL ||--o| JOURNAL : owns
    LEDGER |o--o{ JOURNAL : groups
    JOURNAL ||--o{ JOURNAL_TRANSACTION : contains
    JOURNAL ||--o{ JOURNAL_CHECKPOINT : seals
    JOURNAL_TRANSACTION }o--o| REFERENCE_MODEL : references
```

      Loading Any model can own a journal (the `HasJournal` owner morph). Transactions can point back at any other model — an invoice, an order, a product — via their own `reference` morph. Checkpoints store a journal's cumulative totals and lock the period behind them, and journals may — but don't have to — be grouped under typed ledgers for double-entry reporting.

Requirements
------------

[](#requirements)

- PHP 8.2+
- Laravel 12+

Installation
------------

[](#installation)

```
composer require academe/laravel-journal

php artisan vendor:publish --tag=journal-config
php artisan vendor:publish --tag=journal-migrations
php artisan migrate
```

The service provider is auto-discovered. The config publish is optional — the package config is merged automatically. Publishing the migrations is required on fresh installs: the package deliberately does not auto-load its migrations, so nothing is created until you publish and run them. If you are upgrading from consilience/accounting, do **not** run them — use the rename migration in [UPGRADE.md](UPGRADE.md) instead.

Quick start
-----------

[](#quick-start)

Add the `HasJournal` trait to any model that should own a journal:

```
use Academe\LaravelJournal\Concerns\HasJournal;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    use HasJournal;
}
```

Then initialise a journal and start posting:

```
use Money\Money;

$user->initJournal('USD');

$transaction = $user->journal->credit(Money::USD(10000), 'Opening credit');
$user->journal->debit(7500);

$balance = $user->journal->currentBalance(); // Money::USD(2500)
```

To give every new instance of a model a journal automatically, call `initJournal()` from the model's `created` event instead of by hand:

```
class User extends Model
{
    use HasJournal;

    protected static function booted(): void
    {
        static::created(fn (self $user) => $user->initJournal());
    }
}
```

With no arguments, `initJournal()` uses `config('journal.base_currency')`.

How it works
------------

[](#how-it-works)

- Each model instance that uses `HasJournal` gets **one journal**, linked via a polymorphic `owner` relation (`journals.owner_type` / `owner_id`).
- Amounts are stored as **integer minor units** (cents, pence, and so on), using `moneyphp/money`'s `Money` value object as the public API.
- **Credits are positive, debits are negative** when viewed as a signed amount: `JournalTransaction::$amount` returns the entry as a single signed `Money` value. Internally they're kept in separate `credit` and `debit` columns.
- `journals.balance` is a **cached column** kept in sync automatically whenever a `JournalTransaction` is saved or deleted. The cached value equals `totalBalance()` (it includes future-dated transactions), not `currentBalance()`.

The finer points live in the focused pages below: when the cached balance is recomputed and how stale instances behave in [balances](docs/balances.md) and [configuration](docs/configuration.md), and currency guarding on posting in [double entry](docs/double-entry.md).

### Why journals are owned by models

[](#why-journals-are-owned-by-models)

A journal belongs to your application, not to the package: it is owned by one of your own models, and that owner is what gives it meaning. The package handles only the money side — currency, cached balance, transactions, checkpoints — while everything that makes the account mean something to your application (its name, its description, who may see it, when it is created or archived) lives on the owner, where your application already manages those concerns. That is why the `journals`table stores no name and no description: a journal's identity is entirely delegated to its owner. The `owner` morph is non-nullable and unique per (`owner_type`, `owner_id`) pair, so the relationship is strictly one-to-one — a `journals` row means nothing on its own; it is "the journal of `User` #42".

Owners tend to fall into two camps:

- **Domain objects that naturally have financial state** — the design's sweet spot. A `User` with an account balance, a `Wallet`, a `GiftCard`with remaining value, an `Order` accruing charges, a driver owed payouts. The journal is the answer to "where did this thing's balance come from?", reached from the object you already have in hand: `$user->journal->currentBalance()`.
- **Stand-in models for pure accounting accounts.** An account that isn't a domain object — Cash, Sales, Accounts Receivable — is a small model whose rows exist to own journals; that's what `CompanyAccount` is doing in the [ledger examples](docs/ledgers.md). This is the cost of the journal-first design: where a chart-of-accounts-first package hands you free-standing named accounts, here a named account is a one-line model plus a row.

One consequence of the unique index: a model that needs several journals — a multi-currency wallet, say — can't own them all directly. Introduce a child model (one row per currency, for example) and hang one journal off each.

Documentation
-------------

[](#documentation)

Focused guides live under [docs/](docs/):

- [Balances](docs/balances.md) — how balances are calculated at journal and ledger level (the two sign conventions), the balance methods, and the staleness of the cached column after posting.
- [Formatting and parsing amounts](docs/money-formatting.md) — the `MoneyFormatter` helper: `Money` to string and back, plain or locale-aware.
- [Referencing models](docs/references.md) — linking transactions to invoices, orders, products.
- [Tags](docs/tags.md) — labelling transactions with key/value metadata.
- [Double entry](docs/double-entry.md) — atomic `TransactionGroup` commits, fetching a group, reversing a group.
- [Multiple currencies](docs/multi-currency.md) — running more than one currency, FX clearing journals, realised and unrealised gains.
- [Ledgers](docs/ledgers.md) — grouping journals under typed ledgers, the three usage scenarios, custom ledger types.
- [Checkpoints](docs/checkpoints.md) — fast balances, closed periods, reopening a period.
- [Exceptions](docs/exceptions.md) — the exception hierarchy and what each exception carries.
- [Configuration](docs/configuration.md) — `config/journal.php`: base currency, model substitution, balance-cache timing, soft deletes.

Upgrading from consilience/accounting or scottlaurent/accounting, or between versions of this package: [UPGRADE.md](UPGRADE.md).

For a complete worked example, see [academe/laravel-journal-window-cleaner-demo](https://github.com/academe/laravel-journal-window-cleaner-demo): "Shiny &amp; Sons", a VAT-registered window cleaning round with six months of seeded history, running its bookkeeping on this package. Customer balances are journals, every charge and payment commits as a balanced `TransactionGroup` with the VAT split out, and typed ledgers keep the accounting equation live — plus month close on checkpoints and a quarterly VAT return read straight off the VAT journal. SQLite only, no build step.

Roadmap
-------

[](#roadmap)

Planned follow-up work:

- **Checkpoint follow-ons** — ledger-level rollup rows, and archiving or pruning of old transaction rows once they're safely behind a checkpoint.

The core stays pure bookkeeping mechanics: higher-level accounting concepts — invoices, payments, allocation and clearing — are for packages layered on top, not this one.

Licence
-------

[](#licence)

MIT. See [LICENSE.txt](LICENSE.txt).

###  Health Score

44

—

FairBetter than 90% of packages

Maintenance94

Actively maintained with recent releases

Popularity15

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 94.4% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~3 days

Total

6

Last Release

32d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/395934?v=4)[Jason Judge](/maintainers/judgej)[@judgej](https://github.com/judgej)

---

Top Contributors

[![judgej](https://avatars.githubusercontent.com/u/395934?v=4)](https://github.com/judgej "judgej (17 commits)")[![bradydan](https://avatars.githubusercontent.com/u/3979917?v=4)](https://github.com/bradydan "bradydan (1 commits)")

---

Tags

laravelAccountingdouble entrybookkeepingjournalledger

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/academe-laravel-journal/health.svg)

```
[![Health](https://phpackages.com/badges/academe-laravel-journal/health.svg)](https://phpackages.com/packages/academe-laravel-journal)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3365.5M359](/packages/psalm-plugin-laravel)[laravel/cashier

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

2.5k31.8M168](/packages/laravel-cashier)[craftcms/cms

Craft CMS

3.6k3.7M3.5k](/packages/craftcms-cms)[laravel/ai

The official AI SDK for Laravel.

1.1k6.4M362](/packages/laravel-ai)[yajra/laravel-oci8

Oracle DB driver for Laravel via OCI8

8793.4M29](/packages/yajra-laravel-oci8)[spatie/laravel-health

Monitor the health of a Laravel application

89313.5M196](/packages/spatie-laravel-health)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
