Quick Start
This guide will walk you through creating your first AppFlowy Editor. You’ll learn how to set up a basic editor, initialize it with content, and understand the core concepts.
Create a Blank Editor
The simplest way to create an editor is to start with a blank state. Here’s a minimal example:
Create EditorState
First, create an EditorState with a blank document: import 'package:appflowy_editor/appflowy_editor.dart' ;
import 'package:flutter/material.dart' ;
class MyEditorPage extends StatefulWidget {
const MyEditorPage ({ super .key});
@override
State < MyEditorPage > createState () => _MyEditorPageState ();
}
class _MyEditorPageState extends State < MyEditorPage > {
late final EditorState editorState;
@override
void initState () {
super . initState ();
// Create a blank editor with an initial empty paragraph
editorState = EditorState . blank (withInitialText : true );
}
@override
void dispose () {
editorState. dispose ();
super . dispose ();
}
@override
Widget build ( BuildContext context) {
return Scaffold (
appBar : AppBar (title : const Text ( 'My Editor' )),
body : AppFlowyEditor (
editorState : editorState,
),
);
}
}
Add to your app
Use the editor page in your app: import 'package:appflowy_editor/appflowy_editor.dart' ;
import 'package:flutter/material.dart' ;
import 'package:flutter_localizations/flutter_localizations.dart' ;
void main () {
runApp ( const MyApp ());
}
class MyApp extends StatelessWidget {
const MyApp ({ super .key});
@override
Widget build ( BuildContext context) {
return MaterialApp (
localizationsDelegates : const [
GlobalMaterialLocalizations .delegate,
GlobalCupertinoLocalizations .delegate,
GlobalWidgetsLocalizations .delegate,
AppFlowyEditorLocalizations .delegate,
],
home : const MyEditorPage (),
);
}
}
Run your app
Run your Flutter app to see the editor in action:
The withInitialText: true parameter creates a blank editor with an empty paragraph. Set it to false for a completely empty document.
Initialize from JSON
You can create an editor with pre-existing content by initializing it from a JSON document:
import 'dart:convert' ;
import 'package:appflowy_editor/appflowy_editor.dart' ;
import 'package:flutter/material.dart' ;
class EditorWithContent extends StatefulWidget {
const EditorWithContent ({ super .key});
@override
State < EditorWithContent > createState () => _EditorWithContentState ();
}
class _EditorWithContentState extends State < EditorWithContent > {
late final EditorState editorState;
@override
void initState () {
super . initState ();
// Your JSON content
final jsonString = '''{
"document": {
"type": "page",
"children": [
{
"type": "heading",
"data": {"level": 1, "delta": [{"insert": "Welcome to AppFlowy Editor"}]}
},
{
"type": "paragraph",
"data": {"delta": [{"insert": "Start editing your document here."}]}
}
]
}
}''' ;
final json = jsonDecode (jsonString);
editorState = EditorState (
document : Document . fromJson (json),
);
}
@override
void dispose () {
editorState. dispose ();
super . dispose ();
}
@override
Widget build ( BuildContext context) {
return AppFlowyEditor (
editorState : editorState,
);
}
}
For better control over scrolling behavior, use an EditorScrollController:
class ScrollableEditor extends StatefulWidget {
const ScrollableEditor ({ super .key});
@override
State < ScrollableEditor > createState () => _ScrollableEditorState ();
}
class _ScrollableEditorState extends State < ScrollableEditor > {
late final EditorState editorState;
late final EditorScrollController editorScrollController;
@override
void initState () {
super . initState ();
editorState = EditorState . blank (withInitialText : true );
editorScrollController = EditorScrollController (
editorState : editorState,
shrinkWrap : false ,
);
}
@override
void dispose () {
editorScrollController. dispose ();
editorState. dispose ();
super . dispose ();
}
@override
Widget build ( BuildContext context) {
return AppFlowyEditor (
editorState : editorState,
editorScrollController : editorScrollController,
);
}
}
Customize Editor Style
Customize the appearance of your editor with EditorStyle:
import 'package:appflowy_editor/appflowy_editor.dart' ;
import 'package:flutter/material.dart' ;
import 'package:google_fonts/google_fonts.dart' ;
class StyledEditor extends StatelessWidget {
final EditorState editorState;
const StyledEditor ({ super .key, required this .editorState});
@override
Widget build ( BuildContext context) {
return AppFlowyEditor (
editorState : editorState,
editorStyle : EditorStyle . desktop (
cursorColor : Colors .blue,
selectionColor : Colors .grey.shade300,
cursorWidth : 2.0 ,
textStyleConfiguration : TextStyleConfiguration (
text : GoogleFonts . poppins (
fontSize : 16 ,
color : Colors .black,
),
bold : GoogleFonts . poppins (
fontWeight : FontWeight .w600,
),
code : GoogleFonts . robotoMono (),
),
padding : const EdgeInsets . symmetric (horizontal : 40 ),
maxWidth : 640 ,
),
);
}
}
Use EditorStyle.desktop() for desktop/web platforms and EditorStyle.mobile() for mobile platforms to get platform-appropriate defaults.
Handle Editor Changes
Listen to document changes to save content or sync with a backend:
class EditorWithChangeListener extends StatefulWidget {
const EditorWithChangeListener ({ super .key});
@override
State < EditorWithChangeListener > createState () => _EditorWithChangeListenerState ();
}
class _EditorWithChangeListenerState extends State < EditorWithChangeListener > {
late final EditorState editorState;
@override
void initState () {
super . initState ();
editorState = EditorState . blank (withInitialText : true );
// Listen to document changes
editorState.transactionStream. listen ((event) {
final transaction = event.$1;
final time = event.$2;
if (time == TransactionTime .after) {
// Save or sync your content here
final jsonString = jsonEncode (editorState.document. toJson ());
print ( 'Document updated: $ jsonString ' );
}
});
}
@override
void dispose () {
editorState. dispose ();
super . dispose ();
}
@override
Widget build ( BuildContext context) {
return AppFlowyEditor (
editorState : editorState,
);
}
}
Customize your editor with header and footer widgets:
AppFlowyEditor (
editorState : editorState,
header : Padding (
padding : const EdgeInsets . only (bottom : 10.0 ),
child : Image . asset (
'assets/images/header.png' ,
fit : BoxFit .fitWidth,
height : 150 ,
),
),
footer : const SizedBox (height : 100 ),
)
Create platform-specific editor experiences:
import 'package:universal_platform/universal_platform.dart' ;
Widget buildEditor ( EditorState editorState) {
if ( UniversalPlatform .isDesktopOrWeb) {
return AppFlowyEditor (
editorState : editorState,
editorStyle : EditorStyle . desktop (
padding : const EdgeInsets . symmetric (horizontal : 40 ),
maxWidth : 640 ,
),
);
} else {
return AppFlowyEditor (
editorState : editorState,
editorStyle : EditorStyle . mobile (
padding : const EdgeInsets . all ( 20 ),
),
);
}
}
Understanding Core Concepts
EditorState
The EditorState is the heart of AppFlowy Editor. It contains:
Document : The content structure (nodes, text, formatting)
Selection : Current cursor position and text selection
Transaction Stream : Stream of document changes
Renderer : Manages how content is displayed
Document Structure
Documents are composed of nodes arranged in a tree structure:
Page Node : Root node containing all content
Block Nodes : Headings, paragraphs, lists, quotes, etc.
Delta : Rich text content with formatting attributes
Transactions
All document changes happen through transactions:
final transaction = editorState.transaction;
transaction. insertText (
node,
0 ,
'Hello, World!' ,
);
await editorState. apply (transaction);
Next Steps
Now that you have a basic editor running, explore more advanced features:
Customize Themes Learn how to customize colors, fonts, and styles
Block Components Create custom block components for your editor
Shortcuts Add custom keyboard shortcuts and commands
Toolbar Customize the floating toolbar and context menu