Start with Money

Start your data modeling with multi-currency support in mind. A simple decimal value is not going to cut it.

Cover Image for Start with Money

There’s a lot of advice out there about how to store monetary values in code. One common recommendation is to use something like DECIMAL(19, 4), and for good reason: most currencies divide to two decimal places, many to zero, and a few three. Four decimal places will cover nearly every case in the real world. But focusing only on how to store the number misses a deeper issue: what you’re actually storing.

Amount Alone Is Not Enough

A decimal value—say, 19.99—is meaningless without knowing what it refers to. Is that US dollars? Euros? Pesos? Yen? Storing just the numeric value leaves too much room for assumption and error. If you only serve a large market like the US or the Eurozone, you can get away with it for a while. But assumptions like “we only support USD” or “the database column is implicitly dollars” are brittle and short-lived. Dream bigger, your scrappy startup will serve clients across the globe some day.

Money Is a Tuple: Amount + Currency

Money is not just a number—it’s a typed number. Its “unit” is the currency. A proper money value is a pair consisting of:

{
  amount: Decimal;
  currency: 'USD' | 'EUR' | 'JPY' | ...;
}

This makes the value unambiguous, portable, and safe to operate on. You avoid mixing dollars with euros, and you make it possible to handle multiple currencies in the future without a painful refactor. This becomes especially important when you’re dealing with:

  • International customers
  • Currency conversions
  • Tax calculations
  • Financial reports
  • Auditing requirements

Add the Currency Early—Not Because It’s Free, But Because It Only Gets Harder

Adding currency support is not free. Consider not having it as technical debt. Retrofitting it into a mature codebase is a serious undertaking—ask me how I know. It touches everything: data models, serialization, validation, business logic, external APIs, reporting.

The advice isn’t “add it because it’s cheap.” The advice is: add it while it’s still cheap. That early assumption—“we’ll always use a single currency”—can collapse with one strategic decision. By then, it’s too late to avoid months of work.

Recommendations

  • Always store the currency alongside the amount. You should (almost) never strip off this currency information and pass a plain decimal to a function or in an API response. The currency should flow along with it nearly everywhere.
  • Avoid floats. Use fixed-point decimals or exact numeric types to avoid rounding errors. Go with DECIMAL(19, 2) if in doubt, or whatever is prescribed by regulations that are relevant to you. If you do need 3 decimal places, I would imagine that would be a fairly easy migration.
  • Represent money values as structured objects in your APIs from day one:
{
  "amount": "19.99",
  "currency": "USD"
}
  • Don’t wait until you need multi-currency support to retrofit this—you’ll just end up hacking in an extra currency field later, and have an unsatisfying API to protect backwards compatibility.
  • Use a library that understands money. This saves you from reimplementing validation, arithmetic, formatting, and conversions. For example, django-money provides an intuitive Money class, model fields, and forms integration—all while enforcing currency correctness at every layer.
  • Avoid doing any currency conversions. You may not even have access to what the FX rate is until you have already charged a customer. You don't want to be chasing down bugs around currency conversions being slightly off. If you can leave this to the accountants and analysts, do it. 😅
  • Make sure that adding two money values of differing currencies together is disallowed behavior. US$50 + KR₩1000 is not US$1050. This could result in a costly bug.
  • Treat money as a value type. In your domain model, money should be a first-class object, not just a number that happens to live next to a string called {field}_currency.

Useful Helpers

You'll often find that you need to initialize a variable to zero, and then add multiple money values to it, or you'll need to compare a money value to zero. Create a helper function to do this for you, that takes either a money value or a currency, and returns a money value of zero in that currency.

Don't be afraid to monkeypatch the Money class if you are using django-money. You may find that it isn't formatting money amounts the way you might expect.