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.com

This 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-tokens without a theme. 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-tokens and a theme. 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>