This guide walks you through integrating the Accura IDScan Plugin into a Next.js project using the App Router.
Step 1: Initialize Project
If you do not have an existing Next.js project, create one using the official CLI:
npxcreate-next-app@latestmy-id-appcdmy-id-app
Step 2: Install Plugin
Install the Accura IDScan Plugin package from the npm registry:
npminstallaccuraidscanplugin
Step 3: TypeScript Support
Since the accuraidscanplugin package does not ship with TypeScript declarations, create a type definition file at the project root to resolve module errors:
// types.d.ts (place in the project root)declaremodule'accuraidscanplugin';
Ensure this file is referenced in your tsconfig.json's include array:
Step 4: Implementation
Create components/IDScanner.tsx. The snippet below demonstrates only the plugin import and instantiation — the minimal code required to activate the ID scan engine in a Next.js client component:
Step 5: Response Handling
When the plugin completes an ID card scan, it invokes the handleCapture callback with a payload object. This object may contain one or both of the following properties:
Property
Type
Description
front
string
Base64 Data URL of the front side of the ID card
back
string
Base64 Data URL of the back side of the ID card (if applicable)
What is Base64? Base64 is a binary-to-text encoding scheme that converts raw binary image data into a sequence of printable ASCII characters. Each scanned card image is delivered as a Data URL string (e.g., data:image/jpeg;base64,/9j/...), combining a MIME type prefix with the encoded image payload. Before transmitting to a server, this string must be decoded back into binary form (a Blob) to construct a valid multipart HTTP request.
The following demonstrates the base64-to-Blob conversion and API submission:
Step 6: Demo Implementation
The following is the complete, production-ready component. Copy and paste it directly into components/IDScanner.tsx. The original logic is preserved exactly as-is.
"use client"; // Required: marks this as a Client Component for browser-only execution
import { useEffect, useRef } from "react";
export default function IDScanner() {
const pluginRef = useRef(null); // Persists the plugin instance across renders
const initialized = useRef(false); // Prevents double-init from React Strict Mode
useEffect(() => {
// Exit early if the plugin has already been initialized
if (initialized.current) return;
initialized.current = true;
// Dynamically import the plugin at runtime to avoid SSR-related errors.
// Next.js renders components on the server by default; browser-dependent APIs
// (camera, DOM manipulation) are unavailable in that environment.
// Dynamic import defers execution to the client side exclusively.
import("accuraidscanplugin").then((Module) => {
const IDCardPlugin = Module.default;
// Instantiate the plugin with:
// 1. The capture callback: invoked when scan is complete
// 2. Configuration: specifies the target country, card template, and UI styling
pluginRef.current = new IDCardPlugin(
handleCapture, // Called automatically upon successful ID card scan
{
countryCode: "UGA", // country code of the card
cardCode: "UGNIDF", // Card code of the card
topTextSize: "", // Top instruction overlay text size (default if empty)
topTextColor: "", // Top instruction overlay text color (default if empty)
topTextWeight: "", // Top instruction overlay font weight (default if empty)
bottomTextSize: "", // Bottom instruction overlay text size (default if empty)
bottomTextColor: "", // Bottom instruction overlay text color (default if empty)
bottomTextWeight: "", // Bottom instruction overlay font weight (default if empty)
}
);
// Launch the camera interface and begin the ID card detection session.
pluginRef.current.start().then(() => {
console.log("ID Scanner Ready");
});
}).catch(err => console.error("Plugin failed to load:", err));
// Cleanup: invoked when the component unmounts (e.g., page navigation).
// Ensures the camera stream is released and all plugin resources are freed.
return () => {
if (pluginRef.current) {
pluginRef.current.destroy();
}
};
}, []);
return <></>;
}
// Utility: converts a base64 Data URL string into a binary Blob.
// Multipart form uploads require raw binary data rather than text-encoded base64.
const base64ToBlob = (base64DataURL: string) => {
// Separate the MIME type header from the encoded payload at the comma boundary.
// meta = "data:image/jpeg;base64"
// content = "/9j/4AAQSkZJRgAB..."
const [meta, content] = base64DataURL.split(",");
// Parse the MIME type from the header (e.g., "image/jpeg").
const mimeMatch = meta.match(/:(.*?);/);
const mime = mimeMatch ? mimeMatch[1] : "image/jpeg";
// Decode the base64 payload into raw ASCII binary characters using atob().
const binary = atob(content);
// Allocate a typed byte array of the same length as the decoded binary content.
const array = new Uint8Array(binary.length);
// Reconstruct the original binary bytes by mapping each character to its code point.
for (let i = 0; i < binary.length; i++) {
array[i] = binary.charCodeAt(i);
}
// Wrap the binary array in a Blob with the correct content type for server handling.
return new Blob([array], { type: mime });
};
// Sends a card image Blob to the server-side verification endpoint via multipart POST.
const sendToAPI = async (blob: Blob, isface: string, card_code: string, filename: string) => {
const formData = new FormData();
formData.append("scan_image", blob, filename); // The binary image file
formData.append("isface", isface); // "front" or "back" — card side identifier
formData.append("country_code", "UGA"); // ISO country code of the scanned ID
formData.append("card_code", card_code); // Card template identifier
formData.append("passport", "false"); // Set "true" if the document is a passport
formData.append("webcam", "false"); // Set "true" if the image was captured via webcam
try {
const response = await fetch("http://ip:port/doc_liveness.php", {
method: "POST",
body: formData,
});
const data = await response.json();
console.log(`API Response (${isface}):`, data);
// Inspect the document authenticity/liveness score from the response.
if (data && data.score !== undefined) {
console.log(`Score (${isface}): ${data.score}`);
}
} catch (error) {
console.error(`Error sending ${isface} to API:`, error);
}
};
// Primary capture callback — invoked automatically when the plugin completes a scan.
// Receives: base64 — payload object with front and/or back card image Data URLs.
const handleCapture = async (base64: any) => {
console.log("Capture result:", base64);
// Convert and dispatch the front card image if present in the payload.
if (base64.front) {
const frontBlob = base64ToBlob(base64.front);
await sendToAPI(frontBlob, "front", "UGNIDF", "front.jpg");
}
// Convert and dispatch the back card image if present in the payload.
if (base64.back) {
const backBlob = base64ToBlob(base64.back);
await sendToAPI(backBlob, "back", "UGNIDB", "back.jpg");
}
};
"use client";
import { useEffect, useRef, useState } from "react";
export default function IDScanner() {
const pluginRef = useRef(null);
const [ready, setReady] = useState(false);
const initialized = useRef(false);
const base64ToBlob = (base64DataURL: string) => {
const [meta, content] = base64DataURL.split(",");
const mimeMatch = meta.match(/:(.*?);/);
const mime = mimeMatch ? mimeMatch[1] : "image/jpeg";
const binary = atob(content);
const array = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
array[i] = binary.charCodeAt(i);
}
return new Blob([array], { type: mime });
};
const sendToAPI = async (blob: Blob, isface: string, card_code: string, filename: string) => {
const formData = new FormData();
formData.append("scan_image", blob, filename); //Upload your image file
formData.append("isface", isface); //put card side either the card image is front or back
formData.append("country_code", "UGA"); //put country_code of the card image
formData.append("card_code", card_code); //put card_code of the card image
formData.append("passport", "false"); //if image is a passport put true else put false
formData.append("webcam", "false"); //if image is captured from a webcam put true else if image is captured from a mobile put false
try {
const response = await fetch("http://ip:port/doc_liveness.php", {
method: "POST",
body: formData,
});
const data = await response.json();
console.log(`API Response (${isface}):`, data);
if (data && data.score !== undefined) {
console.log(`Score (${isface}): ${data.score}`);
}
} catch (error) {
console.error(`Error sending ${isface} to API:`, error);
}
};
const handleCapture = async (base64: any) => {
console.log("Capture result:", base64);
if (base64.front) {
const frontBlob = base64ToBlob(base64.front);
await sendToAPI(frontBlob, "front", "UGNIDF", "front.jpg");
}
if (base64.back) {
const backBlob = base64ToBlob(base64.back);
await sendToAPI(backBlob, "back", "UGNIDB", "back.jpg");
}
};
useEffect(() => {
if (initialized.current) return;
initialized.current = true;
import("accuraidscanplugin").then((Module) => {
const IDCardPlugin = Module.default;
pluginRef.current = new IDCardPlugin(handleCapture, {
countryCode: "UGA",
cardCode: "UGNIDF",
topTextSize: "",
topTextColor: "",
topTextWeight: "",
bottomTextSize: "",
bottomTextColor: "",
bottomTextWeight: "",
});
pluginRef.current.start().then(() => {
console.log("ID Scanner Ready");
setReady(true);
});
}).catch(err => console.error("Plugin failed to load:", err));
return () => {
if (pluginRef.current) {
pluginRef.current.destroy();
}
};
}, []);
return (
<></>
);
}
import IDScanner from "./components/IDScanner";
export default function Home() {
return (
<main>
<IDScanner />
</main>
);
}