Apply
See the component in action in the live demo.
In your client-side code, load the apply script ideally in the <head> of your page.
<script type="module" src="https://js.clientloop.com/apply.iife.js"></script>Then, use the apply session component to render the application
<apply-session configuration-id="CONFIGURATION_ID"></apply-session>Note that the configuration ID is the unique identifier of the application configuration you created in the ClientLoop dashboard.
Selecting an environment
By default <apply-session> talks to ClientLoop production. To test your
integration against the ClientLoop staging environment, set the optional
env attribute to stage:
<apply-session configuration-id="CONFIGURATION_ID" env="stage"></apply-session>Omit env (or set it to prod) for production.
Pre-populating the applicant's email
If the URL of the page hosting <apply-session> includes an applicant
query parameter, the email field on the start view is pre-filled with
that value. The applicant can still edit it before requesting a
verification code.
https://your-site.example.com/apply?applicant=jordan@example.comThis is useful when linking to your apply page from an email or CRM record where you already know who the applicant is — skip a step for them and start the verification flow with the email already in place.
Skipping verification with a pre-created session
If you have already created an apply session server-side with the
applySessionCreate mutation, you have verified the applicant's identity
yourself and want to bypass the built-in email verification step. Pass the
returned session id to the session-id attribute and the component loads
that session directly, landing the applicant on the application checklist
instead of the start/verification screens.
<apply-session session-id="SESSION_ID"></apply-session>session-id takes precedence over configuration-id. Because the session
is supplied by the host, it is not persisted across reloads the way a
verified session is — re-supply session-id each time the page loads.
Theming
<apply-session> renders with the light-loop theme by default. To switch to
a different built-in theme, set the theme attribute. The available themes are
light-loop, dark-loop, checkout, and dark.
<apply-session
configuration-id="CONFIGURATION_ID"
theme="dark-loop"
></apply-session>Providing your own theme
When you want the form to match your brand — including colors you only know at
runtime — provide your own theme through the theme-tokens attribute (or the
themeTokens property). It takes a map of design tokens: colors such as
--color-primary and --color-base-100, corner radii such as --radius-box,
and color-scheme (light or dark) so native controls and scrollbars match.
<apply-session> is themed with daisyUI, so the built-in
themes and every theme-tokens key are daisyUI theme variables. The easiest way
to build a full custom theme is daisyUI's
theme generator: design it visually
there, then copy the generated CSS variables into theme-tokens.
How your tokens combine with theme:
- Replace the theme — set
theme-tokenswithout atheme. Your tokens become the entire theme, so provide a complete set; any token you leave out is simply not styled rather than falling back to a built-in theme. - Override a built-in theme — set
theme-tokensand atheme. Your tokens override that base, and anything you don't specify is inherited from the chosen built-in theme. This is the easy way to tweak a few colors.
Pass the tokens as JSON in the attribute:
<!-- Replace the theme entirely with your own tokens -->
<apply-session
configuration-id="CONFIGURATION_ID"
theme-tokens='{"--color-primary":"#155EEF","--color-accent":"#23B8FF","--color-base-100":"#ffffff","color-scheme":"light"}'
></apply-session>
<!-- Start from dark-loop and override just the primary color -->
<apply-session
configuration-id="CONFIGURATION_ID"
theme="dark-loop"
theme-tokens='{"--color-primary":"#155EEF"}'
></apply-session>Or set the themeTokens property in JavaScript, which is handy when the colors
come from an API response:
<script>
const session = document.querySelector('apply-session');
session.themeTokens = {
'--color-primary': '#155EEF',
'--color-accent': '#23B8FF',
'--color-base-100': '#ffffff',
'color-scheme': 'light',
};
</script>Listening to events
The <apply-session> component emits DOM CustomEvents as the applicant
moves through the application. All events bubble up to <apply-session>,
so you can attach a single listener on the element and react to anything
that happens inside it. Use these to drive analytics, show your own
confirmation UI, or redirect the applicant when they finish.
Usage
<script>
const session = document.querySelector('apply-session');
session.addEventListener('apply-code-sent', (event) => {
// event.detail = { email: '...' }
});
session.addEventListener('apply-completed', (event) => {
// event.detail = { sessionId: '...', applicationId: '...', applicationStatus: '...' }
});
</script>Lifecycle events
These mark the major milestones of the apply flow.
| Event | When it fires | event.detail |
|---|---|---|
apply-code-sent |
The applicant submitted their email and the server accepted it. A verification code is on its way to them. | { email } |
apply-code-verified |
The applicant entered the verification code and it was accepted. The session is now active. | The full session ↓ |
apply-completed |
The application has been submitted and left the draft state. Fires at most once per session. |
{ sessionId, applicationId, applicationStatus } |
apply-exit |
The session has been exited — the applicant clicked Exit, or the session window expired. State is cleared. | { sessionId, applicationId } (the prior ids, if any) |
Section events
Each section of the application reports when it loaded, when the applicant successfully submitted it, and when they backed out without submitting.
The apply-business-complete and apply-services-complete events carry
the full updated session in event.detail — not just the fields from
that one form — so you can read the latest completion flags and section
state after each save. apply-owner-saved carries the single saved owner,
plus the applicationId it belongs to.
Every payload-bearing event carries the applicationId of the application
the session is filling out, so you can correlate events with the
application on your side.
| Event | When it fires | event.detail |
|---|---|---|
apply-business-loaded |
The "Tell us about your business" form has loaded. | — |
apply-business-complete |
Business information was saved successfully. | The updated session ↓ |
apply-business-closed |
Applicant pressed Back without submitting. | — |
apply-services-loaded |
The "Products and services" form has loaded. | — |
apply-services-complete |
Products and services were saved successfully. | The updated session ↓ |
apply-services-closed |
Applicant pressed Back without submitting. | — |
apply-owner-saved |
An owner was added or updated. | The saved owner ↓ |
apply-owner-cancel |
Applicant left the owner form without saving. | — |
apply-agreement-loaded |
The applicant opened an agreement preview. | — |
apply-agreement-complete |
The applicant accepted all required agreements. | — (no payload; listen for the event) |
apply-agreement-closed |
The applicant closed an agreement preview. | — |
apply-business-complete / apply-services-complete payload
Both events deliver the same shape — the entire current session. The
example below shows a session right after the business form was saved
(businessInfoCompleted: true). Nullable fields are null until the
applicant fills them in.
session.addEventListener('apply-business-complete', (event) => {
// event.detail:
// {
// id: 'as_2f9c1e7b…',
// applicationId: 'app_7d41a9c2…',
// expiresAt: '2026-06-09T18:30:00.000Z',
// expired: false,
// applicationStatus: 'draft',
// businessInformation: {
// legalName: 'Acme Coffee LLC',
// dba: 'Acme Coffee',
// startDate: '2021-03-15',
// website: 'https://acmecoffee.example.com',
// country: 'usa',
// entityType: 'llc',
// ein: '12-3456789',
// phone: '+15555550123',
// address: '123 Market St',
// city: 'Springfield',
// state: 'IL',
// postalCode: '62704',
// avgOrderAmount: 'From100To500',
// },
// productsAndServices: {
// industry: null,
// industryOther: null,
// services: null,
// supportEmail: null,
// supportPhone: null,
// },
// owners: [],
// idvCompleted: false,
// businessInfoCompleted: true,
// productsAndServicesCompleted: false,
// bankLinked: false,
// totalOwnership: 0,
// ownersCompleted: false,
// agreementsCompleted: false,
// transferInstrumentCompleted: false,
// sections: [
// { name: 'BusinessInfo', completed: true },
// { name: 'ProductsAndServices', completed: false },
// { name: 'Owners', completed: false },
// { name: 'Agreements', completed: false },
// ],
// }
});applicationStatus is one of draft, submitted, approved, or
declined. country is usa or can. entityType is one of sp,
gp, lp, llp, llc, scorp, ccorp, bcorp, coop, nonprofit,
gov. avgOrderAmount is Under100, From100To500, From501To1000,
or Over1000. Each sections[].name is a stable section identifier
(BusinessInfo, ProductsAndServices, Owners, Agreements, Idv,
BankLink, TransferInstrument).
apply-owner-saved payload
session.addEventListener('apply-owner-saved', (event) => {
// event.detail:
// {
// id: 'ow_8b3d4a01…',
// applicationId: 'app_7d41a9c2…',
// givenName: 'Jordan',
// familyName: 'Rivera',
// title: 'Owner',
// email: 'jordan@acmecoffee.example.com',
// phone: '+15555550123',
// dob: '1985-07-22',
// maskedSsn: '***-**-6789', // full SSN is never returned
// address: '123 Market St',
// city: 'Springfield',
// state: 'IL',
// postalCode: '62704',
// percentageOwnership: 100,
// ownerType: 'BENEFICIAL_OWNER', // or 'CONTROL_PERSON'
// }
});Every owner field except id may be null while the owner record is
still incomplete. applicationId is always present.
Bank / card linking and identity verification
These fire from the hosted linking UIs. Use them if you need to know that the applicant has been handed off to (or returned from) a third-party flow.
| Event | When it fires |
|---|---|
apply-clp-bank-link-loaded |
The ClientLoop bank-link UI finished loading. |
apply-clp-bank-link-complete |
The applicant finished linking their bank via ClientLoop. |
apply-clp-bank-link-closed |
The applicant closed the ClientLoop bank-link UI. |
apply-plaid-bank-link-loaded |
The Plaid bank-link UI finished loading. |
apply-plaid-bank-link-complete |
The applicant finished linking their bank via Plaid. |
apply-plaid-bank-link-closed |
The applicant closed the Plaid bank-link UI. |
apply-identity-loaded |
The Plaid identity-verification UI finished loading. |
apply-identity-complete |
Identity verification finished successfully. |
apply-identity-closed |
The applicant closed the identity-verification UI. |
Example: show a custom success view
<apply-session configuration-id="CONFIGURATION_ID"></apply-session>
<div id="apply-thanks" hidden>
Thanks — we'll be in touch about your application.
</div>
<script>
const session = document.querySelector('apply-session');
const thanks = document.getElementById('apply-thanks');
session.addEventListener('apply-completed', () => {
session.hidden = true;
thanks.hidden = false;
});
</script>