# How to display investment holdings
URL: https://www.quiltt.dev/get-started/tutorials/investment-holdings
Description: Build a portfolio view with Quiltt. Enable the Investments product, connect a brokerage account, and query holdings with the GraphQL API. React tutorial with code examples.
Navigation: get-started → tutorials → investment-holdings
Tags: Tutorials, Investments
Content Length: 9k characters

This guide shows you how to display a user's investment portfolio in your app. You enable the Investments product on a Connector, let a user connect a brokerage account, then query their positions with the [GraphQL API](/api/graphql) and render them.

The portfolio view you build:

1. Enables the `INVESTMENTS` product so Connections sync holdings
2. Queries profile-level `holdings` for a consolidated view across every account
3. Reads the `position` field to separate long and short exposure
4. Reads each holding's `at` timestamp so you know how current the data is

The examples use React with `@quiltt/react`, but the GraphQL queries apply to any client.

## How investment data works

An investment **Holding** represents a position — a stock, bond, ETF, or mutual fund — held in an investment account. Each Holding references a **Security** (its name and ticker), and Quiltt exposes holdings at two levels:

| Level | Query | Scope |
| ----- | ----- | ----- |
| Profile | `holdings` | Positions rolled up across every investment account |
| Account | `account.holdings` | Positions for a single account, plus provider remote data |

Holdings update when Quiltt syncs the underlying Connection, not in real time. Because Plaid doesn't send webhooks for investment balance or position changes, Quiltt polls Plaid connections that hold investment accounts more often than other connections, so positions stay reasonably current. Profile-level holdings are recalculated after each sync completes and the `profile.ready` event fires.

For the complete field list and the long/short model, see the [Investment Holdings API reference](/api/holdings).

## Prerequisites

- [Quiltt Dashboard](https://dashboard.quiltt.dev) account ([sign up](https://dashboard.quiltt.dev/signup))
- The authentication setup from the [Authentication Tutorial](/get-started/tutorials/authentication) — this guide reuses `QuilttAuthProvider` and a Session token
- Node.js 18 or higher

Info:
Holdings are only available for accounts that contain investment positions, such as brokerage, IRA, or 401k accounts. Depository and credit accounts return no holdings.

## 1. Enable the Investments product

Holdings sync only when the Connection is created with the Investments product. In the [Dashboard](https://dashboard.quiltt.dev):

1. Go to **Connectors** and create or edit a Connector.
2. Open the **Connect** settings.
3. Enable the **INVESTMENTS** product.
4. Enable a provider. Use **Mock** in a `SANDBOX` or `TEST` Environment for guaranteed holdings, or **Finicity** to test against realistic brokerage data.

Info:
Mark **INVESTMENTS** as **Required** only if every user must connect an investment account. Required products limit the institution list to those that support them, so leave it optional when investment accounts are a nice-to-have. See the [Connector guide](/connector) for how required products affect institution coverage.

To request Investments on a Connection that already exists, launch the Connector in [Reconnect mode](/connector/reconnect) with the `connectionId`.

## 2. Connect an investment account

Reuse the `QuilttButton` from the [Authentication Tutorial](/get-started/tutorials/authentication) to launch the Connector. When the user finishes, Quiltt syncs the Connection in the background and calculates holdings.

```tsx
// app/page.tsx
'use client'

const CONNECTOR_ID = process.env.NEXT_PUBLIC_QUILTT_CONNECTOR_ID

    

QuilttButton:
Connect an investment account

  )]
```

In a `SANDBOX` Environment, select the **Mock** provider to connect an account seeded with three deterministic positions: Apple (`AAPL`), Microsoft (`MSFT`), and Alphabet (`GOOGL`).

## 3. Query profile-level holdings

Use the `holdings` query for a consolidated portfolio view. When a user owns the same security across multiple accounts, Quiltt combines the positions: `quantity` totals the shares, `value` totals the market value, and `costBasis` averages the cost per share.

```graphql
query GetHoldings {
  holdings {
    nodes {
      id
      quantity
      position
      price
      value
      costBasis
      at
      security [name
        tickerSymbol]
    }
  }
}
```

A single node looks like this:

```json
{
  "id": "hld_12xBsJKrI455PNcaCkqpGk",
  "quantity": 250.75,
  "position": "LONG",
  "price": 178.32,
  "value": 44713.74,
  "costBasis": 38542.50,
  "at": "2024-06-09T12:34:56Z",
  "security": ["name": "Apple Inc.",
    "tickerSymbol": "AAPL"]
}
```

Test the query first in the GraphQL Explorer (**Dashboard → Profiles → Explorer**) before wiring it into your app.

## 4. Render the portfolio

Fetch holdings with `useQuery` from `@quiltt/react` and render each position. Read the `position` field to label short positions rather than inferring direction from the sign of `quantity`.

```tsx
// app/components/Portfolio.tsx
'use client'

const HOLDINGS_QUERY = gql`
  query GetHoldings {
    holdings {
      nodes {
        id
        quantity
        position
        value
        costBasis
        at
        security [name
          tickerSymbol]
      }
    }
  }
`

  const [data, loading] = useQuery(HOLDINGS_QUERY)

  if (loading) return Loading portfolio...

  const holdings = (data?.holdings?.nodes ?? []).filter((holding) => holding != null)
  const total = holdings.reduce((sum, holding) => sum + (holding.value ?? 0), 0)

  return (
    

Table:
Security | 
          Position | 
          Quantity | 
          Value | 

      {holdings.map((holding) => (
          [holding.security?.tickerSymbol ?? holding.security?.name ?? 'Unknown'] | 
            [holding.position] | 
            [holding.quantity] | 
            {holding.value == null ? '—' : `$$[holding.value.toFixed(2)]`} | 

        ))}

      Total value | 
          $[total.toFixed(2)] | 

  )
}
```

Warning:
Quiltt reports short positions with a negative `quantity` and a `position` of `SHORT`. When you total exposure, read the `position` field to separate long from short holdings — summing `value` alone mixes the two directions.

Add `Portfolio` alongside your Connector button so it renders once a user has connected an account:

```tsx
// app/page.tsx
'use client'

const CONNECTOR_ID = process.env.NEXT_PUBLIC_QUILTT_CONNECTOR_ID

    

QuilttButton:
Connect an investment account

      [Portfolio]
  )]
```

## 5. Keep holdings fresh

Holdings reflect the last sync, so refetch when Quiltt signals new data. Subscribe to `profile.ready` in the [Dashboard](https://dashboard.quiltt.dev) under **Webhooks** — it fires after a sync completes and profile-level holdings are recalculated. When your handler receives it, refetch the `holdings` query for that Profile.

Each holding's `at` field records when Quiltt last observed the position. Display it so users know how current their portfolio is:

```tsx
As of [new Date(holding.at).toLocaleString()]
```

For a full webhook handler pattern — signature verification, idempotency, and server-side fetching — see the [transaction sync guide](/get-started/tutorials/transaction-sync); the same handler shape applies to holdings.

## Verification

Test the flow end to end in a `SANDBOX` Environment:

1. Confirm your Connector has the **INVESTMENTS** product enabled.
2. Click **Preview Connector** in the Dashboard, then connect an account with the **Mock** provider.
3. Run the `holdings` query in the GraphQL Explorer. You should see three positions — `AAPL`, `MSFT`, and `GOOGL` — each with a `quantity`, `value`, `costBasis`, and `LONG` position.
4. Load your app. The portfolio table should render the same three holdings with a total value in the footer.

## Troubleshooting

| Problem | Cause | Fix |
| ------- | ----- | --- |
| `holdings` returns an empty list | The Connection synced without the Investments product | Enable **INVESTMENTS** on the Connector, then reconnect the account in [Reconnect mode](/connector/reconnect). |
| No holdings after connecting | The account has no investment positions | Connect a brokerage, IRA, or 401k account. Depository and credit accounts return no holdings. |
| Holdings look stale | Holdings update on sync, not in real time | Read the `at` timestamp and refetch on the `profile.ready` webhook. |
| Short position shows a negative quantity | Short positions are reported with negative `quantity` | Read the `position` field (`LONG` or `SHORT`) instead of inferring direction from the sign. |

## Next steps

You now display a consolidated investment portfolio that stays current as users' data syncs. For positions broken down by account or raw provider data, switch to the account-level `account.holdings` query.

### Related tutorials

- [Authentication Tutorial](/get-started/tutorials/authentication) - Session token setup and management
- [Multi-Aggregator Connectors Tutorial](/get-started/tutorials/connectors) - Build a full connection flow
- [How to sync transactions to your database](/get-started/tutorials/transaction-sync) - Server-side webhook handler pattern

### Reference documentation

- [Investment Holdings API reference](/api/holdings) - Complete field list, long/short model, and test logins
- [Connector guide](/connector) - Products, providers, and required vs optional products
- [Webhooks guide](/webhooks) - Event types, payloads, and verification

### Questions?

[Contact support](mailto:support@quiltt.io)