---
title: "Creating reviews — Reviews for Laravel | Roundly"
description: "Create reviews with the fluent facade — ratings, text, metadata, force-approve, verified flags — and know what gets validated."
url: https://roundly-consulting.com/open-source/docs/reviews-for-laravel/creating-reviews
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Reviews for Laravel](https://roundly-consulting.com/open-source/docs/reviews-for-laravel.md)

# Creating reviews

Reviews::for($subject) scopes to the subject and ->by($author) starts a fluent PendingReview; chain setters and finish with create():

```php
use RoundlyConsulting\Reviews\Facades\Reviews;

$review = Reviews::for($restaurant)   // the reviewable subject
    ->by($user)                       // the author
    ->rating(5)
    ->title('Loved it')
    ->content('Great place')
    ->meta(['visit' => 'dinner'])     // array or Collection
    ->create();

// Rating-only (no text):
Reviews::for($restaurant)->by($user)->rating(4)->create();

// Text-only (no rating):
Reviews::for($restaurant)->by($user)->content('Friendly staff, slow kitchen')->create();

// Force-approve on create (bypasses moderation):
Reviews::for($restaurant)->by($user)->content('Trusted')->approved()->create();

// Mark a verified purchase / verified reviewer:
Reviews::for($restaurant)->by($user)->rating(5)->verified()->create();
```

Or start from the subject — addReview() goes through the same manager:

```php
// Start from the subject — for($restaurant)->by($user) is filled in for you:
$restaurant->addReview($user)->rating(5)->content('Best ramen in town')->create();
```

## Builder reference

| Method | Purpose |
| --- | --- |
| `Reviews::for(Model $reviewable)` | The subject being reviewed — returns a ReviewableScope. |
| `->by(Model $author)` | The author — returns the PendingReview; subject and author are fixed from here. |
| `rating(?int $rating)` | Score within min\_rating…max\_rating; null for text-only. |
| `title(?string $title)` | Optional headline. |
| `content(?string $content)` | Optional body text. |
| `meta(array\|Collection\|null $meta)` | Free-form metadata, stored as JSON. |
| `approved()` | Approve on create, bypassing the moderator. |
| `verified(bool $verified = true)` | Mark a verified purchase / reviewer. |
| `withPhoto() / withPhotos()` | Attach uploaded files (or local paths). |
| `withDraftPhoto(string $token)` | Bind a media-library draft upload. |
| `withPhotoFromDisk($path, $disk)` | Attach a file already on a disk. |
| `withPhotoFromUrl(string $url)` | Download and attach a remote image. |
| `create()` | Validate, moderate, persist and return the Review. |

## What create() does

- Rejects an empty review — a review needs a rating, content, or both. Content that is only whitespace counts as none; the text itself is stored as given, never trimmed.
- Validates the rating against min\_rating…max\_rating.
- With one\_per\_author on, rejects a second non-deleted review by the same author for the same subject — owner responses don’t count. The check runs again under the author’s row lock with a locking read, so concurrent submissions can’t both get in — even inside a host transaction under MySQL’s default REPEATABLE READ. One limit: on PostgreSQL, a host transaction at REPEATABLE READ (not the default) can’t see a review committed after its first read, so the check can miss it.
- Sets the status: approved() or auto\_approve approves immediately and stamps approved\_at; otherwise the configured moderator decides — with the author and subject already associated — falling back to default\_status.
- Saves the review and attaches its photos in one transaction on the review model’s connection — a failed upload or an over-limit request rolls the whole create back. When media-library’s connection (photos attached) or the author’s (one\_per\_author) differs, it gets its own transaction around that one: one commit per connection, not a distributed transaction.
- Dispatches ReviewCreated after the transaction, so listeners see the persisted photos — then ReviewApproved or ReviewRejected when the review lands approved or rejected.

## Validation & exceptions

Every package exception extends RoundlyConsulting\\Reviews\\Exceptions\\ReviewException (a RuntimeException), so one catch covers them all:

```php
use RoundlyConsulting\Reviews\Exceptions\InvalidRatingException;
use RoundlyConsulting\Reviews\Exceptions\InvalidReviewException;
use RoundlyConsulting\Reviews\Exceptions\ReviewException;

try {
    Reviews::for($restaurant)->by($user)->rating(7)->create();
} catch (InvalidRatingException $e) {
    // "The rating 7 must be between 1 and 5."
} catch (InvalidReviewException $e) {
    // empty review, duplicate author, too many photos, photos disabled
} catch (ReviewException $e) {
    // the common base of every package exception
}
```

| Exception | Thrown when |
| --- | --- |
| `InvalidReviewException::empty()` | A review has no rating and no content beyond whitespace — on create or after an update. |
| `InvalidReviewException::duplicate()` | one\_per\_author is on and the author already reviewed the subject. |
| `InvalidReviewException::tooManyPhotos()` | The photos would exceed photos.max. |
| `InvalidReviewException::photosDisabled()` | A photo is attached while photos.enabled is false. |
| `InvalidRatingException::outOfRange()` | A rating falls outside min\_rating…max\_rating — on create or update. |

Every exception message — rating, empty, duplicate and both photo errors — and the banned-word reason resolve in the current locale. English and Slovak ship; publish reviews-translations and edit lang/vendor/reviews only to reword them or add a language. photos.too\_many is a plural line, rendered with trans\_choice() on :max:

| Key | English default |
| --- | --- |
| `reviews::messages.rating.out_of_range` | The rating :given must be between :min and :max. |
| `reviews::messages.review.empty` | A review must have either a rating or content. |
| `reviews::messages.review.duplicate` | This author has already reviewed this subject. |
| `reviews::messages.review.banned_word` | This review contains language that is not allowed. |
| `reviews::messages.photos.too_many` | {1} A review may have at most :max photo.\|\[0,\*\] A review may have at most :max photos. |
| `reviews::messages.photos.disabled` | Review photos are disabled; enable reviews.photos.enabled to attach photos. |

## The DTO and the action

The subject and author are fixed once by() returns the builder, so it can’t be re-pointed at another subject. create() hands a CreateReviewData to Reviews::create(), which runs the CreateReview action — the same operation in one call:

```php
use RoundlyConsulting\Reviews\DataTransferObjects\CreateReviewData;
use RoundlyConsulting\Reviews\Facades\Reviews;

// The builder's terminal step, in one call:
$review = Reviews::create(new CreateReviewData(author: $user, reviewable: $restaurant, rating: 5));
```

Or run the action yourself (see DI and actions):

```php
use RoundlyConsulting\Reviews\Actions\CreateReview;
use RoundlyConsulting\Reviews\DataTransferObjects\CreateReviewData;

$review = app(CreateReview::class)->execute(new CreateReviewData(
    author: $user,
    reviewable: $restaurant,
    content: 'Great place',
    title: 'Loved it',
    rating: 5,
    meta: collect(['visit' => 'dinner']),
    approved: false,
    verified: true,
));
```

## 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](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## 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.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/reviews-for-laravel.md)
