Skip to main content
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. 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 first.
  • A submitted loan — The Borrower Dashboard Element displays a single loan that has already been submitted (submitted: true). Use the Loan Application Element for in-progress applications; once the loan is submitted, switch to the Borrower Dashboard with that loan’s ID. See 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:
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.
import { BorrowerDashboardElement } 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 (
    <BorrowerDashboardElement
      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 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:
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 <BorrowerDashboardElement 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.

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 and the complete integration guide 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.
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.

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.
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 (
    <BorrowerDashboardElement
      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 and the complete integration guide.

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, Component, and Page sections. See Theming and the Theme reference for the full API and examples.
<BorrowerDashboardElement
  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" } },
    },
  }}
/>
All props passed to BorrowerDashboardElement 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).

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.
import { BorrowerDashboardElement } 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 (
    <BorrowerDashboardElement
      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.

Next steps

  • Polling for updates — Monitor loan stage, tasks, and other status changes in your app
  • Theming — Deep dive on theme structure and options for the dashboard
  • Loading loans — When to use Loan Application vs Borrower Dashboard and how to retrieve loans for a user
  • Complete integration guide — Full flow from deal and loan creation through underwriting and closing