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

Configuration

The package works with zero configuration. The published config/reviews.php in full:

<?php

use RoundlyConsulting\Reviews\Enums\ReviewStatus;
use RoundlyConsulting\Reviews\Models\Review;
use RoundlyConsulting\Reviews\Models\ReviewVote;
use RoundlyConsulting\Reviews\Moderation\NullModerator;

return [
    'model' => Review::class,
    'vote_model' => ReviewVote::class,

    'key_type' => env('REVIEWS_KEY_TYPE', 'bigint'),

    'min_rating' => env('REVIEWS_MIN_RATING', 1),
    'max_rating' => env('REVIEWS_MAX_RATING', 5),

    'default_status' => ReviewStatus::Pending->value,
    'auto_approve' => env('REVIEWS_AUTO_APPROVE', false),
    'reset_status_on_edit' => env('REVIEWS_RESET_STATUS_ON_EDIT', true),
    'one_per_author' => env('REVIEWS_ONE_PER_AUTHOR', false),

    'register_facade_alias' => env('REVIEWS_REGISTER_FACADE_ALIAS', true),

    'moderator' => NullModerator::class,
    'moderation' => [
        'banned_words' => array_values(array_filter(array_map(
            'trim',
            explode(',', (string) env('REVIEWS_BANNED_WORDS', '')),
        ))),
    ],

    'cache_aggregates' => env('REVIEWS_CACHE_AGGREGATES', false),

    'photos' => [
        'enabled' => env('REVIEWS_PHOTOS_ENABLED', true),
        'bucket' => env('REVIEWS_PHOTOS_BUCKET', 'photos'),
        'disk' => env('REVIEWS_PHOTOS_DISK'),
        'private_disk' => env('REVIEWS_PHOTOS_PRIVATE_DISK', 'local'),
        'max' => env('REVIEWS_PHOTOS_MAX', 5),
        'max_file_size' => env('REVIEWS_PHOTOS_MAX_FILE_SIZE', 5 * 1024 * 1024),
        'accepted_mime_types' => ['image/jpeg', 'image/png', 'image/webp', 'image/gif'],
        'responsive_widths' => [320, 640, 1024],
        'visibility' => env('REVIEWS_PHOTOS_VISIBILITY', 'public'),
        'warm_on_approval' => env('REVIEWS_PHOTOS_WARM_ON_APPROVAL', true),
    ],
];

Every key

KeyDefaultEnvPurpose
modelReview::class—Review model; point it at your subclass to extend it. Any other class throws InvalidConfigurationException.
vote_modelReviewVote::class—Helpful-vote model; point it at your subclass to extend it. Any other class throws InvalidConfigurationException.
key_typebigintREVIEWS_KEY_TYPEKey type of the reviewable, author and voter morph columns — bigint, uuid or ulid (anything else throws InvalidConfigurationException). Read when the migrations run.
min_rating1REVIEWS_MIN_RATINGLowest allowed rating, 0–255 (the rating column is an unsigned tinyint).
max_rating5REVIEWS_MAX_RATINGHighest allowed rating, from min_rating (at least 1) to 255.
default_statuspending—Status a review lands in when the moderator leaves it undecided (and auto-approve is off). Any value that isn’t a ReviewStatus throws.
auto_approvefalseREVIEWS_AUTO_APPROVEApprove new — and re-moderated, edited — reviews immediately, skipping the moderator.
reset_status_on_edittrueREVIEWS_RESET_STATUS_ON_EDITOn an edit to the rating, title or content, send an undecided review back to default_status (false: keep its status). The moderator re-runs either way.
one_per_authorfalseREVIEWS_ONE_PER_AUTHORBlock a second non-deleted review by the same author for the same subject; owner responses don’t count. Checked under the author’s row lock.
register_facade_aliastrueREVIEWS_REGISTER_FACADE_ALIASRegister the global Reviews alias; any other string is used as the alias name. false/0/off/no (or an explicit null) skip it; a blank value is not set, so the alias is registered.
moderatorNullModerator::class—ReviewModerator consulted on create and on edits to the rating, title or content; swap in WordListModerator or your own. A class that doesn’t implement ReviewModerator throws.
moderation.banned_words[]REVIEWS_BANNED_WORDSComma-separated words the WordListModerator rejects on. Set in PHP, it must be a list of non-empty strings.
cache_aggregatesfalseREVIEWS_CACHE_AGGREGATESKeep cached reviews_count / reviews_avg in sync on opted-in reviewables.
photos.enabledtrueREVIEWS_PHOTOS_ENABLEDMaster switch for review photos; when false the bucket is never declared and withPhoto() throws.
photos.bucketphotosREVIEWS_PHOTOS_BUCKETThe media bucket photos are stored in (a string; blank = not set → photos).
photos.disknullREVIEWS_PHOTOS_DISKDisk for every photo. Not set (null or blank) = by visibility: private → photos.private_disk, public → media-library’s default disk.
photos.private_disklocalREVIEWS_PHOTOS_PRIVATE_DISKNon-public disk for private photos and their variants when photos.disk is not set (a string; blank = not set → local).
photos.max5REVIEWS_PHOTOS_MAXPer-review photo limit, at least 0 (0 = unlimited, only when set explicitly); overflow throws tooManyPhotos().
photos.max_file_size5242880REVIEWS_PHOTOS_MAX_FILE_SIZELargest accepted upload, in bytes (5 MB), at least 0 (0 = no per-photo cap, only when set explicitly).
photos.accepted_mime_typesjpeg, png, webp, gif—Allowed image mime types ([] accepts any type).
photos.responsive_widths[320, 640, 1024]—Responsive width ladder of positive integers (null uses the media-library default).
photos.visibilitypublicREVIEWS_PHOTOS_VISIBILITYpublic or private (exact, lowercase); anything else throws rather than publishing the photos.
photos.warm_on_approvaltrueREVIEWS_PHOTOS_WARM_ON_APPROVALQueue variant generation when a review is approved.

Every bool switch is read strictly: true/1/on/yes turn it on, false/0/off/no turn it off, a blank value (REVIEWS_PHOTOS_ENABLED=) is not set so the default applies, and anything else (say REVIEWS_PHOTOS_ENABLED=disabled) throws InvalidConfigurationException instead of quietly reading as the default.

Every other setting is read just as strictly. A setting that is not set — absent, null, or blank like a host’s REVIEWS_PHOTOS_DISK= — takes its default (photos.disk stays unset). Integers accept an int or a plain integer string (“10”, as every env value is a string); five, 5.5 or 1e3 throws rather than becoming 0 — which for photos.max would have meant unlimited photos. A photos.visibility typo such as privat throws rather than making the photos public, and a non-string bucket or disk name, a non-list mime-type or width setting, or a junk entry in one throws too.

Environment

Most switches are env-driven, so you rarely need to publish the file at all:

REVIEWS_MIN_RATING=1
REVIEWS_MAX_RATING=10
REVIEWS_AUTO_APPROVE=false
REVIEWS_ONE_PER_AUTHOR=true
REVIEWS_BANNED_WORDS=spam,scam
REVIEWS_CACHE_AGGREGATES=true
REVIEWS_PHOTOS_MAX=3
REVIEWS_PHOTOS_VISIBILITY=private

REVIEWS_BANNED_WORDS is split on commas and trimmed, so spaces around the words are harmless.

Inspecting the live configuration

The package adds a Reviews section to Laravel’s about command — models, rating scale, moderation switches, aggregates and photo settings:

php artisan about --only=reviews

It reports switches, bounds and presence only: the banned-word list appears as a count and the photo disk as SET or DEFAULT, so the output is safe to paste into a ticket. A broken rating scale, default status, banned-word list or photo setting renders as INVALID instead of failing the command.

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.