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 Notes Laravel Package

arcanedev/laravel-notes

A lightweight notes system for Laravel that lets you attach notes to any Eloquent model through a polymorphic relationship. Ships with a HasNotes trait, migration, configurable Note model, and a simple API for creating and retrieving notes on your records.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish everything first. The package ships a config file, a migration, and a service provider with auto-discovery; there is no facade you need to register manually.

composer require arcanedev/laravel-notes

php artisan vendor:publish --provider="Arcanedev\LaravelNotes\LaravelNotesServiceProvider"
# or target pieces individually
php artisan vendor:publish --provider="Arcanedev\LaravelNotes\LaravelNotesServiceProvider" --tag=config
php artisan vendor:publish --provider="Arcanedev\LaravelNotes\LaravelNotesServiceProvider" --tag=migrations

php artisan migrate

Look at these three files before writing any code:

  1. config/notes.php — points at the Note model class and the author (user) model. On Laravel 8+ this default is typically App\User, so change it to App\Models\User if that is your namespace.
  2. The published migration — confirms the column names (notable_type/notable_id for the model being annotated, plus author columns and a content column). Read it; do not assume.
  3. vendor/arcanedev/laravel-notes/src/Traits/HasNotes.php — this is the entire public API surface on your models.

Attach the trait to the model you want to annotate:

use Arcanedev\LaravelNotes\Traits\HasNotes;

class Order extends Model
{
    use HasNotes;
}

First use case — an internal note on a record:

$order->createNote('Customer called about a duplicate charge; refund issued manually.');

$order->notes;                  // Collection of Note models
$order->notes()->latest()->get();
$order->notesCount();          // handy for badges in an index table

That is the whole loop: create a note, read the notes back. Everything else is wiring it into your UI and authorization layer.

Implementation Patterns

One polymorphic table, many annotatable models. Because notes are stored with notable_type/notable_id, you can put HasNotes on Order, Customer, Invoice, and Ticket and get a single notes table you can query globally — useful for an admin-wide "recent notes" feed:

Note::with('notable', 'author')->latest()->paginate(50);

Author attribution. Pass the author explicitly in jobs, commands, and queued listeners — there is no authenticated user in those contexts:

$order->createNote($body, $request->user());

// queued job / artisan command
$order->createNote("Auto-flagged by reconciliation job.", $systemUser);

Rely on the auth-resolved default only inside HTTP controllers where auth()->user() is guaranteed.

Never expose note creation without a policy. The package has no authorization, validation, or rate limiting. Wrap it:

// StoreNoteRequest
public function rules(): array
{
    return ['content' => ['required', 'string', 'max:5000']];
}

// NoteController@store
$this->authorize('createNote', $order);
$order->createNote($request->validated('content'), $request->user());

Eager-load to avoid N+1 in list views. A table of 50 orders each rendering a note count and latest note is the classic trap:

$orders = Order::withCount('notes')
    ->with(['notes' => fn ($q) => $q->latest()->limit(1)])
    ->paginate(25);

Custom note model when you outgrow the defaults. Extend the shipped model, add casts, scopes, or soft deletes, then swap it in config:

namespace App\Models;

use Arcanedev\LaravelNotes\Models\Note as BaseNote;

class Note extends BaseNote
{
    protected $casts = ['content' => 'string', 'created_at' => 'immutable_datetime'];

    public function scopeInternal($query)
    {
        return $query->whereNull('author_id');
    }
}
// config/notes.php
'note' => App\Models\Note::class,

Use it for human notes, not audit trails. These are deliberate annotations typed by a person ("refund approved by finance"), not automatic change tracking. Pair it with spatie/laravel-activitylog or owen-it/laravel-auditing for field-level history rather than trying to make this package do both.

Render as a timeline component. A small Blade/Livewire partial that takes any HasNotes model and renders notes()->with('author')->latest()->paginate(20) works across every annotatable model — build it once.

Testing. Assertions are plain database/relationship assertions; there is no testing helper to learn:

$order->createNote('looks good', $user);

$this->assertDatabaseHas('notes', [
    'notable_id'   => $order->id,
    'notable_type' => $order->getMorphClass(),
    'content'      => 'looks good',
]);

$this->assertCount(1, $order->fresh()->notes);

Create notes through the trait in factories rather than instantiating Note directly, so the polymorphic keys stay consistent.

Gotchas and Tips

  • Maintenance risk is real. Last release was March 2022, no dependents, ~48 stars. It is a thin trait over three columns, so it will probably keep working, but pin the version and smoke-test after every Laravel upgrade. If you are on Laravel 10/11, verify the service provider and migration run before committing to it. Otherwise a 40-line polymorphic table of your own is a legitimate substitute.
  • Config points at the wrong user model. The default author model references the old App\User namespace. On Laravel 8+ this silently resolves to a non-existent class and author relations break. Fix config/notes.php immediately after publishing.
  • Morph maps break historical rows. If you later call Relation::enforceMorphMap(), existing notes.notable_type values are stored as full class names and will no longer resolve. Decide on a morph map before you write production notes, or write a data migration.
  • No cascade deletes by default. Deleting an Order leaves orphaned notes unless the published migration defines a foreign key with cascadeOnDelete (it generally cannot, because the relation is polymorphic). Add a deleting hook or scheduled cleanup: Note::whereDoesntHave('notable')->delete().
  • Stored content is not sanitized. Escape on output ({{ $note->content }}). If you need markdown rendering, pipe it through a sanitizer — never {!! !!} raw user input.
  • No pagination, no soft deletes, no policies, no events beyond Eloquent defaults. All of that is your responsibility. The model does fire standard Eloquent events, so Note::created() listeners are the cleanest extension point for notifications or search indexing.
  • Debugging queries: if $model->notes is empty but rows exist, check the morph type first — it is almost always a namespace or morph-map mismatch. Note::first()->notable_type will tell you instantly.
  • Long notes: confirm whether the content column is TEXT or VARCHAR in your published migration; enforce a max: rule in the form request either way so the UI truncation and DB limit agree.
  • Counts get stale fast in list views if you cache them. Prefer withCount('notes') per request over cached counters unless you have a clear invalidation story on note creation.
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.
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
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle