---
title: "Three generations of Flutter interop: How we rebuilt our SDK on native bindings"
canonical_url: "https://www.nutrient.io/blog/nutrient-flutter-bindings-architecture/"
md_url: "https://www.nutrient.io/blog/nutrient-flutter-bindings-architecture.md"
last_updated: "2026-08-26T15:10:28.350Z"
description: "The engineering story behind Nutrient Flutter SDK’s bindings architecture: from hand-written platform channels to Pigeon to generated JNI/FFI bindings, the decisions we locked down first, and the bugs that surfaced once the method channel was gone."
---

When we [wrote about Pigeon](https://www.nutrient.io/blog/pigeon-usage-at-nutrient/) in November 2025, we ended on a hedge. The last section was called “The future: FFIgen and JNIgen,” and we said we were “in the early phases of experimenting” with a bindings-based approach. That was the honest position at the time: We thought it would work, and we weren’t ready to promise it.

It worked. Nutrient Flutter SDK 6.0 makes the bindings API the recommended surface for new apps, and it drives each platform’s native SDK directly (JNI on Android, FFI on iOS, and JS interop on the web) with no method channel in the path.

This post is the part that doesn’t fit in a changelog: how we got here across three generations of the bridge layer, the decisions we locked down before writing the implementation, and the things that bit us once the channel was gone. If you just want to know what changed in 6.0 and how to upgrade, the [migration guide](https://www.nutrient.io/guides/flutter/migration-guides/flutter-6-migration-guide.md) is the faster read.

## How we got here

Every cross-platform Flutter plugin has to answer the same question: How does Dart talk to the native SDK underneath? We’ve answered it three times in about seven years, and each answer was really a reaction to what the last one couldn’t do.

The first answer, back in 2019, was a hand-written `MethodChannel`: `invokeMethod` calls over a global `com.nutrient.global` channel and a per-view `com.nutrient.widget.<id>` one, with arguments crossing as untyped maps and the method itself named by a string. It was the idiomatic Flutter approach of the day, and it worked, but it kept the compiler on the sidelines: A mistyped method name or a wrong-shaped map failed at runtime on one platform, and you’d find out when whoever ran that platform’s example app next hit it. Every new API meant hand-writing the Dart call and the string dispatch on both Android and iOS and keeping all three synchronized by discipline alone.

So the second answer, in 4.0 (November 2024), was to stop writing that bridge and start generating it. With Pigeon, you declare the interface once in a Dart schema and it emits the Dart caller alongside the Kotlin and Swift handlers, which turns a signature change from a runtime surprise on one platform into a compile error on all of them at once. That was a real step forward, enough that we [wrote up the wins and the limitations](https://www.nutrient.io/blog/pigeon-usage-at-nutrient/) at the time. We rolled it in gradually, shipping a `useLegacy:` flag so apps could stay on the old channel while we moved. By 4.2.2 (March 2025), we restructured the Android handler to prepare for a clean deprecation path.

But Pigeon only generates the serialization; it doesn’t remove it. Every call still crosses a serialized boundary and still has to be asynchronous. And here was the wall we kept hitting: Pigeon models flat data and method calls, not the inheritance hierarchies our native SDKs are actually built from. Worse, it only ever bridged what we had explicitly declared, so reaching a native API we hadn’t exposed meant forking the plugin and writing the channel yourself.

This is what led to the third answer, and the subject of the rest of this post. We weren’t alone in reaching for it. The Flutter team reached the same conclusion in its own [case for moving past method channels](https://blog.flutter.dev/flutters-path-towards-seamless-interop-4bf7d4579d9a), framing FFIgen and JNIgen as the successors to method channels precisely because, “unlike method channels, FFIgen and JNIgen will enable APIs to be called synchronously and will support tree-shaking.” Those are the same two limits we’d hit. Rather than generate a *bridge* between Dart and native, we generate *bindings*: Dart that calls the native SDK’s own types directly, one mechanism per platform:

| Platform | Binding mechanism                |
| -------- | -------------------------------- |
| Android  | JNI via `jnigen`                 |
| iOS      | FFI via `ffigen`                 |
| Web      | JS interop via `dart:js_interop` |

We shipped it as a beta in 5.4 (February 2026), running side by side with Pigeon, and we promoted it to the recommended surface for new apps in 6.0 (July 2026) behind a new `package:nutrient_flutter/bindings.dart` import. The difference from a bridge is architectural rather than cosmetic: Instead of a native object sitting on the far side of a channel that you poke by name, the object is simply *there* (`PSPDFViewController`, `PdfFragment`, the web SDK’s `Instance`), and the plugin stops having to be the gatekeeper for every capability it never thought to expose.

**Where the generations stand in 6.0.** The three-generation stack is now two. 6.0 removed the generation-1 hand-written `MethodChannel` bridge outright: It duplicated Pigeon method-for-method and was only reachable through already-deprecated code paths, so `Pspdfkit.useLegacy` and the `useLegacy:` parameter are gone with no replacement. Pigeon is deprecated but not removed: The legacy `Nutrient` class still works and existing integrations keep running, but it’s marked for removal in a future major release. `Nutrient.present()` and `presentInstant()` are likewise deprecated in 6.0, not removed.

One nuance worth naming, because the two surfaces are easy to conflate: The legacy API still rides on Pigeon, and the bindings API rides on JNI/FFI/JS interop. A handful of `MethodChannel`s do survive in 6.0, but purely as *transports*: Their `binaryMessenger` backs the Pigeon APIs and carries the adapter-bridge callbacks. They never carry the old `invokeMethod` request/response API.

## A federated plugin in three tiers

The SDK is structured as a federated Flutter plugin, following the [official Flutter federated-plugin pattern](https://docs.flutter.dev/packages-and-plugins/developing-packages#federated-plugins), the setup Flutter recommends for a plugin that spans multiple platforms. The bindings system splits into three tiers: an app-facing package, an abstract contract package, and one concrete implementation package per platform.

```

┌─────────────────────────────────────────┐
│          Flutter app/user code          │
├─────────────────────────────────────────┤
│   nutrient_flutter  (widgets + bridge)  │
├─────────────────────────────────────────┤
│  nutrient_flutter_platform_interface    │  ← contracts (abstract)
├──────────────┬──────────────┬───────────┤
│   _android   │     _ios     │   _web    │  ← implementations (concrete)
└──────────────┴──────────────┴───────────┘

```

Each package has a single, clear responsibility:

| Package                               | Role                                                       |
| ------------------------------------- | ---------------------------------------------------------- |
| `nutrient_flutter`                    | App-facing widgets (`NutrientDocumentView`), legacy bridge |
| `nutrient_flutter_platform_interface` | Abstract interfaces, models, controller base               |
| `nutrient_flutter_android`            | Android implementation (JNI)                               |
| `nutrient_flutter_ios`                | iOS implementation (FFI)                                   |
| `nutrient_flutter_web`                | Web implementation (JS interop)                            |

The one rule that holds the whole thing together is that all contracts live in the platform-interface package, and no platform-specific code lives there. The Android, iOS, and web packages each implement those contracts against their native SDK, and at runtime, the top layer resolves the right one and hands back a typed controller. Running through the whole arrangement is a bias toward deprecating rather than ripping out: The old Pigeon surface stays in 6.0 with warnings, so an app can move a screen at a time.

That federation isn’t entirely free, and the sharpest edge is one worth being upfront about. A bindings app has to list every platform package as a direct dependency:

```yaml

dependencies:
  nutrient_flutter: ^6.0.0
  nutrient_flutter_platform_interface: ^1.1.0
  nutrient_flutter_android: ^1.1.0
  nutrient_flutter_ios: ^1.1.0
  nutrient_flutter_web: ^1.1.0

```

That’s because the platform packages declare `implements: nutrient_flutter`, and a non-endorsed federated implementation only auto-registers on a direct dependency, never transitively. We tried both ways out and reverted both. Dropping `implements:` looked right on the Dart side, but it’s load-bearing on the native side too: It tells Flutter to suppress `nutrient_flutter`’s own native module, and without it, both modules link into the app and collide on the duplicate native types they share (`FlutterAppCompatActivity` among them), failing the Android dex-merge and the iOS linker outright. Adding `default_package` federation instead stops Flutter from registering `nutrient_flutter`’s legacy plugin at all, which takes the Pigeon handlers behind every existing `Nutrient.present(...)` call down with it. Both roads dead-end on the same root cause: The legacy Android and iOS plugins still ship a native module of their own. Collapsing to one direct dependency is a follow-up that waits on relocating that legacy native code into the federated modules.

## Contracts, adapters, and controllers

Two roles do most of the work in that middle layer, and keeping them separate is the whole trick.

The first is the *contract*. The platform-interface package defines abstract interfaces (a document interface, and separate manager interfaces for annotations, bookmarks, and forms) and nothing else. No platform code lives here. Because these are pure contracts, the same call reads and behaves identically whether the document underneath is the Android, iOS, or Web SDK, and where a platform genuinely can’t honor one, that surfaces as a documented gap rather than a silent difference. Splitting the managers out by concern also composes: You reach an operation as `controller.document.annotations`, not as a flat pile of methods. That composition is the direct answer to what Pigeon couldn’t do for us, since a bridge that models flat calls has to flatten the class hierarchies our native SDKs are built from.

The second role is the *adapter*, and the design collapses it into the controller: A platform adapter _is_ the controller you’re handed. `NutrientDocumentView` resolves the right one at view-creation time and gives you back a typed `NutrientController` once the document is ready. The concrete adapters implement the contracts against their native SDK and hold the per-view state: the fragment and document on Android, the FFI handles on iOS, the `Instance` on web. That per-view state is also why an adapter can’t be shared across two live views at once; the SDK asserts in debug if you try, and each concurrent view gets its own.

The one place this layering becomes tangible for everyday code is events. Under the channel, events were platform-specific registrations you wired up differently on each side. The bindings collapse them into a single typed `controller.events` stream (the same shape everywhere, filterable per event type or exhaustively `switch`-able), with the platform-specific streams still there underneath when you need them:

```dart

NutrientDocumentView(
  documentPath: 'document.pdf',
  onControllerReady: (controller) {
    controller.events.pageChanged.listen((e) => print('Page ${e.pageIndex}'));
  },
);

```

## The escape hatch is the point

This is the decision we’d defend hardest. The unified contract is deliberately the *minimum* common surface, and every adapter exposes the raw native handles right next to it under a consistent `native*` convention: `AndroidAdapter.nativePdfDocument`, `IOSAdapter.nativeViewController`, `NutrientWebAdapter.nativeInstance`. The question we held the design to was blunt: Is the door to the native object graph open on every platform? It had to be yes without qualification, because the alternative is the failure mode we lived with for years: A capability we hadn’t explicitly bridged simply didn’t exist for you, and your only options were to fork the plugin or wait on us. Our roadmap became your blocker.

Now a gap is an inconvenience, not a wall. You reach `PSPDFFormParser`, `FormProvider`, or the web SDK’s `Instance` directly and keep moving, and we get to be honest about what isn’t bridged yet instead of treating every gap as an emergency.

## What bit us along the way

Dropping the method channel removes a serialization boundary, and that boundary was also doing work you don’t notice until it’s gone. A channel forces everything through one narrow, uniform gate: same threading semantics, same asynchronous contract, same “everything is a map” typing on every platform. Bindings hand you the native object as it actually is, which means you also inherit each platform’s real rules. These are the ones that cost us the most time.

The one we’d warn a past version of ourselves about is a threading trap on Android. Dart JNI calls run on Flutter’s UI thread, which on Android is *not* the platform/main thread, and view-mutating `PdfFragment` calls (`zoomTo`, `setPageIndex`, `scrollTo`, `enterAnnotationCreationMode`) take a “run immediately if laid out” fast path that only fires on the main thread. Called off it, they silently no-op: no exception, no log, no crash, just a clean return and a view that doesn’t move. Debugging that is miserable precisely because nothing is wrong: Your Dart code is correct, the object is real, the method exists, and the SDK is quietly deciding not to act. Under the old channel, the class of bug couldn’t happen, because the channel hopped to the platform thread for you and you never had to know it existed. We decided against replicating that hidden hop, since auto-hopping every call would make the fast path unpredictable and bury a real cost. Instead, we reached for an explicit idiom: `AndroidAdapter.runOnMainThread`, wrapping `dart:ui`’s `runOnPlatformThread`:

```dart

await adapter.runOnMainThread(() {
  adapter.nativePdfFragment?.setPageIndex(3, true);
});

```

Read-only JNI calls like `getPageCount` and `getZoomScale` are safe from either thread and don’t need it.

Android had a second, more structural surprise waiting. [jnigen](https://dart.dev/interop/java-interop) can’t call Kotlin `suspend` functions at all: It drops the trailing `Continuation` parameter and emits a method ID that doesn’t exist, so the call fails at runtime with `NoSuchMethodError`. Our native Android SDK is Kotlin-first, so plenty of the APIs we wanted were `suspend`. The way through is to lean on the SDK’s own synchronous and RxJava companions; we wrapped the annotation ones in a public `AnnotationProviderBlocking`, and the same idiom generalizes to looking up any `*Blocking` or `*RxJava` companion via `JClass.forName(...)` and `staticMethodId(...)`.

```dart

final provider = adapter.nativePdfDocument!.getAnnotationProvider();
final annotations = AnnotationProviderBlocking.getAnnotations(provider, 0);
// … use, then release each element and the list.

```

iOS is friendlier in one respect and stricter in another. Flutter merges the UI and platform threads there, so direct UIKit and FFI calls on the view controller are generally safe (a real advantage), but delegates come with a hard rule: A non-void Objective-C delegate method will `SIGABRT` when invoked from a non-isolate thread, and only void callbacks, via `implementAsListener`, are safe. That constraint has teeth in practice: It’s why the `note` and `delete` annotation menu actions still need a small Swift trampoline, because they hang off `menuForAnnotations`, a non-void delegate that can’t be implemented from Dart at all.

Beneath all of these sit the ordinary sharp edges of raw JNI, two of which cost us real debugging time and are now handled inside the bindings. Listener objects get garbage-collected while still natively registered unless you hold them in a Dart field, and passing a `float` to a `(F)` Java method through the high-level call puts a bare Dart `double` into the 64-bit `jvalue.d` slot while the JVM reads the 32-bit `.f` slot, so you get garbage rather than an error unless each value is wrapped in `JValueFloat`.

## What we’d tell you if you’re considering this

Seven years took us from hand-written channels, to Pigeon generating the bridge, to bindings that skip the bridge altogether. Each generation solved the last one’s biggest problem and introduced a new class of problem we hadn’t had before, which is just what the trade looks like, honestly.

Three things stand out looking back. The channel was doing more than we thought: not just serialization overhead to delete, but a uniformity layer (one threading model, one asynchronous contract, one type discipline everywhere), and once it’s gone, each platform’s real rules are yours to respect. Most of our hard bugs weren’t in the bindings but in assumptions the channel had quietly protected. That’s also why we kept reaching for a loud idiom over a silent convenience: `runOnMainThread` and the `*Blocking` companions name the sharp edge rather than paper over it, because a hidden fix trades a bug you can find for a cost you can’t see. And it’s why the escape hatch shipped *with* the abstraction, not after it: An abstraction with no way out turns every gap into a blocker on someone else’s roadmap.

For what changed in 6.0 and how to move an app across, see the [migration guide](https://www.nutrient.io/guides/flutter/migration-guides/flutter-6-migration-guide.md). If you’re starting fresh, import `package:nutrient_flutter/bindings.dart` and begin with `NutrientDocumentView`; if you’re upgrading, nothing forces your hand this release: The legacy surface is deprecated, not removed, so you can move one screen at a time.

### References

- [Flutter SDK 6 migration guide](https://www.nutrient.io/guides/flutter/migration-guides/flutter-6-migration-guide.md) — The full legacy-to-bindings mapping table and every breaking change.

- [Platform adapters guide](https://www.nutrient.io/guides/flutter/platform-adapters.md) — Architecture diagrams, the usage tiers, and writing a custom adapter.

- [Customize the SDK with native APIs](https://www.nutrient.io/guides/flutter/customize.md) — Reaching native APIs through the bindings.

- [Nutrient Flutter SDK API reference](https://www.nutrient.io/guides/flutter/api.md) — The current public surface.

- [Pigeon usage at Nutrient](https://www.nutrient.io/blog/pigeon-usage-at-nutrient/) — The previous chapter, on why we adopted Pigeon and where it fell short.

- [Flutter’s path towards seamless interop](https://blog.flutter.dev/flutters-path-towards-seamless-interop-4bf7d4579d9a) — The Flutter team’s own case for FFIgen and JNIgen over method channels.

- [Dart Java interop (`package:jnigen`)](https://dart.dev/interop/java-interop) — The official documentation for the Android binding generator.
---

## Related pages

- [The business case for accessibility: Five ways it drives enterprise value](/blog/5-ways-accessibility-drives-enterprise-value.md)
- [Accessibility Untangled Why It Matters Guide](/blog/accessibility-untangled-why-it-matters-guide.md)
- [Advanced Techniques For React Native Ui Components](/blog/advanced-techniques-for-react-native-ui-components.md)
- [`vector_store` holds your indexed documents (see the multimodal RAG post](/blog/agentic-rag.md)
- [Ai Document Automation Extraction To Action](/blog/ai-document-automation-extraction-to-action.md)
- [Ai Legal Assistant Document Authoring](/blog/ai-legal-assistant-document-authoring.md)
- [Amazon Textract Alternatives](/blog/amazon-textract-alternatives.md)
- [Start (clears any prior buffer), navigate the document, then stop into a file.](/blog/android-faster-pdf-rendering.md)
- [Android Pdf Out Of Memory Handling](/blog/android-pdf-out-of-memory-handling.md)
- [Angular File Viewer Pdf Image Office Files](/blog/angular-file-viewer-pdf-image-office-files.md)
- [Auto Tagging And Document Accessibility In Dotnet Sdk](/blog/auto-tagging-and-document-accessibility-in-dotnet-sdk.md)
- [Simple PII redaction.](/blog/automated-pii-removal.md)
- [Best Document Ai Platforms](/blog/best-document-ai-platforms.md)
- [Best Document Viewers](/blog/best-document-viewers.md)
- [Build Vs Buy Document Extraction](/blog/build-vs-buy-document-extraction.md)
- [The CEO’s AI playbook: Why decision architecture beats model selection](/blog/ceo-ai-playbook-decision-architecture.md)
- [1. Extract and chunk the PDF.](/blog/chat-with-pdf.md)
- [Complete Guide To Pdfjs](/blog/complete-guide-to-pdfjs.md)
- [Construction Document Data Extraction](/blog/construction-document-data-extraction.md)
- [Convert One Drive Files To Pdf In Sharepoint](/blog/convert-one-drive-files-to-pdf-in-sharepoint.md)
- [Create And Edit Pdfs In Flutter](/blog/create-and-edit-pdfs-in-flutter.md)
- [Create Pdfs With React](/blog/create-pdfs-with-react.md)
- [Creating A Document Scanner With Ocr In Python](/blog/creating-a-document-scanner-with-ocr-in-python.md)
- [Creating And Filling Pdf Forms Programmatically In Javascript](/blog/creating-and-filling-pdf-forms-programmatically-in-javascript.md)
- [The CTO’s AI playbook: Why accountability architecture beats orchestration](/blog/cto-ai-playbook-accountability-architecture.md)
- [Digital Signatures](/blog/digital-signatures.md)
- [Digital Workflow Automation](/blog/digital-workflow-automation.md)
- [Document Ai Vs Ocr](/blog/document-ai-vs-ocr.md)
- [Document Extraction Confidence Scores](/blog/document-extraction-confidence-scores.md)
- [Document Viewer](/blog/document-viewer.md)
- [Document Watermarking](/blog/document-watermarking.md)
- [Emerging threats: Your logging system may be an agentic threat vector](/blog/emerging-threats-your-logging-system.md)
- [Extract Patient Data On Premises](/blog/extract-patient-data-on-premises.md)
- [app.py](/blog/extract-text-from-pdf-using-python.md)
- [Fillable Pdf](/blog/fillable-pdf.md)
- [How To Add Digital Signature To Pdf Using React](/blog/how-to-add-digital-signature-to-pdf-using-react.md)
- [How To Build A Dotnet Maui Pdf Viewer](/blog/how-to-build-a-dotnet-maui-pdf-viewer.md)
- [How To Build A Flutter Pdf Viewer](/blog/how-to-build-a-flutter-pdf-viewer.md)
- [or](/blog/how-to-build-a-javascript-pdf-viewer-with-pdfjs.md)
- [How To Build A Javascript Pdf Viewer](/blog/how-to-build-a-javascript-pdf-viewer.md)
- [or](/blog/how-to-build-a-nextjs-pdf-viewer.md)
- [How To Build A Powerpoint Viewer Using Javascript](/blog/how-to-build-a-powerpoint-viewer-using-javascript.md)
- [Using Yarn](/blog/how-to-build-a-react-excel-viewer.md)
- [How To Build A React Native Pdf Viewer](/blog/how-to-build-a-react-native-pdf-viewer.md)
- [How To Build A React Powerpoint Viewer](/blog/how-to-build-a-react-powerpoint-viewer.md)
- [How To Build A Reactjs File Viewer](/blog/how-to-build-a-reactjs-file-viewer.md)
- [or](/blog/how-to-build-a-reactjs-pdf-viewer-with-react-pdf.md)
- [or](/blog/how-to-build-a-reactjs-pdf-viewer.md)
- [How To Build A Reactjs Viewer With Pdfjs](/blog/how-to-build-a-reactjs-viewer-with-pdfjs.md)
- [How To Build A Vuejs Pdf Viewer With Pdfjs](/blog/how-to-build-a-vuejs-pdf-viewer-with-pdfjs.md)
- [How To Build A Vuejs Pdf Viewer](/blog/how-to-build-a-vuejs-pdf-viewer.md)
- [How To Build An Android Pdf Viewer](/blog/how-to-build-an-android-pdf-viewer.md)
- [How To Build An Angular Pdf Viewer With Ng2 Pdf Viewer](/blog/how-to-build-an-angular-pdf-viewer-with-ng2-pdf-viewer.md)
- [How To Build An Angular Pdf Viewer With Pdfjs](/blog/how-to-build-an-angular-pdf-viewer-with-pdfjs.md)
- [How To Convert Docx To Pdf Using Javascript](/blog/how-to-convert-docx-to-pdf-using-javascript.md)
- [How To Convert Docx To Pdf Using Python](/blog/how-to-convert-docx-to-pdf-using-python.md)
- [How To Convert Html To Pdf Using Html2pdf](/blog/how-to-convert-html-to-pdf-using-html2pdf.md)
- [or](/blog/how-to-convert-html-to-pdf-using-react.md)
- [How To Convert Html To Pdf Using Wkhtmltopdf And Csharp](/blog/how-to-convert-html-to-pdf-using-wkhtmltopdf-and-csharp.md)
- [or](/blog/how-to-convert-html-to-pdf-using-wkhtmltopdf-and-python.md)
- [How To Convert Word To Pdf In Nodejs](/blog/how-to-convert-word-to-pdf-in-nodejs.md)
- [or](/blog/how-to-create-a-react-js-signature-pad.md)
- [How To Create Pdfs With React To Pdf](/blog/how-to-create-pdfs-with-react-to-pdf.md)
- [How To Edit Pdfs Using Ios Pdf Library](/blog/how-to-edit-pdfs-using-ios-pdf-library.md)
- [How To Embed A Pdf Viewer In Your Website](/blog/how-to-embed-a-pdf-viewer-in-your-website.md)
- [How To Extract Tables From Pdf And Images](/blog/how-to-extract-tables-from-pdf-and-images.md)
- [How To Generate Pdf From Html With Nodejs](/blog/how-to-generate-pdf-from-html-with-nodejs.md)
- [base_url tells WeasyPrint where to resolve relative asset paths](/blog/how-to-generate-pdf-reports-from-html-in-python.md)
- [How To Merge Pdfs Using Javascript](/blog/how-to-merge-pdfs-using-javascript.md)
- [How To Ocr Pdfs In Linux](/blog/how-to-ocr-pdfs-in-linux.md)
- [How To Print Pdf In Csharp](/blog/how-to-print-pdf-in-csharp.md)
- [Open an image.](/blog/how-to-use-tesseract-ocr-in-python.md)
- [From an HTML string.](/blog/html-in-pdf-format.md)
- [Html To Pdf In Javascript](/blog/html-to-pdf-in-javascript.md)
- [Javascript Document Editor](/blog/javascript-document-editor.md)
- [Javascript Pdf Editors](/blog/javascript-pdf-editors.md)
- [Javascript Pdf Libraries](/blog/javascript-pdf-libraries.md)
- [Langextract Vs Llamaindex Extraction Comparison](/blog/langextract-vs-llamaindex-extraction-comparison.md)
- [Linearized Pdf](/blog/linearized-pdf.md)
- [or](/blog/merge-pdfs.md)
- [Swift Package Manager](/blog/mobile-pdf-sdk.md)
- [`elements` come from your document parser — each has a type and content.](/blog/multimodal-rag.md)
- [Nutrient Flutter 6 Bindings Api](/blog/nutrient-flutter-6-bindings-api.md)
- [Nutrient Vs Conga Composer](/blog/nutrient-vs-conga-composer.md)
- [Online Document Viewer](/blog/online-document-viewer.md)
- [Open Pdf In Your Web App](/blog/open-pdf-in-your-web-app.md)
- [Building WCAG 2.2, Section 508, and PDF/UA-compliant PDFs with an SDK](/blog/pdf-accessibility.md)
- [Extract data from PDF files: A developer guide to structured data from PDFs and scans](/blog/pdf-data-extraction-developer-guide.md)
- [Pdf Extraction Benchmark Opendataloader Bench](/blog/pdf-extraction-benchmark-opendataloader-bench.md)
- [Pdf Extraction Document Case Studies](/blog/pdf-extraction-document-case-studies.md)
- [Pdf Page Labels](/blog/pdf-page-labels.md)
- [Pdf Sdk Compliance Security Checklist](/blog/pdf-sdk-compliance-security-checklist.md)
- [Pdf Sdk Performance Benchmark](/blog/pdf-sdk-performance-benchmark.md)
- [Pdf Ua Compliance Guide](/blog/pdf-ua-compliance-guide.md)
- [Pdfjs Accessibility Structtree Printing](/blog/pdfjs-accessibility-structtree-printing.md)
- [Pdfjs Advanced Loading Streaming Workers](/blog/pdfjs-advanced-loading-streaming-workers.md)
- [Pdfjs Annotation Editor Layer](/blog/pdfjs-annotation-editor-layer.md)
- [Pdfjs Area Annotations Canvas Capture](/blog/pdfjs-area-annotations-canvas-capture.md)
- [Pdfjs Coordinate Systems Pdf To Screen](/blog/pdfjs-coordinate-systems-pdf-to-screen.md)
- [Pdfjs Document Outline Bookmarks Metadata](/blog/pdfjs-document-outline-bookmarks-metadata.md)
- [Pdfjs Eventbus Guide](/blog/pdfjs-eventbus-guide.md)
- [macOS](/blog/pdfjs-file-format-conversion-to-pdf.md)
- [macOS](/blog/pdfjs-generating-pdf-thumbnails-pdf2pic.md)
- [Pdfjs Limitations Commercial Upgrade](/blog/pdfjs-limitations-commercial-upgrade.md)
- [Pdfjs Native Annotation Layer Forms](/blog/pdfjs-native-annotation-layer-forms.md)
- [Pdfjs Navigation Zoom Rotation](/blog/pdfjs-navigation-zoom-rotation.md)
- [Pdfjs Pdf Page Manipulation Pdf Lib](/blog/pdfjs-pdf-page-manipulation-pdf-lib.md)
- [Pdfjs React Viewer Setup](/blog/pdfjs-react-viewer-setup.md)
- [Pdfjs Rendering Overlays React Portals](/blog/pdfjs-rendering-overlays-react-portals.md)
- [Pdfjs Server Side Text Extraction](/blog/pdfjs-server-side-text-extraction.md)
- [Pdfjs Sticky Note Annotations](/blog/pdfjs-sticky-note-annotations.md)
- [Pdfjs Text Highlight Annotations](/blog/pdfjs-text-highlight-annotations.md)
- [Pdfjs Text Search Pdffindcontroller](/blog/pdfjs-text-search-pdffindcontroller.md)
- [Pdfjs Thumbnail Sidebar](/blog/pdfjs-thumbnail-sidebar.md)
- [Process Flows](/blog/process-flows.md)
- [React Native Pdf Annotation](/blog/react-native-pdf-annotation.md)
- [Using Yarn](/blog/react-pdf-editor.md)
- [React Pdf Loading States Errors Passwords](/blog/react-pdf-loading-states-errors-passwords.md)
- [React Pdf Setup Basic Rendering](/blog/react-pdf-setup-basic-rendering.md)
- [React Pdf Text Layer Custom Renderer](/blog/react-pdf-text-layer-custom-renderer.md)
- [Requisition System](/blog/requisition-system.md)
- [labels.py](/blog/route-documents-automatically-classify-api.md)
- [or](/blog/sample-blog-updated.md)
- [Sdk Product Updates Q2 2026](/blog/sdk-product-updates-q2-2026.md)
- [Add DWS MCP Server to your Claude Code project.](/blog/teaching-llms-to-read-pdfs.md)
- [Open an image file.](/blog/tesseract-python-guide.md)
- [Define the HTML part of the document.](/blog/top-10-ways-to-generate-pdfs-in-python.md)
- [Top 5 Javascript Pdf Viewers](/blog/top-5-javascript-pdf-viewers.md)
- [or](/blog/top-js-pdf-libraries.md)
- [Convert an HTML file to PDF.](/blog/top-ten-ways-to-convert-html-to-pdf.md)
- [Vector Pdf](/blog/vector-pdf.md)
- [Wcag2 Accessibility Requirements Documents](/blog/wcag2-accessibility-requirements-documents.md)
- [Web Sdk Is Now Headless](/blog/web-sdk-is-now-headless.md)
- [What Are Annotations](/blog/what-are-annotations.md)
- [What Is A Vpat](/blog/what-is-a-vpat.md)
- [What Is Document Processing](/blog/what-is-document-processing.md)
- [What Is Intelligent Document Processing](/blog/what-is-intelligent-document-processing.md)
- [What Is Pdf Ua](/blog/what-is-pdf-ua.md)
- [Why Pdfium Is A Trusted Platform For Pdf Rendering](/blog/why-pdfium-is-a-trusted-platform-for-pdf-rendering.md)
- [Why Your Ai Agent Hallucinates Pdf Table Data](/blog/why-your-ai-agent-hallucinates-pdf-table-data.md)

