medienreaktor / neos-studio
Neos Studio - A revolutionary, blazingly fast, collaborative (multiplayer!) editing UI for Neos 9.
Package info
github.com/medienreaktor/Medienreaktor.NeosStudio
Language:TypeScript
Type:neos-package
pkg:composer/medienreaktor/neos-studio
Requires
- php: ^8.2
- medienreaktor/neos-api: ^1.9.0
- neos/neos: ^9.1
README
A revolutionary, blazingly fast, collaborative (multiplayer!) editing UI for Neos 9. Built from scratch on a clean HTTP API — fully independent of Neos.Neos.Ui, free to make its own architectural choices. This is what content editing in Neos can feel like: instant, fluid, together with your team, and open for extension from day one.
Neos Studio is a modern single-page application (Vite + React + TypeScript + TanStack Query + Tailwind CSS v4) that talks to Neos exclusively through Medienreaktor.NeosApi — a unified OAuth-secured REST API over the Event-Sourced Content Repository. No Fusion-rendered backend modules, no shared React runtime with the classic UI — just a fast, typed, cache-smart client in front of a well-defined, documented API.
Why a new UI? The classic Neos UI is a great piece of engineering that has served editors well for years — and Studio owes a lot to the ideas it pioneered. But some of its 2016-era architectural constraints are hard to move past today: the plugin API ties extensions to React 16, and the internal wire protocol was never designed as a public contract. Neos Studio starts from a different premise: an API-first backend, a lean modern frontend, and extensibility through observable registries that are the public contract. It grows alongside the classic UI, adopting editing surfaces one at a time.
Highlights
🤝 Collaborative editing — multiplayer for Neos
Edit together in the same workspace, live. Every shared workspace offers a Collaborative entry in the workspace switcher: pick it, and you and your colleagues edit the same content directly — no personal-workspace detours, no publish-to-see-each-other, no conflicts to untangle afterwards.
- See who's there: avatar initials next to the switcher, markers on the document a colleague is on (document tree) and the element they're focusing (content outliner and live preview, outlined in their color with a nametag).
- See what they do, as it happens: colleagues' edits stream in within ~2 seconds — or in well under a second with the realtime sidecar (below). Changed elements re-render in place in the preview (out-of-band rendering — your scroll position and your own inline edits survive); structural changes refresh the trees.
- Nothing extra to install: no WebSocket server, no Node sidecar, no message broker required. The Event-Sourced Content Repository already keeps one totally ordered change log per workspace — Studio simply tails it over plain HTTP through pure PHP endpoints. If it runs Neos 9, it runs multiplayer.
- Scales up when you do: an optional realtime sidecar — a small Hocuspocus-based WebSocket server — upgrades the transport to instant push: presence without heartbeats, one change-feed tail per workspace instead of one poll per editor, sub-second latency. Studio falls back to plain polling automatically whenever the sidecar is unreachable, and back again when it returns. See the realtime sidecar.
- Emergent, not a mode: sessions are ordinary Neos
SHAREDworkspaces (create them in the Workspaces module, manage access with the usual roles). Two people in the same workspace — that is the multiplayer. Publishing the session to live works exactly like publishing any workspace.
🗂️ Task workflow — feature branches for content
Content work, organized like code: every task is a feature branch. Creating a task spins up a workspace of its own, so an editor can pick a task up, check it out, do the work and send it to review — without touching live or stepping on anyone else's changes.
- A Kanban board, as a panel: task branches as cards in status columns — Open, In review, Done — with assignee and comment count. Dragging a card drives the workflow: into In review submits the task, back to Open reopens it.
- Review before done: dropping a card on Done never publishes blindly — it opens the Review Changes dialog on the task's workspace, so a reviewer inspects the changes and picks what to publish. Reviewers are whoever holds the configurable reviewer role (default:
Neos.Neos:LivePublisher). - Completion is event-driven: a catch-up hook on the Content Repository's event stream watches task workspaces — once the branch is fully published, the task flips to done and everyone involved is notified, no matter which surface (or API client) triggered the publish.
- One-click checkout from the board, the task dialog or the workspace switcher, where task branches sit in their own group with status-colored badges. Creating a task — from the switcher or the board — checks it out for you immediately.
- Comments on tasks: every task carries its own discussion thread; submitting for review and reopening take a comment, too.
- Notifications built in: a bell in the top bar collects assignments, review requests, reopens, comments and completions — clicking one jumps straight to the task.
- Ordinary Neos underneath: task branches are plain
SHAREDworkspaces plus task metadata, with access through standard workspace roles — the creator and reviewers manage, the assignee collaborates, and uninvolved editors never see task branches at all. Publishing, syncing and conflict resolution work like on any workspace.
⚡ Blazingly fast, everywhere
- Instant loads — a static Vite-built SPA served at
/neos/studio, with TanStack Query caching and background revalidation. Navigating between documents doesn't reload the world; it reuses what's already in the cache. - Silent auth — Studio provisions its own first-party OAuth client (authorization code + PKCE) on first load. Logged-in backend users get a token silently: no consent screen, no setup, and automatic client-side token refresh.
- Lazy everything — document tree and content outliner load on demand, honoring the Neos
loadingDepthsettings, with node type icons, visibility states and pending-change markers.
🧩 Dockable panel system
The entire workspace is built from panels: document tree, content outliner, inspector, media browser, node creation, clipboard, preview — every surface is a panel that can be docked, resized and toggled. Panels live in a PanelRegistry, which means third-party packages register their own panels into the same layout system the built-in ones use. Your panel is a first-class citizen, not an iframe in a corner.
✍️ Lightweight inline Rich Text Editing
Inline editing runs on TipTap 3 — a lean, headless, ProseMirror-based editor instead of a monolithic CKEditor build. The RTE lives inside the preview iframe with a floating toolbar, driven by a guest-side formatting registry, so what you edit is exactly what renders. The same normalized formatting engine powers the inspector's rich-text editor, and the shared Link Editor (with a pluggable tab registry) handles links in both the RTE and the inspector's link fields.
🖱️ Context menus and drag & drop
- Context menus on every node — in the document tree, the content outliner and the media browser — with copy, cut, paste, delete, hide and more.
- Drag & drop node moves in the trees, constraint-aware.
- Drag-to-create: pick a node type from the creation panel and drop it straight into the live preview, or use the insert dialog (before / inside / after, filtered by node type constraints).
- A node clipboard that survives navigation, with its own panel and document/content separation.
🔍 A complete, extensible inspector
Full parity with the classic inspector — and then some:
- Editors: text, textarea, rich text, select box (with data source support), references, asset / assets, image, link, date & time, range slider, boolean, code, node type, URI path segment … all registered through an editor registry, all replaceable.
- Views: NodeInfo, Column, Table and TimeSeries views plus data-source-driven widgets — through a views registry.
- Validators: the
Neos.Neos/Validationbuilt-ins with live inline errors, tab badges and save-blocking — through a validators registry. - ClientEval support (
ClientEval:expressions for hidden state and editor options), transient values, and dimension shine-through indicators with one-click "create variant" — in the inspector and directly in the preview.
🌐 Full editing environment
- Live preview with an inline-editing guest bridge — edit content directly in the rendered page.
- Media module: full asset management plus picker mode for asset editors.
- Workspaces, sites and dimensions: switchers for all three, pending-change tracking, publish and discard.
- Trash panel: deleting a node is a soft removal, so every deleted page waits in the trash until the deletion is published — with who deleted it and when, and one click to restore it (deleted parent pages come back with it).
- User & profile management: administer users, and every editor gets self-service profile settings (name, email, password, interface language).
- Localized UI in English and German via Neos' own XLIFF infrastructure (450+ keys) — translated the Neos way, extendable the Neos way.
- Consistent design system: shadcn-style components on Base UI primitives, themed with the official Neos UI palette. Dark, focused, familiar.
🔌 Extensible by design — registries all the way down
Extensibility isn't bolted on; it's the architecture. Studio's building blocks are observable registries:
| Registry | What you can add |
|---|---|
| Panels | Whole new workspace surfaces, docked anywhere |
| Inspector editors | Custom property editors for any node type property |
| Inspector views | Custom read-only views and widgets |
| Validators | Custom client-side validation |
| Link editor tabs | New link source types in the shared link modal |
| Modals | App-level dialogs |
| Workspace decorators | Badges and grouping for workspaces in the switcher and administration (this is how task branches get their status colors) |
| Node decorators | Per-node visuals on every tree row: replace the type icon, layer badge overlays onto it, tint or dim the whole row (this is how hidden nodes dim and deleted nodes get their red badge) |
| Keyboard shortcuts | App-wide shortcuts alongside the built-in ones |
Third-party packages ship a small IIFE bundle that binds to the shell's public plugin API (window.NeosStudio — React instance, useStudio() app state, and all registries) with full TypeScript types generated from the shell's own source. The shell injects your bundle via a single Settings.yaml entry — no build-system fusion, no webpack surgery, no version lock-in dance. Registration is late-bindable and observable: register, and the UI re-renders.
Start here: Medienreaktor.NeosStudio.ExamplePlugins — a copy-me boilerplate that registers an example panel, a custom inspector editor (a color picker) and a node decorator from a completely separate package.
The package family
| Package | What it is |
|---|---|
| Medienreaktor.NeosApi | The foundation: OAuth 2.1 (PKCE, refresh token rotation, client credentials, dynamic registration), a read API over the ContentGraph, a write API for CR commands with batching and idempotency, workspace publishing, media API, data sources. Feature-based endpoint policy per role, structural content authorization through the CR itself. Useful far beyond Studio — for integrations, importers and MCP servers. |
| Medienreaktor.NeosStudio (this package) | The editing UI built on that API. |
| Medienreaktor.NeosStudio.ExamplePlugins | Plugin boilerplate: example panel + example inspector editor, with the full build setup for extending Studio from your own package. |
Getting started
Requires Neos ^9.1, PHP ^8.2 and Node 22 (pinned via .nvmrc) for building the frontend.
composer require medienreaktor/neos-api medienreaktor/neos-studio # build the SPA cd DistributionPackages/Medienreaktor.NeosStudio/Resources/Private/Studio nvm use npm install npm run build # outputs to Resources/Public/Studio/ (committed to the repo) # publish and flush on the Neos side ./flow resource:publish ./flow flow:cache:flush
Open /neos/studio and log in with your Neos backend account. That's it — Studio lazily provisions its own OAuth client on first load; there is nothing to configure.
The realtime sidecar (optional)
Multiplayer needs no extra infrastructure — but it can use some. Resources/Private/Realtime/ ships a small Hocuspocus WebSocket server that upgrades Studio's collaboration transport from HTTP polling to push. Without it, everything keeps working: collaboration falls back to plain HTTP polling against the Neos API (2s change feed, 5s presence heartbeat). The fallback is also automatic at runtime — while the sidecar is unreachable, connected Studios poll; when it comes back, they stop.
One WebSocket per editing session (document name workspace:<name>):
- Presence — clients announce their position as stateless messages; the sidecar keeps a server-authoritative roster per workspace (identity comes from the validated token, never from the client) and broadcasts changes instantly. No heartbeats, no TTL ghosts: a closed tab leaves with its connection.
- Change feed — the sidecar tails each active workspace's event feed once per
FEED_INTERVAL_MSthrough a shared-secret server-to-server endpoint (/api/realtime/workspaces/{name}/events) and fans new events out to every editor. That replaces one API poll per editor per 2s with one poll per workspace, and remote edits reach colleagues in well under a second. - Yjs seam — the (currently unused) Yjs document behind each connection is where collaborative text editing attaches later, without a new connection concept.
Authentication
Two credentials, deliberately separate:
- Every client connection authenticates with the editor's own OAuth bearer token. The sidecar validates it against the user-scoped API (a baseline read of
/api/workspaces/{name}/eventsproves the token is alive AND the user may read that workspace) and resolves identity via/api/me. - The sidecar's own feed reads authenticate with a shared secret (
X-Realtime-Secret), configured on both sides:Medienreaktor.NeosStudio.realtime.sharedSecret(Neos) andREALTIME_SHARED_SECRET(sidecar). While the Neos-side secret is empty, the server-to-server endpoint answers 404 — an unconfigured installation exposes nothing.
Running
cd DistributionPackages/Medienreaktor.NeosStudio/Resources/Private/Realtime
npm install
REALTIME_SHARED_SECRET=... NEOS_BASE_URL=https://your-site npm start
| Variable | Default | Purpose |
|---|---|---|
PORT |
1234 |
Listen port |
NEOS_BASE_URL |
http://127.0.0.1:8080 |
Base URL of the Neos installation |
NEOS_HOST_HEADER |
(none) | Host header override for internal hostnames |
REALTIME_SHARED_SECRET |
(required) | Must equal Medienreaktor.NeosStudio.realtime.sharedSecret |
FEED_INTERVAL_MS |
1000 |
Feed tail cadence per active workspace |
Then point the Studio at it:
Medienreaktor: NeosStudio: realtime: websocketUrl: "wss://realtime.your-site.example" sharedSecret: "..." # openssl rand -hex 32
TLS termination is expected to happen in front (reverse proxy); the sidecar itself speaks plain WS/HTTP.
Development
The SPA sources live in Resources/Private/Studio/ (Vite, src/, index.html); the build output goes to Resources/Public/Studio/. The realtime sidecar lives next to it in Resources/Private/Realtime/ (plain Node, no build step).
src/
main.tsx entry: installs plugin-API globals, mounts the app
app/ application shell, providers, shared QueryClient
auth/ authorization-code + PKCE flow, token refresh
api/ data layer: typed apiFetch(), query-key factory, one hook file per resource
components/ui/ shadcn-style components on Base UI primitives (owned code)
features/ one folder per feature: tree, inspector, preview, media,
panels, creation, clipboard, editing, links, dimensions,
workspaces, tasks, notifications, collaboration, sites,
users, profile, modals, shortcuts
guest/ the script injected into the preview iframe:
inline editing, TipTap RTE, toolbar, link editing
plugin-api/ the public plugin API surface (window.NeosStudio)
lib/, hooks/ shared utilities
Conventions: every query key comes from api/keys.ts (never inline literals); one hook file per API resource; feature folders own their UI and hooks; @/ aliases src/. Styling is Tailwind v4 (CSS-first config) mapped onto the shadcn token contract with the official Neos palette; primitives are Base UI (@base-ui/react), not Radix.
npm run dev starts the Vite dev server for fast iteration; end-to-end auth is tested against the built-and-served shell at /neos/studio (the OAuth redirect URIs are bound to that origin).
License
Neos Studio is free software, released under the GNU General Public License, version 3 or later.
Copyright (C) 2026 medienreaktor GmbH
Built by medienreaktor with ❤️ for the Neos community. Feedback, issues and plugin experiments very welcome — this is where the Neos editing experience is headed. Come shape it.
