Back to Blog

Build a React Native Pokemon Card Scanner App with OCR

PokemonPriceTracker Team

4 min read
Build a React Native Pokemon Card Scanner App with OCR

Build a React Native Pokemon Card Scanner

Scan Cards with Your Phone Camera

Point your phone at a Pokemon card and instantly get:

  • Card name and set
  • Current market price
  • PSA grading recommendations
  • Add to collection with one tap

This tutorial shows you how to build it with React Native + Expo.

Tech Stack

  • React Native with Expo
  • expo-camera for camera access
  • Tesseract.js for OCR
  • PokemonPriceTracker API
  • SQLite for offline storage

Quick Start

npx create-expo-app pokemon-scanner
cd pokemon-scanner
npx expo install expo-camera expo-image-picker expo-file-system
npm install tesseract.js axios react-native-sqlite-storage

Step 1: Camera Component

import React, { useState } from 'react';
import { View, Button, Image } from 'react-native';
import { Camera } from 'expo-camera';
import * as ImagePicker from 'expo-image-picker';

export function CardScanner() {
  const [hasPermission, setHasPermission] = useState(null);
  const [image, setImage] = useState(null);

  React.useEffect(() => {
    (async () => {
      const { status } = await Camera.requestCameraPermissionsAsync();
      setHasPermission(status === 'granted');
    })();
  }, []);

  const takePicture = async () => {
    const result = await ImagePicker.launchCameraAsync({
      mediaTypes: ImagePicker.MediaTypeOptions.Images,
      quality: 1,
    });

    if (!result.canceled) {
      setImage(result.assets[0].uri);
      processImage(result.assets[0].uri);
    }
  };

  const processImage = async (uri: string) => {
    // OCR and API call coming next
  };

  return (
    <View>
      <Button title="Scan Card" onPress={takePicture} />
      {image && <Image source={{ uri: image }} style={{ width: 200, height: 280 }} />}
    </View>
  );
}

Step 2: OCR Processing

import Tesseract from 'tesseract.js';

async function extractCardText(imageUri: string): Promise<string> {
  try {
    const { data: { text } } = await Tesseract.recognize(
      imageUri,
      'eng',
      {
        logger: m => console.log(m)
      }
    );

    return text;
  } catch (error) {
    console.error('OCR Error:', error);
    return '';
  }
}

Step 3: API Integration

import axios from 'axios';

const API_KEY = 'your_api_key';
const API_BASE = 'https://www.pokemonpricetracker.com/api/v2';

async function identifyCard(extractedText: string) {
  try {
    const response = await axios.post(
      `${API_BASE}/parse-title`,
      { title: extractedText },
      {
        headers: {
          'Authorization': `Bearer ${API_KEY}`,
          'Content-Type': 'application/json'
        }
      }
    );

    if (response.data.matches && response.data.matches.length > 0) {
      return response.data.matches[0].card;
    }

    return null;
  } catch (error) {
    console.error('API Error:', error);
    return null;
  }
}

Step 4: Complete Scanner Flow

import React, { useState } from 'react';
import { View, Text, Button, Image, ActivityIndicator } from 'react-native';

export function CardScannerApp() {
  const [loading, setLoading] = useState(false);
  const [card, setCard] = useState(null);

  const handleScan = async () => {
    setLoading(true);

    // Step 1: Take picture
    const result = await ImagePicker.launchCameraAsync({
      quality: 0.8,
    });

    if (result.canceled) {
      setLoading(false);
      return;
    }

    // Step 2: OCR
    const text = await extractCardText(result.assets[0].uri);

    // Step 3: Identify card
    const cardData = await identifyCard(text);

    if (cardData) {
      setCard(cardData);
    } else {
      Alert.alert('Card Not Found', 'Could not identify the card. Try again with better lighting.');
    }

    setLoading(false);
  };

  return (
    <View style={{ padding: 20 }}>
      <Button title="Scan Pokemon Card" onPress={handleScan} />

      {loading && <ActivityIndicator size="large" />}

      {card && (
        <View style={{ marginTop: 20 }}>
          <Image source={{ uri: card.image?.url }} style={{ width: 200, height: 280 }} />
          <Text style={{ fontSize: 20, fontWeight: 'bold' }}>{card.name}</Text>
          <Text>Set: {card.setName}</Text>
          <Text style={{ fontSize: 18, color: 'green' }}>
            Market Price: ${card.prices.market.toFixed(2)}
          </Text>
        </View>
      )}
    </View>
  );
}

Step 5: SQLite Storage

import SQLite from 'react-native-sqlite-storage';

const db = SQLite.openDatabase({ name: 'pokemon_collection.db' });

function initDatabase() {
  db.transaction(tx => {
    tx.executeSql(
      `CREATE TABLE IF NOT EXISTS scanned_cards (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        tcg_player_id TEXT UNIQUE,
        name TEXT,
        set_name TEXT,
        price REAL,
        scanned_at DATETIME DEFAULT CURRENT_TIMESTAMP
      )`,
      []
    );
  });
}

function saveCard(card) {
  db.transaction(tx => {
    tx.executeSql(
      'INSERT OR REPLACE INTO scanned_cards (tcg_player_id, name, set_name, price) VALUES (?, ?, ?, ?)',
      [card.tcgPlayerId, card.name, card.setName, card.prices.market]
    );
  });
}

Deployment

# iOS
eas build --platform ios

# Android
eas build --platform android

# Both
eas build --platform all

Tips for Better Scanning

  1. Good lighting: Natural daylight works best
  2. Flat surface: Lay card flat, avoid glare
  3. Card name focus: Focus on card name and set info
  4. Retry logic: Allow multiple scan attempts
  5. Manual fallback: Let users type card name if OCR fails

Cost Analysis

  • Free tier: 100 scans/day
  • API tier ($9.99/mo): 20,000 scans/day
  • Perfect for personal use

Get Free API Key →

PokemonPriceTracker Team

Stay Updated

Subscribe to our newsletter for the latest Pokemon card market trends, investment opportunities, and exclusive insights delivered straight to your inbox.