# Money and Accounts (/docs/guides/money-and-accounts)

ESX Legacy stores a player's money in **accounts**: `money` (cash), `bank` and `black_money` by default. On the server you read and change them through the [xPlayer](/docs/es_extended/server/xplayer) object, for example `xPlayer.addAccountMoney('bank', 500, 'Job reward')`, and you configure which accounts exist and how much new players start with in `es_extended/shared/config/main.lua`.

## Overview [#overview]

| Account | Meaning | Notes |
| --- | --- | --- |
| `money` | Cash on hand | Shortcut methods `getMoney`, `addMoney`, `removeMoney`, `setMoney` |
| `bank` | Bank balance | `transferable = false`, receives paychecks |
| `black_money` | Dirty money | Used by drug and money-wash scripts |

Accounts are saved as JSON in the `users.accounts` column, for example `{"bank":50000,"money":0,"black_money":0}`. ESX saves players every 10 minutes and when they disconnect. Only the server can change money; clients receive updates through the `esx:setAccountMoney` event.

## Configure accounts [#configure-accounts]

```lua title="es_extended/shared/config/main.lua"
Config.Accounts = {
    bank = {
        label = TranslateCap("account_bank"),
        round = true,
        transferable = false,
    },
    black_money = {
        label = TranslateCap("account_black_money"),
        round = true,
    },
    money = {
        label = TranslateCap("account_money"),
        round = true,
    },
}

Config.StartingAccountMoney = { bank = 50000 }
```

| Key | Type | Description |
| --- | --- | --- |
| `label` | `string` | Display name. The defaults are translated through the locale files. |
| `round` | `boolean` | Round amounts to whole numbers. Defaults to `true` when omitted. |
| `transferable` | `boolean` | `false` stops players from giving or dropping this account through the default inventory. Defaults to transferable. |

### Change the starting money [#change-the-starting-money]

`Config.StartingAccountMoney` is used when a character is created, and as the fallback value for any account a loading player does not have yet. New players get no cash by default. To give them some:

```lua title="es_extended/shared/config/main.lua"
Config.StartingAccountMoney = { money = 500, bank = 5000 }
```

Existing players are not affected unless the account is new to them.

## Read and change money (server) [#read-and-change-money-server]

All account methods take the account name as a string. Amounts must be numbers.

```lua
xPlayer.getAccount(accountName)                      -- { name, money, label, round, index } or nil
xPlayer.getAccounts(minimal)                         -- list, or { name = money } when minimal is true
xPlayer.addAccountMoney(accountName, money, reason)  -- true on success
xPlayer.removeAccountMoney(accountName, money, reason)
xPlayer.setAccountMoney(accountName, money, reason)
xPlayer.getMoney() / addMoney(money, reason) / removeMoney(money, reason) / setMoney(money)
```

| Name | Type | Description |
| --- | --- | --- |
| `accountName` | `string` | `money`, `bank`, `black_money` or a custom account. Case-insensitive lookup. |
| `money` | `number` | Amount. Must be greater than 0 for add/remove, at least 0 for set. |
| `reason` | `string?` | Free text passed to the money events, useful for logging. |

### Return values and errors [#return-values-and-errors]

- `removeAccountMoney` returns `false` when the balance would go negative or when rounding turns the amount into `0`. It never allows negative balances.
- `addAccountMoney` returns `false` if the new balance would overflow.
- An **unknown account name**, a non-number amount, or an amount of `0` or less raises a Lua **error** with the default inventory. Validate input first, especially values that came from a client.

### Example: a safe purchase [#example-a-safe-purchase]

```lua title="server/main.lua"
ESX = exports['es_extended']:getSharedObject()

local PRICE <const> = 250

RegisterNetEvent('coffee:buy', function()
    local xPlayer = ESX.GetPlayerFromId(source)
    if not xPlayer then return end

    -- Price is decided on the server, never sent by the client
    if not xPlayer.removeAccountMoney('money', PRICE, 'Coffee shop') then
        return xPlayer.showNotification('Not enough cash')
    end

    xPlayer.addInventoryItem('coffee', 1)
end)
```

Removing the money first and checking the boolean is enough to prevent the classic "buy with no money" exploit. Do not trust a price, account or amount sent by the client; see [Callbacks](/docs/guides/callbacks) for validating client input.

### Example: pay from bank, fall back to cash [#example-pay-from-bank-fall-back-to-cash]

```lua title="server/main.lua"
local function charge(xPlayer, amount, reason)
    if xPlayer.removeAccountMoney('bank', amount, reason) then
        return 'bank'
    end
    if xPlayer.removeAccountMoney('money', amount, reason) then
        return 'money'
    end
    return nil
end
```

## Read money on the client [#read-money-on-the-client]

The client gets a copy of the accounts in `ESX.PlayerData.accounts`. Use it for display only.

```lua title="client/main.lua"
local bank = ESX.GetAccount('bank')
if bank then
    print(('Bank balance: $%s'):format(ESX.Math.GroupDigits(bank.money)))
end

RegisterNetEvent('esx:setAccountMoney', function(account)
    print(('%s is now %s'):format(account.name, account.money))
end)
```

## Money events [#money-events]

| Event | Side | Arguments |
| --- | --- | --- |
| `esx:addAccountMoney` | Server | `source, accountName, money, reason` |
| `esx:removeAccountMoney` | Server | `source, accountName, money, reason` |
| `esx:setAccountMoney` | Server | `source, accountName, money, reason` |
| `esx:setAccountMoney` | Client | `account` (the full account table) |

A simple server-side money log:

```lua title="server/logs.lua"
AddEventHandler('esx:removeAccountMoney', function(playerId, accountName, amount, reason)
    if amount >= 100000 then
        print(('[money] %s removed %s from %s (%s)'):format(playerId, amount, accountName, reason))
    end
end)
```

## Add a custom account [#add-a-custom-account]

<Steps>
<Step>
### Add the account to Config.Accounts [#add-the-account-to-configaccounts]

```lua title="es_extended/shared/config/main.lua"
Config.Accounts = {
    bank = { label = TranslateCap("account_bank"), round = true, transferable = false },
    black_money = { label = TranslateCap("account_black_money"), round = true },
    money = { label = TranslateCap("account_money"), round = true },
    crypto = { label = "Crypto", round = false, transferable = false },
}
```

Use a plain string label unless you also add a locale key.
</Step>
<Step>
### Optionally give a starting balance [#optionally-give-a-starting-balance]

```lua title="es_extended/shared/config/main.lua"
Config.StartingAccountMoney = { bank = 50000, crypto = 0 }
```
</Step>
<Step>
### Restart the server [#restart-the-server]

When players load, ESX builds their account list from `Config.Accounts`. Existing players get the new account with the starting value (or `0`), and it is saved into `users.accounts` on the next save. Now `xPlayer.addAccountMoney('crypto', 0.25, 'Mining')` works, and `/giveaccountmoney` accepts `crypto`.
</Step>
</Steps>

<Callout type="warn" title="ox_inventory">
With ox_inventory, accounts listed in the `inventory:accounts` convar (default `["money", "black_money"]`) are stored as **items** in the ox inventory, not in `users.accounts`. ESX routes the account methods to ox_inventory and returns `false` instead of raising an error on invalid input. Only add a custom account to that convar if it should be a physical item. See [ox_inventory](/docs/guides/ox-inventory).
</Callout>

## Paychecks [#paychecks]

Salaries come from the player's job grade (`job_grades.salary`) and are paid into `bank`.

| Config key | Default | Description |
| --- | --- | --- |
| `Config.EnablePaycheck` | `true` | Turn paychecks on or off |
| `Config.PaycheckInterval` | `7 * 60000` | Interval in milliseconds |
| `Config.OffDutyPaycheckMultiplier` | `0.5` | Off-duty players get this share of their salary |
| `Config.EnableSocietyPayouts` | `false` | Pay salaries from the job's society account (needs esx_society) |
| `Config.LogPaycheck` | `false` | Log paychecks to Discord |
| `Config.PaycheckBatchSize`, `PaycheckBatchDelay`, `PaycheckJitter` | `100`, `250`, `30000` | Spread payouts on big servers |

With society payouts enabled, a player whose company cannot cover the salary gets a "company has no money" notification and is not paid. Jobs without a registered society are paid normally. `xPlayer.togglePaycheck(false)` pauses paychecks for one player.

## Society (company) money [#society-company-money]

Company money lives in `esx_addonaccount` shared accounts named `society_<job>`, not on the player. Use the `GetSharedAccount` export on the server:

```lua title="server/main.lua"
local account = exports.esx_addonaccount:GetSharedAccount('society_police')
if account then
    account.addMoney(5000)
    print(('Police funds: %s'):format(account.money))
end
```

The account object has `addMoney`, `removeMoney` and `setMoney`. Setup is described in [Create a job](/docs/guides/create-a-job#add-a-society-company-account-and-boss-menu) and on the [esx_addonaccount](/docs/addons/esx_addonaccount) page.

## Common issues [#common-issues]

<Accordions>
<Accordion title="'Tried To Set Add To Invalid Account ...'">
The account name is not in `Config.Accounts`. Check spelling; cash is `money`, not `cash`.
</Accordion>
<Accordion title="'... To An Invalid Number'">
The amount was `0`, negative, `nil` or a string. Convert with `tonumber` and check `> 0` before calling.
</Accordion>
<Accordion title="Changes in the database are overwritten">
The player was online. ESX holds accounts in memory and saves them back. Change online players through xPlayer methods or the admin commands.
</Accordion>
<Accordion title="Decimal amounts become whole numbers">
`round = true` rounds every amount. Set `round = false` for accounts that need decimals.
</Accordion>
</Accordions>

## Related [#related]

- [xPlayer reference](/docs/es_extended/server/xplayer)
- [Main config](/docs/es_extended/config)
- [Create a job](/docs/guides/create-a-job)
- [esx_banking](/docs/addons/esx_banking)
- [Callbacks](/docs/guides/callbacks)
