PHPackages                             justinholtweb/craft-stars - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. justinholtweb/craft-stars

ActiveCraft-plugin

justinholtweb/craft-stars
=========================

Review &amp; testimonial management for Craft CMS — star ratings, moderation, schema.org markup, and spam protection.

5.0.0(yesterday)00proprietaryPHP ^8.2

Since Jul 19Compare

[ Source](https://github.com/justinholtweb/craft-stars)[ Packagist](https://packagist.org/packages/justinholtweb/craft-stars)[ RSS](/packages/justinholtweb-craft-stars/feed)WikiDiscussions Synced today

READMEChangelog (1)Dependencies (4)Versions (4)Used By (0)

Stars — Review &amp; Testimonial Management for Craft CMS 5
===========================================================

[](#stars--review--testimonial-management-for-craft-cms-5)

A structured review system for Craft CMS with star ratings, four-state moderation, pros/cons, admin responses, spam protection, and schema.org JSON-LD markup.

Requirements
------------

[](#requirements)

- Craft CMS 5.0+
- PHP 8.2+

Installation
------------

[](#installation)

```
composer require justinholtweb/craft-stars
php craft plugin/install stars
```

Features
--------

[](#features)

- **Star ratings** — configurable max (1-5 or 1-10)
- **Four-state moderation** — pending, approved, rejected, spam
- **Pros &amp; cons** — optional structured pro/con lists per review
- **Admin responses** — reply to reviews from the CP with timestamp
- **Spam protection** — honeypot, Google reCAPTCHA v3, IP rate limiting, submission time check
- **Schema.org** — JSON-LD output with `Review` + `AggregateRating` markup
- **Email notifications** — configurable recipients on new submissions
- **User permissions** — view, manage, moderate, respond, delete
- **Bulk actions** — approve, reject, mark as spam from the element index
- **Craft 5 native element editor** — sidebar fields, metadata, element chips

Usage
-----

[](#usage)

### Frontend Form

[](#frontend-form)

```

    {{ csrfInput() }}
    {{ actionInput('stars/reviews/save') }}
    {{ redirectInput('/thank-you') }}

    {# Honeypot (hidden from users, caught by bots) #}

    Your Name

    Email

    Rating

        {% for i in 1..5 %}
            {{ '★'|repeat(i) }}{{ '☆'|repeat(5 - i) }}
        {% endfor %}

    Review

    Submit Review

```

#### With Pros &amp; Cons

[](#with-pros--cons)

```
Pros

Cons

```

#### AJAX Submission

[](#ajax-submission)

```
const form = document.querySelector('#review-form');
form.addEventListener('submit', async (e) => {
    e.preventDefault();
    const res = await fetch('/', {
        method: 'POST',
        headers: { 'Accept': 'application/json' },
        body: new FormData(form),
    });
    const data = await res.json();
    if (data.success) {
        // Review submitted
    } else {
        // Handle data.error or data.errors
    }
});
```

### Displaying Reviews

[](#displaying-reviews)

```
{% set reviews = craft.reviews.forEntry(entry).all() %}
{% set avg = craft.reviews.averageRating(entry) %}
{% set count = craft.reviews.count(entry) %}

{% if count > 0 %}
    {{ avg|number_format(1) }} out of 5 ({{ count }} {{ count == 1 ? 'review' : 'reviews' }})

    {% for review in reviews %}

            {{ review.reviewerName }}
            {{ '★'|repeat(review.rating) }}{{ '☆'|repeat(5 - review.rating) }}
            {{ review.dateCreated|date('M j, Y') }}

            {% if review.reviewText %}
                {{ review.reviewText }}
            {% endif %}

            {% set pros = review.prosArray %}
            {% if pros|length %}

                    {% for pro in pros %}{{ pro }}{% endfor %}

            {% endif %}

            {% set cons = review.consArray %}
            {% if cons|length %}

                    {% for con in cons %}{{ con }}{% endfor %}

            {% endif %}

            {% if review.adminResponse %}

                    Response: {{ review.adminResponse }}

            {% endif %}

    {% endfor %}
{% endif %}
```

### Rating Distribution

[](#rating-distribution)

```
{% set dist = craft.reviews.distribution(entry) %}

{% for stars, count in dist|reverse %}
    {{ stars }} stars: {{ count }}
{% endfor %}
```

### Schema.org Markup

[](#schemaorg-markup)

Place in your `` to output valid JSON-LD for Google Rich Results:

```
{{ craft.reviews.schemaOrg(entry)|raw }}
```

### Twig API Reference

[](#twig-api-reference)

MethodReturnsDescription`craft.reviews.forEntry(entry)``ReviewQuery`Approved reviews for an entry, newest first`craft.reviews.averageRating(entry)``float`Average rating (approved only)`craft.reviews.count(entry)``int`Count of approved reviews`craft.reviews.distribution(entry)``array``{1: n, 2: n, ...}` rating histogram`craft.reviews.schemaOrg(entry)``string`JSON-LD `` tagAll methods accept an `Entry` object or an entry ID integer.

Configuration
-------------

[](#configuration)

All settings are available in the CP under **Stars &gt; Settings**. You can also override them in `config/stars.php`:

```
