Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Editorjs Php Laravel Package

setono/editorjs-php

Setono Editor.js PHP is a PHP library for parsing, sanitizing, and rendering Editor.js JSON as HTML. It provides custom block handlers, validation, and extensible configuration, making it easy to safely display Editor.js content server-side with clean, controlled output in Laravel or any PHP applica...

View on GitHub
Deep Wiki
Context7

Getting Started

Install with Composer:

composer require setono/editorjs-php

The package is a thin PHP SDK around the JSON that Editor.js produces. Editor.js saves output in the shape:

{
  "time": 1710000000000,
  "version": "2.28.2",
  "blocks": [
    {"id": "abc", "type": "paragraph", "data": {"text": "Hello <b>world</b>"}},
    {"id": "def", "type": "header", "data": {"text": "Title", "level": 2}}
  ]
}

Look first at the src/ tree — it is small, and the layout tells you almost everything:

  • Block/ — one typed class per Editor.js block type (ParagraphBlock, HeaderBlock, ListBlock, ImageBlock, …).
  • Parser/ — turns the decoded JSON array into a Block collection.
  • Renderer/ — turns a Block back into HTML.
  • Exception/ — what you will catch when the payload is malformed or contains an unknown block.

Your first use case should be the canonical one: store the raw Editor.js JSON, render it to HTML server-side.

use Setono\EditorJS\Parser\Parser;
use Setono\EditorJS\Renderer\Renderer;

$payload = json_decode($request->getContent(), true);

$parser   = new Parser();
$content  = $parser->parse($payload);        // block objects
$html     = (new Renderer())->render($content);

// persist the original JSON, not the HTML, as the source of truth
$post->content = json_encode($payload);

Verify the exact constructor signatures against src/Parser and src/Renderer before writing a lot of code — the package is small and under-documented (42 stars), so src/ is the authoritative reference, not the README.

Implementation Patterns

Parse on write, render on read. Treat the Editor.js JSON as the immutable source. Parse it once when the request comes in to validate that every block is understood, then persist. Render on demand (or cache the HTML). This keeps the door open to re-rendering with a fixed renderer when you upgrade the package or fix a template.

Two-phase validation. Use the parser as a validator in your form request / controller:

try {
    $content = (new Parser())->parse($payload);
} catch (\Throwable $e) {
    throw ValidationException::withMessages(['content' => 'Unsupported editor content.']);
}

This prevents storing JSON from block types the renderer cannot handle, which otherwise surfaces as a 500 on a public page weeks later.

Custom blocks via registration, not forking. For a block type the package does not ship (product embed, callout, table), implement your own block class following the same contract as the shipped ones in src/Block, then register it with the parser/renderer. Keep custom blocks in App\EditorJs\Block\...; the package is MIT-licensed but small enough that forking it is a maintenance trap.

Fallback block for the unknown. In practice you will meet table, embed, code, delimiter, or plugin-specific types. Register a RawBlock/fallback that either renders $data as-is or returns an empty string. Decide explicitly: fail loudly in dev, degrade gracefully in production.

Cache the rendered HTML, keyed by content hash.

$key = 'post:html:' . hash('xxh3', $post->content);
return Cache::rememberForever($key, fn () => (new Renderer())->render(
    (new Parser())->parse(json_decode($post->content, true))
));

Parsing is cheap; HTML rendering in a loop is the part worth memoizing.

Service container binding. In Laravel, bind Parser and Renderer as singletons in a service provider so the block registry is configured in one place. In Symfony, the same object graph is a single services.yaml entry. Do not new them at every call site once you have custom blocks registered.

API / headless output. Expose GET /api/posts/{id}/content?format=html|json. Return the stored JSON for the editor to rehydrate; return rendered HTML for consumers. Keep the raw blocks array available — Editor.js needs it to re-open a document for editing.

Sanitize at render, not at save. If your editor allows HTML inside paragraph/header, save it verbatim (so editing round-trips) but pipe rendered output through HTMLPurifier in the renderer or the view. Saving sanitized HTML destroys content the user may re-edit.

Gotchas and Tips

  • The JSON envelope. Editor.js output is {time, blocks, version}, not a bare array. Some helpers accept the whole envelope, others only blocks. Read the parser signature; passing the wrong level is the most common first-day error.
  • Unknown block type = exception. The failure mode you will see in production logs is a class-not-found or a parser exception for type: "raw" or a plug-in block. Install a fallback block before you ship, not after.
  • Empty blocks are real. Editor.js happily emits {"type":"paragraph","data":{"text":""}} for trailing blank lines. Filter empty paragraphs and empty headers before rendering, or you will get stray <p></p> margins throughout the page.
  • Nested lists. A list block's items can be strings or objects ({content, items: [...]}) depending on version and whether the list is a checklist. Confirm the renderer recurses; otherwise flatten in your own block class.
  • Image URLs are not guaranteed absolute. data.file.url depends entirely on what the uploader returned — a S3 key, a relative path, or a full URL. Normalize to an absolute URL in your block subclass or in a Twig/Blade template.
  • raw and paragraph are XSS vectors. Editor.js does not sanitize; it only encodes what is typed. Sanitize on output. This is the single highest-severity pitfall in this package.
  • Version and pinning. Star count is low and the last release is dated in the future relative to typical package ecosystems — pin an exact version in composer.json, read the CHANGELOG/git tags, and check the composer.json require block for the PHP constraint (8.x) and any ext-* dependencies. Do not rely on the README being current.
  • Debugging pipeline. When rendering looks wrong, dd(json_decode($post->content, true)) first, then dd($content->getBlocks()) after parsing. Most bugs are at the boundary between decoded array and block object, not inside the renderer.
  • Extension points to learn. The block base class/interface, the parser's block-type map, and the renderer's block template resolution. Those three are the entire surface area; everything else is a convenience wrapper.
  • Do not store rendered HTML as the source of truth. It couples your database to a specific renderer version and makes editing round-trips lossy. Store JSON, cache HTML separately with a hash-keyed cache entry you can flush.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib