---
Source: https://docs.microblink.com/blinkid/scan-passport
Title: Scan a passport
Description: Scan the MRZ and biographical data from passport documents with BlinkID
---

# Scan a passport

Passports have a data page (the page with the photo) that contains two zones:

- **VIZ (Visual Inspection Zone):** the biographical data printed in the upper half—name, nationality, date of birth, document number, and similar fields
- **MRZ (Machine Readable Zone):** the two lines of OCR-formatted text at the bottom of the page, encoding the same core fields in a machine-readable format

BlinkID extracts both zones and cross-validates the data between them.

## Default behavior

By default, BlinkID scans only the data page (`passportDataPageScanOnly: true`).
This is the correct setting for most use cases: the data page contains all the core biographical data.

Some passports have a second inner page (for example, a visa page) that additional data can be extracted from.
To also scan this second page, set `passportDataPageScanOnly` to `false` and use `automatic` scanning mode.
The SDK will then prompt the user to turn to the second page when relevant.

## Scan the data page (default)

No special configuration is needed—just initialize as normal and BlinkID will scan the passport data page.

<Tabs queryString="platform">

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

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

  const blinkId = await createBlinkId({
    licenseKey: "your-license-key",
    // passportDataPageScanOnly defaults to true
  });

  blinkId.addOnResultCallback((result) => {
    console.log("Result:", result);
    void blinkId.destroy();
  });
  ```

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

  ```kotlin
  // passportDataPageScanOnly defaults to true — no special config needed
  BlinkIdCameraScanningScreen(
      blinkIdSdk = blinkIdSdk,
      onScanningSuccess = { result ->
          // use result
      },
      onScanningCanceled = { }
  )
  ```

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

  ```swift
  // passportDataPageScanOnly defaults to true — no special config needed
  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>

## Also scan the second page

Set `passportDataPageScanOnly` to `false` to require a second-page scan for passports that support it.

<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: {
        passportDataPageScanOnly: false,
      },
    },
  });
  ```

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

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

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

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

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

  </TabItem>
</Tabs>

## Read biographical data (VIZ)

The VIZ data is available in the aggregated top-level result fields, as well as in the per-side `viz` field of `subResults`.

<Tabs queryString="platform">

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

  ```typescript
  blinkId.addOnResultCallback((result) => {
    // Top-level aggregated fields
    console.log("Full name:", result.fullName?.latin?.value);
    console.log("Date of birth:", result.dateOfBirth?.originalString?.latin?.value);
    console.log("Nationality:", result.nationality?.latin?.value);
    console.log("Document number:", result.documentNumber?.latin?.value);
    console.log("Date of expiry:", result.dateOfExpiry?.originalString?.latin?.value);
    console.log("Sex:", result.sex?.latin?.value);

    // Raw VIZ from the data page
    const dataPage = result.subResults[0];
    console.log("VIZ:", dataPage.viz);

    void blinkId.destroy();
  });
  ```

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

  ```kotlin
  onScanningSuccess = { result ->
      // Top-level aggregated fields
      println("Full name: ${result.fullName?.value}")
      println("Date of birth: ${result.dateOfBirth?.value}")
      println("Nationality: ${result.nationality?.value}")
      println("Document number: ${result.documentNumber?.value}")
      println("Date of expiry: ${result.dateOfExpiry?.value}")
      println("Sex: ${result.sex?.value}")

      // Raw VIZ from the data page
      val dataPage = result.subResults[0]
      println("VIZ: ${dataPage.viz}")
  }
  ```

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

  ```swift
  onScanCompleted: { resultState in
      guard let result = resultState.scanningResult else { return }

      // Top-level aggregated fields
      print("Full name:", result.fullName?.value ?? "")
      print("Date of birth:", result.dateOfBirth?.value ?? "")
      print("Nationality:", result.nationality?.value ?? "")
      print("Document number:", result.documentNumber?.value ?? "")
      print("Date of expiry:", result.dateOfExpiry?.value ?? "")
      print("Sex:", result.sex?.value ?? "")

      // Raw VIZ from the data page
      let dataPage = result.subResults[0]
      print("VIZ:", dataPage.viz as Any)
  }
  ```

  </TabItem>
</Tabs>

## Read MRZ data

MRZ data is available in `subResults[0].mrz`.
It contains the same core fields as the VIZ, plus MRZ-specific fields like `primaryId`, `secondaryId`, `opt1`, and `opt2`.

<Tabs queryString="platform">

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

  ```typescript
  blinkId.addOnResultCallback((result) => {
    const mrz = result.subResults[0].mrz;
    if (mrz) {
      console.log("Document number:", mrz.documentNumber);
      console.log("Nationality:", mrz.nationality);
      console.log("Date of birth:", mrz.dateOfBirth.originalString);
      console.log("Date of expiry:", mrz.dateOfExpiry.originalString);
      console.log("Gender:", mrz.gender);
      console.log("Primary ID:", mrz.primaryId);
      console.log("Secondary ID:", mrz.secondaryId);
      console.log("Raw MRZ:", mrz.rawMrzString);
      console.log("MRZ verified:", mrz.verified);
    }
    void blinkId.destroy();
  });
  ```

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

  ```kotlin
  onScanningSuccess = { result ->
      val mrz = result.subResults[0].mrz
      if (mrz != null) {
          println("Document number: ${mrz.documentNumber}")
          println("Nationality: ${mrz.nationality}")
          println("Date of birth: ${mrz.dateOfBirth.value}")
          println("Date of expiry: ${mrz.dateOfExpiry.value}")
          println("Gender: ${mrz.gender}")
          println("Primary ID: ${mrz.primaryId}")
          println("Secondary ID: ${mrz.secondaryId}")
          println("Raw MRZ: ${mrz.rawMrzString}")
          println("MRZ verified: ${mrz.verified}")
      }
  }
  ```

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

  ```swift
  onScanCompleted: { resultState in
      guard let result = resultState.scanningResult else { return }
      if let mrz = result.subResults[0].mrz {
          print("Document number:", mrz.documentNumber)
          print("Nationality:", mrz.nationality)
          print("Date of birth:", mrz.dateOfBirth.value)
          print("Date of expiry:", mrz.dateOfExpiry.value)
          print("Gender:", mrz.gender)
          print("Primary ID:", mrz.primaryId)
          print("Secondary ID:", mrz.secondaryId)
          print("Raw MRZ:", mrz.rawMrzString)
          print("MRZ verified:", mrz.verified)
      }
  }
  ```

  </TabItem>
</Tabs>

The `verified` field is `true` when all MRZ check digits are valid—a basic integrity check for the scanned MRZ.

## Related articles

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


Last updated on Jul 24, 2026
