nadar/quill-delta-parser
A PHP library that parses Quill WYSIWYG editor Delta JSON into sanitized HTML. Every element is handled by the same flexible, extendable mechanism, making custom elements easy to add, output safer, and the parser simple to understand and customize. Install via Composer.
Installation / Upgrade
composer require nadar/quill-delta-parser:^3.7.1
If you are already on 3.7.0, upgrade to the security release:
composer update nadar/quill-delta-parser
3.7.1 fixes a stored XSS issue in URI schemes used by link, image, and video attributes. Upgrade if you render user-supplied deltas.
Basic Usage
use Nadar\QuillDeltaParser\Parser;
$delta = [
['insert' => 'Hello '],
[
'insert' => 'World',
'attributes' => [
'bold' => true,
'font' => ['size' => '24px'],
],
],
];
$parser = new Parser();
$html = $parser->parse($delta);
// Output: <p>Hello <span style="font-size: 24px;"><strong>World</strong></span></p>
First Use Case Parse raw Quill Delta JSON from an API or frontend into sanitized HTML for:
$model->content_html = $html{!! $html !!}For user-generated content, treat 3.7.1 as the minimum safe version and still audit custom listeners/pre/post-processing that generate HTML.
Frontend ↔ Backend Sync
// Frontend: Quill.js
const delta = quill.getContents();
// Send delta JSON to Laravel via API
// Backend: Laravel Controller
$delta = json_decode(request('delta'), true);
$html = (new Parser())->parse($delta);
Database Storage
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->text('content_html');
$table->json('content_delta')->nullable();
});
Store the raw delta when you may need to re-render later, and store the rendered HTML for fast display.
Blade Rendering
<div class="quill-editor">
{!! $post->content_html !!}
</div>
URI Scheme Allowlist for Links / Images / Videos
3.7.1 validates URI schemes before writing href/src attributes. Unsafe schemes such as javascript: are neutralized: links become #, and unsafe images/videos are omitted.
If you intentionally support custom schemes such as ftp, extend the corresponding listener:
$link = new Link();
$link->safeSchemes = ['http', 'https', 'mailto', 'tel', 'ftp'];
$lexer->overwriteListener(new Link(), $link);
Apply the same idea to image/video listeners if you need custom media schemes. Keep this list narrow.
Font Size Customization
$parser = new Parser();
$parser->setFontSizeMap([
'small' => '12px',
'medium' => '16px',
'large' => '24px',
]);
Custom Elements with Font Support
$parser = new Parser();
$parser->addCustomElement('mention', function ($attrs, $text) {
$fontSize = $attrs['font']['size'] ?? '16px';
return '<span class="mention" style="font-size: ' . $fontSize . '" data-id="' . $attrs['id'] . '">@' . $text . '</span>';
});
Escape any dynamic values inside custom element callbacks.
Delta Validation
use Nadar\QuillDeltaParser\Validator;
$validator = new Validator();
if ($validator->validate($delta)) {
$html = (new Parser())->parse($delta);
}
Use Validator for delta shape, not as a full HTML sanitizer.
Batch Processing
$parser = new Parser();
$deltas = collect($request->deltas);
$htmls = $deltas->map(fn ($delta) => $parser->parse($delta));
Stored XSS / URI Schemes
3.7.1 fixes stored XSS via link, image, and video URI attributes.javascript: links are neutralized to #; unsafe images/videos are not rendered.Custom URI Schemes After Upgrade
ftp:// or another non-default scheme, it may now be neutralized.$safeSchemes property:
$link = new Link();
$link->safeSchemes = ['http', 'https', 'mailto', 'tel', 'ftp'];
$lexer->overwriteListener(new Link(), $link);
javascript, data, or other unsafe schemes.Nested Deltas
Font Size Attribute Handling
font.size maps to inline style="font-size: ...".setFontSizeMap() when your frontend uses semantic sizes like small, medium, large.Log Raw Deltas
\Log::debug('Raw Delta:', $delta);
Verify the Quill op structure, especially attributes:
['insert' => 'text', 'attributes' => ['bold' => true, 'font' => ['size' => '24px']]]
Inspect HTML Output
$html = $parser->parse($delta);
\Log::debug('Generated HTML:', [$html]);
Specifically inspect rendered href and src attributes after upgrading.
Test Unsafe URI Payloads
javascript: link and confirm it renders as #.ftp, add a test proving it still renders only where intended.Custom Element Quirks
font.size vs font-size.Override Default Tags
$parser->setTagMap([
'bold' => 'b',
'italic' => 'i',
]);
URI Scheme Allowlists
$link = new Link();
$link->safeSchemes = ['http', 'https', 'mailto', 'tel', 'ftp'];
$lexer->overwriteListener(new Link(), $link);
Font Size Customization
$parser->setFontSizeMap([
'small' => '12px',
'medium' => '16px',
'large' => '24px',
]);
Pre/Post-Processing
$parser->setPreProcess(function ($delta) {
return $delta;
});
$parser->setPostProcess(function ($html) {
return '<div class="content">' . $html . '</div>';
});
Be extra careful with post-processing: it can reintroduce unsafe HTML if it wraps or injects untrusted values.
Performance
Parser instance for bulk parsing:
$parser = new Parser();
foreach ($deltas as $delta) {
$html = $parser->parse($delta);
}
How can I help you explore Laravel packages today?