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

Cms Workspaces Laravel Package

typo3/cms-workspaces

TYPO3 Workspaces adds editorial workflow to TYPO3 CMS, letting teams edit content in draft workspaces, preview and review changes, and publish to the live site only when ready.

View on GitHub
Deep Wiki
Context7

Getting Started

The first thing to internalise: typo3/cms-workspaces is not a Laravel package. It is a TYPO3 Core system extension (typo3/sysext/workspaces/), Composer-installable only inside a TYPO3 installation, where it relies on the TYPO3 bootstrap, the Symfony DI container, TcaSchemaFactory, and DataHandler as its write path. There is no ServiceProvider, no facade, no config file you can publish. composer require typo3/cms-workspaces in a Laravel app will install a package whose classes are dead weight unless TYPO3 is bootstrapped around them.

So "getting started" means picking one of two realistic adoption paths:

  1. Reference architecture (most likely, ~90% of cases). You are building editorial draft/publish/staging in Laravel and want a battle-tested model for it. Read in this order:
    • typo3/sysext/workspaces/Classes/Service/WorkspaceService.php — the public API surface (list workspaces, check access, publish/swap).
    • Classes/Controller/ — the backend module, i.e. the UI flow for review → publish.
    • Configuration/TCA/ + ext_tables.sql — the schema: sys_workspace, sys_workspace_stage, and the versioning columns stamped onto every versionable table.
    • Documentation/ in the sysext, plus the changelogs in typo3/sysext/core/Documentation/Changelog/ for behaviour changes.
  2. Headless backend (if you genuinely run TYPO3). Enable the extension, create workspaces in the backend module, and consume published content from Laravel over JSON/API. Your Laravel app then never touches workspace tables directly.

First use case worth prototyping: a "draft → review → publish" pipeline over one Eloquent model, before you touch anything else. If that model works, everything else is scale.

Implementation Patterns

TYPO3 solves versioning with per-record version rows, not a separate history table. Every versionable record carries t3ver_wsid (workspace id, 0 = live), t3ver_oid (the live record it shadows), t3ver_state (new / delete / move placeholder) and t3ver_stage. The live table is the published state; drafts live as sibling rows in the same table, filtered out by workspace.

That single design decision is the most valuable thing to port. A Laravel-idiomatic translation:

// migration sketch — mirrors t3ver_* columns
$table->unsignedBigInteger('workspace_id')->default(0)->index(); // 0 = live
$table->unsignedBigInteger('version_of')->nullable()->index();   // t3ver_oid
$table->string('version_state')->default('edit');                // new|edit|delete|move
$table->string('stage')->default('editing');                     // editing|review|publish

Then a global scope + middleware pair, so controllers never think about workspaces:

// app/Models/Concerns/Versionable.php
public static function bootVersionable(): void
{
    static::addGlobalScope('workspace', function (Builder $q) {
        $ws = app(WorkspaceContext::class)->id(); // resolved in middleware
        $q->where('workspace_id', $ws);
        // drafts shadow live rows: hide the live row when a draft exists
        $q->whereNotExists(fn ($sub) => $sub->from('posts as d')
            ->whereColumn('d.version_of', 'posts.id')
            ->where('d.workspace_id', $ws));
    });
}

Patterns worth copying wholesale:

  • Workspace context via middleware + scoped container binding. TYPO3 resolves the active workspace from the backend user session; in Laravel resolve it from a route param, a signed preview cookie, or an X-Workspace header, bind it as a scoped singleton so queue jobs get a clean one. Never read it from request() deep in a model.
  • Preview as a signed URL, not a login. TYPO3's frontend preview passes ADMCMD_prev with a hash. In Laravel: URL::temporarySignedRoute('posts.show', now()->addHours(2), ['post' => $id, 'ws' => $wsId]), and a middleware that binds the workspace from the query string only if the signature is valid.
  • Stages are a state machine, not a boolean. sys_workspace_stage is a first-class table with per-stage responsible users and notification emails. Model it as a stages table + spatie/laravel-model-states (or a plain enum + transition guard), and treat "publish" as a distinct permission from "edit".
  • Publishing is a swap, in a transaction. TYPO3's DataHandler swaps t3ver_oid records into live and flushes caches per affected page. Your equivalent: copy draft → live row (or flip workspace_id to 0), write an audit row, dispatch a job to purge cache/tag invalidation, all inside DB::transaction(). Emit a domain event (PostPublished) rather than doing side effects inline — that's the PSR-14 event/listener pattern TYPO3 itself mandates for new extension points.
  • Workspace membership is a pivot with roles. sys_workspace ↔ be_users/be_groups with owner/member/reviewer. Mirror with a workspace_user pivot and a policy; owners can freeze/delete a workspace, members cannot.
  • If you run TYPO3 headless, drive publishing through the workspace service and a scheduler job, then have Laravel read only workspace_id = 0 rows over an API. Do not join against t3ver_* tables from Laravel — the semantics are TYPO3-version-specific.

Integration tips: keep the versionable trait opt-in per model; add a php artisan workspace:preview {id} command for local QA; and build a Workspace Filament/Livewire resource early — TYPO3's backend module is the UX template (list workspaces → list changes → diff → publish/discard).

Gotchas and Tips

  • Licence. GPL-2.0. Reading it to learn is fine; copying Classes/ code into a proprietary Laravel app is a distribution problem. Reimplement the model, don't vendor the code.
  • It's a monorepo sysext, not a standalone library. Zero dependents on Packagist is not a red flag — the real usage is "every TYPO3 install with editorial workflows". Version constraints track the TYPO3 major (^13.4, ^14), and breaking changes ship as RST changelog entries, not semver. Pin TYPO3 itself.
  • Row growth is real. Every edit of every field creates a version row in TYPO3; live cleanup is a separate concern. In your port, put a pruning command on a schedule from day one — cleaning up drafts after publish is the easiest thing to forget and the most painful to retrofit.
  • Delete and move are not "nothing". t3ver_state distinguishes a new record, an edited record, a deleted record, and a move placeholder. Moves especially need a placeholder row in the source position or pagination silently drops items. Budget real time for delete/move semantics; they are where naive implementations break.
  • Relations are the hard part. TYPO3 resolves versioned relations through sys_refindex and swaps them on publish. In Laravel, decide up front whether a draft relates to draft children (correct, expensive) or to live children (cheap, wrong for nested content). Don't defer this decision to week three.
  • Preview caching bites. Published changes need a cache flush keyed to the affected pages and any listing/feeds. TYPO3 flushes per page id; Laravel's tag-based cache invalidation is a reasonable substitute — but only if you tagged from the start.
  • Permissions are layered, not flat. Workspace access, record ownership, stage responsibility, and the "publish" capability are four checks. Debugging a "can't publish" report is almost always the stage, not the workspace.
  • If you write TYPO3 extension code around it, follow the repo's current conventions rather than copying old sysext code: consume TcaSchemaFactory instead of $GLOBALS['TCA'], use #[AsEventListener] instead of hooks, constructor injection instead of GeneralUtility::makeInstance(), and give every exception a unique integer code (Build/Scripts/runTests.sh -s listExceptionCodes enforces it). Frontend assets under Resources/Public/{JavaScript,Css} are build output — never edit them.
  • Testing. TYPO3 proves this behaviour with functional tests that import CSV fixtures (importCSVDataSet(), which does not resolve EXT: paths — use __DIR__-relative). For your Laravel port, the equivalent discipline is a fixture workspace + a test that asserts a draft is invisible on live, visible in preview, and swapped on publish. Write that test before the second model adopts the trait.
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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route