Skip to content
Tech Stack21 September 2026 · 9 min readUpdated 30 September 2026

Storing Rich Text in FlutterFlow: Delta, HTML, Markdown

Pick the storage format before you pick the editor. Delta JSON, HTML and markdown each lock you into a different future, and swapping later means migrating every document you already have.

Storing Rich Text in FlutterFlow: Delta, HTML, Markdown

What you'll learn

  • Why does the storage format matter more than the editor?
  • What is Delta JSON, and when should you store it?
  • Which is better for rich text: Delta, HTML or markdown?
Article contents

Pick the storage format before you pick the editor. The editor is a widget you can swap. The format is every document your users have already written, and changing it means a migration script, a full backup and a very careful evening.

Consider a team that needs a rich text field in a FlutterFlow app. Someone searches the community forum, finds a Quill custom widget that looks good in a demo, wires its output to a Firestore string and ships. The toolbar works, bold is bold, and a field called body holds a blob of JSON. Nothing is wrong yet. Six weeks later someone asks for search across all notes, and the format presents its bill. That scenario is hypothetical, but the order of events is common enough to plan around.

Your first action: write down the three operations your app will perform most on stored documents. Be specific, such as "render in a list preview" rather than "display text".

FlutterFlow's palette does not settle the question for you. The built-in Markdown widget documents two properties, Data and Selectable, and it displays markdown; its documentation page describes no editing. Editing comes from custom code, so every rich text editor you add is also a storage decision. My own FlutterFlow Marketplace portfolio includes a Content Creator Toolkit bundle, submitted in the April 2026 batch, with five widgets: a Quill rich text editor producing Delta JSON, a drawing canvas, a markdown renderer, an HTML renderer, and a Fleather editor with PDF export. They are separate widgets because they solve separate problems that share a name.

Why does the storage format matter more than the editor?

A blank cream sheet of paper threaded through a black metal lattice overgrown with sage vines, an old keyboard half-buried in moss at the bottom of the frame

The format decides what you can render, search, diff, sanitise and migrate for years, while the editor only decides what typing feels like this week.

Think about what happens after a user hits save. The document is rendered on a detail screen, then again, smaller and plainer, in a list preview. Someone searches for a phrase inside it. A teammate wants to see what changed between versions. A client wants a PDF. Much later, someone asks why a document written on one platform renders differently on another.

Every one of those is a format operation, and none is an editor operation. The editor finished its job when the user stopped typing. So the useful question is not which editor is best for FlutterFlow, but which operations you will perform a thousand times a day and which you will perform twice. If Flutter itself is new to you, the short version of what Flutter is will help with the rest.

What is Delta JSON, and when should you store it?

A segmented green vine growing along a dark circuit board trace, each joint clearly visible, tipped with a small glowing cyan bud, with dust in the grooves and a dried brown leaf at its base

Delta is an operation log: an ordered list of insert, retain and delete instructions that rebuild a document when you replay them from the start.

The Quill Delta specification calls it "a strict subset of JSON" that is human readable and easily parsable by machines. An insert adds text or an embed with optional formatting attributes. A retain keeps existing characters and can apply formatting to them. A delete removes characters, and the spec notes that "since delete operations do not include what was deleted, a Delta is not reversible."

The shape is deliberate. The same page says Delta "is suitable for Operational Transform and can be used in realtime, Google Docs like applications." If two people type into one paragraph at the same moment, you need a format that expresses what changed, not only what the document looks like now.

flutter_quill is direct about where the result should live. As of 30 September 2026 its page reads: "We recommend storing the Document as Delta JSON instead of other formats (e.g., HTML, Markdown, PDF, Microsoft Word, Google Docs, Apple Pages, XML)." The stated reason is that HTML and Delta are structurally different, so converting back and forth can lose formatting.

Delta is awkward for search. A sentence with one bold word in the middle is stored as several inserts, not one string, so a naive query over the JSON can miss a phrase that is split by formatting. That is a reading of the format, not a measured failure, and it is fixable: extract plain text when you save, keep it in a second field, and index that. You would do the same on a FlutterFlow, Firebase and Stripe stack whichever format you chose.

Which is better for rich text: Delta, HTML or markdown?

There is no universally better format. Choose the one that makes your most frequent operation cheap: Delta for collaborative editing, HTML for content served elsewhere, markdown for readable diffs.

The table below is my assessment of each format by operation, because the operation is what you are choosing.

OperationDelta JSONHTMLMarkdown
RenderingNeeds the matching editor package to display faithfullyOne widget from an HTML packageOne widget with a small, predictable feature set
Full-text searchNeeds a separate extracted plain-text fieldNeeds tag stripping before indexingIndexable as-is, with some syntax noise
DiffingStructurally correct, hard to show a humanNoisy, tag churn hides the text changeLine diff readable by anyone
SanitisationConstrained by the attributes your editor emitsFull sanitiser required on writeOnly needed if raw HTML blocks are allowed
PortabilityQuill and Fleather family, mainlyRead by every platformRead by every platform
Migration costCheap out to HTML, lossy coming backCostly into Delta, structure mismatchCheap either way, ceiling limits what carries

A collaborative notes app lives in the diffing and realtime rows, so Delta wins on merit. A CMS whose content also feeds a website lives in the rendering and portability rows, so HTML earns its sanitisation cost. A tool where users write release notes lives in the diffing and search rows, and markdown costs almost nothing.

HTML: the easy render and the hard sanitise

Pale cream water pouring through a fine dark mesh set into an illustrated circuit board, sage leaves passing cleanly through while sharp black shards pile up on the buckled mesh

HTML renders with little widget code and charges you a sanitisation pipeline in return. Anything a user can store, a user can aim at the next reader.

The render side is easy. flutter_widget_from_html builds a Flutter widget tree from HTML, supports more than 70 tags, and states that "SCRIPT, STYLE" tags and their contents will be ignored. For an app where users write and read their own content, that may be enough.

It stops being enough when a second reader exists. Ignoring script tags at render time is not sanitising at write time. The stored string still contains whatever arrived. Consider a web dashboard that reads the same Firestore field: it would receive the same string, and your Flutter renderer's habits do not travel with it.

If you store HTML, sanitise on the way in. The sanitize_html package for Dart parses with an HTML5 parser, builds a DOM tree and filters elements and attributes against an allow-list, stripping inline JavaScript, CSS and form elements. Run the sanitiser on a path you control, such as your backend, and use an equivalent allow-list library if that backend is not Dart. A client-only check can be bypassed.

Markdown: the easy diff and the low ceiling

Markdown is plain text, so the tools you already own work on it: diff, grep, git, a text index, a human reading a log.

A markdown document diffs line by line, which gives you version history your users can read. A Delta diff is structurally correct but hard to show as "here is what your colleague changed" without a translation layer.

The ceiling is real too. The CommonMark 0.31.2 specification, dated 28 January 2024, does not define tables. Extensions such as GitHub Flavored Markdown add them, and once your app depends on an extension you store a dialect that one renderer understands. There is also a trapdoor: CommonMark defines an HTML block as "a group of lines that is treated as raw HTML (and will not be escaped in HTML output)." Allow raw HTML in markdown and you inherit every sanitisation problem above, hidden inside a format people consider safe.

Can you store two formats?

Yes: Delta for fidelity, plus a plain-text or rendered copy for everything that is not the editor. The catch is a consistency problem, because you now have two fields, one source of truth and a write path that must update both or neither. That is worth it for search and rarely worth it for rendering alone.

What does PDF export demand from the format?

PDF export needs an unambiguous mapping from stored formatting to layout, which favours formats where you know every construct that can appear.

This is my reasoning, not a spec claim. With loose HTML the PDF generator must decide what to do with nested tables, floats and inline styles. With Delta, the converter only has to handle the attributes your editor can emit, such as bold, italic, lists, headings and links. Fleather, at version 1.28.0 as of 30 September 2026, uses the Parchment document model with Quill.js Delta as its underlying format, and says that makes it ready for collaborative editing. PDF export in my toolkit bundle is part of that bundle's own code, not something the Fleather page documents.

Can a renderer be discontinued while the format survives?

Yes, and markdown offers a recent example. The Flutter team's flutter_markdown discontinuation issue was opened on 10 February 2025. Its continuation, flutter_markdown_plus, is maintained by Foresight Mobile and was at version 1.0.12 on 30 September 2026.

In principle, a team that stored markdown only had to change a dependency, and nothing in its database had to move. That is the argument for boring formats. Formats with a published specification and several independent implementations outlive any one package. A custom widget also has to build inside FlutterFlow's cloud build, and some packages that work in plain Flutter break there, which makes renderer churn more expensive. I cover that boundary in when to drop to pure Flutter custom code, and the Flutter developer tools list covers the neighbouring choices.

How do you pick a rich text format in order?

Work in this order rather than starting from the editor demo.

  1. Write down the three operations your app will perform most on stored documents, in specific terms.
  2. If realtime collaborative editing is on the list, store Delta JSON.
  3. If the content is also served to a website, email or public API, store HTML and budget a server-side sanitiser before launch.
  4. If your users are technical, or readable version history matters more than formatting range, store markdown and accept the ceiling.
  5. Whichever you chose, add a plain-text field at write time. It makes search possible and is cheap insurance against the next migration.
  6. Only now pick the editor widget, one that emits the format you already decided on.

Open the file where your rich text is stored and look at the first document. Can you grep it, diff it and sanitise it? If the answer to all three is no, which operation are you going to fix first?

Free resource

Free SaaS MVP Scope Template

A Notion document with the full feature checklist, MVP vs. nice-to-have table, pre-build questions, and cost signals — so you walk into any developer call knowing exactly what to ask for.

Get the template →
DL

Dusko Licanin

Full-Stack Developer · Banja Luka, Bosnia

Full-stack developer shipping SaaS MVPs, web apps, and mobile apps using AI-augmented workflows — without agency coordination overhead. Live portfolio: BookBed, Callidus, Pizzeria Bestek.

Frequently Asked Questions

What is Delta JSON in Flutter Quill?

Delta JSON is an ordered list of insert, retain and delete operations that rebuild a rich text document when replayed from the start. An insert adds text or an embed with optional formatting attributes, a retain keeps existing characters and can format them, and a delete removes characters. The Quill spec notes that because deletes do not record what was removed, a Delta is not reversible. It also says Delta suits Operational Transform, the mechanism behind realtime collaborative editing, so it expresses what changed rather than only what the document looks like now.

Does FlutterFlow have a rich text editor or markdown renderer?

FlutterFlow documents a built-in Markdown widget that displays markdown, but its documentation page describes no editing. The Markdown widget page lists two properties: Data, where the markdown string goes, and Selectable, which controls whether the text can be selected. Anything with a formatting toolbar comes from custom code, such as a widget wrapping a Flutter package. That is the moment you choose a storage format, whether you notice it or not, so decide the format before you pick the package.

What is the best rich text editor and storage format for Flutter?

There is no single best pair, but there is a reliable way to choose: pick the format that makes your most frequent operation cheap, then an editor that emits it. Realtime collaborative editing points at Delta JSON, and flutter_quill recommends storing documents as Delta JSON rather than HTML or markdown. Content served to a website or email points at HTML with a sanitiser on the write path. Readable version history points at markdown. Whichever you pick, store a plain-text copy alongside it for search.

How do you render HTML in a FlutterFlow app safely?

Render it through a custom widget that wraps an HTML package, and sanitise on write, not only on render. flutter_widget_from_html supports more than 70 tags and ignores SCRIPT and STYLE tags and their contents, but ignoring tags at render time does not clean the stored string. Other readers, such as a web dashboard or an export job, would still receive whatever was written. Filter elements and attributes against an allow-list on a backend path you control, using a library such as sanitize_html if that backend is Dart.

Can you migrate stored HTML to Delta JSON later?

You can, but it is the costly direction and it can lose information. HTML permits nested structures, inline styles and arbitrary markup that a Delta-based editor may not represent, so a converter has to drop or approximate what falls outside the editor's attributes. flutter_quill cites exactly this structural mismatch as its reason to store Delta rather than HTML. Going from Delta to HTML is usually the cleaner direction. Decide before launch which direction you might need, and keep a plain-text copy of every document as a fallback.