Skip to main content

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:
1

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,
      ),
    );
  }
}
2

Add to your app

Use the editor page in your app:
main.dart
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(),
    );
  }
}
3

Run your app

Run your Flutter app to see the editor in action:
Terminal
flutter run
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,
    );
  }
}
You can also load JSON from assets or network. Check out the Markdown plugin and Quill Delta plugin for other import formats.

Add a Scroll Controller

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),
)

Platform Detection

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
Check out the example app source code for more comprehensive examples and patterns.

Build docs developers (and LLMs) love