Loading...
Search the Quiltt documentation
Loading...
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 and render them.
The portfolio view you build:
INVESTMENTS product so Connections sync holdingsholdings for a consolidated view across every accountposition field to separate long and short exposureat timestamp so you know how current the data isThe examples use React with @quiltt/react, but the GraphQL queries apply to any client.
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.
QuilttAuthProvider and a Session tokenHoldings are only available for accounts that contain investment positions, such as brokerage, IRA, or 401k accounts. Depository and credit accounts return no holdings.
Holdings sync only when the Connection is created with the Investments product. In the Dashboard:
SANDBOX or TEST Environment for guaranteed holdings, or Finicity to test against realistic brokerage data.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 for how required products affect institution coverage.
To request Investments on a Connection that already exists, launch the Connector in Reconnect mode with the connectionId.
Reuse the QuilttButton from the Authentication Tutorial to launch the Connector. When the user finishes, Quiltt syncs the Connection in the background and calculates holdings.
// app/page.tsx
'use client'
import { QuilttButton } from '@quiltt/react'
const CONNECTOR_ID = process.env.NEXT_PUBLIC_QUILTT_CONNECTOR_ID
export default function Home() {
return (
<QuilttButton connectorId={CONNECTOR_ID} className="border p-2">
Connect an investment account
</QuilttButton>
)
}
In a SANDBOX Environment, select the Mock provider to connect an account seeded with three deterministic positions: Apple (AAPL), Microsoft (MSFT), and Alphabet (GOOGL).
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.
query GetHoldings {
holdings {
nodes {
id
quantity
position
price
value
costBasis
at
security {
name
tickerSymbol
}
}
}
}
A single node looks like this:
{
"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.
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.
// app/components/Portfolio.tsx
'use client'
import { useQuery, gql } from '@quiltt/react'
const HOLDINGS_QUERY = gql`
query GetHoldings {
holdings {
nodes {
id
quantity
position
value
costBasis
at
security {
name
tickerSymbol
}
}
}
}
`
export default function Portfolio() {
const { data, loading } = useQuery(HOLDINGS_QUERY)
if (loading) return <div>Loading portfolio...</div>
const holdings = (data?.holdings?.nodes ?? []).filter((holding) => holding != null)
const total = holdings.reduce((sum, holding) => sum + (holding.value ?? 0), 0)
return (
<table>
<thead>
<tr>
<th>Security</th>
<th>Position</th>
<th>Quantity</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{holdings.map((holding) => (
<tr key={holding.id}>
<td>{holding.security?.tickerSymbol ?? holding.security?.name ?? 'Unknown'}</td>
<td>{holding.position}</td>
<td>{holding.quantity}</td>
<td>{holding.value == null ? '—' : `$${holding.value.toFixed(2)}`}</td>
</tr>
))}
</tbody>
<tfoot>
<tr>
<td colSpan={3}>Total value</td>
<td>${total.toFixed(2)}</td>
</tr>
</tfoot>
</table>
)
}
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:
// app/page.tsx
'use client'
import { QuilttButton } from '@quiltt/react'
import Portfolio from './components/Portfolio'
const CONNECTOR_ID = process.env.NEXT_PUBLIC_QUILTT_CONNECTOR_ID
export default function Home() {
return (
<main className="flex flex-col gap-4">
<QuilttButton connectorId={CONNECTOR_ID} className="border p-2">
Connect an investment account
</QuilttButton>
<Portfolio />
</main>
)
}
Holdings reflect the last sync, so refetch when Quiltt signals new data. Subscribe to profile.ready in the Dashboard 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:
<span>As of {new Date(holding.at).toLocaleString()}</span>
For a full webhook handler pattern — signature verification, idempotency, and server-side fetching — see the transaction sync guide; the same handler shape applies to holdings.
Test the flow end to end in a SANDBOX Environment:
holdings query in the GraphQL Explorer. You should see three positions — AAPL, MSFT, and GOOGL — each with a quantity, value, costBasis, and LONG position.| 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. |
| 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. |
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.