> Canonical URL: https://codingcat.dev/post/firebase-2025-02-gemini-genkit-pdf-structured-data

# Extracting structured data from PDFs using Gemini 2.0 and Genkit in Firebase: Web Architecture & Implementation Guide

![CodingCat.dev tutorial cover image](https://cdn.sanity.io/images/hfh83o0w/production/95a5992809fff56cd2c4b8b78fccb7f197213e8a-1200x630.svg?w=1200&q=82&fit=max&auto=format)[![Alex Patterson](https://cdn.sanity.io/images/hfh83o0w/production/a606dfaa381aab08ddbe16a33fe1d3e1fb37c927-400x400.jpg?w=96&h=96&q=82&fit=max&auto=format)Alex Patterson](/author/alex-patterson)February 21, 2025

Building AI features in TypeScript or Go quickly turns into spaghetti when you try to bolt structured output validation, tool calling, and OpenTelemetry tracing onto raw model API calls. Following the announcement of **Extracting structured data from PDFs using Gemini 2.0 and Genkit**  by Peter Friese on the [**Official Firebase Blog**](https://firebase.blog/posts/2025/02/gemini-genkit-pdf-structured-data/) (2025-02-20), here is our hands-on CodingCat.dev breakdown of what actually shipped, how it works under the hood, and how we wire it into production TypeScript, Next.js, Astro, SvelteKit, React, and Angular applications. Last week, Google released Gemini 2.0, and after reading this blog post about parsing PDFs using Gemini 2.0, I decided to rebuild the sample using Genkit.

## The Mental Model: Why Extracting structured data from PDFs using Gemini 2.0 and Genkit Matters for Web Devs

As a Google Developer Expert (GDE) for Firebase and Senior Solutions Engineer at CodingCat.dev, I evaluate every Firebase update through one lens: does it reduce boilerplate while keeping our runtime security and cloud bills predictable? Firebase Genkit gives full-stack engineers strongly typed flows, dotprompt templates, local developer UI trace inspection, and production observability wired directly into Google Cloud and Firebase.

> AJ Pro-Tip from Alex: Always wrap your Genkit flows with explicit Zod input/output schemas and enforce Firebase App Check attestation on callable endpoints so bots cannot rack up unauthenticated token spend.

## How It Works: Technical Breakdown & Implementation

For those who are unfamiliar, Genkit is an AI integration framework that makes implementing AI features for your apps easier.

In this blog post, I am going to focus on Genkit for Node.js, but the team is also working on building support for Go ([**available in alpha**](https://genkit.dev/go/docs//get-started-go)) and Python ([**under development**](https://github.com/firebase/genkit/tree/main/py)). This means you can use Genkit on any runtime that supports one of those three languages. The sample app I’m discussing in this blog post is a simple command-line utility that you can run on macOS or any other platform that supports Node.js, but you can also use Genkit on the backend, for example in [**Cloud Functions for Firebase**](https://firebase.google.com/docs/functions), [**Cloud Run**](https://cloud.google.com/run), or any other environment that supports Node.js.

### Setup

Before you can use Gemini 2.0 Flash in Genkit, you need to install and add Genkit and the Google AI SDK plugin for Genkit.

You can then instantiate Genkit and define a default model. This model will then be used in all subsequent calls you make via the `ai` instance.

```
import
import

// instantiate Genkit
const
  plugins: [
  model: gemini20Flash,
});
```

For a detailed walkthrough of the setup process, check out this video:

### Extracting structured data

In this first example, let’s look at how you can use Gemini 2.0 Flash and Genkit to extract structured data from PDFs. Let’s imagine you’ve received a PDF with an invoice. To further process the invoice, you need to extract the invoice number, date, all the line items (including description, amount, and their individual price), as well as the gross price of the invoice.

Thanks to Gemini’s [**vision capabilities**](https://ai.google.dev/gemini-api/docs/vision?lang=node), we can let it handle the OCR for us. The following code snippet shows how to call any LLM via Genkit’s unified generation API, and pass a multimodal prompt (including a URL to the PDF file we want to analyse as well as an instruction).

```
export
  const
    prompt: [
      { media: { url } },
      { text:
    ]
  });
  return
}
```

If you’d call this method now for example by passing [**this URL**](https://storage.googleapis.com/generativeai-downloads/data/pdf_structured_outputs/invoice.pdf) (which points to an invoice), you’d get the following result:

```
[
  {
    'Invoice no': '
    'Date of issue': '
    Seller: {
      Name: 'Williams LLC',
      Address: '
      Tax Id': '
      IBAN: 'GB
    },
    Client: {
      Name: 'Hernandez-Anderson',
      Address: '
      'Tax Id': '
    },
    Items: [
      [Object], [Object],
      [Object], [Object],
      [Object], [Object],
      [Object]
    ],
    Summary: {
      'VAT [%]': '
      'Net worth': '
      VAT: '
      'Gross worth': '
      Total: '$
      'Total VAT': '$
      'Total Gross worth': '$
    }
  }
]
```

Which is pretty impressive - after all, the model was able to correctly extract all information. However, this might not be in the format you need this in.

This is where [**Genkit’s support for structured output**](https://g.dev/ai/genkit-structured-output-models) comes in handy. You can define a schema for your data using [**Zod**](https://zod.dev/), like this:

```
export
  const
    prompt: [
      { media: { url } },
      { text:
    ]
  });
  return
}
```

Then, pass the schema to Genkit’s `generate` call to enforce this schema.

```
export
  const
    prompt: [
      { media: { url } },
      { text:
    ],
  });
  return
}
```

And now, calling the function with the same PDF file will yield the following result:

```
{
  date: '
  invoiceNumber: '
  items: [
    {
      description: 'Lilly Pulitzer dress Size
      grossWorth:
      quantity:
    },
    {
      description: 'New ERIN Erin Fertherston Straight Dress White Sequence Lining Sleeveless SZ
      grossWorth:
      quantity:
    },
    {
      description: 'Sequence dress Size Small',
      grossWorth:
      quantity:
    },
    {
      description: 'fire los angeles dress Medium',
      grossWorth:
      quantity:
    },
    {
      description:
      grossWorth:
      quantity:
    },
    {
      description: 'Lularoe Nicole Dress Size Small Light Solid Grey/ White Ringer Tee Trim',
      grossWorth:
      quantity:
    },
    {
      description: 'J.Crew Collection Black & White sweater Dress sz S',
      grossWorth:
      quantity:
    }
  ],
  totalGrossWorth:
}
```

Since this adheres to the schema we provided, it will be much easier to process.

Gemini 2.0 Flash supports structured output, but even if you’re using an LLM that doesn’t support natively structured output, Genkit can help by augmenting the prompt and coercing the returned data for you. What’s really amazing about this: you won’t have to change your code, thanks to Genkit’s unified generation API (check out [**this thread on Bluesky**](https://bsky.app/profile/peterfriese.dev/post/3li5dz6evvc2n) in which I demonstrate this).

### Parsing hand-written form input

I mentioned Gemini’s OCR capabilities, so let’s try extracting the hand-written entries in the following PDF:

One way to achieve this would be to define a new schema and then call Genkit’s `generate` method using this new schema. But there is a more flexible way to do this. It turns out that the actual code for calling Gemini to extract data from a PDF is always the same - only the output format (and potentially the instructions) change for each type of PDF we want to analyse.

So why not make this a bit more generic?

Genkit provides a way to extract prompts into external files, using the [**Dotprompt**](https://genkit.dev/docs/dotprompt) library. This allows you to keep your code free from any prompt, making it easier to change the prompt independently of the code.

```
---
model
input
  schema
    pdfUrl
  default
    pdfUrl
output
  schema
    formNumber
    startDate
    beginningOfYear
    endOfYear
    name
    address
    phoneNumber
    ein
---
Extract the hand-written form data.

{{media url
```

This prompt file defines the model Genkit should use (Gemini 2.0 Flash), as well as the prompt itself. But it also specifies the input and output schemas for the prompt. For example, under `input/schema`, we define an input parameter named `pdfUrl` of type `string`, which can then be used in the prompt to pass the URL. Likewise, under `output/schema`, we define all the keys we want to extract from the PDF file, including their types.

Note that the descriptions of the keys do not just serve the purpose of documentation - Genkit will use them in the prompt to specifically tell the model how to find the data for the respective keys.

To load a prompt from a file, use `ai.prompt(filename)`. This has a beneficial side effect: we can now create`.prompt` files for all the different types of data we want to extract from PDF files, and just pass the filename to the `extract` function:

```
export
  const
  const
  return
}
```

Instead of using `ai.generate()` to call the LLM, you can execute a prompt by caling the prompt itself, and passing the required parameters.

Calling this method using `extract("form", "https://storage.googleapis.com/generativeai-downloads/data/pdf_structured_outputs/handwriting_form.pdf")` will yield the following result:

```
{
  address: '
  beginningOfYear:
  ein: '
  endOfYear:
  formNumber: 'CA
  name: 'Annual Return Plan',
  phoneNumber: '
  startDate: '
}
```

Which - if you ask me - is mind-boggling.

### Where to go from here

To learn more about Genkit, check out the [**documentation**](https://genkit.dev/docs/get-started/). Also, feel free to drop by my [**weekly livestreams**](https://www.youtube.com/playlist?list=PLsnLd2esiGRTmfGZcZMnEy6hkBHXBH_en) - I’m currently working on implementing a Second Brain app, and I’m using Genkit for some of the AI-powered features.

If you’d like to try out the sample app, the [**source code is available on GitHub**](https://github.com/peterfriese/genkit-samples).

## Production Web Architecture & Security Checklist

Whether we are deploying on [**Firebase App Hosting**](https://firebase.google.com/docs/app-hosting), Cloudflare Workers, or Vercel, shipping AI features to production requires defense-in-depth across three layers:

- **Enforce Firebase App Check & Attestation: ** Protect your web endpoints, Cloud Functions, Data Connect GraphQL resolvers, and Firebase AI Logic calls against unauthorized replay or billing abuse using [**Firebase App Check with reCAPTCHA Enterprise**](https://firebase.google.com/docs/app-check).
- **Validate Locally with the Firebase Emulator Suite: ** Run deterministic integration tests against local Firestore, Auth, Functions, and Data Connect PostgreSQL emulators before promoting schema or rule changes to production.
- **Keep Server & Client Boundaries Type-Safe: ** Use the modular Firebase Web SDK and generated TypeScript bindings so SSR frameworks like Next.js, Astro, SvelteKit, and Angular serialize state cleanly without hydration flickers.

## Conclusion

Accelerate your full-stack web and AI development by pairing **Extracting structured data from PDFs using Gemini 2.0 and Genkit**  with strict Security Rules, Firebase App Check, and local emulator testing. Come tell us on [**X (@CodingCatDev)**](https://x.com/CodingCatDev) and [**LinkedIn (CodingCat.dev)**](https://www.linkedin.com/company/codingcatdev) how you are using Firebase in your stack, and subscribe to the [**CodingCat.dev YouTube Channel**](https://www.youtube.com/@CodingCatDev?sub_confirmation=1) so you don't miss what we build next!

## Watch Our Related Firebase Video Tutorial on YouTube

Want to see how we architect and debug Firebase web applications live? Watch our related CodingCat.dev session [**Firebase SQL with Data Connect**](https://youtu.be/N25khadfUag) below, and make sure to [**Subscribe to CodingCat.dev on YouTube**](https://www.youtube.com/@CodingCatDev?sub_confirmation=1) for weekly full-stack web and AI deep dives:

## Resources, Official Docs & Related CodingCat.dev Guides

- **Primary Release Source: ** [Extracting structured data from PDFs using Gemini 2.0 and Genkit on the Official Firebase Blog](https://firebase.blog/posts/2025/02/gemini-genkit-pdf-structured-data/) (published 2025-02-20 by Peter Friese)
- **Official Firebase Documentation: ** [Firebase Developer Docs & Web SDK Reference](https://firebase.google.com/docs/web/setup)
- **Related on CodingCat.dev: [Announcing Firebase Genkit 1.0 for Node.js](/post/firebase-2025-02-announcing-genkit)**
- **Related on CodingCat.dev: [Firebase + Gemini Code Assist](/post/firebase-2025-03-gemini-code-assist)**
- **Related on CodingCat.dev: [Firebase SQL with Data Connect](/podcast/firebase-sql-with-data-connect)**
- **Related on CodingCat.dev: [Build a Custom No-Code App in 90 Minutes](/podcast/build-a-custom-no-code-app-in-90-minutes)**

## Frequently Asked Questions (FAQ)

### What are the key capabilities introduced in Extracting structured data from PDFs using Gemini 2.0 and Genkit?

Extracting structured data from PDFs using Gemini 2.0 and Genkit (announced 2025-02-20 by the Firebase team) enhances the platform for Firebase, Google Cloud, Web Dev, AI, TypeScript, Serverless workloads. Last week, Google released Gemini 2.0, and after reading this blog post about parsing PDFs using Gemini 2.0, I decided to rebuild the sample using Genkit. Developers can review the primary announcement on the [**Firebase Blog**](https://firebase.blog/posts/2025/02/gemini-genkit-pdf-structured-data/) alongside our implementation patterns and production checklist above.

### How do web developers use Extracting structured data from PDFs using Gemini 2.0 and Genkit with Next.js, SvelteKit, React, or Angular?

Web developers can integrate these features using the modular Firebase JavaScript/TypeScript SDK, Firebase Data Connect web bindings, and [**Firebase App Hosting**](https://firebase.google.com/docs/app-hosting). Always pair client-side SDK initialization with Firebase App Check and strict Security Rules, and test locally with the Firebase Local Emulator Suite before deploying.

### Where can I watch video tutorials on Firebase Web and AI development?

You can watch our hands-on Firebase video walkthroughs on the [**CodingCat.dev YouTube Channel**](https://www.youtube.com/@CodingCatDev?sub_confirmation=1), including [**Firebase SQL with Data Connect**](https://youtu.be/N25khadfUag), and follow us on [**X (@CodingCatDev)**](https://x.com/CodingCatDev) for weekly full-stack tutorials.

## Recent Posts

[View More](/blog/page/1)[![PHP2Shell (CVE-2026-109618): How AI Found a Decade-Old Bug in 70% of the Web](https://cdn.sanity.io/images/hfh83o0w/production/e83065d0a4e2f841ce64d3f4aea8ddbbf07e5cb9-1376x768.jpg?w=1376&q=82&fit=max&auto=format)](/post/php2shell-cve-2026-109618-ai-php-security-scramble)

### [PHP2Shell (CVE-2026-109618): How AI Found a Decade-Old Bug in 70% of the Web](/post/php2shell-cve-2026-109618-ai-php-security-scramble)

October 22, 2026

Two critical pre-authentication RCE flaws in PHP (CVE-2026-109618 and CVE-2026-109619), uncovered by AI models from OpenAI and Google Gemini, sparked a global patching scramble across 70% of the web. Here is how PHP2Shell works and how to patch your servers.

[![Alex Patterson](https://cdn.sanity.io/images/hfh83o0w/production/a606dfaa381aab08ddbe16a33fe1d3e1fb37c927-400x400.jpg?w=96&h=96&q=82&fit=max&auto=format)Alex Patterson](/author/alex-patterson)[![Sanity Knowledge Bases](https://cdn.sanity.io/images/hfh83o0w/production/38b0f021c51d97043c8a1ebf31d505a0ea877b7c-2752x1536.jpg?w=1920&q=82&fit=max&auto=format)](/post/sanity-knowledge-bases)

### [Sanity Knowledge Bases](/post/sanity-knowledge-bases)

October 17, 2026

Eliminate query-time guessing in your AI agents. Learn how Sanity Context Knowledge Bases compile structured datasets, crawled documentation websites, and uploaded files ahead of time, resolve conflicting facts, and serve a compact outline over Context MCP.

[![Alex Patterson](https://cdn.sanity.io/images/hfh83o0w/production/a606dfaa381aab08ddbe16a33fe1d3e1fb37c927-400x400.jpg?w=96&h=96&q=82&fit=max&auto=format)Alex Patterson](/author/alex-patterson)