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 to admin_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 pending so 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 &nbsp;, so convert them back to normal spaces
		var html = quill.getSemanticHTML().replace( /&nbsp;/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 &nbsp; (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]
  1. Activate "My Quill Form" under "Plugins" in the admin
  2. Create a page, put the text above in a Shortcode block, and publish it
  3. Log in as a user with the Author role or higher, enter a title and body on that page, and submit
  4. Confirm that "Submitted" appears and that a "Pending Review" post has been created under "Posts" in the admin
  5. 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 with data: (Base64), but wp_kses_post() doesn't allow data: 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 &nbsp; replacement in 2.0.3

Related articles:

Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)

お気軽にご相談ください

お見積りへ お問い合わせへ
▲