Skip to main content

Overview

The Form module provides a complete pre-built eKYC form UI that guides users through the identity verification process. It handles the full flow from welcome screen through document selection, data entry, review, submission, and results - all with a single component. The form module supports three configuration types:
  • OCR - Document capture with automatic data extraction
  • Manual - Manual data entry by the user
  • Doc Upload - Document file upload for verification

Accessing the Module

The Form module is accessed through the SDK’s component factory using the React provider:
const sdk = await OneSdk({
  mode: 'production',
  session: { token: 'your-token' }
});

const formComponent = sdk.component('form', {
  name: 'WELCOME',
  mode: 'individual',
  type: 'manual'
});

Configuration

Module Options

Options are passed during module initialization:
OptionTypeRequiredDescription
nameReactFormIdsKeysYesThe screen to start on. See Screens.
mode'individual'NoVerification mode. Defaults to 'individual' for personal KYC.
type'ocr' | 'manual' | 'doc_upload'NoConfiguration type. Determines the verification flow pattern. Primarily affects the Document Selection, Personal Details, Review, and Retry screens. Static screens (Welcome, Consent, Start) and progress screens (Loading, Result) are not affected. Doc upload screens are only shown when type is 'doc_upload'.
The module options also accept page-level configuration overrides. See Configuration for all available customization options.

Recipe Configuration

The Form module requires provider configuration in your recipe. Form configuration (screens, type, styling) is set through module options when calling sdk.component('form', options), not in the recipe.
const sdk = await OneSdk({
  session: { token: 'your-token' },
  recipe: {
    form: {
      provider: {
        name: 'react',
        googleApiKey: 'your-google-api-key',
        useGoogleAutoCompleteSuggestion: true
      }
    }
  }
});

Recipe Options

PropertyTypeRequiredDescription
provider.name'react'YesMust be 'react' for the React form provider.
provider.googleApiKeystringNoGoogle API key that enables address autocomplete on address field types. Google Maps is optional — when no key is provided, users enter their address manually. The key must have Geocoding API and Places API (New) authorized. See Google Maps setup for GCP configuration steps.
provider.useGoogleAutoCompleteSuggestionbooleanNoSet to true for all integrations. Switches from the deprecated AutocompleteService to the newer AutocompleteSuggestion API. The deprecated path causes address search failures (REQUEST_DENIED) on API keys that restrict by service. Requires @frankieone/one-sdk v1.8.2 or later and Places API (New) enabled on the key.
docUploadobjectNoDocument upload configuration. See Document Upload Configuration.

Google Maps Address Autocomplete

Action required for all integrations using address search. Google’s Geocoding API now returns REQUEST_DENIED for keys that do not have it explicitly authorized. Address search will silently fail until the key is updated. Follow the steps below.
Address autocomplete requires two APIs enabled on your Google Cloud API key:
APIWhy it’s needed
Geocoding APIResolves a selected suggestion into structured address components. Required by both the old and new autocomplete paths — address search fails with REQUEST_DENIED without it.
Places API (New)Powers the AutocompleteSuggestion lookup when useGoogleAutoCompleteSuggestion is true.

GCP setup steps

  1. Open Google Cloud Console → APIs & Services → Library.
  2. Search for and enable Geocoding API.
  3. Search for and enable Places API (New).
  4. Go to APIs & Services → Credentials and open your API key.
  5. Under API restrictions → Restrict key, add both Geocoding API and Places API (New) to the allowed list.
  6. Save.

Script tag

Load the Maps JavaScript API before initializing OneSdk:
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_KEY&loading=async&libraries=places"></script>

OneSDK recipe configuration

const sdk = await OneSdk({
  session: { token: 'your-token' },
  recipe: {
    form: {
      provider: {
        name: 'react',
        googleApiKey: 'YOUR_GOOGLE_API_KEY',
        useGoogleAutoCompleteSuggestion: true
      }
    }
  }
});
useGoogleAutoCompleteSuggestion requires @frankieone/one-sdk v1.8.2 or later. Run npm install @frankieone/one-sdk@latest to upgrade.

Methods

mount()

Mounts the Form component to a DOM element. Signature:
mount(mountElement: string | HTMLElement): Promise<WrapperContext>
Parameters:
ParameterTypeRequiredDescription
mountElementstring | HTMLElementYesCSS selector string (e.g., '#form-container') or HTMLElement object where the form will be mounted
Returns: Promise<WrapperContext> - Resolves when the form is mounted. The WrapperContext contains a cleanup() method. Example:
// Mount to element by ID
await formComponent.mount('#form-container');

// Mount to element reference
const container = document.getElementById('form-container');
await formComponent.mount(container);

unmount()

Unmounts the Form component from the DOM. Signature:
unmount(): void
unmount() clears all content from the element the component was mounted to — not just the component itself. If another component (e.g., an IDV flow) is sharing the same container, unmounting will destroy its content too. When you need to show a loading screen alongside another component, mount the loading screen to a separate element so that unmounting it does not affect the other component. See the IDV with Review flow for an example.
Example:
formComponent.unmount();

provider

Read-only property indicating the current vendor. Type: 'react'
console.log(formComponent.provider); // 'react'

Events

The Form module uses a structured event system with two levels:

Global Events

These events apply to the form module as a whole:
EventParametersDescription
ready{ domElement, config }Form component is ready. Contains the mount element and resolved configuration.
loaded{ domElement, provider }Form component is loaded and rendered.
results{ documents, applicant, entityId }Verification results received. Contains the submitted documents, applicant data, and entity ID.
navigation{ page }User navigated to a different screen. Contains the screen name.
destroy(none)Form component is being destroyed/unmounted.
Example:
formComponent.on('ready', ({ domElement, config }) => {
  console.log('Form ready with config:', config);
});

formComponent.on('results', ({ documents, applicant, entityId }) => {
  console.log('Verification complete for:', entityId);
  console.log('Documents:', documents);
  console.log('Applicant:', applicant);
});

formComponent.on('navigation', ({ page }) => {
  console.log('User navigated to:', page);
});

Screen Events

Each screen emits events following the pattern form:{screen}:{stage}. See Screens for the complete screen event reference. Event Pattern:
form:welcome:ready
form:welcome:loaded
form:welcome:failed
form:consent:ready
form:review:success
form:result:pending
...
Event Stages:
StageDescription
readyUser pressed the screen’s CTA (call-to-action) button, completing the screen
loadedScreen has been rendered
failedScreen failed to load (except result — see Result screen events)
successCTA pressed on a SUCCESS-state result screen, or verification succeeded (review with customResult only)
partialCTA pressed on a PARTIAL-state result screen, or partial verification result (review with customResult only)
pendingCTA pressed on a PENDING-state result screen, or verification pending (review with customResult only)
backUser navigated back (document only)
navigateUser navigated to another screen (document, doc_upload only)
form:review:* events require customResult: trueThe form:review:success, form:review:failed, form:review:partial, and form:review:pending events are only emitted when customResult: true is set in manual mode. When customResult is false (the default), the form automatically navigates to and displays the configured result screen without emitting these events.In OCR mode, the only terminal event is ready on the final screen.
Event Payload: All screen events emit an EventPayload:
{
  componentName?: string;     // Module name
  provider?: string;          // Provider name ('react')
  inputInfo: {
    name?: string;            // Screen name (e.g., 'welcome', 'review')
    type?: string;            // Config type ('ocr', 'manual', 'doc_upload')
    documentType?: string;    // Document type if applicable
    index?: number;           // Document index if applicable
  }
}

Complete Example

import OneSdk from '@frankieone/one-sdk';

async function startVerificationForm() {
  // Initialize SDK
  const sdk = await OneSdk({
    mode: 'production',
    session: { token: await getSessionToken() },
    recipe: {
      form: {
        provider: {
          name: 'react',
          googleApiKey: 'your-google-api-key',
          useGoogleAutoCompleteSuggestion: true
        }
      }
    }
  });

  // Create form component
  const formComponent = sdk.component('form', {
    name: 'WELCOME',
    mode: 'individual',
    type: 'manual'
  });

  // Global events
  formComponent.on('ready', ({ domElement, config }) => {
    console.log('Form ready');
  });

  formComponent.on('loaded', ({ domElement, provider }) => {
    console.log('Form loaded with provider:', provider);
  });

  // Track navigation
  formComponent.on('navigation', ({ page }) => {
    console.log('Current screen:', page);
    updateProgressBar(page);
  });

  // Screen-level events
  formComponent.on('form:welcome:loaded', (payload) => {
    console.log('Welcome screen displayed');
  });

  formComponent.on('form:consent:loaded', (payload) => {
    console.log('Consent screen displayed');
  });

  formComponent.on('form:document:loaded', (payload) => {
    console.log('Document selection displayed');
  });

  formComponent.on('form:review:ready', (payload) => {
    console.log('User pressed CTA on review screen');
  });

  // Review-level result events (only with customResult: true in manual mode)
  formComponent.on('form:review:success', (payload) => {
    console.log('Verification passed — handle custom result');
  });

  formComponent.on('form:review:partial', (payload) => {
    console.log('Partial match — handle custom result');
  });

  // Result screen CTA events (fired when user presses CTA on result screens)
  formComponent.on('form:result:success', (payload) => {
    console.log('User pressed CTA on SUCCESS result screen');
  });

  formComponent.on('form:result:pending', (payload) => {
    console.log('User pressed CTA on PENDING result screen');
  });

  // Final results
  formComponent.on('results', ({ documents, applicant, entityId }) => {
    console.log('Verification complete');
    console.log('Entity:', entityId);
    handleVerificationResult({ documents, applicant, entityId });
  });

  // Mount the form
  await formComponent.mount('#form-container');
}

Best Practices

  1. Set up event listeners before mounting - Register all event handlers before calling mount() to avoid missing early events like ready and loaded.
  2. Handle all result screen CTA events - Listen for form:result:success, form:result:partial, form:result:pending, and form:result:failed to detect when the user dismisses a result screen. If using customResult: true on the review component, also handle the corresponding form:review:* events.
  3. Use the navigation event for progress tracking - The global navigation event fires on every screen change, making it ideal for progress bars, analytics, and conditional UI outside the form.
  4. Configure type to match your flow - Choose 'ocr', 'manual', or 'doc_upload' based on your integration. The type determines which screens are shown and how data is collected. See Configuration Types.
  5. Customize screens via page config, not DOM manipulation - Use the page configuration and CSS class keys to customize appearance and content rather than manipulating the DOM directly.

Common Issues

Component not displaying

Ensure the mount element exists in the DOM before calling mount() and has sufficient size:
#form-container {
  width: 100%;
  min-height: 600px;
}

Google address autocomplete not working

Symptoms — one or both of these appear in the browser console:
Geocoding Service: This API key is not authorized to use this service or API. Status: REQUEST_DENIED
google.maps.places.AutocompleteService is not available to new customers. Please use google.maps.places.AutocompleteSuggestion instead.
Root causes:
  • REQUEST_DENIED — the API key does not have the Geocoding API authorized. Both the old and new autocomplete paths call the Geocoding API after retrieving suggestions; without it, address search returns nothing.
  • AutocompleteService warning — useGoogleAutoCompleteSuggestion is not set to true. The deprecated AutocompleteService is unavailable on keys created after March 1, 2025 and will eventually be removed for all customers.
Fix:
  1. In Google Cloud Console, enable Geocoding API and Places API (New) on the key and add both to the key’s API restrictions allow-list. See Google Maps setup.
  2. Set useGoogleAutoCompleteSuggestion: true in the recipe’s form.provider config.
  3. Ensure @frankieone/one-sdk is v1.8.2 or later — upgrade with npm install @frankieone/one-sdk@latest if needed.

Form fields not matching expected document type

The fields shown on the Review and Personal Details screens depend on the type option and the document type selected. Ensure your form field configuration and country-specific settings match the document types available in your flow.

Sub-Pages

Screens

All 13 form screens, their events, and behavior

Configuration

Page customization, CSS styling, document upload, and form fields