TL;DR:
PAN validation in Flutter involves two steps:
(1) regex validation to check the format (5 letters + 4 digits + 1 letter), and (2) API verification with Setu to confirm the PAN exists in NSDL records. This guide covers both with complete code examples.
In this blog post, we will cover complete PAN card validation in Flutter, from basic PAN regex format checking to real-time verification using the Setu PAN API. Whether you need to validate PAN number format on the client side or verify it against NSDL records, this guide has you covered.
Why PAN Card Verification is Important
PAN card verification in Flutter apps is crucial for fintech applications, especially in banking, insurance, and financial services. Building a KYC verification Flutter app requires reliable PAN validation. Here's why:
- KYC Requirements: As part of the mandatory KYC process, verifying PAN cards ensures that users are authenticated and that financial transactions are legitimate.
- Legal Compliance: The Indian government mandates PAN card verification for transactions exceeding a certain threshold to avoid tax evasion and ensure regulatory compliance.
- Loan Approvals and Investment: In fintech, during the loan application process or when users are making a significant financial investment, PAN card verification is essential to validate the user's identity.
- Form Input Validation: Before making any API call, validating the PAN card format on the client side using PAN regex in Dart saves API costs and provides instant user feedback.
Understanding the PAN Card Format
Before writing any code, you need to understand what makes a PAN number valid. Every PAN follows a specific 10-character structure:
**Format: AAAAA9999A**
| Position | Characters | Meaning |
|----------|------------|---------|
| 1-3 | AAA | Random alphabets (A-Z) |
| 4 | A | Category of PAN holder |
| 5 | A | First letter of surname/name |
| 6-9 | 9999 | Sequential number (0001-9999) |
| 10 | A | Alphabetic check digit |
### PAN Category Codes (4th Character)
| Code | Category |
|------|----------|
| P | Individual / Person |
| C | Company |
| H | Hindu Undivided Family (HUF) |
| A | Association of Persons (AOP) |
| B | Body of Individuals (BOI) |
| G | Government |
| J | Artificial Juridical Person |
| L | Local Authority |
| F | Firm / Partnership |
| T | Trust |
**Example:** In PAN "ABCPK1234A":
- ABC = Random letters
- P = Individual (Person)
- K = First letter of surname (e.g., Kumar)
- 1234 = Sequential number
- A = Check digit
Understanding this structure helps you build accurate validation logic.Step-by-Step: PAN Validation Using Regex in Flutter
Before calling any API, you should validate the PAN format on the client side. This saves API calls and provides instant feedback to users.
### The Regex Pattern
The regex pattern for PAN validation is:
```dart
final panRegex = RegExp(r'^[A-Z]{5}[0-9]{4}[A-Z]{1}$');
```
**Breaking it down:**
- `^` — Start of string
- `[A-Z]{5}` — Exactly 5 uppercase letters
- `[0-9]{4}` — Exactly 4 digits
- `[A-Z]{1}` — Exactly 1 uppercase letter
- `$` — End of string
### Creating a PAN Validator Extension
For cleaner code, create a String extension:
```dart
extension PanCardValidator on String {
bool isValidPanCard() {
if (this.isEmpty) return false;
// Convert to uppercase for case-insensitive matching
final pan = this.toUpperCase().trim();
// Check length first
if (pan.length != 10) return false;
// Apply regex pattern
final panRegex = RegExp(r'^[A-Z]{5}[0-9]{4}[A-Z]{1}$');
return panRegex.hasMatch(pan);
}
// Get PAN holder category
String? getPanCategory() {
if (!this.isValidPanCard()) return null;
final categories = {
'P': 'Individual',
'C': 'Company',
'H': 'Hindu Undivided Family',
'A': 'Association of Persons',
'B': 'Body of Individuals',
'G': 'Government',
'J': 'Artificial Juridical Person',
'L': 'Local Authority',
'F': 'Firm',
'T': 'Trust',
};
final categoryCode = this.toUpperCase()[3];
return categories[categoryCode];
}
}
```
### Using in TextFormField
```dart
TextFormField(
decoration: InputDecoration(
labelText: 'Enter PAN Number',
hintText: 'e.g., ABCPK1234A',
),
textCapitalization: TextCapitalization.characters,
maxLength: 10,
validator: (value) {
if (value == null || value.isEmpty) {
return 'PAN number is required';
}
if (!value.isValidPanCard()) {
return 'Enter a valid PAN number';
}
return null;
},
onChanged: (value) {
// Real-time validation feedback
if (value.length == 10) {
final category = value.getPanCategory();
print('PAN Category: $category');
}
},
)
```
### Why Regex Validation Alone Isn't Enough
Regex only validates the format. It cannot tell you if:
- The PAN actually exists in NSDL records
- The PAN is active or deactivated
- The name matches the PAN holder
- The PAN is linked to Aadhaar
For these checks, you need API verification with Setu PAN API (covered in the next section).Comparing Validation Approaches
## Regex vs API: When to Use What
| Aspect | Regex Validation | Setu API Verification |
|--------|------------------|----------------------|
| **Speed** | Instant (client-side) | 200-500ms (API call) |
| **Cost** | Free | Per-request pricing |
| **Accuracy** | Format only | Full verification |
| **Use Case** | Form validation | KYC compliance |
| **Offline** | Works offline | Requires internet |
### Recommended Approach
For fintech applications, use both:
1. **First: Regex validation** — Catch format errors instantly
2. **Then: API verification** — Confirm PAN exists in NSDL
```dart
Future<PanVerificationResult> verifyPan(String panNumber) async {
// Step 1: Regex validation
if (!panNumber.isValidPanCard()) {
return PanVerificationResult(
success: false,
error: 'Invalid PAN format',
);
}
// Step 2: API verification
try {
final apiResult = await ApiService.verifyPan(panNumber);
return PanVerificationResult(
success: true,
data: apiResult,
);
} catch (e) {
return PanVerificationResult(
success: false,
error: 'API verification failed: $e',
);
}
}
```
This approach:
- Reduces unnecessary API calls (saves cost)
- Provides instant feedback on format errors
- Only hits the API when the format is validWhat is Setu PAN API?(Flutter Integration)
Setu PAN API allows you to use just one API to verify your customer's PAN details in your Flutter application. For developers building fintech apps, this Setu PAN API Flutter integration provides direct connection with NSDL for the best uptimes.We directly connect with NSDL to maintain the best uptimes.
Here’s a quick overview of the PAN API. Additionally, here are the URLs you would need for this API
Headers Contact Setu for providing the credentials required to successfully call Setu APIs. This contains:
- x-client-id
- x-client - secret
- x-product-instance-id
Verify PAN Card NumberVerify PAN Card Number in Flutter
Call this API to verify a PAN provided by your customer. A quick explanation of the request params—
- pan is the PAN value. It may belong to different categories like Person, Company, Trust, Government, Firm, etc.
- Consent indicates whether you have collected consent from your customer. To get a successful verification, it must contain Y or y.
- The reason is the explanation of why you are requesting a PAN from your customer. It should be explained in 20 characters or more.
Note: While the implementation of consent and reason cannot be enforced by Setu, we recommend collecting explicit consent from your customers and also explaining to your customers the reason why you are verifying their PAN.
While testing on Sandbox, you may use the following sample values
- Use ABCDE1234A for a valid PAN
- Use ABCDE1234B for an invalid PAN, i.e, a PAN number has been found but is invalid. A PAN is considered invalid by NSDL for different reasons.
For example, if it is a blacklisted one, or maybe because it is not linked to an Aadhaar card. - If you use any other values for PAN, you will get a 404 PAN not found error.
Request Body
{
"data": {
"aadhaar_seeding_status": "LINKED", // optional
"category": "Individual",
"full_name": "John Doe"
},
"message": "PAN is valid",
"verification": "success",
"traceId": "1-6346a91a-620cf6cc4f68d2e30316881e"
}
Prerequisites
Make sure you have:
- A basic understanding of Flutter and Dart before you start.
- Flutter is installed on your system.
- A Setu account is required in order to use the PAN verification API. Here is where you may register. link
Step 1: Configure Your Flutter Project for PAN Validation
If you don't already have a Flutter project, start by creating one:
flutter create pan_verification_app
cd pan_verification_app
Step 2: Add Dependencies
In your pubspec.YAML file, add the http, and get packages to handle HTTP requests and state management:
dependencies:
flutter:
sdk: flutter
http: ^0.14.0
get: ^4.6.5
Step 3: Create the API Service Class
To manage the API requests, create a new file called lib/services/api_service.dart:
import 'package:http/http.dart' as http;
import 'dart:convert';
class ApiService {
static const String apiUrl = 'https://dg-sandbox.setu.co/api/verify/pan';
static const String clientId = 'YOUR_CLIENT_ID';
static const String clientSecret = 'YOUR_CLIENT_SECRET';
static const String productInstanceId = 'YOUR_PRODUCT_INSTANCE_ID';
static const String bearerToken = 'YOUR_BEARER_TOKEN';
static Future<Map<String, dynamic>> verifyPan(String panNumber) async {
final response = await http.post(
Uri.parse(apiUrl),
headers: {
'x-client-id': clientId,
'x-client-secret': clientSecret,
'x-product-instance-id': productInstanceId,
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer $bearerToken',
},
body: json.encode({
'pan': panNumber,
'consent': 'Y',
'reason': 'Reason for verifying PAN set by the developer',
}),
);
if (response.statusCode == 200) {
return json.decode(response.body);
} else {
throw Exception('Failed to verify PAN: ${response.body}');
}
}
}
Step 4: Create the Controller
Create a new file lib/controllers/pan_controller.dart to handle the PAN validation business logic using GetX state management. This controller manages the PAN number verification flow.
import 'package:get/get.dart';
import '../services/api_service.dart';
class PanController extends GetxController {
var panNumber = ''.obs;
var result = ''.obs;
var isLoading = false.obs;
void verifyPan() async {
if (panNumber.value.isEmpty) {
result.value = 'Please enter a PAN number.';
return;
}
isLoading.value = true;
try {
final data = await ApiService.verifyPan(panNumber.value);
result.value = 'PAN Verification Successful: ${data['status']}';
} catch (e) {
result.value = 'PAN Verification Failed: $e';
} finally {
isLoading.value = false;
}
}
}
Step 5: Create the UI
In lib/main.dart, create the PAN card validation UI with GetX for state management. This screen allows users to enter their PAN number and see the verification result.
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'controllers/pan_controller.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
home: PanVerificationScreen(),
);
}
}
class PanVerificationScreen extends StatelessWidget {
final PanController _panController = Get.put(PanController());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('PAN Verification'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
onChanged: (value) => _panController.panNumber.value = value,
decoration: InputDecoration(
labelText: 'Enter PAN Number',
),
),
SizedBox(height: 20),
Obx(() => _panController.isLoading.value
? CircularProgressIndicator()
: ElevatedButton(
onPressed: _panController.verifyPan,
child: Text('Verify PAN'),
)),
SizedBox(height: 20),
Obx(() => Text(_panController.result.value)),
],
),
),
);
}
}
Step 6: Run the App
Use an emulator or connect your smartphone to launch the app, and run the command below:
flutter run.
Enter a valid PAN number in the text field and click the button to see the verification result.
Also Read: Digi-Locker Integration with Flutter: A Comprehensive Guide 2026
Conclusion
In this article, we covered complete PAN card validation in Flutter using the Setu PAN API. We demonstrated Flutter PAN verification with GetX for state management and splitting the API call into a distinct service class. This approach to validate PAN number in Flutter apps is production-ready for any KYC verification use case.
By including error management, input validation, and an improved user interface, you may further improve this application. See the official Setu API documentation for further information. Official documentation
Note: In the blog, we are managing the state with GetX. Any alternative state management system that meets the requirements of your project is acceptable.


