Documentation
OllowEditor Documentation
OllowEditor is a modern, lightweight, framework-friendly rich text editor for JavaScript, TypeScript, React, MERN, Next.js, Node.js, NestJS, Python, Django, Flask, FastAPI, CMS forms, admin dashboards, and publishing workflows.
Introduction
This page consolidates the standalone browser editor README, the npm package README, and the Python integration README into one reference. The core editor remains a browser-based JavaScript and CSS application. The npm package wraps that editor for JavaScript, TypeScript, React, Next.js, and MERN workflows, while the Python package distributes the compiled browser assets and adds framework helpers for Django, Django REST Framework, Flask, and FastAPI.
Package Options
JavaScript Editor
Use the compiled ollow.js and ollow.css files directly in browser pages, CMS forms, and standalone integrations.
NPM Package
Use @codefortify/olloweditor for JavaScript, TypeScript, React, Next.js, MERN stack, Node.js, and NestJS workflows.
Python Package
Use olloweditor when a Python web app needs packaged OllowEditor assets plus helpers for Django, DRF, Flask, or FastAPI.
Feature Overview
| Feature | JavaScript/NPM | Python/pip | Notes |
|---|---|---|---|
| Rich text editing | Yes | Yes | Python package serves the same browser editor assets. |
| Toolbar formatting | Yes | Yes | Includes typography, lists, links, bookmarks, and formatting controls. |
| Images | Yes | Yes | Frontend capability documented in the editor and Python READMEs. |
| Image upload callback | Yes | Framework-specific backend wiring | npm exposes uploadImage; Python apps receive submitted HTML and can host upload endpoints separately. |
| Gallery | Yes | Yes | Supported by the frontend editor capabilities list. |
| YouTube embed | Yes | Yes | Python package relies on the same browser bundle. |
| Tables | Yes | Yes | Supported by the editor README and npm features list. |
| Code blocks | Yes | Yes | Supported in all distributions through the frontend editor. |
| Markdown import/export | Yes | Yes | Frontend workflow; the editor still stores HTML. |
| Export HTML | Yes | Yes | HTML export runs in the browser editor. |
| Export PDF | Yes | Yes | Depends on browser print support. |
| Import DOCX | Yes | Yes | Requires a browser-compatible parser such as Mammoth at runtime. |
| Export DOCX | Yes | Yes | Depends on the configured browser-side exporter or fallback adapter. |
| React wrapper | Yes | No | Provided by @codefortify/olloweditor/react. |
| Next.js support | Yes | No | Client-side loading with ssr: false. |
| Django integration | Backend-independent only | Yes | OllowEditorWidget, OllowEditorField, staticfiles, admin support. |
| Flask integration | Backend-independent only | Yes | Extension, asset blueprint, and Jinja helpers. |
| FastAPI integration | Backend-independent only | Yes | Static mount helper and template helpers. |
Installation Commands
npm
npm i @codefortify/olloweditor
yarn
yarn add @codefortify/olloweditor
pnpm
pnpm add @codefortify/olloweditor
pip
pip install olloweditor
Django extra
pip install "olloweditor[django]"
Flask extra
pip install "olloweditor[flask]"
FastAPI extra
pip install "olloweditor[fastapi]"
All Python integrations
pip install "olloweditor[all]"
Basic HTML Setup
The standalone browser editor uses a synced <textarea>, the compiled stylesheet, and the compiled browser script. The textarea receives the final HTML output after editing.
<form method="post">
<textarea
id="ollo-editor"
name="content"
data-theme="dark"
data-persist-theme="true"
><h2>Article title</h2>
<p>Start writing your story...</p></textarea>
<button type="submit">Save Article</button>
</form>
<link rel="stylesheet" href="ollow.css" />
<script src="ollow.js"></script>
Editor Initialization
Initialize the browser editor after the page loads. The global API is available as both OllowEditor and NationWireEditor. The editor README also documents global plugin registration with OllowEditor.registerPlugin(...).
<script>
document.addEventListener("DOMContentLoaded", function () {
OllowEditor.init("#ollo-editor", {
theme: "dark",
persistTheme: true,
upload: {
imageUrl: "/upload/image",
galleryUrl: "/upload/gallery",
attachmentUrl: "/upload/attachment",
allowFallback: false
}
});
});
</script>
Confirmed editor options in the browser README include per-editor themes with light, dark, and auto, automatic textarea synchronization, and upload adapter configuration for image, gallery, and attachment flows.
Toolbar Features
The browser editor README documents a broad toolbar surface including typography, formatting, source mode, export tools, and media blocks. The table below combines the editor README and npm package feature list.
| Feature | Description |
|---|---|
| Undo / Redo | History controls for editing sessions. |
| Font family and size | Approved typography controls with saved safe classes. |
| Paragraph and headings | Paragraph plus heading levels including H2, H3, and H4. |
| Bold / Italic / Underline / Strikethrough | Inline text formatting actions. |
| Links and bookmarks | Hyperlink management plus internal anchor insertion. |
| Lists and pull quotes | Bullet lists, numbered lists, and quote-style blocks. |
| HTML mode | Switch between visual editing and sanitized source mode. |
| Markdown and DOCX tools | Markdown import/export plus DOCX import/export workflows. |
| Images, galleries, YouTube embeds | Media blocks with upload and alignment support. |
| Tables and code blocks | Structured content blocks for technical and editorial content. |
| Responsive toolbars | Desktop menu bar, tablet groups, and mobile overflow drawers. |
| Keyboard shortcuts | Shortcut set for formatting, lists, sync, and modal handling. |
Media Upload Integration
OllowEditor supports configurable upload flows for images, galleries, attachments, and drag-and-drop image insertion. The editor still runs in the browser, while your application decides where files are stored and what public URLs are returned.
OllowEditor is textarea-first and browser-based. It keeps the original textarea synchronized with sanitized HTML, so the same editor works with server-rendered forms, JavaScript applications, React applications, and Python framework integrations.
const editor = window.OllowEditor.create(
document.getElementById("editor"),
{
theme: "auto",
persistTheme: true,
upload: {
imageUrl: "/api/uploads/images",
galleryUrl: "/api/uploads/galleries",
attachmentUrl: "/api/uploads/attachments",
allowFallback: false
},
plugins: {
callout: true
}
}
);
Image Upload
imageUrl handles a single image sent in the image field.
Gallery Upload
galleryUrl handles gallery images, each sent using the image field.
Attachment Upload
attachmentUrl handles a file attachment sent in the file field.
Drag & Drop Images
Dropped images use the configured image upload flow. Image fallback rules apply to this flow too.
Upload Response Format
Single image and attachment uploads should return a url. Gallery uploads should return urls. Returned URLs must be public or otherwise accessible to the page that renders the saved HTML.
{
"url": "/media/editor/images/file.jpg"
}
{
"urls": [
"/media/editor/gallery/1.jpg",
"/media/editor/gallery/2.jpg"
]
}
Request Field Names
| Upload Type | Request Field Name | Expected Response |
|---|---|---|
| Image upload | image | { "url": "..." } |
| Gallery upload | image | { "urls": ["...", "..."] } |
| Attachment upload | file | { "url": "..." } |
Image and gallery uploads use the image field. Attachment uploads use the file field.
CSRF Handling
The browser runtime checks CSRF values in this order:
- Uses configured headers when present.
- Looks for
csrfmiddlewaretoken. - Falls back to the
csrftokencookie.
Fallback Behavior
- If no upload URL is configured for images or galleries, OllowEditor can use
FileReader. - If an upload URL is configured and
allowFallback: true, the editor can fall back to local image data URLs after upload failure. - Attachments require a configured upload URL.
JavaScript Upload Endpoint Example
const editor = window.OllowEditor.create(
document.getElementById("editor"),
{
upload: {
imageUrl: "/api/uploads/images",
galleryUrl: "/api/uploads/galleries",
attachmentUrl: "/api/uploads/attachments",
allowFallback: true
}
}
);
Backend Examples
Express
import express from "express";
import multer from "multer";
const app = express();
const upload = multer({ dest: "uploads/" });
app.post("/api/uploads/images", upload.single("image"), async (req, res) => {
const url = `/uploads/${req.file.filename}`;
res.json({ url });
});
app.post("/api/uploads/galleries", upload.array("image"), async (req, res) => {
const urls = req.files.map((file) => `/uploads/${file.filename}`);
res.json({ urls });
});
app.post("/api/uploads/attachments", upload.single("file"), async (req, res) => {
const url = `/uploads/${req.file.filename}`;
res.json({ url });
});This is a simple example. Real production applications should validate file type, file size, user authorization, and storage path.
Django
from django.http import JsonResponse
from django.views.decorators.http import require_POST
from django.core.files.storage import default_storage
@require_POST
def upload_image(request):
file = request.FILES.get("image")
if not file:
return JsonResponse({"error": "No image uploaded"}, status=400)
path = default_storage.save(f"editor/images/{file.name}", file)
return JsonResponse({"url": default_storage.url(path)})
@require_POST
def upload_gallery(request):
files = request.FILES.getlist("image")
urls = []
for file in files:
path = default_storage.save(f"editor/gallery/{file.name}", file)
urls.append(default_storage.url(path))
return JsonResponse({"urls": urls})
@require_POST
def upload_attachment(request):
file = request.FILES.get("file")
if not file:
return JsonResponse({"error": "No file uploaded"}, status=400)
path = default_storage.save(f"editor/attachments/{file.name}", file)
return JsonResponse({"url": default_storage.url(path)})For production, validate permissions, MIME type, extension, size, and CSRF.
FastAPI
from fastapi import FastAPI, UploadFile, File
from fastapi.responses import JSONResponse
from pathlib import Path
import shutil
app = FastAPI()
UPLOAD_DIR = Path("uploads")
UPLOAD_DIR.mkdir(exist_ok=True)
@app.post("/api/uploads/images")
async def upload_image(image: UploadFile = File(...)):
file_path = UPLOAD_DIR / image.filename
with file_path.open("wb") as buffer:
shutil.copyfileobj(image.file, buffer)
return {"url": f"/uploads/{image.filename}"}
@app.post("/api/uploads/attachments")
async def upload_attachment(file: UploadFile = File(...)):
file_path = UPLOAD_DIR / file.filename
with file_path.open("wb") as buffer:
shutil.copyfileobj(file.file, buffer)
return {"url": f"/uploads/{file.filename}"}This is a minimal example. In production, use safe filenames, validation, authorization, and proper static/media serving.
Gallery Support
The browser editor can create responsive gallery sections from multiple images.
<section class="ollow-media ollow-gallery">
<div class="ollow-gallery-header">
<h3>Gallery title</h3>
<p>Gallery note or caption</p>
</div>
<div class="ollow-gallery-grid">
<figure>
<img src="image-1.jpg" alt="Gallery image 1" />
</figure>
<figure>
<img src="image-2.jpg" alt="Gallery image 2" />
</figure>
</div>
</section>
YouTube Embed
The browser editor accepts standard YouTube watch, short, and embed URLs and converts them into a wrapped iframe embed.
<figure class="ollow-media ollow-embed">
<div class="ollow-video-wrapper">
<iframe
src="https://www.youtube.com/embed/VIDEO_ID"
title="YouTube video player"
frameborder="0"
allowfullscreen
loading="lazy">
</iframe>
</div>
<figcaption>Video caption</figcaption>
</figure>
Markdown Import/Export
The browser editor supports importing Markdown into the current content and exporting editor HTML back to Markdown. Confirmed supported Markdown structures include headings, paragraphs, links, images, lists, blockquotes, horizontal rules, inline code, fenced code blocks, and basic tables.
const editor = OllowEditor.get("#ollo-editor");
editor.importMarkdown("## Heading\n\nParagraph text", {
mode: "replace"
});
const markdown = editor.exportMarkdown();
Export HTML
Export HTML uses sanitized editor content. The editor README confirms support for body-only export and full standalone HTML export with optional embedded styles.
const html = editor.exportHTML({
fullDocument: true,
includeStyles: true,
title: "Article Export"
});
Export PDF
PDF export is browser-based. The editor generates a print-ready HTML document and relies on the browser print or save-as-PDF flow rather than producing PDF bytes directly.
editor.exportPDF({
title: "Article",
pageSize: "A4",
orientation: "portrait",
margin: "normal"
});
Import DOCX
DOCX import is client-side and expects an optional browser parser such as Mammoth.js to be available when that workflow is used. Supported formatting includes headings, paragraphs, inline formatting, lists, links, tables, and images when the parser supports them.
OllowEditor.init("#editor", {
docx: {
enabled: true
}
});
editor.importDOCX(file, {
mode: "replace",
preserveFormatting: true,
importImages: true
});
Export DOCX
True DOCX generation depends on an optional browser-compatible exporter. If that adapter is unavailable, OllowEditor can fall back to a Word-compatible HTML document flow instead of pretending to generate a native .docx.
editor.exportDOCX({
filename: "article.docx",
title: "Article Title",
includeImages: true,
fallbackToDoc: true
});
NPM Installation
The npm package ships the core editor, the React wrapper, the packaged stylesheet, and bundled TypeScript declarations.
npm install @codefortify/olloweditor
CSS Import
Import the package stylesheet once in the application entry or page where the editor is used.
import "@codefortify/olloweditor/style.css";
Quick Start
The npm package mounts into a selector or DOM element and returns an OllowEditorCore instance.
import { createOllowEditor } from "@codefortify/olloweditor";
import "@codefortify/olloweditor/style.css";
const editor = createOllowEditor("#editor", {
initialHTML: "<p>Hello OllowEditor</p>",
placeholder: "Start writing...",
onChange: (html) => {
console.log(html);
}
});
Vanilla JavaScript Usage
<div id="editor"></div>
<script type="module">
import { createOllowEditor } from "@codefortify/olloweditor";
import "@codefortify/olloweditor/style.css";
const editor = createOllowEditor("#editor", {
initialHTML: "<p>Hello OllowEditor</p>",
placeholder: "Start writing...",
onChange: (html) => {
console.log(html);
}
});
</script>
TypeScript Usage
The npm package bundles its type declarations. No separate @types package is required.
import {
createOllowEditor,
type OllowEditorOptions,
type OllowEditorCore
} from "@codefortify/olloweditor";
import "@codefortify/olloweditor/style.css";
const options: OllowEditorOptions = {
initialHTML: "<p>Hello TypeScript</p>",
placeholder: "Write something...",
onChange: (html: string) => {
console.log(html);
}
};
const editor: OllowEditorCore = createOllowEditor("#editor", options);
React Usage
import { useState } from "react";
import { OllowEditor } from "@codefortify/olloweditor/react";
import "@codefortify/olloweditor/style.css";
export default function App() {
const [content, setContent] = useState("");
return (
<OllowEditor
value={content}
onChange={setContent}
placeholder="Write your article..."
/>
);
}
React TypeScript Usage
import { useState } from "react";
import {
OllowEditor,
type OllowEditorReactProps
} from "@codefortify/olloweditor/react";
import "@codefortify/olloweditor/style.css";
export default function App() {
const [content, setContent] = useState<string>("");
const uploadImage: OllowEditorReactProps["uploadImage"] = async (file) => {
const formData = new FormData();
formData.append("image", file);
const response = await fetch("/api/uploads/image", {
method: "POST",
body: formData
});
const data: { url: string } = await response.json();
return data.url;
};
return (
<OllowEditor
value={content}
onChange={setContent}
placeholder="Write your article..."
uploadImage={uploadImage}
/>
);
}
Next.js Usage
OllowEditor uses browser APIs, so load it client-side with dynamic import and ssr: false.
import dynamic from "next/dynamic";
import "@codefortify/olloweditor/style.css";
const OllowEditor = dynamic(
() =>
import("@codefortify/olloweditor/react").then((mod) => mod.OllowEditor),
{ ssr: false }
);
export default function Page() {
return <OllowEditor placeholder="Write in Next.js..." />;
}
MERN Stack Usage
Confirmed npm README workflow: React frontend, OllowEditor, HTML emitted through onChange, then a backend API stores and returns content for rendering in the application.
import { useState } from "react";
import { OllowEditor } from "@codefortify/olloweditor/react";
import "@codefortify/olloweditor/style.css";
export default function BlogCreateForm() {
const [content, setContent] = useState("");
async function handleSubmit(event) {
event.preventDefault();
await fetch("/api/posts", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
title: "My first post",
content
})
});
}
return (
<form onSubmit={handleSubmit}>
<OllowEditor
value={content}
onChange={setContent}
placeholder="Write your blog post..."
/>
<button type="submit">Save Post</button>
</form>
);
}
import express from "express";
const app = express();
app.use(express.json());
app.post("/api/posts", async (req, res) => {
const { title, content } = req.body;
// Save title and content to MongoDB using your model.
// Example:
// await Post.create({ title, content });
res.json({
success: true,
message: "Post saved successfully"
});
});
Express Upload Example
The npm README documents uploadImage as a backend callback contract rather than a bundled upload provider.
import express from "express";
import multer from "multer";
const app = express();
const upload = multer({ dest: "uploads/" });
app.post("/api/uploads/image", upload.single("image"), async (req, res) => {
// Store the uploaded file and generate a public URL.
const url = `/uploads/${req.file.filename}`;
res.json({ url });
});
NestJS Upload Example
import { Controller, Post, UploadedFile, UseInterceptors } from "@nestjs/common";
import { FileInterceptor } from "@nestjs/platform-express";
@Controller("api/uploads")
export class UploadController {
@Post("image")
@UseInterceptors(FileInterceptor("image"))
uploadImage(@UploadedFile() file: Express.Multer.File) {
return {
url: `/uploads/${file.filename}`
};
}
}
API Reference
createOllowEditor(
selector: string | HTMLElement,
options?: OllowEditorOptions
): OllowEditorCore
interface OllowEditorOptions {
initialHTML?: string;
placeholder?: string;
readOnly?: boolean;
className?: string;
onChange?: (html: string) => void;
uploadImage?: (file: File) => Promise<string> | string;
}
getHTML()returns the current editor HTML.setHTML(html)replaces the current editor content.focus()moves focus into the editor surface.destroy()removes the editor instance and listeners.
React Props
interface OllowEditorReactProps {
value?: string;
onChange?: (html: string) => void;
placeholder?: string;
uploadImage?: (file: File) => Promise<string> | string;
readOnly?: boolean;
className?: string;
}
Package Exports
import { createOllowEditor } from "@codefortify/olloweditor";
import { OllowEditor } from "@codefortify/olloweditor/react";
import "@codefortify/olloweditor/style.css";
pip Installation
The Python package does not reimplement OllowEditor in Python. It packages olloweditor.browser.js, olloweditor.css, and olloweditor-init.js.
Official integrations include Django OllowEditorField and OllowEditorWidget, Django REST Framework OllowEditorHTMLField, Flask OllowEditor(app) / init_app, and FastAPI mount_olloweditor() and template helpers.
pip install olloweditor
Optional Extras
Install only the extra your Python application needs.
Django
pip install "olloweditor[django]"
Django REST Framework
pip install "olloweditor[drf]"
Flask
pip install "olloweditor[flask]"
FastAPI
pip install "olloweditor[fastapi]"
All integrations
pip install "olloweditor[all]"
Django Integration
The Python package includes OllowEditorWidget, OllowEditorField, staticfiles integration, and admin support. It keeps a textarea synchronized with HTML so normal Django form processing continues to work.
from django.db import models
from olloweditor.integrations.django import OllowEditorField
class Article(models.Model):
title = models.CharField(max_length=255)
content = OllowEditorField()
Django Forms
from django import forms
from olloweditor.integrations.django import OllowEditorWidget
from .models import Article
class ArticleForm(forms.ModelForm):
content = forms.CharField(
widget=OllowEditorWidget(
options={
"theme": "auto",
}
)
)
class Meta:
model = Article
fields = ["title", "content"]
<form method="post">
{% csrf_token %}
{{ form.media }}
{{ form.as_p }}
<button type="submit">Save article</button>
</form>
Django Admin
Once olloweditor.apps.OllowEditorConfig is installed, OllowEditorField uses OllowEditorWidget in generated ModelForms, including standard Django admin form construction.
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"olloweditor.apps.OllowEditorConfig",
]
Django REST Framework
DRF does not render the browser editor for external API clients. It accepts the HTML string generated by an OllowEditor frontend through OllowEditorHTMLField.
from rest_framework import serializers
from olloweditor.integrations.drf import OllowEditorHTMLField
class ArticleSerializer(serializers.Serializer):
title = serializers.CharField()
content = OllowEditorHTMLField(
allow_blank=True,
required=False,
)
def sanitize_article_html(value: str) -> str:
return trusted_html_sanitizer.clean(value)
class ArticleSerializer(serializers.Serializer):
title = serializers.CharField()
content = OllowEditorHTMLField(
sanitizer=sanitize_article_html,
)
Flask Integration
The Python package exposes a Flask extension, packaged asset blueprint, and Jinja helpers.
from flask import Flask, render_template, request
from olloweditor.integrations.flask import OllowEditor
app = Flask(__name__)
olloweditor = OllowEditor(app)
@app.route("/", methods=["GET", "POST"])
def index():
content = ""
if request.method == "POST":
content = request.form.get("content", "")
return render_template("index.html", content=content)
FastAPI Integration
The Python package includes a static mount helper and template helper registration pattern for FastAPI applications.
from fastapi import FastAPI, Request
from fastapi.templating import Jinja2Templates
from olloweditor.integrations.fastapi import (
mount_olloweditor,
olloweditor_assets,
)
app = FastAPI()
mount_olloweditor(app)
templates = Jinja2Templates(directory="templates")
templates.env.globals["olloweditor_assets"] = olloweditor_assets
@app.get("/")
def index(request: Request):
return templates.TemplateResponse(
request=request,
name="index.html",
context={},
)
Static Assets
Installing olloweditor gives the Python package three packaged frontend assets plus resource helpers.
olloweditor.browser.js
olloweditor.css
olloweditor-init.js
get_static_root()
get_asset_path(filename)
asset_exists(filename)
Confirmed framework behavior: Django serves these through staticfiles, Flask through the extension blueprint, FastAPI through StaticFiles, and the base package exposes resource helpers through olloweditor.resources.
Template Helpers
Flask and FastAPI both expose helper functions that inject the packaged asset tags into server-rendered templates.
Flask
<head>
{{ olloweditor_assets() }}
</head>
FastAPI
<head>
{{ olloweditor_assets() }}
</head>
Python Backend Flow
The Python package keeps the browser editor tied to normal form posts or JSON requests instead of introducing a Python-native editing engine.
# Django
content = request.POST.get("content", "")
# Flask
content = request.form.get("content", "")
# FastAPI
from typing import Annotated
from fastapi import Form
def create_article(
content: Annotated[str, Form()],
):
return {"content": content}
Python Framework Media Uploads
OllowEditor provides framework integrations for storing uploaded images, galleries, and attachments separately from the synchronized editor HTML. Each host application remains responsible for authentication, storage, media delivery, and production security.
Django Media Uploads
Django uploads are disabled until configured. When enabled, IMAGE, GALLERY, and ATTACHMENT requests are authenticated by default, files are stored through Django default_storage, and the returned public URLs are inserted into the editor HTML.
Django Upload Settings
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"
OLLOWEDITOR = {
"UPLOADS_ENABLED": True,
"UPLOAD_REQUIRE_LOGIN": True,
"UPLOAD_PERMISSION": None,
"IMAGE_UPLOAD_PATH": "olloweditor/images/%Y/%m/",
"GALLERY_UPLOAD_PATH": "olloweditor/gallery/%Y/%m/",
"ATTACHMENT_UPLOAD_PATH": "olloweditor/attachments/%Y/%m/",
"MAX_IMAGE_SIZE": 10 * 1024 * 1024,
"MAX_GALLERY_FILES": 20,
"MAX_ATTACHMENT_SIZE": 25 * 1024 * 1024,
"MAX_IMAGE_PIXELS": 40_000_000,
"ALLOWED_IMAGE_EXTENSIONS": [
"jpg",
"jpeg",
"png",
"gif",
"webp",
],
"ALLOWED_ATTACHMENT_EXTENSIONS": [
"pdf",
"doc",
"docx",
"xls",
"xlsx",
"ppt",
"pptx",
"txt",
"zip",
],
"ALLOW_BASE64_UPLOADS": False,
}
| Setting | Default |
|---|---|
UPLOADS_ENABLED | False |
UPLOAD_REQUIRE_LOGIN | True |
UPLOAD_PERMISSION | None |
IMAGE_UPLOAD_PATH | "olloweditor/images/%Y/%m/" |
GALLERY_UPLOAD_PATH | "olloweditor/gallery/%Y/%m/" |
ATTACHMENT_UPLOAD_PATH | "olloweditor/attachments/%Y/%m/" |
MAX_IMAGE_SIZE | 10 * 1024 * 1024 |
MAX_GALLERY_FILES | 20 |
MAX_ATTACHMENT_SIZE | 25 * 1024 * 1024 |
MAX_IMAGE_PIXELS | 40_000_000 |
ALLOWED_IMAGE_EXTENSIONS | jpg, jpeg, png, gif, webp |
ALLOWED_ATTACHMENT_EXTENSIONS | pdf, doc, docx, xls, xlsx, ppt, pptx, txt, zip |
ALLOW_BASE64_UPLOADS | False |
Django Upload URLs
Include the package URLs. The prefix remains controlled by the host project.
from django.conf import settings
from django.conf.urls.static import static
from django.urls import include, path
urlpatterns = [
path(
"olloweditor/",
include("olloweditor.integrations.django.urls"),
),
]
if settings.DEBUG:
urlpatterns += static(
settings.MEDIA_URL,
document_root=settings.MEDIA_ROOT,
)
POST /olloweditor/upload/image/POST /olloweditor/upload/gallery/POST /olloweditor/upload/attachment/
static(..., document_root=...) helper serves development media only. Production applications should use object storage, a CDN, Nginx, Apache, or another appropriate media service.Configured server-upload mode stores a public media URL in the HTML:
<img src="/media/olloweditor/images/2026/07/generated.png">Configured server-upload mode does not save a base64 data URL:
<img src="data:image/png;base64,...">default_storage.save(...) and default_storage.url(...); they do not join paths onto MEDIA_ROOT or return physical filesystem paths.Django REST Framework Uploads
OllowEditorHTMLField accepts and validates the HTML string produced by a separate OllowEditor frontend. DRF does not initialize the JavaScript editor.
from rest_framework import serializers
from olloweditor.integrations.drf import OllowEditorHTMLField
class ArticleSerializer(serializers.Serializer):
title = serializers.CharField()
content = OllowEditorHTMLField(
allow_blank=True,
required=False,
)Register the reusable multipart upload views
from django.urls import path
from olloweditor.integrations.drf import (
OllowEditorAttachmentUploadView,
OllowEditorGalleryUploadView,
OllowEditorImageUploadView,
)
urlpatterns = [
path(
"api/olloweditor/upload/image/",
OllowEditorImageUploadView.as_view(),
),
path(
"api/olloweditor/upload/gallery/",
OllowEditorGalleryUploadView.as_view(),
),
path(
"api/olloweditor/upload/attachment/",
OllowEditorAttachmentUploadView.as_view(),
),
]The views use DRF MultiPartParser and FormParser, reuse the Django upload settings and default_storage, and require authentication when UPLOAD_REQUIRE_LOGIN is true. Set DRF authentication globally, or subclass a view to set authentication_classes and permission_classes. Session authentication retains Django CSRF checks; token and JWT authentication remain host-application decisions.
Plain text previews
Use plain text in API list responses. Do not return stored HTML as a trusted preview.
from rest_framework import serializers
from olloweditor.previews import extract_olloweditor_text
class ArticleSerializer(serializers.ModelSerializer):
preview = serializers.SerializerMethodField()
def get_preview(self, obj) -> str:
return extract_olloweditor_text(
obj.content,
max_length=140,
)Flask Uploads
The Flask extension registers packaged asset routes, upload endpoints, Jinja helpers, and an optional development media route for local storage.
from flask import Flask, g
from olloweditor.integrations.flask import OllowEditor
def upload_user_is_authenticated() -> bool:
return getattr(g, "user", None) is not None
def upload_user_has_permission() -> bool:
user = getattr(g, "user", None)
return bool(user and user.can_upload)
app = Flask(__name__)
app.config.update(
OLLOWEDITOR_UPLOADS_ENABLED=True,
OLLOWEDITOR_UPLOAD_AUTH_REQUIRED=True,
OLLOWEDITOR_AUTH_CHECK=upload_user_is_authenticated,
OLLOWEDITOR_PERMISSION_CHECK=upload_user_has_permission,
OLLOWEDITOR_UPLOAD_PERMISSION_REQUIRED=True,
OLLOWEDITOR_UPLOAD_ROOT="./media",
OLLOWEDITOR_MEDIA_URL="/media/",
)
olloweditor = OllowEditor(app)The default URL prefix is /olloweditor. It serves the three upload endpoints under /olloweditor/upload/ and injects these Jinja globals:
olloweditor_assets()olloweditor_textarea(...)extract_olloweditor_text(...)
<!doctype html>
<html lang="en">
<head>
{{ olloweditor_assets() }}
</head>
<body>
<form method="post">
{{ olloweditor_textarea("content", article.content) }}
<button type="submit">Save</button>
</form>
</body>
</html>OLLOWEDITOR_CSRF_TOKEN_CALLBACK, and OLLOWEDITOR_CSRF_HEADER_NAME. Flask-WTF and Flask-Login are not mandatory; connect their CSRF and authentication mechanisms through application callbacks.Set OLLOWEDITOR_STORAGE to an object implementing UploadStorageProtocol.save(...) and delete(...) for S3, Google Cloud Storage, Azure Blob Storage, a CDN-backed service, or private application storage. The default LocalFilesystemUploadStorage uses OLLOWEDITOR_UPLOAD_ROOT and OLLOWEDITOR_MEDIA_URL.
extract_olloweditor_text(article.content, max_length=140) for list pages. Do not render arbitrary stored content with Jinja |safe.FastAPI Uploads
OllowEditorFastAPI mounts the packaged static assets, installs upload routes, adds template helpers, and can mount a development media directory for its local storage adapter.
from fastapi import FastAPI
from fastapi.templating import Jinja2Templates
from olloweditor.integrations.fastapi import OllowEditorFastAPI
async def require_user() -> bool:
return True
async def require_upload_permission() -> bool:
return True
app = FastAPI()
templates = Jinja2Templates(directory="templates")
olloweditor = OllowEditorFastAPI(
uploads_enabled=True,
upload_root="./media",
media_url="/media/",
auth_required=True,
auth_dependency=require_user,
permission_dependency=require_upload_permission,
)
olloweditor.init_app(app, templates=templates)static assets: /olloweditor/static/IMAGE: POST /olloweditor/upload/image/GALLERY: POST /olloweditor/upload/gallery/ATTACHMENT: POST /olloweditor/upload/attachment/
The routes use FastAPI UploadFile and require python-multipart, which is included in the fastapi extra. Authentication and permission callbacks may be synchronous or asynchronous; the host application supplies its own identity system.
olloweditor_assets(), olloweditor_textarea(...), and extract_olloweditor_text(...). A custom storage implementing UploadStorageProtocol can replace local filesystem storage. The local media mount is intended for development, not production delivery.Upload Response Contract
Successful responses return structured JSON. Uploaded binaries are stored separately from rich text. Responses contain public URLs from the configured storage backend, never physical filesystem paths. Cloud and CDN adapters may return absolute public URLs.
{
"success": true,
"type": "image",
"url": "/media/olloweditor/images/generated.png",
"name": "original.png",
"size": 12345
}{
"success": true,
"type": "attachment",
"url": "/media/olloweditor/attachments/generated.pdf",
"name": "report.pdf",
"size": 12345
}{
"success": true,
"type": "gallery",
"files": [
{
"url": "/media/olloweditor/gallery/one.png",
"name": "one.png",
"size": 12345
},
{
"url": "/media/olloweditor/gallery/two.png",
"name": "two.png",
"size": 23456
}
]
}Gallery success preserves selection order.
{
"success": false,
"error": {
"code": "invalid_file_type",
"message": "This file type is not allowed."
}
}detail key. Successful responses use the structures shown above.Security Notes
extract_olloweditor_text(...) for previews and sanitize untrusted HTML before rendering.Security Notes
OllowEditor sanitizes and normalizes content on the client, including pasted HTML, source-mode edits, Markdown import, DOCX import, URLs, embeds, and plugin-inserted markup. This is not a complete security boundary.
If content or uploads are untrusted, the host application should:
- Validate and sanitize HTML on the server.
- Validate upload authorization.
- Validate MIME type, extension, and size.
- Apply normal CSRF protection on upload endpoints.
- Use an appropriate Content Security Policy.
Browser Support
The npm README lists the following modern browser targets:
- Chrome
- Edge
- Firefox
- Safari
- Other modern Chromium-based browsers
Package Structure
olloweditor/
├── src/
│ ├── core/
│ ├── react/
│ ├── styles/
│ └── types/
├── dist/
├── examples/
├── website/
├── package.json
├── vite.config.js
├── tsconfig.json
└── README.md
Local Development
The npm README documents the standard frontend development flow, and the Python README adds the Python-specific environment setup.
git clone https://github.com/CodeFortifyCloud/olloweditor.git
cd olloweditor
npm install
npm run dev
npm ci
npm run build
npm run typecheck
npm test
npm run build:python-assets
npm run verify:python-assets
cd python
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e ".[all,dev,test]"
Build
npm run build
cd python
rm -rf build dist src/*.egg-info
python -m build
python -m twine check dist/*
Publishing
npm pack --dry-run
npm pack
npm login
npm publish --access public
pip install dist/olloweditor-<version>-py3-none-any.whl
python scripts/check_wheel_contents.py dist/*.whl
python scripts/verify_wheel_installs.py dist/*.whl
The Python README documents build and verification commands, but it does not publish a final PyPI release command in the same way the npm README does.
License
MIT License.