A personal finance manager (PFM) is a tool that helps individuals track and manage their financial assets and expenses. It is important to use a PFM because it can help you make informed financial decisions such as creating a budget, setting financial goals, and identifying areas where you can save money. A PFM can also alert users to any unusual activity on their accounts, and avoid financial fraud 🥷😱. You can see the end result here: My Expenses.

First of all, what the heck is Pluggy? Pluggy is an API that enables applications to connect bank and broker accounts of their users and collect their data, accounts, transactions, and more. We will use it for that 🤯, connect real bank accounts in our application!

In order to create this PFM we will follow this Pluggy documentation (highly recommend reading it to understand everything between each step) and use Nextjs because it will be easier to manage the frontend and backend logic.

At the end of this tutorial, we will end up with a project looking like this: ⭐✨


Pluggy credentials

We need to get our credentials in order to be able to connect users’ bank accounts, we only need to create an account in dashboard.pluggy.ai and get the clientId and clientSecret from an application.

Here you have a tutorial to see how to do it: 😉


Let’s get into the code!

The first step is to build the project, since we will use next, we only need to run npx create-next-app@latest. I will select JavaScript for this example to make it easier, but you can select TypeScript too since Pluggy's libraries (pluggy-node and pluggy-js) have TypeScript support!


After creating the project you just need to write in the command line cd ./name-of-the-project and then run npm run dev and see that all is working fine 🙂.

Running npm run dev you will see the Nextjs template:


In the root of the project create a file called .env.local (I will do it by typing touch .env.local).

And add the clientId and clientSecret from the application that you created above, like this:

.env.local example


Now we just need to install a Pluggy package to be able to connect bank accounts. You just need to run the following command:

npm install react-pluggy-connect pluggy-sdk


😉 Remember if you are using typescript, pluggy-sdk has TypeScript support!

We can remove unneeded code located in pages/index.js and end up with this:

import Head from 'next/head'
import styles from '../styles/Home.module.css'


export default function Home() {
  return (
    <div className={styles.container}>
      <Head>
        <title>Pluggy PFM</title>
        <meta name="description" content="The best PFM for Brazil!" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
      </Head>
    </div>
  )
}


Adding Pluggy Connect and handling frontend logic

What is Pluggy Connect?

Basically, it is a Widget, which it’s our frontend solution that you can init in your application and handles all the logic that you need to connect a bank account with Pluggy, you only need to listen to the callbacks to know what is happening with the user inside Pluggy Connect.



Configuration

You can add different props to the widget, to be able to know what is the user doing inside the application, you can use all of our callback, like onEvent, onClose, onSuccess, etc. You can see all the available props here. You can play with them by yourself in the project that we will build 🤙.


Customization of Pluggy Connect Widget

You can customize a lot of things in our application from our dashboard, like the logo, the primary color, the institutions that will be listed to the user, and more! You can see more about customization here ⭐️.


Get into the code

Now, we can import react-pluggy-connect and add the logic to open, close, and listen to Pluggy Connect events and handle the user’s data we will follow this repo from the tutorial I mention above.

First, we need to import Pluggy Connect, since we are using Nextjs, we need to import like:

const PluggyConnect = dynamic(
  () => import('react-pluggy-connect').then(mod => mod.PluggyConnect),
  { ssr: false }
)


How to use it?

We need a connectToken (we will review how to get the token from the backend in a moment) to instantiate the widget, also, we have a button that we will listen to the click before open Pluggy Connect.

{isWidgetOpen && connectToken && (
        <PluggyConnect
          updateItem={itemIdToUpdate}
          connectToken={connectToken}
          includeSandbox={true}
          onClose={() => setIsWidgetOpen(false)}
          onSuccess={handleSuccess}
        />
      )}


Every time that both connectToken and isWidgetOpen are true, the Pluggy Connect widget will be instantiated in our app to be able to connect users’ bank and broker accounts.

Also, if you see, we are passing a callback called onSuccess that code will be executed every time an account connects successfully, in this callback, we will handle all the logic to communicate with the backend, you can see more about Connect’s configuration here.

The callback will look like:

const handleSuccess = useCallback(async (itemData) => {
    // all the logic to do after the connection is success 
  }, [])


Note that we are adding some other things like:
updateItem which is the item we will use when a user wants to refresh their data.

connectToken is the actual token used in the connection (the only requirement for the widget to work).

onClose callback is used to know when the user closes the widget.

After adding the logic to communicate with the backend and show the data, we will end up with a file that looks like this:

import Head from 'next/head'

import dynamic from 'next/dynamic'
import { useCallback, useState, useEffect } from 'react'

const PluggyConnect = dynamic(
  () => import('react-pluggy-connect').then(mod => mod.PluggyConnect),
  { ssr: false }
)

export default function Home() {
  const [connectToken, setConnectToken] = useState('')
  const [itemIdToUpdate, setItemIdToUpdate] = useState()
  const [startDate, setStartDate] = useState()
  const [isWidgetOpen, setIsWidgetOpen] = useState(false)
  const [categoryBalances, setCategoryBalances] = useState({
    category: null,
    balance: null
  })

  useEffect(() => {
    // when component mounts we get the connect token
    const fetchToken = async () => {
      const response = await fetch('/api/connect-token')
      const { accessToken } = await response.json()
      setConnectToken(accessToken)
    }

    fetchToken()
  }, [])

  // when user connect account finished, we handle the logic to get the report
  const handleSuccess = useCallback(async (itemData) => {
    // call the api to get the report data
    const reportResponse = await fetch('/api/item-report', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ itemId: itemData.item.id })
    })

    const {
      categoryBalances: categoryBalancesResponse,
      startDate: startDateResponse
    } = await reportResponse.json()
    setStartDate(startDateResponse)
    setCategoryBalances(categoryBalancesResponse)
    setItemIdToUpdate(itemData.item.id)
  }, [])

  return (
    <div
      style={{
        display: 'flex',
        alignItems: 'center',
        flexDirection: 'column',
        minHeight: '100vh'
      }}
    >
      <Head>
        <title>Pluggy PFM</title>
        <meta name='description' content='The best PFM for Brazil!' />
        <meta name='viewport' content='width=device-width, initial-scale=1' />
      </Head>

      <h1 className=' pb-4'>
        <i className='las la-wallet'></i> Pluggy My Expenses
      </h1>

      {categoryBalances && startDate && (
        <div className='pb-4'>
          <h3 className='pb-5 mt-5'>
            Your movements since {new Date(startDate).toLocaleDateString()} per
            category
          </h3>
          <table className='table'>
            <thead>
              <th>Category</th>
              <th>Amount</th>
            </thead>
            <tbody>
              {categoryBalances.map(categoryBalance => (
                <tr key={categoryBalance.category}>
                  <td>{categoryBalance.category}</td>
                  <td
                    className={`${categoryBalance.balance > 0
                      ? 'text-success'
                      : 'text-danger'
                      }`}
                  >
                    R$ {Math.abs(categoryBalance.balance)}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
      <button
        className='btn btn-primary'
        style={{
          backgroundColor: '#ef294b',
          borderColor: '#ef294b'
        }}
        onClick={() => setIsWidgetOpen(true)}
      >
        {itemIdToUpdate ? 'Refresh my data' : 'Connect your account'}
      </button>

      {isWidgetOpen && connectToken && (
        <PluggyConnect
          updateItem={itemIdToUpdate}
          connectToken={connectToken}
          includeSandbox={true}
          onClose={() => setIsWidgetOpen(false)}
          onSuccess={handleSuccess}
        />
      )}
    </div>
  )
}


note: we are using a style and icon library and you can use by adding it into your _document.js file like:

import { Html, Head, Main, NextScript } from 'next/document'

export default function Document() {
  return (
    <Html lang="en">
      <Head>
        <link rel="stylesheet" href="https://maxst.icons8.com/vue-static/landings/line-awesome/line-awesome/1.3.0/css/line-awesome.min.css"></link>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet"></link>
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  )
}


We end up with this cool screen, but it’s not working, right? Let’s build the backend needed for this integration 🤘.


Building the backend

If you saw the code example above, you will notice that we talk about a connect-token, well… what is that? And why should we create an endpoint in our backend to build it, instead of creating it in the frontend?
Well, if you had those questions, here is your star ⭐!

First, a little of background. When you read Pluggy’s documentation, you will notice that with your Dashboard credentials, you will be able to create an auth-token (different to connect-token), well, both Dashboard credentials and auth-token must never be allocated in the client (frontend), because if a malicious attacker wants to steal your data and have access to them, it could access to all your Pluggy data (we have another secure point in Pluggy, that are the itemsIds but we will get into that in the future).

That's why we have the connect-token, it only has access to the connection that was created with the token, and it’s limited, so it could be in the frontend without any problem, you can read more about this here.

Building our first endpoint /connect-token

Inside the /pages/api directory, we need to create a file called connect-token.js and where we will init our PluggyClient and create our connect-token. Here is the code to do that.

import { PluggyClient } from 'pluggy-sdk'

const PLUGGY_CLIENT_ID = process.env.PLUGGY_CLIENT_ID
const PLUGGY_CLIENT_SECRET = process.env.PLUGGY_CLIENT_SECRET
if (!PLUGGY_CLIENT_ID || !PLUGGY_CLIENT_SECRET) {
  throw new Error('Both PLUGGY_CLIENT_ID and PLUGGY_CLIENT_SECRET are required')
}

const client = new PluggyClient({
  clientId: PLUGGY_CLIENT_ID,
  clientSecret: PLUGGY_CLIENT_SECRET
})

export default async function handler (_req, res) {
  const connectToken = await client.createConnectToken()

  res.status(200).json(connectToken)
}


After doing that, we will be able to open Pluggy Connect! Yayy! (yes I change a little the styling, but you can be creative yourself to do it 🙂)


Building our data endpoint /item-report


In this endpoint, you will see how to get data from Pluggy Items.

We said that we will show the balance per category in this example.

After starting to build this endpoint, I will install lodash, it’s totally optional, but it will help us in the development of the endpoint and make it easier to understand the example.

To install it, you only need to run:

npm install lodash


After doing that, thanks to pluggy-sdk we can start building our endpoint, I told you that we will show the balance per category, so we need to create a file inside the /api folder called item-report.js. And we will type the following code:

import { sortBy, sumBy, groupBy } from 'lodash'
import { PluggyClient } from 'pluggy-sdk'

const PLUGGY_CLIENT_ID = process.env.PLUGGY_CLIENT_ID
const PLUGGY_CLIENT_SECRET = process.env.PLUGGY_CLIENT_SECRET
if (!PLUGGY_CLIENT_ID || !PLUGGY_CLIENT_SECRET) {
    throw new Error('Both PLUGGY_CLIENT_ID and PLUGGY_CLIENT_SECRET are required')
}

const client = new PluggyClient({
    clientId: PLUGGY_CLIENT_ID,
    clientSecret: PLUGGY_CLIENT_SECRET
})

export default async function handler(req, res) {
    const { itemId } = req.body

    // get item accounts
    const { results: itemAccounts } = await client.fetchAccounts(itemId)

    const transactions = []
    // get accounts transactions
    for (const account of itemAccounts) {
        const accountTransactions = await client.fetchAllTransactions(account.id)
        transactions.push(...accountTransactions)
    }

	// grouping transactions by transaction category
	// if the category is null we use "Other"
    const transactionsPerCategory = groupBy(
        transactions,
        (transaction) => transaction.category ?? 'Other'
    );


    const categoryBalances = []

    for (const category in transactionsPerCategory) {
        const transactions = transactionsPerCategory[category]
		// getting the total balance of each category adding the 
		// amount property of each transaction
        const balance = +sumBy(
            transactions,
            (transaction) => transaction.amount
        ).toFixed(2)
        categoryBalances.push({ category, balance })
    }

    const startDate = sortBy(transactions, ['date'])[0].date


    res.status(200).json({
		// sorting the balances from the least to greatest
        categoryBalances: sortBy(categoryBalances, ['balance']),
        startDate,
    })
}


As you can see, we are grouping the transactions of all the item accounts per category and then adding the balance.

That’s all! 🚀

You built a PFM in 5 minutes!!

I’m currently jumping in one leg because of your achievement! YOU BUILD A PFM IN A RECORD TIME! But this is only the first step. I will give you some points to keep in mind, and how you can start working on this.


Next steps:


➡️ The itemId is an important resource in Pluggy’s data. You should save it in a database every time a user connects an account. Here you have an example of how to do it with supabase here!

➡️ You can customize your Pluggy Connect in the dashboard in the tab of customization! Try it yourself! You can change the color brand, logo, banks, and more! See more about customization here

Show us what you did and what you think!


➡️ You can improve a lot the style of this project, show us your styles on Twitter by tagging me and Pluggy at @MontoneNico and @pluggyai

➡️ If you have any feedback about the product or developer experience you can talk with us on our Dashboard/Twitter ❤️

That’s all, let us know via Twitter when you finish the tutorial! It will be awesome!