This guide walks you through integrating the Loan Application Element from scratch. By the end you’ll have a working Element that can start new applications, load existing ones, and—importantly—pre-fill borrower and property data via the GraphQL API so users see information you’ve already collected.
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.
- Environment details — Your Pylon customer ID and environment (
test or prod). You’ll need these for Content Security Policy and for any GraphQL calls you make (e.g. pre-filling or creating loans).
- Package installed:
npm install @pylonlending/react-elements --save
2. Add the Element
Import the Element and pass an auth lease callback. This is a function that your app uses to fetch a fresh lease from your backend. The Element will call it when it initializes and whenever it needs to re-authenticate. The callback must return a Promise<AuthLease> (the exact JSON returned by your auth route). Do not modify or filter the lease object—pass it through as-is.
import { LoanApplicationElement } 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 LoanApplicationPage() {
return (
<LoanApplicationElement
authLeaseCallback={fetchPylonAuthLease}
/>
);
}
Use your actual auth route path in place of "/auth/pylon" if it’s different. At this point the Element is working: it will create and display a new loan application. The user will step through the application flow inside the embedded UI.
Behavior:
- With no
loanId prop (or loanId={undefined}), the Element always starts a new application.
- The Element renders in an iframe and communicates with the Pylon API using the lease. Ensure your auth endpoint is only accessible to logged-in users so leases aren’t exposed to unauthenticated clients.
3. Load an existing loan
To let users resume an in-progress application, pass the loan ID to the Element. You might get that ID from:
- Your own API or database (e.g. after creating a loan server-side)
- A “my applications” list loaded via the GraphQL API (e.g.
borrowerUser { loans { id submitted } })
- A URL parameter or app state after redirecting the user to the application page
<LoanApplicationElement
authLeaseCallback={fetchPylonAuthLease}
loanId={loanId}
/>
- Omit
loanId or set it to undefined when you want a new application.
- The
user_id (or equivalent) used in your auth handshake must match the user associated with the loan, or the Element will not load the loan. See Loading loans for how to choose between the Loan Application Element and the Borrower Dashboard Element based on loan status (submitted).
4. Pre-filling data with the GraphQL API
Pre-filling is a core integration pattern: you create a loan and populate borrower and property data via the GraphQL API, then open the Element with that loan’s ID. The Element displays all of that data already filled in so the borrower can review, correct, and complete the rest—instead of typing everything from scratch.
Why pre-fill:
- Streamlined onboarding — You already have name, email, phone, or address from your app; push it into the loan so the borrower doesn’t re-enter it.
- Hybrid workflows — Collect some information in your own UI (e.g. property address or loan purpose), then hand off to the Element for the full application.
- Better UX — Borrowers see their information pre-populated and only add or edit what’s missing.
How pre-fill works
- Create a loan (and optionally a deal) — Use the GraphQL API to create a deal and loan. You need a
dealId for the loan; create a deal first if you don’t have one. See the complete integration guide and the GraphQL API Reference for CreateDealInput and CreateLoanInput.
- Add borrower and property data — Use the loan and borrower mutations to add personal information, property address, and any other data you already have. You can add borrowers, subject property, income, and assets via the API. The GraphQL API Reference documents the relevant mutations and input types (e.g.
CreateBorrowerInput, subject property and address inputs).
- Open the Element with the loan ID — Pass the returned
loan.id as the loanId prop. When the Element loads, it fetches the loan and displays all API-populated data as pre-filled; the borrower can then complete or change fields as needed.
You can continue to add or update data via the GraphQL API after the Element is open. The Element reflects the latest data from the API, so you can push updates (e.g. from a background job or another part of your app) and the user will see them.
Example: Create a loan with data, then open the Element
Below is a full example: create a deal, create a loan with borrower and property information, then render the Loan Application Element with that loanId. Replace accessToken, dealId (or create a deal first), and your GraphQL endpoint with your own values.
import { LoanApplicationElement } from "@pylonlending/react-elements";
import { useState, useEffect } from "react";
const GRAPHQL_URL = "https://pylon.mortgage/graphql"; // or your env-specific URL
async function createLoanWithPrefill(accessToken: string, dealId: string) {
const mutation = `
mutation CreateLoan($input: CreateLoanInput!) {
createLoan(input: $input) {
id
}
}
`;
const variables = {
input: {
dealId,
borrowers: [
{
personalInformation: {
firstName: "John",
lastName: "Doe",
email: "john.doe@example.com",
phoneNumber: "555-123-4567",
},
},
],
property: {
address: {
street: "123 Main St",
city: "San Francisco",
state: "CA",
zipCode: "94105",
},
},
},
};
const response = await fetch(GRAPHQL_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({ query: mutation, variables }),
});
if (!response.ok) {
throw new Error("Failed to create loan");
}
const { data } = await response.json();
return data.createLoan.id;
}
function LoanApplicationPage() {
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();
};
// Example: create a pre-filled loan when the page loads (e.g. you have dealId and accessToken from your app)
useEffect(() => {
const accessToken = "YOUR_ACCESS_TOKEN";
const dealId = "YOUR_DEAL_ID";
createLoanWithPrefill(accessToken, dealId)
.then(setLoanId)
.catch((e) => setError(e.message))
.finally(() => setLoading(false));
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
if (!loanId) return null;
return (
<LoanApplicationElement
authLeaseCallback={fetchPylonAuthLease}
loanId={loanId}
/>
);
}
In a real app you might create the loan when the user clicks “Start application” (using data you’ve already collected), or when they complete a previous step in your flow. The important part is: create the loan and add whatever data you have via the API, then pass the resulting loan.id to the Element. For the full set of fields you can set on the loan and borrowers, see the GraphQL API Reference (e.g. CreateLoanInput, CreateBorrowerInput, and related types) 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 and copy
Theme — Pass a theme prop to customize fonts, colors, and layout so the Element 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.
Credit page copy — The credit consent and disclosure text can be overridden for compliance or brand voice. Pass a customization prop with a credit object and any of the following fields; omit a field to keep the default.
| Field | Description |
|---|
consentPreamble | Introductory text before the credit consent section. |
consentForPreQualification | Copy for pre-qualification credit consent. |
softPullConsentForApplication | Copy for soft-pull consent when submitting the application. |
generalConsent | General credit consent message. |
Type:
type Customization = {
credit?: {
consentPreamble?: string;
consentForPreQualification?: string;
softPullConsentForApplication?: string;
generalConsent?: string;
};
};
Example:
<LoanApplicationElement
authLeaseCallback={fetchPylonAuthLease}
loanId={loanId}
customization={{
credit: {
consentPreamble: "My custom consent preamble",
consentForPreQualification: "My custom consent for prequal message",
softPullConsentForApplication: "My custom pull consent message",
generalConsent: "My custom general consent message",
},
}}
/>
All props passed to LoanApplicationElement 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 brings together authentication, optional pre-filled loan creation, theming, credit customization, and polling for submission so you can switch to the Borrower Dashboard when the loan is submitted. Use your own accessToken, dealId, and auth mechanism in production.
import { LoanApplicationElement, BorrowerDashboardElement } from "@pylonlending/react-elements";
import { useState, useEffect } from "react";
const GRAPHQL_URL = "https://pylon.mortgage/graphql";
async function createLoanWithPrefill(accessToken: string, dealId: string) {
const response = await fetch(GRAPHQL_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
query: `
mutation CreateLoan($input: CreateLoanInput!) {
createLoan(input: $input) { id }
}
`,
variables: {
input: {
dealId,
borrowers: [{ personalInformation: { firstName: "Jane", lastName: "Doe", email: "jane@example.com" } }],
property: { address: { street: "456 Oak Ave", city: "Oakland", state: "CA", zipCode: "94601" } },
},
},
}),
});
const { data } = await response.json();
return data.createLoan.id;
}
function LoanApplicationPage() {
const accessToken = "YOUR_ACCESS_TOKEN";
const [loanId, setLoanId] = useState<string | undefined>();
const [showDashboard, setShowDashboard] = useState(false);
const [loading, setLoading] = useState(true);
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();
};
// Create pre-filled loan on mount (use your dealId)
useEffect(() => {
const dealId = "YOUR_DEAL_ID";
createLoanWithPrefill(accessToken, dealId)
.then(setLoanId)
.finally(() => setLoading(false));
}, []);
useEffect(() => {
if (!loanId) return;
const interval = setInterval(async () => {
const response = await fetch(GRAPHQL_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify({
query: `query GetLoanStatus($id: ID!) { loan(id: $id) { id submitted } }`,
variables: { id: loanId },
}),
});
const { data } = await response.json();
if (data?.loan?.submitted) {
setShowDashboard(true);
clearInterval(interval);
}
}, 30 * 60 * 1000);
return () => clearInterval(interval);
}, [loanId]);
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" } },
},
};
const customization = {
credit: {
consentPreamble: "My custom consent preamble",
consentForPreQualification: "My custom consent for prequal message",
},
};
if (loading) return <div>Loading...</div>;
if (showDashboard && loanId) {
return (
<BorrowerDashboardElement
loanId={loanId}
authLeaseCallback={fetchPylonAuthLease}
/>
);
}
if (!loanId) return null;
return (
<LoanApplicationElement
authLeaseCallback={fetchPylonAuthLease}
loanId={loanId}
theme={theme}
customization={customization}
/>
);
}
Replace YOUR_ACCESS_TOKEN and YOUR_DEAL_ID with your own values. For more on polling, see Polling for updates.
Next steps
- Polling for updates — Monitor loan status (e.g. submitted) and react in your app
- Theming — Deep dive on theme structure and options
- 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