Scan a double-sided document
BlinkID uses automatic scanning mode by default.
In this mode, the SDK detects whether a document has a back side with extractable data and, if so, prompts the user to flip the document before completing the session.
No special configuration is needed for this to work.
This article covers how to configure and read results for two-sided scanning, and how to control whether the back side is always required.
Default behavior
With automatic mode, the scanning flow is:
- The user scans the front of the document.
- The SDK detects if the document has a back side with extractable data.
- If it does, the user is prompted to flip the document.
- After the back is scanned, the SDK merges data from both sides into a single result.
If the back side has no extractable data, the SDK skips it by default (controlled by secondSideWithNoExtractableDataSkipped, which defaults to true).
Initialize with automatic mode
Since automatic is the default, no configuration is required for basic double-sided scanning.
- Web (TS)
- Android (Kotlin)
- iOS (Swift)
import { createBlinkId } from "@microblink/blinkid";
const blinkId = await createBlinkId({
licenseKey: "your-license-key",
// scanningMode defaults to "automatic"
});
blinkId.addOnResultCallback((result) => {
console.log("Result:", result);
void blinkId.destroy();
});
BlinkIdCameraScanningScreen(
blinkIdSdk = blinkIdSdk,
// sessionSettings defaults to BlinkIdSessionSettings() with ScanningMode.Automatic
onScanningSuccess = { result ->
// use result
},
onScanningCanceled = { }
)
// BlinkIDSessionSettings defaults to .automatic scanning mode
let analyzer = try await BlinkIDAnalyzer(sdk: sdk)
BlinkIDUXView(
analyzer: analyzer,
onScanCompleted: { resultState in
guard let result = resultState.scanningResult else { return }
print("Result:", result)
}
)
Always scan the back side
By default, the SDK skips the back side if it contains no extractable data.
Set secondSideWithNoExtractableDataSkipped to false to always require a back-side scan, regardless of whether any data can be extracted from it.
This setting requires automatic scanning mode.
- Web (TS)
- Android (Kotlin)
- iOS (Swift)
import { createBlinkId } from "@microblink/blinkid";
const blinkId = await createBlinkId({
licenseKey: "your-license-key",
scanningSettings: {
documentCaptureModule: {
secondSideWithNoExtractableDataSkipped: false,
},
},
});
BlinkIdCameraScanningScreen(
blinkIdSdk = blinkIdSdk,
sessionSettings = BlinkIdSessionSettings(
scanningSettings = ScanningSettings(
documentCaptureModule = DocumentCaptureModuleSettings(
secondSideWithNoExtractableDataSkipped = false,
)
)
),
onScanningSuccess = { result -> },
onScanningCanceled = { }
)
let sessionSettings = BlinkIDSessionSettings(
scanningSettings: ScanningSettings(
documentCaptureModule: DocumentCaptureModuleSettings(
secondSideWithNoExtractableDataSkipped: false
)
)
)
let analyzer = try await BlinkIDAnalyzer(
sdk: sdk,
blinkIdSessionSettings: sessionSettings
)
Read results from both sides
The top-level result object contains aggregated fields merged from all scanned sides.
For side-specific data (images, raw MRZ, VIZ), use subResults.
- Web (TS)
- Android (Kotlin)
- iOS (Swift)
blinkId.addOnResultCallback((result) => {
// Aggregated fields from both sides
console.log("First name:", result.firstName?.latin?.value);
console.log("Last name:", result.lastName?.latin?.value);
console.log("Date of expiry:", result.dateOfExpiry?.originalString?.latin?.value);
// Side-specific data
const front = result.subResults[0];
const back = result.subResults[1]; // undefined if only one side was scanned
console.log("Front VIZ:", front.viz);
console.log("Back barcode:", back?.barcode);
void blinkId.destroy();
});
onScanningSuccess = { result ->
// Aggregated fields from both sides
println("First name: ${result.firstName?.value}")
println("Last name: ${result.lastName?.value}")
println("Date of expiry: ${result.dateOfExpiry?.value}")
// Side-specific data
val front = result.subResults[0]
val back = result.subResults.getOrNull(1)
println("Front VIZ: ${front.viz}")
println("Back barcode: ${back?.barcode}")
}
onScanCompleted: { resultState in
guard let result = resultState.scanningResult else { return }
// Aggregated fields from both sides
print("First name:", result.firstName?.value ?? "")
print("Last name:", result.lastName?.value ?? "")
print("Date of expiry:", result.dateOfExpiry?.value ?? "")
// Side-specific data
let front = result.subResults[0]
let back = result.subResults.count > 1 ? result.subResults[1] : nil
print("Front VIZ:", front.viz as Any)
print("Back barcode:", back?.barcode as Any)
}
Check data consistency between sides
When both sides are scanned, the result includes a dataMatchResult field that indicates whether data extracted from the front and back sides is consistent.
- Web (TS)
- Android (Kotlin)
- iOS (Swift)
blinkId.addOnResultCallback((result) => {
const dataMatch = result.dataMatchResult;
if (dataMatch) {
console.log("Data match state:", dataMatch.statePerField);
}
void blinkId.destroy();
});
onScanningSuccess = { result ->
val dataMatch = result.dataMatchResult
if (dataMatch != null) {
println("Data match state: ${dataMatch.statePerField}")
}
}
onScanCompleted: { resultState in
guard let result = resultState.scanningResult else { return }
if let dataMatch = result.dataMatchResult {
print("Data match state:", dataMatch.statePerField)
}
}