> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pylon.mortgage/llms.txt
> Use this file to discover all available pages before exploring further.

# Setup

> Build the Borrower Dashboard Element into your app

This guide walks you through integrating the Borrower Dashboard Element from scratch. By the end you'll have a working dashboard that shows a submitted loan, and you'll understand how to keep data in sync by updating loans via the GraphQL API so borrowers always see the latest information.

## 1. Prerequisites

Before adding the Element you need:

* **Authentication** — A backend endpoint that returns a Pylon [authorization lease](/elements/setup/authentication). The Element calls this endpoint when it needs to talk to the Pylon API; your route should create or retrieve a Contributor, create a lease, and return the full lease object to the frontend. If you haven't set this up yet, follow [Handling authentication](/elements/setup/authentication) first.
* **A submitted loan** — The Borrower Dashboard Element displays a **single** loan that has already been submitted (`submitted: true`). Use the [Loan Application Element](/elements/loan-application/setup) for in-progress applications; once the loan is submitted, switch to the Borrower Dashboard with that loan's ID. See [Loading loans](/elements/setup/loading-loans) for how to choose between the two.
* **Environment details** — Your Pylon customer ID and environment (`test` or `prod`) for Content Security Policy and any GraphQL calls (e.g. fetching loans or updating data).
* **Package installed:**

```bash theme={null}
npm install @pylonlending/react-elements --save
```

## 2. Add the Element

Import the Element and pass an **auth lease callback** and a **loan ID**. The auth callback is a function that fetches a fresh lease from your backend; the Element calls it when it initializes and when it needs to re-authenticate. It must return a `Promise<AuthLease>` (the exact JSON from your auth route)—do not modify or filter the lease. The `loanId` is the ID of the **submitted** loan you want to display; the dashboard will not load for in-progress (unsubmitted) loans.

```tsx theme={null}
import { BorrowerDashboardComponent } from "@pylonlending/react-elements";

const fetchPylonAuthLease = async (): Promise<AuthLease> => {
  const response = await fetch("/auth/pylon");
  if (!response.ok) {
    throw new Error("Failed to get Pylon auth lease");
  }
  return response.json();
};

export function BorrowerDashboardPage({ loanId }: { loanId: string }) {
  return (
    <BorrowerDashboardComponent
      authLeaseCallback={fetchPylonAuthLease}
      loanId={loanId}
    />
  );
}
```

Use your actual auth route path in place of `"/auth/pylon"` if it's different. At this point the Element is working: it will load the loan and show the borrower dashboard (tasks, documents, loan status, etc.) for that loan.

**Behavior:**

* Both `authLeaseCallback` and `loanId` are required. The dashboard is always tied to one loan.
* The Element renders in an iframe and uses the lease to communicate with the Pylon API. Ensure your auth endpoint is only accessible to logged-in users.
* The `user_id` (or equivalent) in your [auth handshake](/elements/setup/authentication) must match the user associated with the loan, or the Element will not load the loan.

## 3. Where to get the loan ID

You need the ID of a **submitted** loan. Common sources:

* **Your API or database** — After a loan is submitted (via the Loan Application Element or your backend), store the `loan.id` and pass it when the user opens "My loan" or "Dashboard."
* **GraphQL: loans for the current user** — Use the `borrowerUser` query to list loans for the authenticated user, then filter by `submitted: true` and use the chosen loan's `id`:

```graphql theme={null}
query GetBorrowerLoans($userId: ID!) {
  borrowerUser(userId: $userId) {
    loans {
      id
      submitted
    }
  }
}
```

* **URL or app state** — When the user clicks a loan from a list, pass the `loanId` as a route param or state so your page can render `<BorrowerDashboardComponent loanId={loanId} ... />`.

If the user has multiple submitted loans, show a list and let them pick one; then render the Element with that loan's ID. For the full flow of "which Element for which loan," see [Loading loans](/elements/setup/loading-loans).

## 4. Updating data via the GraphQL API

The Borrower Dashboard Element **always displays the latest data from the Pylon API**. Any time you add or update loan data via the GraphQL API—borrower info, income, assets, documents, tasks, or other fields—the Element reflects those changes. You don't need to refresh the page or remount the Element; it fetches current data as the borrower navigates and when data changes.

**Why this matters:**

* **Update data on behalf of borrowers** — Your backend or an internal tool can push income, assets, or other information via the API; the borrower sees it in the dashboard without re-entering it.
* **Sync with your systems** — Keep loan data in sync with your CRM, LOS, or other systems by writing updates through the GraphQL API; the dashboard stays up to date.
* **Custom workflows** — Collect information in your own UI (e.g. a pre-qual form or intake), write it to the loan via the API, and the borrower sees it in the dashboard and can complete remaining tasks.

### How it works

1. **Loan exists and is submitted** — The Borrower Dashboard only shows submitted loans. Data updates apply to the same loan the Element is displaying.
2. **You call the GraphQL API** — Use the appropriate mutations to create or update borrowers, income, assets, subject property, documents, or other entities tied to the loan. See the [GraphQL API Reference](https://pylon.mortgage/documentation/graphql/index.html) and the [complete integration guide](/guides/getting-started/e2e-build) for mutations and input types.
3. **The Element shows the latest data** — The dashboard fetches current data from the API. Changes you make via the API are visible in the Element; no extra step is required on the frontend.

<Info>
  The Borrower Dashboard Element does not cache data indefinitely. It reflects the latest state from the API, so updates you make via the GraphQL API (from your backend, a job, or another part of your app) will appear in the dashboard as the user navigates or when the Element refetches.
</Info>

### Example: Update borrower info via API, then open the dashboard

Below, your backend (or another part of your app) updates the loan's borrower phone number via the GraphQL API. When the user opens the Borrower Dashboard, they see the updated information. Replace `accessToken`, `loanId`, and the GraphQL endpoint with your own values.

```tsx theme={null}
const GRAPHQL_URL = "https://pylon.mortgage/graphql";

// Example: your backend or a server action updates borrower data
async function updateBorrowerPhone(accessToken: string, borrowerId: string, phone: string) {
  const mutation = `
    mutation UpdateBorrower($id: ID!, $input: BorrowerPersonalInformationInput!) {
      borrower(id: $id) {
        updatePersonalInformation(input: $input) {
          borrower { id }
        }
      }
    }
  `;

  const response = await fetch(GRAPHQL_URL, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${accessToken}`,
    },
    body: JSON.stringify({
      query: mutation,
      variables: {
        id: borrowerId,
        input: { phoneNumber: phone },
      },
    }),
  });

  if (!response.ok) throw new Error("Failed to update borrower");
}

// Your page: user opens dashboard for a submitted loan
function BorrowerDashboardPage() {
  const loanId = "loan_abc123"; // from route, state, or borrowerUser query

  const fetchPylonAuthLease = async (): Promise<AuthLease> => {
    const response = await fetch("/auth/pylon");
    if (!response.ok) throw new Error("Failed to get auth lease");
    return response.json();
  };

  return (
    <BorrowerDashboardComponent
      authLeaseCallback={fetchPylonAuthLease}
      loanId={loanId}
    />
  );
}
```

In a real app you might call `updateBorrowerPhone` (or other mutations) from your server when data changes in your systems, or when an admin updates the loan. The important part: **any data you write to the loan via the GraphQL API is what the borrower sees in the dashboard**. For the full set of mutations and types, see the [GraphQL API Reference](https://pylon.mortgage/documentation/graphql/index.html) and the [complete integration guide](/guides/getting-started/e2e-build).

## 5. Content Security Policy

If your app uses a Content Security Policy, the Element needs the following directives to load and communicate with the Pylon API:

```
default-src https://api.{customer-id}.{env}.pylon.mortgage
frame-src https://api.{customer-id}.{env}.pylon.mortgage
script-src https://api.{customer-id}.{env}.pylon.mortgage/elements/pylon.js
connect-src https://api.{customer-id}.{env}.pylon.mortgage
```

Replace `{customer-id}` with your Pylon customer ID and `{env}` with `test` or `prod`. Without these, the iframe or scripts may be blocked and the Element will not render.

## 6. Optional: theme

Pass a `theme` prop to customize fonts, colors, and layout so the dashboard matches your brand. The theme is a nested object with `Global`, `Components`, and `Page` sections. See [Theming](/elements/borrower-dashboard/theming) and the [Theme reference](/elements/theme-reference) for the full API and examples.

```tsx theme={null}
<BorrowerDashboardComponent
  authLeaseCallback={fetchPylonAuthLease}
  loanId={loanId}
  theme={{
    Global: {
      fonts: {
        fontFamily: "Inter, sans-serif",
        fontUrls: ["https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap"],
      },
      colors: { core: { primary: "#0066CC" } },
    },
  }}
/>
```

<Warning>
  All props passed to `BorrowerDashboardComponent` must be **const**—they are read only on the Element's first render. If you change a prop later (e.g. `loanId` or `theme`), the Element will not update. To show a different loan or theme, unmount and remount the Element with the new values (e.g. by using a key or conditional render).
</Warning>

## 7. Complete example

This example fetches the current user's submitted loans via GraphQL, lets the user pick a loan (or uses the first one), and renders the Borrower Dashboard with theme and error handling. Replace `YOUR_ACCESS_TOKEN` and your GraphQL endpoint with your own values.

```tsx theme={null}
import { BorrowerDashboardComponent } from "@pylonlending/react-elements";
import { useState, useEffect } from "react";

const GRAPHQL_URL = "https://pylon.mortgage/graphql";

async function fetchSubmittedLoans(accessToken: string, userId: string) {
  const query = `
    query GetBorrowerLoans($userId: ID!) {
      borrowerUser(userId: $userId) {
        loans {
          id
          submitted
        }
      }
    }
  `;

  const response = await fetch(GRAPHQL_URL, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${accessToken}`,
    },
    body: JSON.stringify({ query, variables: { userId } }),
  });

  if (!response.ok) throw new Error("Failed to fetch loans");

  const { data } = await response.json();
  const loans = data.borrowerUser?.loans ?? [];
  return loans.filter((loan: { submitted: boolean }) => loan.submitted);
}

function BorrowerDashboardPage() {
  const accessToken = "YOUR_ACCESS_TOKEN";
  const userId = "user_123"; // from your auth/session

  const [loanId, setLoanId] = useState<string | undefined>();
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  const fetchPylonAuthLease = async (): Promise<AuthLease> => {
    const response = await fetch("/auth/pylon");
    if (!response.ok) throw new Error("Failed to get auth lease");
    return response.json();
  };

  useEffect(() => {
    fetchSubmittedLoans(accessToken, userId)
      .then((loans) => {
        // Use first submitted loan, or let user pick from list
        if (loans.length > 0) setLoanId(loans[0].id);
      })
      .catch((e) => setError(e.message))
      .finally(() => setLoading(false));
  }, []);

  const theme = {
    Global: {
      fonts: {
        fontFamily: "Inter, sans-serif",
        fontUrls: ["https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap"],
      },
      colors: { core: { primary: "#0066CC" } },
    },
  };

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  if (!loanId) return <div>No submitted loans found.</div>;

  return (
    <BorrowerDashboardComponent
      authLeaseCallback={fetchPylonAuthLease}
      loanId={loanId}
      theme={theme}
    />
  );
}
```

Replace `YOUR_ACCESS_TOKEN` and `userId` with your app's auth (e.g. from session or context). For polling loan stage, tasks, or documents, see [Polling for updates](/elements/borrower-dashboard/polling).

## Next steps

* [Polling for updates](/elements/borrower-dashboard/polling) — Monitor loan stage, tasks, and other status changes in your app
* [Theming](/elements/borrower-dashboard/theming) — Deep dive on theme structure and options for the dashboard
* [Loading loans](/elements/setup/loading-loans) — When to use Loan Application vs Borrower Dashboard and how to retrieve loans for a user
* [Complete integration guide](/guides/getting-started/e2e-build) — Full flow from deal and loan creation through underwriting and closing
