NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Likes for Laravel

Database schema & key types

One polymorphic table records who liked what. Its name comes from likes.table; the columns are fixed:

ColumnTypeNotes
idauto-incrementAlways an auto-incrementing key, whatever key_type says.
actor_type, actor_idmorph pair (unique key)The model that gave the like. actor_id is typed by key_type.
likeable_type, likeable_idmorph pair (unique key)The model that received it. likeable_id is typed by key_type.
typestring, default likeThe reaction type.
created_at, updated_attimestampscreated_at drives the trending window.
deleted_atsoft deleteSet on unlike; a re-like restores the row.

A unique key, likes_actor_likeable_type_unique — prefixed with your table name when you change it — covers (actor_type, actor_id, likeable_type, likeable_id, type) — soft-deleted rows included — so the database itself keeps one row per actor, likeable and reaction type, and every verb and check hits that exact lookup. The published migration in short:

$name = LikesConfig::table();                     // likes.table, read strictly

// Throws for an unrecognized value, so a typo in the host's config fails
// the migration instead of quietly building bigint columns.
$keyType = KeyType::fromConfig('likes.key_type');

Schema::create($name, function (Blueprint $table) use ($name, $keyType): void {
    $table->id();
    $table->morphKey('actor', $keyType, nullable: false);     // actor_type + actor_id
    $table->morphKey('likeable', $keyType, nullable: false);  // likeable_type + likeable_id
    $table->string('type')->default('like');
    $table->timestamps();
    $table->softDeletes();

    // One row per actor + likeable + reaction type, soft-deleted rows included.
    $table->unique(
        ['actor_type', 'actor_id', 'likeable_type', 'likeable_id', 'type'],
        $name.'_actor_likeable_type_unique',
    );
});

morphKey() is a Blueprint macro from the Roundly package toolkit. The service provider registers it on boot, so it exists before php artisan migrate runs.

Key types

key_type types both morph pairs, so actors and likeables share one key type. Pick the one that matches your models:

key_typeMorph columnsUse when your models…
bigintmorphs() — unsigned big integer idsuse Laravel’s default auto-incrementing keys (the default)
uuiduuidMorphs()use HasUuids
ulidulidMorphs()use HasUlids

Set it before you publish and run the migration:

LIKES_KEY_TYPE=uuid   # bigint (default) | uuid | ulid
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Model;
use RoundlyConsulting\Likes\Traits\GivesLikes;
use RoundlyConsulting\Likes\Traits\HasLikes;

class User extends Model
{
    use GivesLikes, HasUuids;
}

class Post extends Model
{
    use HasLikes, HasUuids;
}

A blank value (LIKES_KEY_TYPE=) is not set and keeps bigint. Any other value throws InvalidConfigurationException when the migration runs, instead of silently building bigint columns.

Soft deletes

Unliking soft-deletes the row, and liking again restores it instead of inserting a duplicate — each actor keeps a single row per likeable and reaction type. Checks, counts, scopes and summaries all ignore soft-deleted rows.

Custom model & table

Extend the package model to add your own behaviour, then point the config at it:

namespace App\Models;

use RoundlyConsulting\Likes\Models\Like as BaseLike;

class Like extends BaseLike
{
    // your own casts, relations or scopes
}
// config/likes.php
'model' => App\Models\Like::class,

The model reads likes.table unless your subclass sets $table explicitly — an explicit $table wins, as Eloquent intends. A configured class that isn’t the package model or a subclass of it throws InvalidConfigurationException naming the key — it is never silently replaced by the packaged model.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.