Complete reference for the RichTextBox ASP.NET Core WYSIWYG editor — from installation and Tag Helper attributes to the JavaScript API, events, uploads, and deployment.
Quick start
Add a production-ready WYSIWYG HTML editor to any ASP.NET Core Razor Pages or MVC application in three steps: install the NuGet package, register the services, and drop in the tag helper.
dotnet add package RichTextBoxvar builder = WebApplication.CreateBuilder(args);
builder.Services.AddRichTextBox();
var app = builder.Build();
app.UseStaticFiles();
app.MapRichTextBoxUploads();
app.MapRazorPages();
app.Run();<richtextbox name="Body" toolbar="default" height="400px" />That is the minimal setup. The sections below cover every option, attribute, and API method in detail.
Setup
Install via the .NET CLI:
dotnet add package RichTextBoxOr via the Visual Studio Package Manager Console:
Install-Package RichTextBoxOr add directly to your .csproj:
<PackageReference Include="RichTextBox" Version="1.0.0-preview.1" />Register services with AddRichTextBox() and map the upload endpoints with MapRichTextBoxUploads():
var builder = WebApplication.CreateBuilder(args);
// Register RichTextBox services (default options)
builder.Services.AddRichTextBox();
// Or with custom options:
builder.Services.AddRichTextBox(options =>
{
options.UploadWebPath = "/uploads";
options.MaxUploadBytes = 4 * 1024 * 1024; // 4 MB
options.AllowedImageExtensions = new[] { ".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg" };
options.AllowedFileExtensions = new[] { ".zip", ".pdf", ".doc", ".docx" };
});
var app = builder.Build();
app.UseStaticFiles();
// Map upload and gallery endpoints
app.MapRichTextBoxUploads();
app.MapRazorPages();
app.Run();Place your RichTextBox.lic file in the project content root (same folder as Program.cs):
YourProject/
Program.cs
RichTextBox.lic <-- content root
appsettings.json
Pages/
_ViewImports.cshtml
Index.cshtml
wwwroot/
uploads/ <-- image upload targetAdd the following line to Pages/_ViewImports.cshtml:
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, RichTextBoxTag Helper
All attributes available on the <richtextbox> tag helper element, organized by category.
| Attribute | Type | Default | Description |
|---|---|---|---|
| id | string | auto | HTML element ID for the editor instance |
| name | string | required | Form field name for the HTML content |
| asp-for | expression | - | Model expression for two-way binding |
| html | string | empty | Initial HTML content for the editor |
| class | string | - | CSS class for the editor wrapper element |
| Attribute | Type | Default | Description |
|---|---|---|---|
| toolbar | string | "default" | Toolbar preset: default, full, basic, ribbon, office, or a custom button string |
| toolbar-mobile | string | "mobile" | Toolbar layout for mobile-width screens |
| Attribute | Type | Default | Description |
|---|---|---|---|
| skin | string | "default" | Visual skin: default, gray, office2007blue, rounded-corner, blue |
| width | string | "100%" | Editor width (CSS value: px, %, em) |
| height | string | "300px" | Editor height (CSS value) |
| resize-mode | string | "both" | Resize mode: both, height, none |
| Attribute | Type | Default | Description |
|---|---|---|---|
| paste-mode | string | "Auto" | Paste handling: Auto, Text, Word, Disabled |
| enter-key-tag | string | "p" | Tag for Enter key: p, div, br |
| url-type | string | "default" | URL processing: default, relative, absolute |
| read-only | bool | false | Makes the editor read-only |
| Attribute | Type | Default | Description |
|---|---|---|---|
| enable-context-menu | bool | false | Enable custom right-click context menu |
| context-menu-mode | string | "Default" | Menu preset: Default, Simple, Minimal |
| Attribute | Type | Default | Description |
|---|---|---|---|
| show-tag-list | bool | true | Show tag path in the bottom bar |
| show-statistics | bool | true | Show character/word statistics in the bottom bar |
| Attribute | Type | Default | Description |
|---|---|---|---|
| content-css-url | string | built-in | URL of a CSS file applied to the content area |
| content-css-text | string | "" | Inline CSS text applied to the content area |
| preview-css-url | string | built-in | CSS file for the preview window |
| editor-body-css-class | string | "" | CSS class applied to the editor body element |
| editor-body-css-text | string | "" | Inline CSS for the editor body element |
| Attribute | Type | Default | Description |
|---|---|---|---|
| image-items-json | string | [] | JSON array of image URLs for the insert image dialog |
| gallery-images-json | string | [] | JSON array of {url, text} objects for the gallery |
| html-templates-json | string | [] | JSON array of [name, html] arrays for templates |
| Attribute | Type | Default | Description |
|---|---|---|---|
| auto-save | bool | false | Enable localStorage draft saving |
| auto-save-key | string | auto | localStorage key for draft isolation |
| auto-save-delay | int | 600 | Delay in milliseconds between saves |
| Attribute | Type | Default | Description |
|---|---|---|---|
| max-html-length | int | 0 (unlimited) | Maximum total HTML characters |
| max-text-length | int | 0 (unlimited) | Maximum visible text characters |
Services
Configure the RichTextBox services in Program.cs via the AddRichTextBox(options => { ... }) delegate.
builder.Services.AddRichTextBox(options =>
{
options.UploadWebPath = "/uploads";
options.MaxUploadBytes = 8 * 1024 * 1024; // 8 MB
options.AllowedImageExtensions = new[] { ".jpg", ".png", ".gif", ".webp", ".svg" };
options.AllowedFileExtensions = new[] { ".zip", ".pdf", ".doc", ".docx", ".rtf", ".txt" };
});| Property | Type | Default | Description |
|---|---|---|---|
| UploadWebPath | string | "/uploads" | Web-accessible path for uploaded files |
| MaxUploadBytes | long | 4194304 | Maximum upload file size (4 MB default) |
| AllowedImageExtensions | string[] | .jpg .png .gif .webp .svg | Allowed image file extensions |
| AllowedFileExtensions | string[] | .zip .pdf .doc .docx .rtf .txt | Allowed non-image file extensions |
| RequireValidLicense | bool | true | Require valid license file to render editors and accept uploads |
| LicenseNumber | string | "30076785" | Expected license number |
Toolbars
Use a preset name for quick setup or provide a custom toolbar string with specific buttons.
| Value | Description |
|---|---|
| default | Standard toolbar with common formatting, links, images, and table commands |
| full | Extended toolbar with every available command |
| basic | Minimal toolbar: bold, italic, underline, lists, and links |
| ribbon | Ribbon-style grouped toolbar (Office-like tabs) |
| office | Office-inspired toolbar with grouped sections |
| mobile | Compact toolbar optimized for small screens |
<richtextbox name="Body" toolbar="full" />
<richtextbox name="Body" toolbar="basic" toolbar-mobile="mobile" />Build a custom toolbar by listing button names in curly braces, separated by pipe characters for grouping:
<richtextbox name="Body"
toolbar="{bold,italic,underline}|{fontname,fontsize}|{forecolor,backcolor}|{insertlink,insertimage}|{undo,redo}" />Use {group1}|{group2} to visually separate button groups. Use # to force a new toolbar row.
Appearance
Set the visual theme of the editor with the skin attribute.
Clean, modern look with a light toolbar background. Works well with any layout.
Subtle gray toolbar that blends with neutral-themed interfaces.
Blue gradient toolbar inspired by the classic Office 2007 ribbon style.
Soft rounded corners on the toolbar and editor frame.
Blue-toned toolbar with a professional, corporate appearance.
<richtextbox name="Body" skin="office2007blue" toolbar="full" />Client-side
Every editor instance is accessible from JavaScript via the global window.richTextBoxEditors dictionary or directly as a window property using the editor ID.
// By dictionary lookup
var editor = window.richTextBoxEditors["myEditor"];
// Or directly by ID (when the id matches a valid JS identifier)
var editor = window.myEditor;| Method | Description |
|---|---|
| getHTMLCode() | Returns the current HTML content as a string |
| setHTMLCode(html) | Replaces the editor content with the given HTML string |
| getPlainText() | Returns the visible text content without HTML tags |
| insertHTML(html) | Inserts HTML at the current caret position |
| execCommand(cmd) | Executes a toolbar command programmatically (e.g. "bold", "insertimage") |
| focus() | Sets focus to the editor content area |
| getDocument() | Returns the iframe document object for low-level DOM access |
| getEditable() | Returns the editable body element inside the iframe |
| attachEvent(name, handler) | Subscribes to an editor event |
| detachEvent(name, handler) | Unsubscribes from an editor event |
// Read the current content
var html = editor.getHTMLCode();
console.log("HTML length:", html.length);
// Replace content
editor.setHTMLCode("<h2>Hello World</h2><p>New content here.</p>");
// Insert at caret
editor.insertHTML("<img src='/uploads/photo.jpg' />");
// Execute a command
editor.execCommand("bold");Client-side
Subscribe to editor events using attachEvent and unsubscribe with detachEvent.
| Event Name | Description |
|---|---|
| change | Fired when the editor content changes (keystroke, paste, command, etc.) |
| exec_command | Fired when a toolbar command is executed |
| selectionchange | Fired when the text selection or caret position changes |
var editor = window.richTextBoxEditors["myEditor"];
function onContentChange() {
var html = editor.getHTMLCode();
console.log("Content changed. Length:", html.length);
document.getElementById("preview").innerHTML = html;
}
// Subscribe
editor.attachEvent("change", onContentChange);
// Unsubscribe later
// editor.detachEvent("change", onContentChange);editor.attachEvent("exec_command", function() {
console.log("A command was executed");
});Server-side
MapRichTextBoxUploads() registers two endpoints that handle all file operations for the editor.
| Endpoint | Method | Description |
|---|---|---|
| /richtextbox/upload | POST | Accepts image and document file uploads. Returns the public URL of the uploaded file. |
| /richtextbox/gallery | GET / POST | Browse folders, list images, and create subfolders within the upload directory. |
The user clicks the image or file toolbar button and selects a file in the dialog.
The editor POSTs the file to /richtextbox/upload. The server validates the extension and size, saves the file to wwwroot/uploads/, and returns the public URL.
The returned URL is inserted into the editor content as an <img> tag or a download link.
The /richtextbox/gallery endpoint powers the gallery dialog. Users can browse previously uploaded images, navigate subfolders, and create new folders directly from the editor UI. The gallery reads from and writes to the same wwwroot/uploads/ directory.
builder.Services.AddRichTextBox(options =>
{
// Change the upload directory (relative to wwwroot)
options.UploadWebPath = "/media/uploads";
// Increase max upload size to 10 MB
options.MaxUploadBytes = 10 * 1024 * 1024;
// Restrict allowed image types
options.AllowedImageExtensions = new[] { ".jpg", ".jpeg", ".png", ".webp" };
// Restrict allowed document types
options.AllowedFileExtensions = new[] { ".pdf", ".docx" };
});Data binding
The editor integrates with standard HTML forms, ASP.NET Core model binding, and AJAX submissions.
Wrap the editor in a <form> with a name attribute. The HTML content is submitted as a form field.
<form method="post">
<richtextbox name="Body" toolbar="default" height="400px" />
<button type="submit">Submit</button>
</form>In your page model, bind the value:
[BindProperty]
public string Body { get; set; }
public void OnPost()
{
// Body contains the submitted HTML
var html = Body;
}Use asp-for for strong-typed model binding. The editor reads the initial value from the model and writes back on form submission.
// PageModel
public class EditModel : PageModel
{
[BindProperty]
public ArticleModel Article { get; set; }
}
public class ArticleModel
{
public string Title { get; set; }
public string Content { get; set; }
}<!-- Razor Page -->
<form method="post">
<input asp-for="Article.Title" />
<richtextbox asp-for="Article.Content" toolbar="full" height="500px" />
<button type="submit">Save</button>
</form>Read the editor content with JavaScript and send it via fetch:
async function submitContent() {
var editor = window.richTextBoxEditors["myEditor"];
var html = editor.getHTMLCode();
var response = await fetch("/api/articles", {
method: "POST",
headers: {
"Content-Type": "application/json",
"RequestVerificationToken": document.querySelector(
'input[name="__RequestVerificationToken"]'
).value
},
body: JSON.stringify({ content: html })
});
if (response.ok) {
alert("Saved successfully!");
}
}Production
Key considerations when deploying RichTextBox to production environments.
RichTextBox.lic is included in the publish output (content root)wwwroot/uploads directory exists and is writable by the app pool identityapp.UseStaticFiles() is called before app.MapRichTextBoxUploads()
Install the ASP.NET Core Hosting Bundle. Set maxAllowedContentLength in web.config for large uploads. Ensure the app pool identity has write access to the uploads folder.
Deploy via Visual Studio Publish, Azure CLI, or GitHub Actions. For persistent upload storage, consider Azure Blob Storage instead of the default local file system.
Mount a persistent volume for /app/wwwroot/uploads so uploaded files survive container restarts. Include RichTextBox.lic in the Docker image or mount it as a secret.
Run Kestrel behind an nginx reverse proxy. Set client_max_body_size in your nginx config for large uploads.
builder.WebHost.ConfigureKestrel(options =>
{
options.Limits.MaxRequestBodySize = 50 * 1024 * 1024; // 50 MB
});