Overview

An income statement shows a company’s revenues, expenses, and profits over a given period of time and is an excellent way to present a rolled-up view of the financial performance of a business. An effective income statement interface should present the following:

  • Lists of ledgers grouped by type (expense, revenue) and sub_type (operating expenses, operating revenue, total cost of goods sold, other expenses, other income).
  • Each ledger should display its total amount that sums the ledger’s line entries.
  • Calculated totals that provide a relevant summary of the entire set of data for the time period, for example, gross profit, total operating profit, and net profit.

How it works

Teal’s income statement endpoint returns an array of sub_type groups and profit metrics such as gross profit and net profit. Each sub_type group includes a calculated total of all the sub_type’s ledgers. The values are tabulated for the specified date range.


Build an income statement

Use the Get Income Statement endpoint to get the array of subtype groups and metrics tabulated for the date range. Ledger summaries are returned in the order that you can render them in your user interface. This makes it easy to display a conventional income statement with minimal effort.

1. Make the request

Use /v0/reports/income-statement to get the income statement data.

const IncomeStatement = () => {
    const options = {
        method: 'GET',
        headers: {
            Authorization: AUTHORIZATION_KEY,
            'teal-instance-id': TEAL_INSTANCE_ID
        }
    }

    const incomeStatementRequest = await fetch(
        'https://api.sandbox.teal.dev/v0/reports/income-statement', 
        options
    );

    const incomeStatementData: IncomeStatement = await request.json();

    return (
        // ...
    )
}

export default IncomeStatement;

The response will have ledgers grouped by sub_type, for example “Operating Expenses” or “Operating Revenue”, interspersed with profit metrics.


2. Display subtype groups and calculated totals

Once you have the response data, use a loop to display the top-level ledgers and calculations.

Be aware of the difference between amount and total_amount: amount reports the sum of entries in that ledger, where total_amount reports the sum of entries in that ledger and all child ledgers.

const IncomeStatement = () => {
    // ...
    return (
        <main>
            <h1>Income statement</h1>
            {incomeStatementData.records.map(rootLedger => {
                return (
                    <section key={rootLedger.name}>
                        <h3>{rootLedger.name}</h3>
                        <p>{rootLedger.total_amount}</p>
                        <ul role="list">
                            {rootLedger.children?.map((subLedger) => (
                                <li key={subLedger.ledger_id}>
                                    <h4>{subLedger.name}</h4>
                                    <p>{subLedger.total_amount}</p>
                                </li>
                            ))}
                        </ul>
                    </section>
                )
            })}
        </main>
    )
}

export default IncomeStatement;


3. Filter by date range

Add a date picker to the UI and use the start_date and end_date parameters to filter the request. If you need a date picker to get started, see the Reports introduction for an example. In this example, we use the date picker set the start_date and end_date as query parameters and use those in our request. If the query params have not been set, you should have a default date range to fall back to, for example the year to date, in YYYY-MM-DD format.

To ensure all of our reports are consistent regardless of where a user is located, all dates in Teal are in UTC. If you adjust the displayed timezone to where the user is, you may see transactions listed on incorrect days.

const IncomeStatement = () => {
    // Set the default date range to year to date
    const date = new Date();
    const currentYear = date.getUTCYear();
    const currentMonth = date.getUTCMonth();
    const currentDate = date.getUTCDate();
    const defaultStartDate = `${currentYear}-01-01`;
    const defaultEndDate = `${currentYear}-${currentMonth}-${currentDate}`;

    // Get the search params
    const params = new URL(document.location).searchParams
    const startDate = params.get("start_date") ?? defaultStartDate;
    const endDate = params.get("end_date") ?? defaultEndDate;

    // ...

    const incomeStatementRequest = await fetch(
        `https://api.sandbox.teal.dev/v0/reports/income-statement?start_date=${start_date}&end_date=${end_date}`, 
        options
    );

    return (
        {/* ... */}
    )
}

export default IncomeStatement;


Best practices

  • Because ledgers can be nested recursively, consider your chart of accounts templates and decide how deeply you want to display nested ledgers.
  • Some sub-ledgers may be unused, but will still show up in the response even though their total_amount is 0. You may want to hide these to avoid cluttering the interface.
  • Users may want to see the individual line entries in a specific ledger. We recommend that you link to the ledger rather than display its contents inline.

On amount formats:

  • All amounts are displayed as positive numbers because they describe a total value, not a movement of money.
  • Use a standard number formatter (new Intl.NumberFormat() in JavaScript) to ensure decimal places and commas are appropriately accounted for.
  • Additionally, we strongly recommend that you use the font-variant-numeric: tabular-nums; css rule to display the amounts to keep consistent spacing for optimal legibility.

Relevant resources