Back to Blog
Technology6 min read

How an Invoice Is Stored: One Row per Line Item, Plus a Full Copy

IN
Invoice Generator TeamAuthor
October 28, 2026Published
Also available in:NederlandsDeutsch

An invoice looks like one document, so you'd expect it to be one row in a database. It isn't. Each saved invoice is stored as several rows that share an ID, and each row also carries a full copy of the whole invoice. That design explains how invoices can be searched, how edits are saved, and why an invoice's ID never changes even though its rows are rewritten on every save.

One Row per Line Item

When you save an invoice, the server writes one row for each line item. Each row has that item's description, quantity, and rate, together with the fields every invoice has: invoice number, who it's billed to and from, total, currency, issue and due dates, payment terms, notes, terms, and status. An invoice with five line items becomes five rows. An invoice with no line items still gets one row, with the line-item columns left empty.

Splitting items into rows makes them easy to query. Reports and lookups can work with individual items and quantities without opening up the whole document.

The Group ID Holds the Rows Together

Every row of an invoice shares an invoice group ID, which is created once when the invoice is first saved. That group ID is the invoice's real identity. Share links, payments, comments, view tracking, the audit trail, and recurring schedules all refer to the group ID, never to an individual row. Loading an invoice means fetching every row with that group ID in your workspace and combining them.

Each Row Also Holds a Full Copy

Separate columns can't capture everything on an invoice: discounts, tax settings, custom fields, payment schedules, design choices such as font and accent color, and recorded payments. So every row also stores a full JSON copy of the complete invoice. When the app shows you an invoice, it reads that copy. The separate columns are mainly there for querying, and the copy is what puts the document back together exactly as you saved it.

Invoices saved before the full copy existed are still supported. If a row has no copy, the app rebuilds the invoice from the separate columns, calculating each line's amount as quantity times rate.

Edits Replace All the Rows in One Transaction

Saving an edit doesn't update rows in place. Inside one database transaction, the server deletes every row for that group ID and inserts a new set built from the edited invoice. This handles every kind of change the same way: an added line item, a deleted one, or a reordered list all simply produce a new set of rows.

Because it happens in a transaction, nobody ever sees a half-saved invoice. Other requests see either the old rows or the new ones. The audit entry for the edit and any update to the recurring schedule are written in the same transaction, so the history never says an edit happened if it didn't.

The group ID never changes, so nothing that points to the invoice breaks when its rows are replaced. Payments, comments, and share links stay connected through every edit.

How Currency Is Recorded

Each row stores the currency as a short code and symbol together, such as EUR(€). That value comes either from the currency code and symbol on the invoice, or by looking up the selected currency in the built-in list of countries and their currencies. The full copy also keeps the code and symbol as separate fields. Recording the currency on every invoice, rather than only in a workspace setting, is what lets one workspace hold invoices in different currencies, and what lets reports keep separate totals for each currency instead of adding them together.

Why This Matters to You

Most of the time you'll never notice this design, but it's behind some guarantees you rely on. Saving is all-or-nothing, so an invoice can't be half-saved. Everything linked to an invoice stays linked when you edit it. And the invoice you reopen is rebuilt from the complete copy you saved, with nothing lost.

Related Articles

Technology5 min read

Why Sign-In Attempts Are Rate Limited

Sign-in, sign-up, two-factor checks, and verification resends share a budget of 10 attempts per hour per IP address, which is much stricter than the limit on ordinary app requests. Here's why the numbers differ.

IN
Invoice Generator TeamOctober 16, 2026
Technology6 min read

How Sign-Up and Email Verification Actually Work

Creating an account sets up a personal workspace in the same database transaction, the verification link lasts 24 hours, and clicking an expired one quietly sends you a fresh link instead of leaving you stuck.

IN
Invoice Generator TeamOctober 14, 2026
Technology6 min read

How Your Logo Gets Stored When You Upload It

A logo pasted in as a remote URL gets fetched server-side, checked against a 2MB cap and a real image content type, and converted into a self-contained data URL before it's saved.

IN
Invoice Generator TeamOctober 4, 2026
Technology6 min read

Why You Get Logged Out After an Hour

Every session token expires in exactly one hour, and there's no refresh mechanism behind the scenes extending it quietly — plus the one deliberate exception carved out for two-factor logins.

IN
Invoice Generator TeamSeptember 28, 2026
Technology6 min read

How Client Portal Links Expire

A portal link defaults to 30 days, checks expiry on every request rather than up front, and matches a client's documents by an exact, case-sensitive-safe comparison — not a database foreign key.

IN
Invoice Generator TeamSeptember 26, 2026
Technology6 min read

How Invoice View Tracking Actually Works

Two separate mechanisms record when a client opens your invoice — a running counter and a detailed event log — and a password-protected share link doesn't count as viewed until the password is entered.

IN
Invoice Generator TeamSeptember 24, 2026

Mastered Invoicing?

Put your knowledge into practice and create your first professional invoice today.

Create Your Invoice Now
How an Invoice Is Stored: One Row per Line Item, Plus a Full Copy | Invoice Generator