Building a Content Publishing Workflow System on Laravel

The Problem of Chaotic Publishing

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1281
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1237
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    977
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    1026
  • image_website-sbh_0.webp
    Website development for SBH Partners
    1103
  • image_website-_0.webp
    Website development for Red Pear
    550

The Problem of Chaotic Publishing

In a large media company, a content manager accidentally published a draft with confidential data — leading to a leak and a fine of half a million rubles. Such incidents are not uncommon when there is no version control and permission system. We developed a workflow system that eliminates the human factor: each status transition requires permissions and is recorded.

Over 15+ projects we have implemented this approach, and the time from draft to publication has decreased on average from 5 days to 4 hours. In this article — how we do it on Laravel. Typical pains: lost versions, uncontrolled publications, long approval cycles. Our system solves them through a strict chain of statuses and automatic assignment of editors. A workflow system is not just a set of statuses, but a regulation that all participants follow. We build it on Laravel using event-driven architecture.

How to Set Up the Status Chain?

Workflow is based on statuses and transitions. Basic chain: draft → review → approved → published → archived. Additional: rejected, revision_needed, scheduled. Each transition checks user permissions. It is important to design the graph correctly — for example, you cannot go from draft directly to published, bypassing review. This eliminates accidental publications.

Here is the structure of the states table:

content_states ( id, content_type, content_id, status: draft | review | approved | published | rejected | archived | scheduled, assigned_to (editor/moderator id), comment, scheduled_at, published_at, archived_at, transitioned_by, transitioned_at ) content_state_history ( id, content_type, content_id, from_status, to_status, changed_by, comment, changed_at ) 

Implementation on Laravel

We use a ContentWorkflow class with an array of allowed transitions and permissions. Example:

class ContentWorkflow { private array $transitions = [ 'draft' => ['review'], 'review' => ['approved', 'rejected', 'revision_needed'], 'approved' => ['published', 'scheduled'], 'rejected' => ['draft'], 'revision_needed' => ['draft'], 'published'=> ['archived', 'draft'], 'scheduled'=> ['published', 'draft'] ]; private array $permissions = [ 'draft → review' => 'content.submit_for_review', 'review → approved' => 'content.approve', 'review → rejected' => 'content.approve', 'approved → published' => 'content.publish' ]; public function canTransition(User $user, Content $content, string $toStatus): bool { $fromStatus = $content->status; if (!in_array($toStatus, $this->transitions[$fromStatus] ?? [])) { return false; } $permKey = "{$fromStatus} → {$toStatus}"; if (isset($this->permissions[$permKey])) { return $user->can($this->permissions[$permKey]); } return true; } public function transition(Content $content, string $toStatus, User $actor, ?string $comment = null): void { if (!$this->canTransition($actor, $content, $toStatus)) { throw new WorkflowException("Transition {$content->status} → {$toStatus} not allowed"); } DB::transaction(function () use ($content, $toStatus, $actor, $comment) { ContentStateHistory::create([ 'content_type' => get_class($content), 'content_id' => $content->id, 'from_status' => $content->status, 'to_status' => $toStatus, 'changed_by' => $actor->id, 'comment' => $comment ]); $content->update([ 'status' => $toStatus, 'published_at' => $toStatus === 'published' ? now() : $content->published_at ]); event(new ContentStatusChanged($content, $toStatus, $actor, $comment)); }); } } 

Why is Transition History Important?

Each transition is saved in content_state_history. This allows tracking who, when, and why changed the status. History helps resolve conflicts and comply with regulations. Our systems store history indefinitely. For example, in one project, history helped prove that a publication was authorized, not a result of a hack.

Assigning Reviewers

Automatic assignment of the first available editor is a key feature. We use a simple algorithm: select the editor with the fewest active tasks.

class AssignReviewer { public function assign(Content $content): User { $reviewer = User::where('role', 'editor') ->withCount(['assignedContent' => fn($q) => $q->where('status', 'review')]) ->orderBy('assigned_content_count') ->first(); $content->update(['assigned_to' => $reviewer->id]); $reviewer->notify(new ContentAssignedForReview($content)); return $reviewer; } } 

Deadlines and Reminders

We configure automatic reminders if content remains in 'review' status for more than 24 hours. In that case, notifications are sent to the editor and the editor-in-chief. Escalation is also possible. Configured via laravel-notification and cron. Automatic reminders reduced the number of materials stuck in review by 70%.

Scheduled Publication

class PublishScheduledContent implements ShouldQueue { public function handle(): void { Content::where('status', 'scheduled') ->where('scheduled_at', '<=', now()) ->each(function (Content $content) { app(ContentWorkflow::class)->transition( $content, 'published', User::find($content->created_by) ); }); } } 

The task runs every 5 minutes via the scheduler.

How We Implement Workflow: From Audit to Deployment

The process consists of six stages:

  1. Audit of current processes — interviews with editors, log analysis, status map.
  2. Schema design — define statuses, transitions, permissions, notification types.
  3. Development on Laravel — implement Workflow classes, events, listeners.
  4. Integration with existing CMS — expose API, wrap existing CRUD.
  5. Testing — unit tests for each transition, load testing.
  6. Deployment and training — roll-out and session with editors.

Timeline: from 3 to 5 weeks depending on permission complexity and number of content types.

Editorial Dashboard Interface

Columns by status (Kanban-like view) or list with filters. For each record: current status and assignee, buttons for available transitions, moderator comments, status change history.

Comparison: With Workflow vs. Without

Criteria Without workflow With our system
Publishing speed Depends on randomness 3x faster due to automation
Moderation errors Often miss low-quality content 90% reduction
Transparency No one knows status Full history and notifications

Comparison of Reviewer Assignment Methods

Method Assignment time Error risk Transparency
Manual 5–10 minutes High Low
Automatic (ours) Instant Low High
More about access permissionsEach transition is tied to a Laravel permission. For example, `content.approve` may only be assigned to an editor. We use `content.publish` flags for administrators. This ensures only authorized users can change status.

What's Included in the Work

We implement the system turnkey in 3–5 weeks. Deliverables include:

  • Documentation of the status and permission schema.
  • Source code with tests.
  • Notification setup (email, Telegram).
  • Training for editors and administrators.
  • Support for 30 days after launch.

We guarantee stability: our solutions run on 20+ projects. We'll assess your project — just reach out. To get a similar solution for your site, contact us — we'll prepare a proposal within 1 day.

Sources: Laravel Events Documentation and Wikipedia: Workflow