From first install
to the full experience.
Practical integration guides for the open-source engine and every Pro workflow. Choose your starting point, copy the examples, and keep your application in control.
One Pro command installs Core automatically in a new application.
Existing Core data stays put when adding Pro to a compatible 2.2+ installation.
Optional means opt-in, not extra cost. Both paid plans include every Pro module.
Installation
Core is the free, MIT-licensed package. These instructions target the 2.2 series, which includes optional review images. Pro is a separate commercial companion. Its ^1.0 installation steps apply after the first tagged release is available through CodebyRay.
Requirements
- Core supports Laravel 10–13 and PHP 8.1+, subject to the PHP version required by your Laravel release.
- Pro requires Core ^2.2, which Composer installs automatically. Livewire 3.6+ or 4 is needed only when using Pro's Livewire interface; React and Vue installations do not require Livewire.
- Reviewable models and users need compatible numeric IDs. UUID-based identity is not supported by the current core schema.
- Use a supported, patched framework release in production. Historical compatibility is not a reason to deploy an unpatched framework.
Install Core by itself
Use this path when installing only the free Core package. If you purchased Pro for a new application, skip this command and follow Install Pro in a new application below—Composer will install Core automatically.
composer require codebyray/laravel-review-rateable:^2.2
php artisan vendor:publish --provider="Codebyray\ReviewRateable\ReviewRateableServiceProvider" --tag=config
php artisan vendor:publish --provider="Codebyray\ReviewRateable\ReviewRateableServiceProvider" --tag=migrations
php artisan migrate
Laravel discovers the provider automatically. The default migrations create reviews and ratings; they do not install an image table. Publish each migration once, inspect it, then run it. Re-publishing timestamped migrations can create duplicates.
Upgrading from 1.x is not an in-place upgrade. Version 2 is a rewrite; plan a data migration and update your integration before changing versions.
Model
Add the trait to a saved Eloquent model. The package uses a polymorphic review relationship, so products, posts, services, and bookings can share the same review engine.
namespace App\Models;
use Codebyray\ReviewRateable\Traits\ReviewRateable;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use ReviewRateable;
}
Your application still owns the product model, authentication, authorization, routes, and front end. Do not submit reviews against an unsaved model. When using multiple databases, reviews, ratings, images, and compatible user tables must be available on the target model's connection.
Configuration
Core's settings live in config/review-rateable.php. This example replaces the department map with two application-specific review contexts:
'user_model' => \App\Models\User::class,
'min_rating_value' => 1,
'max_rating_value' => 5,
'approved_review' => false,
'departments' => [
'default' => [
'ratings' => [
'overall' => 'Overall',
'quality' => 'Quality',
'price' => 'Value for money',
],
],
'support' => [
'ratings' => [
'overall' => 'Overall',
'speed' => 'Response speed',
'knowledge' => 'Knowledge',
],
],
],
Rating keys are stored identifiers; labels are display text. Keep keys stable when changing labels. The selected department determines which criteria are processed. Pro's form requires every displayed criterion.
Set user_model to the model behind your authentication provider. Pro's selected guard and core's user model must use the same numeric identity namespace; overlapping user IDs from unrelated providers are not supported.
approved_review defaults to false. Turn it on only if your application intentionally publishes immediately. Approval is a visibility decision, not a purchase-verification check.
After changing cached production configuration, rebuild your configuration cache using your deployment's normal process.
Review
Create a review safely
Use an authenticated endpoint and authorize the target before calling core. Core is a persistence API, not a replacement for your request validation or policies. For example, inside your application's controller action:
use Illuminate\Http\Request;
public function store(Request $request, Product $product)
{
$this->authorize('review', $product);
$validated = $request->validate([
'review' => ['required', 'string', 'min:10', 'max:5000'],
'recommend' => ['sometimes', 'boolean'],
'ratings' => ['required', 'array:overall,quality,price'],
'ratings.overall' => ['required', 'integer', 'between:1,5'],
'ratings.quality' => ['required', 'integer', 'between:1,5'],
'ratings.price' => ['required', 'integer', 'between:1,5'],
]);
$review = $product->addReview([
'review' => $validated['review'],
'recommend' => $request->boolean('recommend'),
'department' => 'default',
'ratings' => $validated['ratings'],
], $request->user()->getKey());
return response()->json(['review_id' => $review->id], 201);
}
Import your own Product class and define the review policy/Gate. If your base controller does not use Laravel's AuthorizesRequests trait, use Gate::authorize('review', $product) instead. Match the validation keys and boundaries to your configured department.
The author ID must come from the authenticated server context. Do not accept approved, author IDs, verification flags, or arbitrary departments from an untrusted payload. Trusted internal writers may explicitly pass approved to override core's default.
Core can also store a review without an author by passing null; Pro's customer submission and author-editing workflows require authentication.
Update, approve, and delete
Authorize each operation and ensure the review belongs to the intended target. These core methods are available on the reviewable model:
$product->updateReview($reviewId, [
'review' => 'My updated experience.',
'recommend' => true,
'ratings' => [
'overall' => 4,
'quality' => 4,
'price' => 5,
],
]);
$product->approveReview($reviewId);
$product->deleteReview($reviewId);
These methods do not apply Pro's author editing, duplicate-submission policy, verification, or content-filter workflows. Route author-facing Pro edits through ReviewEditingManager instead. Deletion is permanent; use approval controls to hide content you want to retain.
Use the service contract
The injectable service provides a decoupled alternative to the trait API. Set the saved target before using it:
use Codebyray\ReviewRateable\Contracts\ReviewRateableContract;
public function store(Product $product, ReviewRateableContract $reviews)
{
// Authorize and validate before this point.
$reviews->setModel($product);
return $reviews->addReview([
'review' => 'A thoughtful, validated review.',
'ratings' => ['overall' => 5],
], auth()->id());
}
Adapt the payload to your configured criteria. Do not reuse a service instance across targets without setting its model again.
Queries and statistics
Core returns approved reviews by default. Request pending reviews only from an authorized private workflow.
$published = $product->getReviews();
$pending = $product->getReviews(false);
$withoutRatings = $product->getReviews(true, false);
$supportReviews = $product->getReviewsByDepartment('support');
$fiveStarReviews = $product->getReviewsByRating(
5, department: 'support'
);
$total = $product->totalReviews();
$supportTotal = $product->totalDepartmentReviews('support');
$overall = $product->averageRating('overall');
$averages = $product->averageRatings();
$supportAverage = $product->averageRatingByDepartment('support', 'overall');
$allCriteriaAverage = $product->overallAverageRating();
$distribution = $product->ratingCounts('default');
$stats = $product->ratingStats('default');
ratingStats() returns counts, percentages, and total. Core's distribution/statistics APIs count rating rows across criteria, not one overall score per review. Likewise, getReviewsByRating() matches a review with any criterion at the requested value. For an overall-only breakdown or filter, scope the ratings relationship to your chosen key. Pro's summary and filters do this using listing.summary_rating_key.
Use the relationship for pagination, eager loading, or custom criteria:
$reviews = $product->reviews()
->where('department', 'default')
->where('approved', true)
->whereHas('ratings', fn ($query) => $query
->where('key', 'overall')
->where('value', 5))
->with(['ratings', 'user'])
->latest()
->paginate(10);
Only eager-load optional relationships after installing their tables. Avoid unapproved content in public JSON, caches, or summaries.
Photos
Image persistence is included in Core, not reserved for Pro. Install the separate table only when you need it:
php artisan vendor:publish --provider="Codebyray\ReviewRateable\ReviewRateableServiceProvider" --tag=review-images-migrations
php artisan migrate
# Only when using Laravel's public disk:
php artisan storage:link
Configure storage and limits in Core's config:
'images' => [
'disk' => 'public',
'directory' => 'review-images',
'thumbnail_directory' => 'review-images/thumbnails',
'max_count' => 10,
'max_file_size' => 5120,
'allowed_mime_types' => [
'image/jpeg',
'image/png',
'image/webp',
'image/gif',
],
'delete_files_on_delete' => true,
],
File size is in kilobytes, for each original or supplied thumbnail. Files are stored beneath review-specific directories. Use a configured Laravel disk whose URL behavior suits your application. SVG uploads are excluded by default because active image formats need additional safeguards.
Add originals and thumbnails
Authorize uploads and validate the request before attachment. Core also enforces its configured count, size, and MIME limits.
$review->addImages($request->file('photos', []));
$image = $review->addImage(
image: $request->file('front'),
altText: 'Front of the product',
thumbnail: $generatedThumbnail,
);
$originalUrl = $image->url();
$thumbnailUrl = $image->thumbnailUrl();
The thumbnail argument is optional. Core does not generate or resize thumbnails; thumbnailUrl() falls back to the original when none was supplied. Create thumbnails and strip EXIF/GPS metadata in your own image-processing workflow.
Remove, reorder, and eager-load
$review->removeImage($imageId);
// Include every attached image ID exactly once.
$review->reorderImages([$thirdImageId, $firstImageId, $secondImageId]);
$reviews = $product->reviews()
->where('approved', true)
->with(['ratings', 'images', 'user'])
->paginate(10);
images() returns ordered attachments. Images expose their original/thumbnail helpers and alt text for your own gallery. Core intentionally does not mandate Blade components, a JavaScript slideshow, or a CSS framework.
File cleanup and privacy
Deleting an image or review model schedules file deletion only after successful database deletion/commit. Cleanup failures are reported without undoing committed database work. Disable delete_files_on_delete if your application owns retention.
Bulk query deletions bypass Eloquent model events and therefore automatic cleanup. Delete model instances or use the package's deleteReview() method.
Database rollback cannot undo filesystem writes. In custom core integrations, track and clean newly stored files if a larger transaction rolls back. Pro's submission/editing workflows manage their own new-upload cleanup.
Pending does not mean private storage. A public disk URL remains accessible even when its review is unapproved. If images need confidentiality, design a private disk and authorized delivery strategy; do not rely on approval alone.
Pro
Pro adds the customer experience and team workflows without replacing Core's tables or models. The public demo runs the real components. Pro is distributed through CodebyRay's authenticated Composer repository rather than Packagist or direct access to its private GitHub repository.
Purchase and private installation
- Choose and purchase a Pro license. General CBR registration remains closed; a successful purchase creates your customer account and sends its password-setup email.
- Sign in and open My Licenses under Software.
- Create one Composer credential named for the project. Save it immediately—the secret is shown only once. Use that same project credential locally, in staging, CI, and production.
- Add the private repository to the application's root
composer.json:
{
"repositories": [
{
"type": "composer",
"url": "https://app.cbrapps.com/packages"
}
]
}
- Create
auth.jsonbesidecomposer.json, using the secret displayed by the license portal:
{
"bearer": {
"app.cbrapps.com": "YOUR_PROJECT_CREDENTIAL"
}
}
Add /auth.json to .gitignore. Never commit the credential, paste it into a support request, or place it in composer.json.
- Choose the installation path that matches your application. A new Pro customer does not need to install Core separately.
Install Pro in a new application
Install Pro directly:
composer require codebyray/laravel-review-rateable-pro:^1.0
Composer authenticates with CBR, downloads the entitled Pro release, and automatically installs the compatible Core ^2.2 dependency from Packagist. Do not run a separate composer require for Core.
Composer never runs package migrations automatically. Publish and migrate Core's core schema first, then publish Pro's configuration:
php artisan vendor:publish --provider="Codebyray\ReviewRateable\ReviewRateableServiceProvider" --tag=config
php artisan vendor:publish --provider="Codebyray\ReviewRateable\ReviewRateableServiceProvider" --tag=migrations
php artisan migrate
php artisan vendor:publish --tag=review-rateable-pro-config
Core's base migrations create the reviews and ratings tables required by Pro. Publish only the optional Pro module migrations your application will use, then run php artisan migrate again.
Add Pro to an existing Core 2.2+ application
Keep the application's existing Core configuration, migrations, and review data. Add Pro directly:
composer require codebyray/laravel-review-rateable-pro:^1.0
php artisan vendor:publish --tag=review-rateable-pro-config
Composer reuses the installed compatible Core release or updates it within the allowed ^2.2 constraint. Do not republish migrations that the application has already published. Confirm Core's base migrations have run before publishing any Pro module migrations.
Core 1.x is not compatible with Pro 1.x. Complete the application's migration to Core 2.2+ before adding Pro; do not force Composer past the version conflict.
Customers do not receive the private GitHub URL or GitHub access. In both installation paths, Core continues to own core reviews, ratings, configuration, and image storage while Pro adds interfaces and optional workflows.
For CI and deployments, store the complete authentication object as a protected secret named COMPOSER_AUTH rather than creating a committed file:
{"bearer":{"app.cbrapps.com":"YOUR_PROJECT_CREDENTIAL"}}
Composer reads COMPOSER_AUTH automatically. Mask the value in logs and restrict it to the project licensed to use it.
Normal composer update includes Pro when its constraints permit a newer entitled release. To update it explicitly with its dependencies, run:
composer update codebyray/laravel-review-rateable-pro --with-dependencies
Coverage expiry does not disable installed code. It limits repository access to releases published during the covered update period; renewing extends access to newer releases. If a credential is lost or exposed, revoke it in My Licenses and create a replacement for the same project.
An authentication error from packages.json usually means the bearer host does not exactly match app.cbrapps.com, the credential was copied incorrectly, or it has been revoked. A package/version not found error can mean no entitled release matches the requested Composer constraint. Confirm the repository entry, credential, license status, and available releases before retrying with composer clear-cache.
Pro interfaces
Every Pro license includes all three interface families. Choose the one that matches the consuming application; there is no interface add-on or separate purchase.
| Interface | Best fit | What Pro supplies |
|---|---|---|
| Livewire 3.6+/4 | Blade-first Laravel applications | Six server-rendered components, Tailwind and Bootstrap templates, and Alpine-powered galleries |
| React 18/19 | React applications and Inertia/custom SPA screens | Publishable JSX components, shared browser client, and base CSS |
| Vue 3 | Vue applications and Inertia/custom SPA screens | Publishable single-file components, shared browser client, and base CSS |
| Vue 2.7 legacy | Existing applications during migration | The same Options API source compiled with the Vue 2 Vite plugin |
The Livewire demo, React demo, and Vue demo read and write the same Laravel records. The UI runtime changes; validation, identity, permissions, throttling, moderation, and storage remain server-side.
The same REVIEW_RATEABLE_PRO_THEME value applies to all three. Livewire selects a complete Tailwind or Bootstrap Blade template set. React and Vue use framework-neutral markup and receive the theme key from the context API, then the published shared stylesheet applies its matching Tailwind-like or Bootstrap-like preset. The JavaScript components therefore do not require Tailwind or Bootstrap themselves. Custom JavaScript themes can override CSS beneath .rrp-client[data-rrp-theme="custom"].
Livewire setup
Livewire is an optional Composer dependency. Install it only when using the Blade components:
composer require livewire/livewire
Set up Tailwind (the default) or Bootstrap CSS using the customization instructions. The complete Livewire templates work directly from Pro without publishing. If you want to edit their Blade markup, publish only the theme you use:
php artisan vendor:publish --tag=review-rateable-pro-tailwind-views
# Or, for Bootstrap:
php artisan vendor:publish --tag=review-rateable-pro-bootstrap-views
The published files live under resources/views/vendor/review-rateable-pro/themes/. See Styles & publishable views for theme selection, CSS setup, and override details. With the selected framework's CSS and Livewire assets installed in your layout, render the two main components:
<livewire:review-rateable-pro-review-form
:reviewable="$product"
:key="'review-form-'.$product->getKey()" />
<livewire:review-rateable-pro-review-list
:reviewable="$product"
:key="'review-list-'.$product->getKey()" />
For another department, pass department="support" to both. The model must use the core trait and be saved. Do not load a second Alpine instance alongside Livewire.
The form includes configured star criteria, recommendation, validation, loading states, and submission throttling. The list includes approved-review summaries, sorting, filters, and pagination. By default, authors may see their own pending reviews; other visitors cannot.
React setup
Enable the API with a safe alias for each reviewable model. Do not accept a PHP class name from the browser:
'api' => [
'enabled' => true,
'prefix' => 'review-rateable-pro/api/v1',
'middleware' => ['web'],
'reviewables' => [
'products' => App\Models\Product::class,
],
'author_name_attribute' => 'name',
],
Publish the React source and install React in the application:
php artisan vendor:publish --tag=review-rateable-pro-react
npm install react react-dom
npm install --save-dev @vitejs/plugin-react
Register react() from @vitejs/plugin-react in vite.config.js, alongside the application's Laravel Vite plugin.
Create a Vite entry such as resources/js/reviews-react.jsx:
import React from 'react';
import { createRoot } from 'react-dom/client';
import { createReviewRateableClient } from './vendor/review-rateable-pro/core/client.js';
import './vendor/review-rateable-pro/core/review-rateable.css';
import { ReviewRateable } from './vendor/review-rateable-pro/react/index.jsx';
const element = document.querySelector('[data-review-rateable]');
const client = createReviewRateableClient({
contextUrl: element.dataset.contextUrl,
});
createRoot(element).render(<ReviewRateable client={client} />);
Mount it from Blade and include the entry in the application's Vite inputs:
<meta name="csrf-token" content="{{ csrf_token() }}">
<div data-review-rateable
data-context-url="{{ route('review-rateable-pro.api.context', [
'reviewable' => 'products',
'reviewableId' => $product->getRouteKey(),
]) }}"></div>
@viteReactRefresh
@vite('resources/js/reviews-react.jsx')
Keep @viteReactRefresh before the React entry. It installs Vite's development refresh preamble; omitting it can leave the mount element empty while the browser reports that the React preamble was not detected. Production builds do not emit the refresh runtime.
React's renderReview prop replaces individual review cards without forking the form, query, or pagination behavior.
Vue setup
Use the same API configuration shown above, publish the Vue source, and install Vue 3 plus its Vite plugin:
php artisan vendor:publish --tag=review-rateable-pro-vue
npm install vue
npm install --save-dev @vitejs/plugin-vue
Register vue() in vite.config.js, then create a Vite entry:
import { createApp } from 'vue';
import { createReviewRateableClient } from './vendor/review-rateable-pro/core/client.js';
import './vendor/review-rateable-pro/core/review-rateable.css';
import { ReviewRateable } from './vendor/review-rateable-pro/vue/index.js';
const element = document.querySelector('[data-review-rateable]');
const client = createReviewRateableClient({
contextUrl: element.dataset.contextUrl,
});
createApp(ReviewRateable, { client }).mount(element);
Use the same Blade mount element as React. Vue exposes a scoped review slot for application-owned cards. For an existing Vue 2.7 application, install @vitejs/plugin-vue2 instead and mount with new Vue({ render: h => h(ReviewRateable, { props: { client } }) }). Vue 2.7 is end-of-life compatibility for migrations, not the recommended choice for a new application.
API security and customization
The default web middleware makes the API same-origin, session-authenticated, and CSRF-protected. For a separate first-party SPA, replace it with the application's established Sanctum/API middleware and CORS policy. Configure view_ability and create_ability for private or purchase-gated models. In a tenant application, bind Codebyray\ReviewRateablePro\Contracts\ReviewableResolver to an implementation that establishes tenant context before resolving the mapped model.
The shared browser client exposes reviews, submit, update, addUpdate, setHelpful, report, reply, and moderate. Server-side Gates remain mandatory even when the application hides a button. The JSON resource intentionally excludes verification evidence and private content-filter records.
Published JavaScript becomes application-owned source under resources/js/vendor/review-rateable-pro. Re-publishing with --force overwrites changes. Keep customized components outside that directory or review and commit the diff before upgrading.
'images' => ['enabled' => true],
'form' => [
'min_length' => 10,
'max_length' => 5000,
'submissions_per_minute' => 3,
'one_review_per_user' => true,
],
'listing' => [
'per_page' => 10,
'show_own_pending' => true,
'summary_rating_key' => 'overall',
],
Enable images only after installing Core's image migration. Pro uses core's storage limits and adds upload previews, ordering controls, thumbnails, and a full-review image dialog. Keep summary_rating_key valid for the selected department.
Optional modules
All Pro modules are included in both paid plans at no additional charge. In these instructions, "optional" means a feature is enabled through configuration and its migrations only when your application needs it—not that it is a separately priced add-on. Core's photo support is also included free.
Each setup below follows the same order: publish the migration, inspect and run it, then edit the published configuration. Turning a feature off hides its functionality without dropping its data. Core's base reviews/ratings migrations must already be installed.
Publish configuration once
If you have not already published the configuration files, run:
php artisan vendor:publish --provider="Codebyray\ReviewRateable\ReviewRateableServiceProvider" --tag=config
php artisan vendor:publish --tag=review-rateable-pro-config
Core's settings live in config/review-rateable.php; Pro's settings live in config/review-rateable-pro.php. The PHP snippets below are entries inside those files' existing return [...] arrays, not complete replacement files. Replace the matching entry and keep the other settings intact. Do not append a second entry with the same key or use --force to overwrite a customized file.
Module setup index
| Feature and setup | Publish tag | Schema |
|---|---|---|
| Photos | review-images-migrations (Core provider) |
review_images |
| Titles | review-rateable-pro-titles-migrations |
Nullable title on reviews |
| Author editing | review-rateable-pro-editing-migrations |
review_edit_states, review_updates |
| Official replies | review-rateable-pro-replies-migrations |
review_replies |
| Helpful votes | review-rateable-pro-helpful-votes-migrations |
review_helpful_votes |
| Verified badges | review-rateable-pro-verification-migrations |
review_verifications |
| Reader reports | review-rateable-pro-reports-migrations |
review_reports |
| Invitations | review-rateable-pro-invitations-migrations |
review_invitations |
| Content filtering | review-rateable-pro-content-moderation-migrations |
review_content_flags |
Moderation, basic forms/listing, and the one-review-per-user form setting do not require separate Pro tables. Base reviews/ratings are always required. Optional features are independently disabled by default.
Enabling a feature without its migration causes database errors. Rolling back an optional migration permanently deletes its saved metadata; disable the feature rather than rolling it back in a populated application.
Module: photos
Publish Core's image migration and run it. The storage-link command is needed only when serving Laravel's public disk through its usual symlink:
php artisan vendor:publish --provider="Codebyray\ReviewRateable\ReviewRateableServiceProvider" --tag=review-images-migrations
php artisan migrate
php artisan storage:link
In config/review-rateable.php, configure image storage and limits:
'images' => [
'disk' => 'public',
'directory' => 'review-images',
'thumbnail_directory' => 'review-images/thumbnails',
'max_count' => 5,
'max_file_size' => 5120,
'allowed_mime_types' => [
'image/jpeg',
'image/png',
'image/webp',
'image/gif',
],
'delete_files_on_delete' => true,
],
max_file_size is kilobytes per image (5120 = 5 MB). Core's image API works after the table is installed; it has no separate enable flag. If using Pro's upload form and gallery, also set this in config/review-rateable-pro.php:
'images' => ['enabled' => true],
The disk must exist in config/filesystems.php. Public URLs are not protected by review approval, and thumbnails are not generated automatically. See image APIs, eager loading, cleanup, and private-storage considerations.
Module: titles
php artisan vendor:publish --tag=review-rateable-pro-titles-migrations
php artisan migrate
In config/review-rateable-pro.php:
'titles' => [
'enabled' => true,
'max_length' => 150,
],
Pro adds a plain-text headline field to its form and shows saved titles in reviews. The length is measured in characters and cannot exceed 255. Existing reviews retain a null title. See titles and the separate one-review-per-user setting.
Module: author editing
php artisan vendor:publish --tag=review-rateable-pro-editing-migrations
php artisan migrate
In config/review-rateable-pro.php:
'editing' => [
'enabled' => true,
'ability' => null,
'require_reapproval' => true,
'updates_enabled' => true,
'update_label' => 'EDIT',
'update_min_length' => 2,
'update_max_length' => 5000,
'changes_per_minute' => 10,
],
Signed-in authors see the management action on their own review. A null ability retains mandatory ownership checks; use a named Gate/policy for additional eligibility rules. require_reapproval makes the entire review pending after a change. Set updates_enabled to false if you want editing without dated follow-ups. Titles and photo editing also require their own modules. See editor integration, dated updates, and stale-edit protection.
Module: official replies
php artisan vendor:publish --tag=review-rateable-pro-replies-migrations
php artisan migrate
In config/review-rateable-pro.php:
'replies' => [
'enabled' => true,
'ability' => 'replyToReview',
'min_length' => 2,
'max_length' => 5000,
],
Define the replyToReview Gate/policy for your owners/team; it receives the reviewable model and review. Enabling the flag does not grant reply permission, and a null reply ability denies access. The private moderation component includes the reply editor when permitted; alternatively embed the dedicated reply form on an authorized page. See the Gate example, component, and reply service.
Module: helpful votes
php artisan vendor:publish --tag=review-rateable-pro-helpful-votes-migrations
php artisan migrate
In config/review-rateable-pro.php:
'helpful_votes' => [
'enabled' => true,
'ability' => null,
'changes_per_minute' => 30,
],
The Pro list adds counts, voting controls, and Most helpful sorting. A null ability allows eligible signed-in readers to vote on another author's published review; authors cannot vote for themselves. Supply a named ability for extra restrictions. See voting rules, service calls, and eager-loading counts.
Module: verified badges
php artisan vendor:publish --tag=review-rateable-pro-verification-migrations
php artisan migrate
In config/review-rateable-pro.php:
'verification' => [
'enabled' => true,
'verifier' => App\Reviews\PurchaseReviewVerifier::class,
'label' => 'Verified Purchase',
],
Create your application's PurchaseReviewVerifier first; this example class is not included in Pro. It must implement Codebyray\ReviewRateablePro\Contracts\ReviewVerifier and return evidence only from trusted purchase/booking records. Set the label to Verified Booking, Verified Customer, or your own text. Enabling badges without a verifier provides no verification, and email verification is not purchase evidence. Copy the complete verifier implementation and learn how to refresh/revoke badges.
Module: reader reports
php artisan vendor:publish --tag=review-rateable-pro-reports-migrations
php artisan migrate
In config/review-rateable-pro.php:
'reports' => [
'enabled' => true,
'ability' => null,
'reasons' => [
'spam' => 'Spam or advertising',
'inappropriate' => 'Inappropriate content',
'misleading' => 'Misleading review',
'other' => 'Other concern',
],
'details_max_length' => 2000,
'submissions_per_minute' => 5,
'per_page' => 10,
],
The public list adds reporting for eligible signed-in readers. Define the moderation ability and embed the report queue on a private, authorized page so your team can resolve/dismiss reports. Reason keys are persisted identifiers; keep them stable. Reports never automatically hide a review. See the private queue component, permissions, and report service.
Module: invitations
php artisan vendor:publish --tag=review-rateable-pro-invitations-migrations
php artisan migrate
In config/review-rateable-pro.php:
'invitations' => [
'enabled' => true,
'ability' => null,
'route' => 'reviews.invitation',
'expires_in_days' => 14,
'changes_per_minute' => 10,
'email_enabled' => false,
'mailer' => null,
'queue' => null,
'mail_class' => Codebyray\ReviewRateablePro\Mail\ReviewInvitationMail::class,
'mail_subject' => 'How was your experience?',
],
Register the named reviews.invitation route before issuing invitations. It needs an {invitation} parameter, authentication using Pro's guard, signed-link validation, and the invitation manager's recipient/token checks. Pro does not register this route. Issuers must pass moderation and view permissions; a null invitation ability does not make issuing public.
The example disables email so links can be issued without a mail transport. For delivery, configure Laravel mail, set email_enabled to true, request sendEmail: true when issuing/renewing, and run an asynchronous queue worker in production. Copy the complete signed route, form, and invitation-service examples.
Module: content filtering
php artisan vendor:publish --tag=review-rateable-pro-content-moderation-migrations
php artisan migrate
In config/review-rateable-pro.php:
'content_moderation' => [
'enabled' => true,
'words' => [
'your blocked word',
'your blocked phrase',
],
'replacement' => '[redacted]',
],
Replace the example terms with your application's literal words/phrases; no profanity dictionary is bundled. Pro redacts matching review bodies, enabled titles, and new dated updates and forces moderation even when automatic approval is enabled. Keep APP_KEY stable and protect the private moderation page because originals are encrypted and visible only to authorized moderators. Custom core/API writers must call the filtering service explicitly. See matching rules, private history, and custom-writer integration.
Apply configuration changes
After editing the settings, clear any cached configuration in local development:
php artisan config:clear
If your production deployment caches configuration, rebuild it after the migrations and final configuration are in place, through your normal deployment process:
php artisan config:cache
Restart long-running queue workers through your deployment process when their configuration changes. Verify each enabled feature using an eligible account, and check that unauthorized users cannot access management actions. Publish each module's migration only once; repeated timestamped publishes can produce duplicate migrations. To disable a module later, set its enabled flag to false rather than deleting its tables. Core's image API has no enable flag, so stop calling it when photos are not used.
Authorization
Pro's customer and management services use the configured guard. Set guard to null for your application's default, or use a named guard.
'guard' => null,
'view_ability' => 'viewProductReviews',
'create_ability' => 'reviewProduct',
'moderation' => ['ability' => 'moderateReviews', 'per_page' => 10],
View and create abilities receive the reviewable model. A null view ability allows public viewing of published reviews; a null create ability allows any authenticated user to submit. A configured but undefined ability denies access.
Choose who can moderate
Pro deliberately does not create an administrator column, role system, or moderator list. Choose one authorization source already owned by the consuming application, then define the configured Gate in App\Providers\AppServiceProvider::boot().
Option A: an application-owned admin flag. If your user table already has a trusted is_admin boolean, use it directly:
use App\Models\Product;
use App\Models\User;
use Illuminate\Support\Facades\Gate;
Gate::define('moderateReviews', fn (User $user, Product $product): bool =>
$user->is_admin
);
If the application does not have that field, create it with an application migration and cast it to a boolean on User. Default it to false and grant it only through a trusted administrative workflow—never accept is_admin from registration, profile, or review-form input.
Schema::table('users', function (Blueprint $table): void {
$table->boolean('is_admin')->default(false)->index();
});
// App\Models\User
protected function casts(): array
{
return ['is_admin' => 'boolean'];
}
Option B: roles or permissions. If the application already uses Spatie Laravel Permission or an equivalent authorization package, delegate the Gate to its existing API. Pro does not require or install that package.
Gate::define('moderateReviews', fn (User $user, Product $product): bool =>
$user->hasRole('admin') || $user->hasPermissionTo('moderate reviews')
);
Create the admin role or moderate reviews permission through that application's normal seeder or administration workflow, assign it only to trusted accounts, and keep the permission's guard consistent with Pro's configured guard.
Option C: model ownership. For a marketplace or multi-owner application, authorize the owner of the specific reviewable model:
Gate::define('moderateReviews', fn (User $user, Product $product): bool =>
$user->getKey() === $product->owner_id
);
Rules can be combined when administrators and model owners should both moderate:
Gate::define('moderateReviews', fn (User $user, Product $product): bool =>
$user->is_admin || $user->getKey() === $product->owner_id
);
Define the other customer-facing abilities separately; moderator permission does not automatically grant submission or other application permissions:
Gate::define('viewProductReviews', fn (?User $user, Product $product): bool => true);
Gate::define('reviewProduct', fn (User $user, Product $product): bool =>
$user->hasVerifiedEmail()
);
Guest-accessible view Gates must accept a nullable user. Require authentication on your submission/admin routes and use your real purchase/booking eligibility rules where needed. If view_ability is configured, moderators must pass both the view ability and moderateReviews.
Management fails closed: a missing, null, or empty moderation ability does not grant access. Normal reviewer authentication is not moderator permission. Target, department, connection, and permissions are rechecked during actions.
Use a shared cache for production throttling across multiple servers. Built-in limits are safeguards, not comprehensive spam prevention. For tenant databases, establish the correct connection on every request and model class; changing a connection during Livewire hydration is rejected rather than silently crossing databases.
Moderation
Manual moderation needs no separate Pro migration. Keep Core's approved_review setting false when new reviews should wait for approval:
// config/review-rateable.php
'approved_review' => false,
Create a private route in the consuming application. Route authorization protects the surrounding page, while the component independently rechecks the same Gate on every render and action:
use Illuminate\Support\Facades\Route;
Route::view('/admin/products/{product}/reviews', 'admin.products.reviews')
->middleware(['auth', 'verified', 'can:moderateReviews,product'])
->name('admin.products.reviews');
Embed the dashboard in resources/views/admin/products/reviews.blade.php:
<livewire:review-rateable-pro-review-moderation
:reviewable="$product"
department="default"
:key="'review-moderation-'.$product->getKey()" />
The dashboard provides status filters, literal review-text search, pagination, and approve/hide controls. It uses core's approval flag without another moderation table.
Approving publishes the review. Hiding sets approved to false while retaining ratings, photos, votes, replies, and other metadata. Core does not distinguish never-approved from hidden reviews, so the dashboard groups them as Pending / hidden. Hiding is not permanent deletion.
The authenticated PHP service supports the same permissions:
use Codebyray\ReviewRateablePro\Services\ReviewManager;
$manager = app(ReviewManager::class);
$manager->setApproval($review, true);
$manager->setApproval($review, false);
Setting approved_review to true publishes clean submissions immediately; authorized moderators can still hide them later. Automatic bad-word/phrase handling is a separate optional feature: after installing and enabling content filtering, a match is redacted and forced into moderation even when automatic approval is enabled.
This release does not install a full persisted moderation audit log or bulk-delete workflow. Use after-commit events for your application's audit storage and notifications.
Replies
Official owner/team replies are optional, with one editable response per published review:
php artisan vendor:publish --tag=review-rateable-pro-replies-migrations
php artisan migrate
'replies' => [
'enabled' => true,
'ability' => 'replyToReview',
'min_length' => 2,
'max_length' => 5000,
],
Define replyToReview to receive the reviewable model and the core review. Reply permission is separate from moderation permission. This example assumes an owner_id field:
use Codebyray\ReviewRateable\Models\Review;
Gate::define('replyToReview', fn (User $user, Product $product, Review $review) =>
$user->id === $product->owner_id
);
The moderation dashboard adds the editor when permitted. Or embed it on your own private owner/team page:
<livewire:review-rateable-pro-review-reply-form
:reviewable="$product"
:review-id="$review->id" />
For custom authorized workflows:
$reply = $manager->saveReply($review, 'Thank you for the feedback.');
$manager->removeReply($review);
$reviews = $product->reviews()
->where('approved', true)
->with(['ratings', 'proReply.author'])
->get();
Replies are plain text and HTML-escaped. A reply records its last authenticated editor and update date, not an arbitrary caller-supplied author. Hiding its review hides the reply; reapproval restores it. Removal is permanent, and this is not a threaded discussion feature.
Helpful
Install the optional vote table, then enable voting:
php artisan vendor:publish --tag=review-rateable-pro-helpful-votes-migrations
php artisan migrate
'helpful_votes' => [
'enabled' => true,
'ability' => null,
'changes_per_minute' => 30,
],
The list adds helpful counts and Most helpful sorting. Authenticated readers can add or withdraw one vote on another author's published review. Authors cannot vote on themselves, and pending/hidden reviews cannot receive votes. Guests see counts and a sign-in prompt.
An optional ability receives the target and review for additional eligibility checks. Duplicate requests are idempotent, not blind toggles; actual changes consume the throttle.
use Codebyray\ReviewRateablePro\Services\HelpfulVoteManager;
$votes = app(HelpfulVoteManager::class);
$votes->setHelpful($review);
$votes->setHelpful($review, false);
$reviews = $product->reviews()
->where('approved', true)
->withCount('proHelpfulVotes')
->get();
Hiding retains votes; hard deletion cascades them. Deleting a user does not automatically erase their historical votes. Apply your application's retention rules. Avoid exposing voter identities in public APIs.
Verification
A badge proves an application-defined relationship, not a review's truthfulness. It is separate from email verification, invitation status, and approval.
php artisan vendor:publish --tag=review-rateable-pro-verification-migrations
php artisan migrate
'verification' => [
'enabled' => true,
'verifier' => App\Reviews\PurchaseReviewVerifier::class,
'label' => 'Verified Purchase',
],
The label is customizable: Verified Purchase, Verified Booking, Verified Customer, or your own wording. Reviewers cannot assign badges.
Implement the verifier using trusted local records. This example assumes your application has orders with user_id, product_id, and status; adapt it for your schema, fulfillment, refunds, and tenant scope:
namespace App\Reviews;
use App\Models\Order;
use App\Models\Product;
use Codebyray\ReviewRateable\Models\Review;
use Codebyray\ReviewRateablePro\Contracts\ReviewVerifier;
use Codebyray\ReviewRateablePro\VerificationResult;
use Illuminate\Database\Eloquent\Model;
class PurchaseReviewVerifier implements ReviewVerifier
{
public function verify(
Model $reviewable,
Review $review,
?Model $author
): ?VerificationResult {
if (! $reviewable instanceof Product || $author === null) {
return null;
}
$order = Order::on($review->getConnectionName())
->where('user_id', $author->getKey())
->where('product_id', $reviewable->getKey())
->where('status', 'completed')
->first();
return $order === null ? null : new VerificationResult(
'completed_order', (string) $order->getKey()
);
}
}
The verifier receives the persisted author, not the current visitor. Returning null grants no badge and removes stale evidence on a recheck. Missing/invalid evidence must not default to verified.
Pro creation and editing recheck automatically within their transactions. Lookup failures roll back those operations. Keep checks fast and local; synchronize remote evidence separately.
Badges are stored snapshots, not live queries. Connect refunds, cancellations, or evidence changes to trusted server-side rechecks:
use Codebyray\ReviewRateablePro\Services\ReviewVerificationManager;
$checks = app(ReviewVerificationManager::class);
$checks->refresh($review);
$checks->revoke($review);
These methods do not require an interactive login; your endpoint/job must supply its own authentication and authorization. Do not expose them as public actions. Use only non-secret evidence identifiers. Source/reference are hidden from normal serialization but not automatically encrypted; do not broadcast evidence-bearing objects publicly.
Titles
Add the nullable title column before enabling headlines:
php artisan vendor:publish --tag=review-rateable-pro-titles-migrations
php artisan migrate
'titles' => ['enabled' => true, 'max_length' => 150],
'form' => [
'min_length' => 10,
'max_length' => 5000,
'submissions_per_minute' => 3,
'one_review_per_user' => true,
],
Titles are plain text, escaped in cards, photo dialogs, and moderation. The limit is measured in characters and capped at 255. Existing reviews retain a null title. Disabling titles hides them without deleting their values.
Core's addReview() does not accept a title. In custom integrations, assign a validated $review->title and save it inside your review-creation transaction.
One review per author
form.one_review_per_user is false by default. Set it to true for one Pro submission per author, target, and department. Pending and hidden reviews count, hard-deleted reviews do not, and existing duplicates remain untouched.
Pro rechecks the rule under a target-row transaction lock. Core API writers must enforce the same locking/policy themselves if you need a global limit. Enable editing separately so authors can update their existing review instead of submitting another.
Editing
Author editing and dated follow-ups use two optional tables:
php artisan vendor:publish --tag=review-rateable-pro-editing-migrations
php artisan migrate
'editing' => [
'enabled' => true,
'ability' => null,
'require_reapproval' => true,
'updates_enabled' => true,
'update_label' => 'EDIT',
'update_min_length' => 2,
'update_max_length' => 5000,
'changes_per_minute' => 10,
],
The author's card displays Manage your review. Authors can edit their text, ratings, recommendation, enabled title, and enabled photos. Removed/reordered photos are draft changes until saved.
A dated update preserves the main review, for example EDIT — [server date]: My experience after a month…. Customize update_label to EDIT, UPDATE, or other plain text. Notes are append-only through this interface; there is no full revision history or note edit/delete workflow.
You can render the editor directly on an authorized author page:
<livewire:review-rateable-pro-review-edit-form
:reviewable="$product"
:review-id="$review->id"
:key="'review-editor-'.$review->id" />
Ownership is mandatory. An optional editing ability adds restrictions but cannot let an administrator impersonate another author to edit their review. The create ability is not reused; define editing eligibility separately if it can expire.
By default, a real edit/update makes the entire review pending, removing it from public cards and statistics until approved again. There is no separate public approved revision while edits await review. Disabling reapproval preserves its current approval state but never approves an already-pending review. New content-filter matches always force moderation.
Stale tabs are rejected using versions and content fingerprints. Reload saved review (discard draft) intentionally abandons the local draft. Votes and replies are retained; verification rechecks if enabled.
Custom integrations should use the authenticated editing service, supplying a fresh version and fingerprint:
use Codebyray\ReviewRateablePro\Services\ReviewEditingManager;
$editor = app(ReviewEditingManager::class);
$review = $review->fresh();
$version = $review->proEditState?->version ?? 0;
$fingerprint = $editor->fingerprint($review);
$saved = $editor->saveReview($review, [
'reviewText' => 'My updated experience after a month.',
'ratings' => ['overall' => 4, 'quality' => 4, 'price' => 5],
'recommend' => true,
], $version, $fingerprint);
Omitted title/photo fields preserve existing values. A supplied imageOrder must contain unique IDs from this review; omitted images are removed after a successful save. Refresh the snapshot before a subsequent operation. Use Laravel's transaction APIs so new-upload rollback and old-file after-commit cleanup can run correctly.
Reports
Reader reports are private and independently optional:
php artisan vendor:publish --tag=review-rateable-pro-reports-migrations
php artisan migrate
'reports' => [
'enabled' => true,
'ability' => null,
'reasons' => [
'spam' => 'Spam or advertising',
'inappropriate' => 'Inappropriate content',
'misleading' => 'Misleading review',
'other' => 'Other concern',
],
'details_max_length' => 2000,
'submissions_per_minute' => 5,
'per_page' => 10,
],
Signed-in readers can report another author's published review with a reason and optional plain-text details. Each account has one lifetime report per review, including closed reports; duplicates do not replace evidence or reopen it. There is no report editing, withdrawal, or re-reporting after review edits.
Reports never automatically hide reviews. Show the queue only on a private page protected by the moderation ability:
<livewire:review-rateable-pro-review-report-queue
:reviewable="$product"
department="default" />
Moderators can resolve or dismiss a report. Both record the decision and server time; hide the review separately if needed. Closed reports cannot be reopened. The queue shows the current review, not a frozen copy of the originally reported content.
The authenticated API provides the same behavior:
use Codebyray\ReviewRateablePro\Models\ReviewReport;
use Codebyray\ReviewRateablePro\Services\ReviewReportManager;
$reports = app(ReviewReportManager::class);
$report = $reports->submit($review, 'spam', 'Optional private details.');
// In a separate authorized moderator workflow:
$reports->close($report, ReviewReport::STATUS_RESOLVED);
Do not eager-load proReports, reporter identities, or report details into public responses. Apply your own privacy and retention policy.
Invitations
Invite existing customer accounts without bypassing approval or granting a verified badge:
php artisan vendor:publish --tag=review-rateable-pro-invitations-migrations
php artisan migrate
'invitations' => [
'enabled' => true,
'route' => 'reviews.invitation',
'ability' => null,
'expires_in_days' => 14,
'changes_per_minute' => 10,
'email_enabled' => true,
'mailer' => null,
'queue' => null,
],
Issuers must pass moderation and view permissions. An optional invitation ability receives the target and recipient for contact/eligibility rules. Recipients must be saved users with a valid email and compatible numeric identity on the target's connection.
Pro does not register a route. Add your own authenticated, signed endpoint with an {invitation} parameter, scoped correctly for your application:
use Codebyray\ReviewRateablePro\Models\ReviewInvitation;
use Codebyray\ReviewRateablePro\Services\ReviewInvitationManager;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::get('/reviews/invitations/{invitation}', function (
Request $request,
ReviewInvitation $invitation,
ReviewInvitationManager $invitations
) {
$data = $request->validate([
'token' => ['required', 'string', 'size:64'],
]);
$invitation = $invitations->open($invitation, $data['token']);
return response()->view('reviews.invitation', [
'invitation' => $invitation,
'reviewable' => $invitation->reviewable,
])->header('Cache-Control', 'private, no-store')
->header('Referrer-Policy', 'no-referrer')
->header('X-Robots-Tag', 'noindex, nofollow');
})->middleware(['auth', 'signed'])->name('reviews.invitation');
Use auth:your-guard for a non-default Pro guard. Multi-tenant applications must bind invitations on the correct tenant connection and apply their own target scope.
Render the form in reviews.invitation inside your normal layout:
<livewire:review-rateable-pro-review-form
:reviewable="$reviewable"
:department="$invitation->department"
:invitation-id="$invitation->id" />
From an authorized private workflow:
$invitations = app(ReviewInvitationManager::class);
$issued = $invitations->issue(
$product, $customer, $product->name, sendEmail: true
);
// $issued->url is returned only for a newly issued link.
$renewed = $invitations->renew($issued->invitation, sendEmail: true);
The signed link is not authentication; the invited account must sign in. Opening and submitting recheck recipient, token, expiry, and permissions. Renewal invalidates old links and open forms.
There is one lifetime invitation per target/department/customer. Duplicate issuance returns the existing record with a null URL and sends no new email. Only a token hash is saved; renew explicitly if you need another link. Existing reviews, including pending/hidden ones, prevent new invitations.
Statuses are pending, opened, expired, and reviewed. Opened means an authenticated page visit, not email tracking. Reviewed means submitted, not approved. Normal Pro submissions complete matching invitations; custom core writers should call recordReview($review) in the author-authenticated creation workflow.
Email additionally requires sendEmail: true on issue/renew. An encrypted job dispatches after commit; configure a real mail transport and asynchronous queue worker in production:
php artisan queue:work
The demo uses an array mailer and does not deliver external emails. Mail acceptance is not proof of receipt, and worker retries can produce duplicates. Monitor failed jobs. Protect signed URLs from logs, referrers, public APIs, and shared caches; only contact customers you are permitted to email.
Content moderation
Redact your configured words/phrases and require human approval, even if core auto-approval is enabled:
php artisan vendor:publish --tag=review-rateable-pro-content-moderation-migrations
php artisan migrate
'content_moderation' => [
'enabled' => true,
'words' => [
'your blocked word',
'your blocked phrase',
],
'replacement' => '[redacted]',
],
No profanity dictionary is bundled. Terms are literal UTF-8, case-insensitive, whole-word matches; phrase whitespace is flexible. Substrings in unrelated words are not matched. Supply up to 1,000 terms of 200 characters each. Empty terms are ignored, and an empty list changes nothing.
The replacement must be non-empty plain text, at most 255 characters, and must not contain a blocked term. Redacted output must still fit configured field lengths. Invalid configuration fails closed.
Pro filters bodies, enabled titles, and new dated updates automatically. New matches force the entire review pending even when approved_review=true or editing.require_reapproval=false.
Moderators can inspect encrypted originals and matched wording in the private moderation dashboard. Approval publishes redacted text, not the original. Public/author views do not load this private history.
Custom core/API writers
Core does not invoke Pro filtering automatically. Authorize and validate first, then filter inside the same write transaction:
use Codebyray\ReviewRateablePro\Services\ReviewContentModerationManager;
$review = $product->getConnection()->transaction(
function () use ($product, $validated, $author) {
$review = $product->addReview([
'review' => $validated['review'],
'ratings' => $validated['ratings'],
], $author->getKey());
return app(ReviewContentModerationManager::class)
->moderateReview($review);
}
);
moderateUpdate($update) is available for custom dated-update writers. These are trusted persistence integrations, not authentication or input-validation boundaries.
Existing reviews are not automatically rescanned when your dictionary changes. This is not semantic/AI moderation, regex matching, OCR, image moderation, leetspeak detection, or official-reply filtering.
Keep APP_KEY stable or use Laravel's supported key-rotation process so private snapshots stay decryptable. Define retention for originals; never log or publicly serialize them.
Customization
Pro ships two framework-based Livewire template sets: Tailwind CSS 3/4 (the default) and Bootstrap 5.3. React and Vue use framework-neutral markup plus matching Tailwind-like and Bootstrap-like presets in the published shared stylesheet. All three interface families read the same theme setting. Your application must provide the selected framework stylesheet for Livewire; React and Vue do not require either framework at runtime. All visible strings use Laravel translation helpers.
The default Pro demo renders the shipped Tailwind templates using this application's compiled Tailwind CSS. The customized example uses application-owned list/gallery templates and this site's scoped visual styling, including score-based star rows. Both share the same reviews, configuration, permissions, and package services. The customized list extends Pro's component and changes its rendered view only, so its presentation persists during Livewire updates.
Choose Tailwind or Bootstrap
Both Livewire template sets are complete and independently publishable. All six Livewire components use the selected theme, including nested author/reply editors and private moderation/report queues. React and Vue receive that same value as data-rrp-theme and use the corresponding shared CSS preset. Theme selection does not enable features, change authorization, create tables, or affect invitation emails. Every interface includes the same core form validation, galleries, full-width rating breakdowns, review actions, and accessible controls.
Publish the configuration if you have not already done so:
php artisan vendor:publish --tag=review-rateable-pro-config
Select one theme in .env:
REVIEW_RATEABLE_PRO_THEME=tailwind
# Alternatively: REVIEW_RATEABLE_PRO_THEME=bootstrap
# Tailwind is the default if this variable is omitted.
If you already published an older config, add theme and themes from the package's current config file; do not overwrite unrelated settings. You can also set 'theme' => 'bootstrap' directly in config/review-rateable-pro.php.
Your application must provide the selected framework's stylesheet. Pro does not install either framework, inject CDN assets, or require Bootstrap's JavaScript. Livewire supplies Alpine; the photo viewer remains a native <dialog> rather than a Bootstrap JavaScript modal.
For Tailwind 4, register the shipped templates in your application stylesheet (paths below assume resources/css/app.css):
@import "tailwindcss";
@source "../../vendor/codebyray/laravel-review-rateable-pro/resources/views/themes/tailwind";
@source "../views/vendor/review-rateable-pro/themes/tailwind";
For Tailwind 3, include these paths in tailwind.config.js alongside your existing content entries:
export default {
content: [
'./resources/**/*.blade.php',
'./vendor/codebyray/laravel-review-rateable-pro/resources/views/themes/tailwind/**/*.blade.php',
],
};
Rebuild your application assets after adding or editing Tailwind templates. For Bootstrap, load your application's Bootstrap 5.3 CSS through its existing asset pipeline; no Tailwind setup is needed.
Templates work straight from the package without publishing. To customize only the selected framework set, use its dedicated tag:
# Tailwind only
php artisan vendor:publish --tag=review-rateable-pro-tailwind-views
# Bootstrap only
php artisan vendor:publish --tag=review-rateable-pro-bootstrap-views
php artisan config:clear
php artisan view:clear
These publish to resources/views/vendor/review-rateable-pro/themes/tailwind/ or themes/bootstrap/. Each contains six livewire/ views plus partials/ for review details, replies, galleries, and small scoped behavior/layout styles (rating selection, thumbnails, and native dialogs). Laravel gives published theme files precedence over shipped theme files. There are no separate top-level livewire/ or partials/ template sets.
For a custom theme, copy an entire framework set into resources/views/vendor/review-rateable-pro/themes/custom/, update its partial includes from themes.bootstrap or themes.tailwind to themes.custom, and add a prefix to the existing themes map:
'theme' => env('REVIEW_RATEABLE_PRO_THEME', 'tailwind'),
'themes' => [
'tailwind' => 'review-rateable-pro::themes.tailwind',
'bootstrap' => 'review-rateable-pro::themes.bootstrap',
'custom' => 'review-rateable-pro::themes.custom',
],
Select REVIEW_RATEABLE_PRO_THEME=custom and clear the config/view caches. Unknown theme keys or missing component templates fall back to Tailwind views, including any application overrides for those Tailwind files. Supply every component and its referenced partials for a consistent custom theme. Selection is server-side configuration, not a browser-controlled view path or a per-component attribute.
Publish all templates
Publish and edit the views when you need deeper customization:
php artisan vendor:publish --tag=review-rateable-pro-views
This publishes all Blade templates to resources/views/vendor/review-rateable-pro: both framework sets in themes/tailwind/ and themes/bootstrap/, and HTML/plain-text invitation emails in mail/. Laravel uses an application's published template in preference to the package template; you can keep only the files you need to override, and unmodified templates continue to use the package's copies.
Customize markup and styling while preserving Livewire bindings, action names, validation output, wire:key identifiers, and gallery Alpine/dialog behavior. Publishing views does not copy PHP component classes or replace server-side authorization. Each theme has a small inline themes/{theme}/partials/styles.blade.php stylesheet for specialized behavior/layout; apps with strict CSP should move it to their own allowed stylesheet and override that partial. Alpine's standard inline expressions also need to be allowed by your application's CSP setup.
Published overrides are application-owned and do not automatically receive later package view changes. Review template diffs when upgrading. Avoid publishing with --force unless you intend to overwrite local customizations; normal publishing skips existing files. After editing templates, use php artisan view:clear if your deployment serves cached views.
This version adds no application routes, login links, or authentication redirects. Wrap components in your own page/layout and provide your own sign-in experience.
Events
Pro services emit Laravel events after the outermost database transaction commits. Use queued listeners for slow work; failures after commit cannot undo saved reviews.
| Event | Typical action |
|---|---|
ReviewModerated |
Approval changed |
ReviewReplyChanged |
Reply saved or removed |
ReviewHelpfulVoteChanged |
Vote added or removed |
ReviewVerificationChanged |
Evidence verified or revoked |
ReviewEdited |
Edited or update added |
ReviewReportChanged |
Submitted, resolved, or dismissed |
ReviewInvitationChanged |
Issued, renewed, opened, or reviewed |
Events live in Codebyray\ReviewRateablePro\Events. They are not automatically broadcast and do not install notifications or an audit store. Report, verification, and invitation events can contain private data; do not broadcast them to public channels.
Component actions also dispatch target-scoped browser events that refresh matching lists in the same page. Direct PHP service calls do not dispatch those browser events, and changes are not automatically synchronized across other visitors' sessions.
Troubleshooting
A table is missing
Check that you published and ran that feature's migration before enabling its config flag. Check the model's actual database connection, not only the default connection.
Reviews are not appearing publicly
Core defaults to pending. Approve the review, confirm the selected department and filters, and check Pro's view ability. Author-only pending visibility does not make the review public. Edits or content-filter matches can require approval again.
Photos show broken links
Check the configured disk, URL/base URL, file existence, public storage symlink, and directory permissions. Core does not generate thumbnails. A private disk needs your own authorized delivery strategy.
A moderator gets HTTP 403
Define the configured explicit moderation Gate/policy and permit the real signed-in user for the target. Check the guard and view ability. Null management abilities deliberately deny access.
Duplicate reviews remain
One-review-per-user affects future Pro form submissions; it does not merge old duplicates or constrain independent core/API writers. Enforce a consistent write/locking policy in those integrations.
An edit is rejected as stale
Another action changed the saved review, ratings, photos, or approval. Reload the saved review intentionally and reapply your changes. Do not bypass the version/fingerprint protection to force an old draft over newer content.
The docs copy button selects code instead of copying
Clipboard access may require HTTPS and browser permission. If blocked, the example is selected and the button asks you to press Ctrl/Cmd+C. Copying always uses the original plain source, not the highlighted HTML.
Upgrading
Back up your database/files and test upgrades in staging. Update Composer dependencies, inspect new migration/config requirements, run pending migrations, and compare any published view overrides with the new package templates.
Core's GitHub repository includes its source, changelog, and issue tracker. Pro's docs describe the current preview APIs. The Pro license terms are published; sales should open only after a tagged release can be installed through the private Composer repository.
Never commit license credentials, Composer authentication, customer data, or populated environment files. Expired Pro update access is not intended to disable installed review functionality.