---
Source: https://docs.microblink.com/blinkid/scan-double-side
Title: Scan a double-sided document
Description: Configure BlinkID to scan both the front and back of an identity document
---

# 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:

1. The user scans the front of the document.
2. The SDK detects if the document has a back side with extractable data.
3. If it does, the user is prompted to flip the document.
4. 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.

<Tabs queryString="platform">

  <TabItem value="web" label="Web (TS)" default>

  ```typescript
  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();
  });
  ```

  </TabItem>
  <TabItem value="android" label="Android (Kotlin)">

  ```kotlin
  BlinkIdCameraScanningScreen(
      blinkIdSdk = blinkIdSdk,
      // sessionSettings defaults to BlinkIdSessionSettings() with ScanningMode.Automatic
      onScanningSuccess = { result ->
          // use result
      },
      onScanningCanceled = { }
  )
  ```

  </TabItem>
  <TabItem value="ios" label="iOS (Swift)">

  ```swift
  // 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)
      }
  )
  ```

  </TabItem>
</Tabs>

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

<Tabs queryString="platform">

  <TabItem value="web" label="Web (TS)" default>

  ```typescript
  import { createBlinkId } from "@microblink/blinkid";

  const blinkId = await createBlinkId({
    licenseKey: "your-license-key",
    scanningSettings: {
      documentCaptureModule: {
        secondSideWithNoExtractableDataSkipped: false,
      },
    },
  });
  ```

  </TabItem>
  <TabItem value="android" label="Android (Kotlin)">

  ```kotlin
  BlinkIdCameraScanningScreen(
      blinkIdSdk = blinkIdSdk,
      sessionSettings = BlinkIdSessionSettings(
          scanningSettings = ScanningSettings(
              documentCaptureModule = DocumentCaptureModuleSettings(
                  secondSideWithNoExtractableDataSkipped = false,
              )
          )
      ),
      onScanningSuccess = { result -> },
      onScanningCanceled = { }
  )
  ```

  </TabItem>
  <TabItem value="ios" label="iOS (Swift)">

  ```swift
  let sessionSettings = BlinkIDSessionSettings(
      scanningSettings: ScanningSettings(
          documentCaptureModule: DocumentCaptureModuleSettings(
              secondSideWithNoExtractableDataSkipped: false
          )
      )
  )

  let analyzer = try await BlinkIDAnalyzer(
      sdk: sdk,
      blinkIdSessionSettings: sessionSettings
  )
  ```

  </TabItem>
</Tabs>

## 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`.

<Tabs queryString="platform">

  <TabItem value="web" label="Web (TS)" default>

  ```typescript
  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();
  });
  ```

  </TabItem>
  <TabItem value="android" label="Android (Kotlin)">

  ```kotlin
  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}")
  }
  ```

  </TabItem>
  <TabItem value="ios" label="iOS (Swift)">

  ```swift
  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)
  }
  ```

  </TabItem>
</Tabs>

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

<Tabs queryString="platform">

  <TabItem value="web" label="Web (TS)" default>

  ```typescript
  blinkId.addOnResultCallback((result) => {
    const dataMatch = result.dataMatchResult;
    if (dataMatch) {
      console.log("Data match state:", dataMatch.statePerField);
    }
    void blinkId.destroy();
  });
  ```

  </TabItem>
  <TabItem value="android" label="Android (Kotlin)">

  ```kotlin
  onScanningSuccess = { result ->
      val dataMatch = result.dataMatchResult
      if (dataMatch != null) {
          println("Data match state: ${dataMatch.statePerField}")
      }
  }
  ```

  </TabItem>
  <TabItem value="ios" label="iOS (Swift)">

  ```swift
  onScanCompleted: { resultState in
      guard let result = resultState.scanningResult else { return }
      if let dataMatch = result.dataMatchResult {
          print("Data match state:", dataMatch.statePerField)
      }
  }
  ```

  </TabItem>
</Tabs>

## Related articles

- [How to scan a single-sided document](scan-single-side.md)
- [How to scan a passport](scan-passport.md)
- [BlinkID settings validation](settings-validation.md)


Last updated on Jul 24, 2026
