2026年11月12日
2026年11月12日
Using Quill (Rich Text Editor) in WordPress: Add It to a Post Form
This article is a localized edition of the Japanese original. Read the Japanese original
Quill is an open-source JavaScript library that adds a rich text input (bold, headings, lists, links and so on) to a web page. This article shows, in the form of a small plugin, how to add Quill to a post submission form on the front end of a WordPress site (the public side). It assumes Quill 2.0.3 as of October 2026.
Where Quill fits
The WordPress post edit screen runs on the block editor, and replacing the block editor with Quill isn't realistic. Quill is better suited to places where the block editor isn't used:
- A form where logged-in members submit draft articles from the front end
- Input fields such as inquiries or reviews where you want to allow text formatting
- Small description fields, for example on the settings screen of your own plugin
There doesn't seem to be a standard WordPress plugin that "just enables Quill," so this article integrates it with code.
What we’ll build
When a logged-in user with the Author role or higher submits a title and body from a form placed on a page, the content is saved as a "Pending Review" post.
wp-content/plugins/my-quill-form/
├── my-quill-form.php … Main plugin file (loading, form, saving)
├── my-quill-form.js … Quill initialization and submit handling
└── vendor/quill/
├── quill.js
└── quill.snow.css
Step 1: Place the Quill files
You can also load Quill from a CDN such as jsDelivr. This article puts the files on your own site so that it doesn't depend on an outside service and doesn't send visitors' access information to a third party.
cd wp-content/plugins
mkdir -p my-quill-form/vendor/quill
cd my-quill-form/vendor/quill
# Download the Quill 2.0.3 distribution files published on npm
curl -fsSLO https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.js
curl -fsSLO https://cdn.jsdelivr.net/npm/quill@2.0.3/dist/quill.snow.css
quill.snow.css is the look of the standard theme (Snow), with the toolbar on top. Quill is licensed under BSD-3-Clause.
Step 2: Create the main plugin file
Create my-quill-form.php. It handles loading the scripts, displaying the form with a shortcode, and saving the submission.
<?php
/**
* Plugin Name: My Quill Form
* Description: Post form using Quill (shortcode my_quill_form)
* Version: 1.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
// Load Quill only on pages that contain the form
add_action( 'wp_enqueue_scripts', function () {
$post = get_post();
if ( ! is_singular() || ! $post || ! has_shortcode( $post->post_content, 'my_quill_form' ) ) {
return;
}
wp_enqueue_style( 'quill-snow', plugins_url( 'vendor/quill/quill.snow.css', __FILE__ ), array(), '2.0.3' );
wp_enqueue_script( 'quill', plugins_url( 'vendor/quill/quill.js', __FILE__ ), array(), '2.0.3', true );
wp_enqueue_script( 'my-quill-form', plugins_url( 'my-quill-form.js', __FILE__ ), array( 'quill' ), '1.0.0', true );
} );
// Shortcode that displays the form
add_shortcode( 'my_quill_form', function () {
if ( ! current_user_can( 'edit_posts' ) ) {
return '<p>Please log in as an Author or higher to submit a post.</p>';
}
$notice = '';
if ( isset( $_GET['submitted'] ) ) {
$notice = '1' === $_GET['submitted']
? '<p>Submitted. Your post will be published after review.</p>'
: '<p>Submission failed. Please try again.</p>';
}
ob_start();
echo $notice; // Fixed strings only
?>
<form id="my-quill-form" method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
<p>
<label for="my-quill-title">Title</label><br>
<input type="text" id="my-quill-title" name="my_title" required style="width:100%">
</p>
<div id="my-quill-editor" style="height:240px"></div>
<input type="hidden" id="my-quill-content" name="my_content">
<input type="hidden" name="action" value="my_quill_submit">
<?php wp_nonce_field( 'my_quill_submit', 'my_quill_nonce' ); ?>
<p><button type="submit">Submit</button></p>
</form>
<?php
return ob_get_clean();
} );
// Save the submission as a "pending" post (logged-in users only)
add_action( 'admin_post_my_quill_submit', function () {
if ( ! isset( $_POST['my_quill_nonce'] )
|| ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['my_quill_nonce'] ) ), 'my_quill_submit' ) ) {
wp_die( 'Invalid request.', '', array( 'response' => 403 ) );
}
if ( ! current_user_can( 'edit_posts' ) ) {
wp_die( 'You do not have permission.', '', array( 'response' => 403 ) );
}
$title = sanitize_text_field( wp_unslash( $_POST['my_title'] ?? '' ) );
$content = wp_kses_post( wp_unslash( $_POST['my_content'] ?? '' ) );
$post_id = 0;
if ( '' !== $title && '' !== trim( wp_strip_all_tags( $content ) ) ) {
// wp_insert_post() expects slashed data, so pass it through wp_slash()
$post_id = wp_insert_post( wp_slash( array(
'post_title' => $title,
'post_content' => $content,
'post_status' => 'pending',
'post_type' => 'post',
'post_author' => get_current_user_id(),
) ), true );
}
$ok = $post_id && ! is_wp_error( $post_id );
$back = wp_get_referer() ? wp_get_referer() : home_url( '/' );
wp_safe_redirect( add_query_arg( 'submitted', $ok ? '1' : '0', $back ) );
exit;
} );
Key points:
- The
admin_post_{action}hook accepts submissions only from logged-in users (submissions from logged-out visitors go toadmin_post_nopriv_{action}, which this example doesn't define) - The body HTML produced by Quill in the browser isn't trusted as is; it is restricted to tags allowed in post content with
wp_kses_post()before saving - The post status is set to
pendingso an administrator reviews it before publishing
Step 3: JavaScript that initializes Quill
Create my-quill-form.js.
// Initialize Quill and put the body HTML into the hidden field on submit
document.addEventListener( 'DOMContentLoaded', function () {
var form = document.getElementById( 'my-quill-form' );
if ( ! form ) {
return;
}
var quill = new Quill( '#my-quill-editor', {
theme: 'snow',
placeholder: 'Enter the body text',
modules: {
toolbar: [
[ { header: [ 2, 3, false ] } ],
[ 'bold', 'italic', 'underline' ],
[ { list: 'ordered' }, { list: 'bullet' } ],
[ 'link' ],
[ 'clean' ]
]
}
} );
form.addEventListener( 'submit', function () {
// Quill 2.0.3's getSemanticHTML() turns every space into , so convert them back to normal spaces
var html = quill.getSemanticHTML().replace( / /g, ' ' );
document.getElementById( 'my-quill-content' ).value = html;
} );
} );
The toolbar items are set in modules.toolbar. Formatting you don't want in post content (text color, for example) can't be chosen if you leave it out here. However, pasted text can still bring formatting in, so don't skip wp_kses_post() on the saving side.
Getting the body HTML
To get the content as HTML in Quill 2, use the official API getSemanticHTML(). You can also read quill.root.innerHTML, but that HTML isn't meant for display outside Quill; for example, Quill 2 represents both bulleted and numbered lists as <ol> with its own attributes.
A bug has been reported in Quill 2.0.3 where getSemanticHTML() replaces every space with (GitHub issue #4509). The code above converts them back to normal spaces before submitting. When you update to a newer version, check whether this step is still needed.
Step 4: Place the form and test it
On the page where you want the form, put the following text in a Shortcode block.
[my_quill_form]
- Activate "My Quill Form" under "Plugins" in the admin
- Create a page, put the text above in a Shortcode block, and publish it
- Log in as a user with the Author role or higher, enter a title and body on that page, and submit
- Confirm that "Submitted" appears and that a "Pending Review" post has been created under "Posts" in the admin
- Paste HTML containing
<script>or similar into the body, submit, and confirm that it has been removed from the saved post
Letting users edit existing content in Quill
To load saved content into Quill for editing, use quill.clipboard.dangerouslyPasteHTML(). As the name says, it interprets the HTML you pass as is, so pass only HTML that has been sanitized with wp_kses_post(). When passing it from PHP, use wp_add_inline_script() and wp_json_encode() to embed it safely as a JavaScript string.
Notes
- If you accept submissions from logged-out visitors, use
admin_post_nopriv_{action}and add spam protection (such as reCAPTCHA) and a submission rate limit. Don't save those submissions as published posts either - This example doesn't handle images. If you add the image button (
image) to the toolbar, Quill by default embeds images in the content as URLs starting withdata:(Base64), butwp_kses_post()doesn't allowdata:URLs, so the image URL is removed on save. To accept images, build a separate process that uploads them to the Media Library - When you update the Quill files, change the version number in Step 1 and the version number passed to
wp_enqueue_script()together
Summary
- Quill isn't a replacement for the block editor; use it for things like front-end post forms
- Put the files on your site and load them only on pages with the form
- Check the nonce and capability on submit, and sanitize the body with
wp_kses_post()before saving - Get the content with
getSemanticHTML(), and watch out for the replacement in 2.0.3
Related articles:
- How to build a rich text editor with contenteditable in WordPress
- How to strengthen WordPress security with nonces
- How to customize TinyMCE in WordPress
Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)