annotation_for_all
A standalone Flutter toolkit for annotating PDFs: freehand drawing, highlight, underline, grading marks (tick / cross / circle / question mark / number stamps), and image insertion.
You hand it a PDF, the user annotates it, you get the annotated PDF back as bytes. That's it β no backend, no storage assumptions, no accounts. What you do with the bytes (save locally, upload, email, whatever) is entirely up to your app.

Features
- βοΈ Freehand drawing with a color picker
- ποΈ Highlight and underline selected text
- β Grading marks: tick, cross, circle, question mark, number stamps (handy for marking student submissions)
- πΌοΈ Image insertion (from gallery, auto-compressed if large)
- πΌοΈβ‘οΈπ Photos/scans work too β
FileAnnotator/ImageToPdfwrap an image as a one-page PDF automatically, so it goes through the same annotator - π
FileAnnotator.openhandles "whatever the user tapped" generically β PDF or image, it annotates; anything else, it offers View/Download instead - π Add/remove blank pages (mobile/desktop only β see Platform notes)
- β©οΈ Undo/redo and a one-tap reset
- Works on Android, iOS, and Web
Install
dependencies:
annotation_for_all: ^0.1.0
Quick start
The simplest way β push the editor and await the result:
import 'package:annotation_for_all/annotation_for_all.dart';
final bytes = await PdfAnnotator.open(context, pdfFile: myPdfFile);
if (bytes != null) {
// user saved β bytes is the fully annotated PDF
await File('checked.pdf').writeAsBytes(bytes);
} else {
// user cancelled without saving
}
On web, pass pdfBytes instead of pdfFile (there's no writable file system
in the browser):
final bytes = await PdfAnnotator.open(context, pdfBytes: myPdfBytes);
Grading workflow
enableMarksDialog is off by default β apps that aren't grading
submissions (a banking app annotating a statement, say) never see this
dialog unless they opt in. If you are grading, turn it on and optionally
set totalMarks for the "out of N" display; Save will then ask for a mark
before handing you both:
await Navigator.push(context, MaterialPageRoute(
builder: (context) => PdfAnnotatorScreen(
pdfFile: submissionFile,
enableMarksDialog: true,
totalMarks: 100, // optional β omit for an open-ended mark with no total shown
onSaved: (bytes, {enteredMarks}) {
// enteredMarks is e.g. "87" β upload bytes + save the mark however you like
Navigator.pop(context);
},
),
));
Even with the dialog on, entering a mark is never mandatory β it always offers "Skip & Save" alongside "Save & Submit", so the person actually using the app decides whether they want to bother with it.
Turning off tools you don't need
Every capability is an independent flag:
PdfAnnotatorScreen(
pdfFile: file,
enableDraw: true,
enableHighlight: true,
enableUnderline: true,
enableImage: false, // no image insertion
enablePageManagement: false,
onSaved: (bytes, {enteredMarks}) { /* ... */ },
)
Choosing which grading marks to show
enabledMarks controls the marks toolbar individually β pass just the
ones you want (e.g. only Tick and Cross, no number/circle/question mark):
PdfAnnotatorScreen(
pdfFile: file,
enabledMarks: {MarkType.rightTick, MarkType.incorrectCross},
onSaved: (bytes, {enteredMarks}) { /* ... */ },
)
Leave it unset for all five (tick, cross, number, circle, question mark), or
pass an empty set ({}) to hide the marks toolbar entirely.
One entry point for "whatever the user tapped"
If you're handling arbitrary attachments and don't know ahead of time whether
it's a PDF, an image, or something else entirely, use FileAnnotator.open
instead of PdfAnnotator.open:
final result = await FileAnnotator.open(
context,
bytes: fileBytes, // or `file: File(...)` off web
fileName: 'homework.docx',
);
switch (result.handledAs) {
case FileHandledAs.annotated:
// it was a PDF, or an image that got wrapped into one β result.annotatedBytes
break;
case FileHandledAs.viewed:
case FileHandledAs.downloaded:
// wasn't a PDF/image β the user viewed or downloaded it instead
break;
case FileHandledAs.cancelled:
break;
}
What it does, based on the file's extension:
.pdfβ opens directly in the annotator.- Images (jpg/jpeg/png/gif/bmp/webp) β wrapped as a one-page PDF first
(via
ImageToPdf.convert, also usable on its own), then opens the same annotator β so a photographed/scanned submission gets the exact same drawing/marks/highlight tools as a real PDF. - Anything else (docx, xlsx, ...) β there's no in-app renderer for arbitrary file types, so instead of failing silently, the user is asked to View (opens with whatever the OS/browser has registered for that file type) or Download (saves it) β nothing is annotated in this case.
Platform notes
-
Web requires a one-time
index.htmlsetup step. The PDF viewer renders pages on web using the PDF.js library, which isn't bundled automatically β you must add this to your app'sweb/index.html, inside<body>, before the Flutter bootstrap script:<script type="module" async> import * as pdfjsLib from 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.9.155/pdf.min.mjs'; pdfjsLib.GlobalWorkerOptions.workerSrc = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.9.155/pdf.worker.min.mjs"; </script>Without this, every PDF fails to open on web with a generic "There was an error opening this document" message β the bytes are fine, the viewer just has no renderer to hand them to. See syncfusion_flutter_pdfviewer's web docs if you need a different PDF.js version.
-
Page add/remove is mobile/desktop only. There's no writable file system on web, and page management works against a
Fileβ the flag is silently ignored on web rather than crashing. -
iOS requires a photo library usage description. The Image tool uses
image_picker, and iOS crashes on first use if your app'sInfo.plistdoesn't declare why it needs photo library access. Add this to your ownios/Runner/Info.plist(not something this package can add for you):<key>NSPhotoLibraryUsageDescription</key> <string>Used to insert a photo into the document you're annotating.</string> -
Syncfusion license: this package renders and edits PDFs using
syncfusion_flutter_pdf/syncfusion_flutter_pdfviewer, which are free to use under Syncfusion's Community License for qualifying individuals and small businesses (revenue/team-size thresholds apply) β check syncfusion.com/license if your usage might fall outside that. This package can't work without them, so that license's terms carry through to anything that depends on it.
Attribution
The annotation controllers (drawing, highlight, underline, image, save) are
adapted from the nextgen_pdf_editor
package (BSD-3-Clause, Β© Asint.net). See NOTICE for details. The grading
marks feature and the top-level API are original to this package.
License
BSD-3-Clause β see LICENSE.
Libraries
- annotation_for_all
- A standalone Flutter PDF annotation toolkit: freehand drawing, highlight, underline, text boxes, grading marks (tick / cross / circle / question mark / number stamps), and image insertion β with no backend or storage assumptions. You get the annotated PDF back as bytes; what you do with them is up to you.