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

Laravel Modo Laravel Package

yeswedev/laravel-modo

Modo is a lightweight Laravel package from yeswedev that gives you a clean, configurable way to extend your application, with an expressive API, sensible defaults, and seamless Laravel integration so you can get up and running in minutes.

View on GitHub
Deep Wiki
Context7

Getting Started

Install via Composer and publish anything the package ships:

composer require yeswedev/laravel-modo
php artisan vendor:publish --provider="YesWeDev\Modo\ModoServiceProvider" --tag="modo-config"
php artisan migrate

The migration is the first thing to inspect — moderation is stateful, so the package will add columns or a companion table (typically *_moderations or moderation_status/moderated_at columns on tracked models). Read the published migration before running it so you know what schema it expects.

Next, check the field class in src/ (vendor/yeswedev/laravel-modo/src/). Nova fields extend Laravel\Nova\Fields\Field, so the public API you'll actually touch is the fluent setters exposed there plus the resolve*/fill* hooks. Look for a base field like Modo or ModeratedField and the trait it expects on your Eloquent model.

First use case — a typical wiring:

use YesWeDev\Modo\Modo;

public function fields(Request $request)
{
    return [
        Modo::make('Body')
            ->moderatedBy('moderator_id')     // name the package actually exposes
            ->statuses(['pending', 'approved', 'rejected']),
    ];
}

Add the expected trait to your model:

use YesWeDev\Modo\Concerns\HasModeration;

class Post extends Model
{
    use HasModeration;
}

Before writing anything, look at:

  1. The migration (schema contract).
  2. The trait (HasModeration or equivalent) — that defines the model-side API.
  3. The Nova field class — that defines the UI-side API.
  4. Any config file (config/modo.php) for table names, status enums, and the user model used for "who moderated".

Implementation Patterns

Field-level moderation on an existing model. Wrap the attributes you want reviewed in a Modo field, then guard writes. Keep the model's $fillable untouched and let the field handle staging — a common pattern is that the field writes to a shadow column until approved:

Modo::make('Description')
    ->originalColumn('description')
    ->pendingColumn('description_pending')
    ->onlyOnForms();

Then in your controller or observer, promote description_pending → description on approval. If the package provides an approval method on the trait, prefer it over manual column copying so audit columns stay consistent.

Index/detail visibility for moderators. Show the moderation state on the index so reviewers can triage:

Modo::make('Description')
    ->showOnIndex()
    ->badgeColors([
        'pending'  => 'bg-yellow-100 text-yellow-800',
        'approved' => 'bg-green-100 text-green-800',
        'rejected' => 'bg-red-100  text-red-800',
    ])
    ->hideWhenCreating(),

Combine with a Nova filter (ModerationStatusFilter) scoped to pending so a reviewer's queue is a saved view rather than a search.

Authorization gating. Don't rely on the field to hide itself. Gate the action:

Modo::make('Description')
    ->canApprove(fn (Request $request) => $request->user()->can('moderate', $post));

Then mirror that policy in your NovaRequest::authorizeToUpdate. Field visibility is a UI hint; the policy is the enforcement.

Audit trail via Nova actions. Attach an "Approve"/"Reject" Nova action that calls the model's moderation method and writes a moderation_events row. This keeps bulk review inside Nova's table view rather than requiring a custom screen.

Observer for downstream effects. When moderation transitions to approved, fire a model event and let observers handle reindexing, cache busting, notifications — don't put that logic in the field or the action.

Testing. Assert on the trait's methods and on NovaRequest field resolution, not on rendered HTML. A field-level moderation test should look like:

$post->stageModeration('description', 'new value');
$this->assertDatabaseHas('posts', ['description_pending' => 'new value']);

$post->approveModeration('description', $moderator);
$this->assertEquals('new value', $post->fresh()->description);

Gotchas and Tips

Schema assumptions. The package is a field wrapper, not a workflow engine — if it assumes specific column names, renaming anything in the migration (or running it against a model whose columns differ) will fail silently. Publish, read, then edit before migrate.

Status enum coupling. Status strings are usually hardcoded in the field's badge map and in the trait's checks. If you introduce a new state ('changes_requested'), you must update both the field config and any model scopes. Grep the vendor directory for the status literals before extending.

Field doesn't persist on its own. Nova fields typically call fillInto — if moderation is staged, the field must override fill to write to the pending column. If your description gets overwritten directly on save, the field hasn't hooked the pending path; check the field's fill() implementation and whether you need ->originalColumn().

resolve( on index. A moderation field that resolves a virtual attribute won't sort or filter correctly unless it maps to a real column. Use ->sortable() only if the underlying status column exists on the model; otherwise add a proper column via the migration.

Visibility vs. authorization. ->hideWhenUpdating() hides the input but a hand-rolled PUT will still submit whatever fields are in $fillable. Combine $fillable scoping with a policy check; don't trust hidden fields.

Zero dependents = unvetted. With no downstream users, breaking-change risk is entirely yours. Pin the version in composer.json ("yeswedev/laravel-modo": "~1.0" or a commit hash) rather than using ^. Fork it into your own namespace if you need to patch — you don't want a composer update to silently change the moderation contract.

Debug tips.

  • php artisan nova:field listing and App\Nova\... field resolution errors usually mean the service provider isn't registered or the package's field macro collides with a Nova version. Check composer.json's Nova constraint against your installed laravel/nova version.
  • If approvals don't trigger observers, the trait is probably calling saveQuietly() or updating via query builder. That bypasses events — dispatch them explicitly or switch to save().
  • Turn on APP_DEBUG and check storage/logs/laravel.log after a save; the field's fill() failure often logs rather than throws.

Extension points. Before writing a custom approval UI, check whether the package exposes a moderation() relation or a ->using(Closure::class) hook — wrapping the existing field is far less work than reimplementing the workflow. If the trait has protected function moderationTable(), override it in a subclass via a trait alias to change the table name without publishing config.

Nova version drift. Nova changes its field Resolve/Fill contracts between majors. Verify the package's tested Nova version against yours; a field that resolves in Nova 4 may render blank in Nova 5 because resolveUsing changed. If there's a mismatch, wrap the field in a custom Field subclass that forwards to the package's methods — cheaper than forking.

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/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
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