Configuration
The package works with zero configuration — every key has a sensible default. The published config/comments.php, with its docblocks trimmed:
use RoundlyConsulting\Comments\Models\Comment;
return [
'model' => Comment::class,
'key_type' => env('COMMENTS_KEY_TYPE', 'bigint'),
'require_approval' => env('COMMENTS_REQUIRE_APPROVAL', false),
'max_length' => env('COMMENTS_MAX_LENGTH', 5000),
'max_depth' => env('COMMENTS_MAX_DEPTH', 5),
'order' => env('COMMENTS_ORDER', 'latest'),
'blocklist' => [],
'blocklist_action' => env('COMMENTS_BLOCKLIST_ACTION', 'reject'),
'mention_resolver' => null,
'authorization' => env('COMMENTS_AUTHORIZATION', false),
// Auto-hide a comment on an upheld report / threshold crossing (reports-for-laravel).
'moderation' => [
'on_resolved' => 'hide', // 'hide' | null — anything else throws
'auto_hide' => true, // react to the global reports threshold
],
// Attachments + inline [media:UUID] rendering (media-library-for-laravel).
'media' => [
'attachments_bucket' => 'attachments',
'visibility' => env('COMMENTS_MEDIA_VISIBILITY', 'private'),
'disk' => env('COMMENTS_MEDIA_DISK'),
'private_disk' => env('COMMENTS_MEDIA_PRIVATE_DISK', 'local'),
'accepted_mime_types' => [],
'max_file_size' => null,
'responsive_widths' => null,
'temporary_url_lifetime' => null,
'inline' => [
'enabled' => true,
'default_variant' => '',
'on_missing' => 'strip', // 'strip' | 'keep' (anything else throws)
],
],
];Every key
| Key | Default | Purpose |
|---|---|---|
model | Comment::class | Eloquent model that stores comments. Point it at your subclass of the packaged Comment; any other class throws. |
key_type | bigint | Key type of the polymorphic columns: bigint, uuid or ulid — anything else throws. Read when the migration runs; keys are stored and read back as-is. |
require_approval | false | New comments start pending and must be approved before they count as visible. |
max_length | 5000 | Max body characters, at least 1; longer throws InvalidCommentBodyException. |
max_depth | 5 | Max reply nesting, at least 1 (a top-level comment is depth 1; soft-deleted ancestors still count); also bounds threaded eager-loading. |
order | latest | Order of approvedComments: latest (newest first) or oldest; anything else throws. |
blocklist | [] | Banned words (whole-word, case-insensitive) or delimited regexes such as /badword/i. A blank or non-string entry throws. |
blocklist_action | reject | On a match, on write and edit: reject (throw CommentRejectedException), pending or hidden; anything else throws. |
mention_resolver | null | Invokable class-string or [Class::class, 'method'] pair, built through the container, resolving an @handle to a model — never a closure (config:cache can’t store one). null only stores handles; a value that doesn’t resolve to a callable — an unknown class, a missing method — throws. |
authorization | false | Consult the Comment policy before every mutation — create, update, delete, restore, moderate, lock and unlock. |
moderation.on_resolved | hide | Hide a comment when a report against it is upheld; null (or a blank value) disables; anything else throws. |
moderation.auto_hide | true | Hide a comment when it crosses the global reports.threshold. |
media.attachments_bucket | attachments | Bucket name the comment registers on Media Library. |
media.visibility | private | private (signed URLs) or public; anything else throws — a typo never picks a visibility for you. |
media.disk | null | Disk for every attachment; null or blank = by visibility (private → media.private_disk, public → the Media Library default disk). |
media.private_disk | local | Disk for private attachments and their variants when media.disk is null — keep it non-public (COMMENTS_MEDIA_PRIVATE_DISK). |
media.accepted_mime_types | [] | Mime allowlist; [] accepts any type. |
media.max_file_size | null | Max attachment size in bytes, at least 1, enforced on upload (FileUnacceptableForBucket); null = Media Library’s media.max_file_size. |
media.responsive_widths | null | Width ladder for image attachments — a list of positive integers (variants named responsive-<width>); null = Media Library’s media.responsive.widths. |
media.temporary_url_lifetime | null | Signed attachment URL lifetime in minutes, at least 1; null = the Media Library default. |
media.inline.enabled | true | Expand [media:UUID] tokens in renderBody(). |
media.inline.default_variant | '' (empty) | Variant used when a token names none — a string; empty = a responsive <img>. |
media.inline.on_missing | strip | strip or keep tokens that don’t resolve; anything else throws. |
Environment
The common switches are env-driven, so you rarely need to publish the file:
COMMENTS_KEY_TYPE=bigint
COMMENTS_REQUIRE_APPROVAL=true
COMMENTS_MAX_LENGTH=2000
COMMENTS_MAX_DEPTH=3
COMMENTS_ORDER=oldest
COMMENTS_BLOCKLIST_ACTION=pending
COMMENTS_AUTHORIZATION=true
COMMENTS_MEDIA_VISIBILITY=private
COMMENTS_MEDIA_PRIVATE_DISK=s3-privateEvery bool switch is parsed as a boolean, so .env values mean what they say: true, 1, on and yes turn it on; false, 0, off and no turn it off. COMMENTS_REQUIRE_APPROVAL=1 holds new comments, COMMENTS_AUTHORIZATION=off skips the policy. A blank value (COMMENTS_AUTHORIZATION=) is not set, so the default applies. Anything else throws an InvalidConfigurationException instead of quietly reading as the default.
Strict settings
Every other setting is just as strict, and every failure throws RoundlyConsulting\PackageToolkit\Exceptions\InvalidConfigurationException naming the key. A setting that is not set — absent, null, or blank like a host’s COMMENTS_MAX_LENGTH= — takes its default; an optional one (media.disk, media.max_file_size, media.responsive_widths, media.temporary_url_lifetime) stays unset:
- Integers accept an int or a plain integer string — every env value is a string — so COMMENTS_MAX_LENGTH=five or 5.5 throws rather than becoming 0. Lengths, depths, sizes, widths and lifetimes must be at least 1.
- A typo in order, blocklist_action, moderation.on_resolved, media.visibility or media.inline.on_missing throws rather than picking a side — a media.visibility typo no longer reads as private, and an on_resolved typo no longer turns auto-hide off.
- A non-string bucket or disk name throws, and so does a junk list (blocklist, accepted_mime_types, responsive_widths).
php artisan about renders a broken setting as INVALID instead of failing.
Key type
key_type decides how the polymorphic actor, commentable, mentionable and lockable columns are keyed: bigint (default), uuid or ulid — a blank value (COMMENTS_KEY_TYPE=) is not set and keeps bigint, any other value throws an InvalidConfigurationException. It is read when the migration runs, so set it before you migrate.
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 cryptoBy 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.