flutter_quill: Rich-text WYSIWYG editor for Flutter (Delta and HTML)

- Andrés Cruz - ES En español

Video thumbnail

In this article, I show you how to integrate a WYSIWYG (What You See Is What You Get) editor in Flutter using the flutter_quill package. Unlike classic web editors like CKEditor —which work directly with HTML—, flutter_quill uses an internal format called Delta to represent rich content. Understanding this difference is key to properly implementing it in your Flutter application.

Previously we learned to create an animated background in Flutter

Features of the flutter_quill plugin

The flutter_quill package offers a ready-to-use rich text editor with a configurable toolbar. Among the most prominent features you can find:

  • Typography: You can change the font directly from the editor's control bar.
  • Font size: Can be adjusted by specifying a numeric value.
  • 0 Code blocks: Allows inserting code snippets with special and visually distinct formatting.
  • 0 Bold, italics, and underline: Basic text formatting options available in the toolbar.
  • 0 Ordered and unordered lists: To structure content clearly.
  • 0 Text and background color: Visual customization directly from the editor.

In summary, flutter_quill allows you to create rich content on a text field easily and without needing to manipulate HTML directly within Flutter. What you see in the editor is exactly what will be rendered in your application.

You can find the package and its official documentation on pub.dev: https://pub.dev/packages/flutter_quill

Installation of flutter_quill

Installation follows the standard process of any Flutter package. Add the dependency in your pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  flutter_quill: ^10.0.0 # Check the latest version on pub.dev

Then run flutter pub get to download the package. Once installed, import the package in the Dart file where you plan to use it and create an instance of the main controller:

import 'package:flutter_quill/flutter_quill.dart';

// Editor controller instance
final QuillController _controllerHtmlEditor = QuillController.basic();

@override
void dispose() {
  // Always dispose of the controller to avoid memory leaks
  _controllerHtmlEditor.dispose();
  super.dispose();
}

An important point: always remember to call dispose() on the controller when you no longer need it, just as you do with animations or text controllers (TextEditingController). Otherwise, you might cause memory leaks in your application.

Initializing existing content

If you need to load existing content into the editor —for example, a comment or an article saved in the database—, you must initialize the controller with that content. Since the editor works internally with the Delta format, if your content is stored as HTML you will need to convert it first. To do this, you can use the helper method htmlToDelta() that we will see later:

// c is the HTML string obtained from the database or API
_controllerHtmlEditor.document = Document.fromDelta(htmlToDelta(c));

This conversion is necessary only if you store your content as HTML. If you instead save Delta directly in your database (as JSON), you can load the content without conversion using Document.fromJson().

UI Configuration and usage of the plugin

Integration into the user interface is divided into two distinct blocks, which might be a bit confusing at first but makes a lot of sense once you understand it:

  • Toolbar (QuillSimpleToolbar): This is the top part of the editor. Here you configure the available buttons: colors, backgrounds, lists, code blocks, etc. It can be customized by passing a QuillSimpleToolbarConfigurations object.
  • Editing area (QuillEditor): This is the rich text field itself. It can be wrapped in a Container to control its appearance, height, borders, and line spacing.

Both blocks share the same instance of QuillController, which acts as the bridge between the toolbar and the content area. Here is the basic code to set up the editor:

QuillSimpleToolbar(
  controller: _controllerHtmlEditor,
  configurations: const QuillSimpleToolbarConfigurations(),
),
Container(
  padding: const EdgeInsets.all(5),
  decoration: BoxDecoration(
    borderRadius: BorderRadius.circular(5),
    border: Border.all(color: Theme.of(context).primaryColor),
    color: appTheme.darkTheme ? Colors.black26 : Colors.black12,
  ),
  height: 300,
  child: QuillEditor.basic(
    controller: _controllerHtmlEditor,
    configurations: const QuillEditorConfigurations(),
  ),
),

In newer versions of the package, such as v11, configuring certain behaviors like link handling is done through QuillEditorConfig and the onLaunchUrl parameter. If you use links in your editor, make sure to implement that callback so links work properly when tapped.

Delta in Flutter: flutter_quill's internal format for rich text

Video thumbnail

Before diving into conversions, it is important to understand what Delta is and why it exists. Delta is the native format with which flutter_quill —and the Quill.js library from which it derives— represents rich content internally. It is a data structure in JSON format, composed of a list of insert operations with optional attributes.

It is similar to HTML in the sense that it describes text with applied styles, but it has its own syntax and logic. They are not directly interchangeable, although conversion packages exist between both formats.

Conversion between Delta and HTML in Flutter

In projects where the web application already exists and stores content as HTML —for example, in my case the Academia platform, where the web editor is CKEditor—, the Flutter app needs to be able to read that HTML and display it in the editor. For class comments, for example, I need the user to be able to write with bold text, headings, and lists, just like they would on the web.

The problem is that flutter_quill does not work with native HTML, but with Delta. Therefore, conversion between both formats is essential in this type of cross-platform project.

Why is Delta not the same as HTML?

The translation between Delta and HTML is neither perfect nor 100% bidirectional. Here is a clear example of how the same content is represented in each format:

// Delta format (JSON)
[
  {"insert": "Title 1"},
  {"insert": "\n", "attributes": {"header": 2}},
  {"insert": "Content", "attributes": {"bold": true}},
  {"insert": "\n"}
]

// HTML equivalent
<h2>Title 1</h2>
<p><strong>Content</strong></p>

As you can see, Delta uses insert objects with attributes to apply styles, while HTML uses semantic tags like <h2> or <strong>. There are cases where conversion loses nuances —for example, certain CSS styles do not have a direct equivalent in Delta—, but for most everyday use cases (bold, headings, lists, links) it works perfectly.

From Delta to HTML: vsc_quill_delta_to_html package

To convert Delta to HTML, I use the vsc_quill_delta_to_html package. Here is the helper function I use in the project:

import 'package:vsc_quill_delta_to_html/vsc_quill_delta_to_html.dart';

/// Converts a list of Delta operations to an HTML string.
/// [delta] is the list of maps representing the document's operations.
String deltaToHTML(List<Map<String, dynamic>> delta) {
  final converter = QuillDeltaToHtmlConverter(delta);
  return converter.convert();
}

You simply pass the Delta —which is a list of maps— to QuillDeltaToHtmlConverter and call convert(). The result is an HTML string ready to be saved in the database or sent to your API.

From HTML to Delta: flutter_quill_delta_from_html package

For the reverse case —converting HTML to Delta to edit existing content—, I use the flutter_quill_delta_from_html package:

import 'package:flutter_quill_delta_from_html/flutter_quill_delta_from_html.dart';

/// Converts an HTML string to Delta format for use in flutter_quill.
/// If conversion fails, returns an empty document to prevent editor errors.
Delta htmlToDelta(String html) {
  try {
    return HtmlToDelta().convert(html);
  } catch (e) {
    // Safe fallback: returns a Delta document with an empty paragraph
    return Document.fromJson([
      {'insert': '\n'},
    ]).toDelta();
  }
}

The try/catch block is important: if the HTML is malformed or contains structures the converter does not recognize, the editor might throw an exception and break. The fallback returns a valid, empty Delta document, ensuring that the editor always renders correctly even if the content cannot be converted.

Code usage is straightforward:

// HTML to Delta conversion
final delta = htmlToDelta(htmlString);

// Delta to HTML conversion
final html = deltaToHTML(deltaList);

Complete workflow: editing HTML content with flutter_quill

To make everything clear, here is the complete end-to-end workflow I use in the Academia project:

When loading content to edit:

  • Step 1: Retrieve the HTML content from the database (via API).
  • Step 2: Convert that HTML to Delta using htmlToDelta(htmlString).
  • Step 3: Assign the Delta to the controller: _controllerHtmlEditor.document = Document.fromDelta(delta).

When saving or submitting edited content:

  • Step 1: Get the current Delta from the editor: _controllerHtmlEditor.document.toDelta().toJson().
  • Step 2: Convert the Delta to HTML using deltaToHTML(deltaList).
  • Step 3: Send the resulting HTML to the API so it can save it in the database.

This way, the Flutter application always handles the Delta format internally, while communication with the backend is done in HTML —the format that the server already knows and with which the web editor works.

Why is this conversion flow necessary?

This flow is necessary because the Academia platform was originally built as a web application, where content is stored as HTML. When extending the platform to Flutter, it was necessary to establish this conversion bridge to maintain compatibility with existing content without having to migrate the entire database or change the backend.

If you are starting a new project in Flutter without the need for web interoperability, you could choose to save Delta directly as JSON and avoid conversions altogether. But if your scenario involves sharing content between a web app and a Flutter app, this is the way to go.

Learn now how to generate a PDF in Flutter

Learn how to use flutter_quill to create a WYSIWYG editor in Flutter. This covers setting up the QuillController and QuillSimpleToolbar, as well as converting between Delta and HTML using the vsc_quill_delta_to_html and flutter_quill_delta_from_html packages.


Únete a la comunidad de desarrolladores que han decidido dejar de picar código y empezar a construir productos reales. Recibe mis mejores trucos de arquitectura cada semana:

I agree to receive announcements of interest about this Blog.