Building a browser-based receipt scanner with LiteRT.js Google's LiteRT.js, a browser runtime for on-device AI inference using WebAssembly and WebGPU, enables developers to run standard .tflite models directly in the browser, as demonstrated in a tutorial for building an OCR receipt scanner that processes data locally without external APIs. The tutorial, published on LogRocket, shows how to use LiteRT.js with React, TensorFlow.js, and LiteRT-LM to preprocess receipt photos, recognize text, and structure results on-device. Advisory boards aren’t only for executives. Join the LogRocket Content Advisory Board today → Google recently launched LiteRT.js, a browser runtime for running on-device AI inference with WebAssembly and WebGPU. LiteRT.js brings Google’s LiteRT runtime, formerly TensorFlow Lite, to the web. Instead of requiring a JavaScript-specific model format, it can run standard .tflite models directly in the browser while taking advantage of modern browser hardware acceleration. In this tutorial, we’ll look at how LiteRT.js works and build an end-to-end optical character recognition OCR receipt scanner. The application will preprocess receipt photos, detect and recognize text locally, reconstruct the document layout, and pass the extracted text to an on-device Gemma model through LiteRT-LM to structure the result. The full pipeline runs locally in the browser, so receipt data does not need to be sent to an external inference API. You’ll need: Before we build the scanner, let’s look at what LiteRT.js changes about running machine learning models in the browser. TensorFlow Lite was originally designed for mobile and embedded systems, while TensorFlow.js was designed specifically for the web. As browsers gained capabilities such as WebAssembly SIMD and WebGPU, however, the gap between native and browser-based inference narrowed. Google subsequently rebranded TensorFlow Lite as LiteRT as part of its broader AI Edge tooling. LiteRT acts as both a model runtime and part of a broader conversion pipeline. Models originating in frameworks such as TensorFlow, PyTorch, and JAX can ultimately be deployed in the .tflite format, while LiteRT.js brings that runtime to the browser. For web applications, the important distinction is that LiteRT.js can execute .tflite models using modern browser compute APIs rather than requiring models to target a JavaScript-specific execution environment. LiteRT.js can target several execution paths: | Backend | Role | Best suited for | |---|---|---| | WebAssembly + XNNPACK | CPU execution and fallback | Broad compatibility and CPU inference | | WebGPU | GPU-accelerated compute | Parallel workloads such as neural network inference | | WebNN | Emerging hardware abstraction | Direct access to available ML accelerators and NPUs | WebAssembly provides the browser-side execution environment, while XNNPACK supplies optimized neural network operators. Over 200k developers use LogRocket to create better digital experiences Learn more → With browser features such as SIMD and multithreading, this gives LiteRT.js a much faster CPU path than implementing the same numerical operations directly in JavaScript. WebGPU gives browser applications access to general-purpose GPU compute. LiteRT.js can use that capability to execute highly parallel operations such as matrix multiplication on the user’s GPU. This avoids many of the constraints associated with treating WebGL, which was designed primarily for graphics, as a general-purpose compute API. WebNN is an emerging browser API for neural network acceleration. Where supported, it is intended to provide access to the device’s available ML hardware, including GPUs and NPUs. Let’s initialize the React application and install the dependencies. The application has two main stages: If you haven’t already created the project, scaffold a React and TypeScript app with Vite: npm create vite@latest document-scanner -- --template react-ts Then install LiteRT.js: npm install @litertjs/core We’ll use @litertjs/tfjs-interop to pass tensors between TensorFlow.js and LiteRT.js: npm install @litertjs/tfjs-interop Install TensorFlow.js: npm install @tensorflow/tfjs Then add its WebGPU backend: npm install @tensorflow/tfjs-backend-webgpu Finally, install LiteRT-LM for the on-device language model: npm install --save @litert-lm/core The resulting stack looks like this: | Package | Purpose | |---|---| @litertjs/core | Loads and executes .tflite models | @litertjs/tfjs-interop | Shares tensors between TensorFlow.js and LiteRT.js | @tensorflow/tfjs | Tensor manipulation and supporting image operations | @tensorflow/tfjs-backend-webgpu | WebGPU backend for TensorFlow.js | @litert-lm/core | Runs the on-device language model | The OCR pipeline needs three files: You can find the OCR models, dictionary, and complete project in the GitHub repository https://github.com/emmanuelhashy/document-scanner . The Gemma model is available from Hugging Face https://huggingface.co/litert-community/gemma-4-E2B-it-litert-lm . Place the required model files inside the project’s public/models/ directory. The code used throughout this tutorial lives under src/ , organized into three main directories: src/ ├── litert/ │ ├── runtime.ts │ └── models.ts ├── ocr/ │ ├── preprocess.ts │ ├── detect.ts │ ├── ctc.ts │ └── layout.ts └── llm/ ├── engine.ts └── structureWithLlm.ts This separation keeps model initialization, OCR processing, and LLM inference independent from one another. More great articles from LogRocket: Don't miss a moment with The Replay, a curated newsletter from LogRocket Learn how LogRocket's Galileo AI watches sessions for you and proactively surfaces the highest-impact things you should work on Use React's useEffect to optimize your application's performance Switch between multiple versions of Node Discover how to use the React children prop with TypeScript Explore creating a custom mouse cursor with CSS Advisory boards aren’t just for executives. Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag We’ll start with the ML runtime itself. Two pieces need to be initialized before inference can run: Initializing a machine learning backend is relatively expensive. React components can mount, unmount, and re-render frequently, so tying runtime initialization directly to component lifecycle can create duplicate GPU contexts and unnecessary memory pressure. Instead, we’ll cache initialization at the module level with a shared Promise . Create runtime.ts : js import { loadLiteRt, getWebGpuDevice, isWebGPUSupported } from '@litertjs/core'; import as tf from '@tensorflow/tfjs'; import { WebGPUBackend } from '@tensorflow/tfjs-backend-webgpu'; import { WASM PATH } from '../ocr/config'; export interface RuntimeInfo { webgpu: boolean; tfjsBackend: string; } let runtimePromise: Promise