Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Core Laravel Package

waterhole/core

Waterhole is an open-source Laravel package for building modern community forums. It provides a full discussion engine with posts, comments, reactions, moderation, member management, and a customizable interface that integrates cleanly into existing Laravel applications.

View on GitHub
Deep Wiki
Context7

Getting Started

Waterhole is a complete forum application shipped as a Laravel package — you are not building a forum, you are hosting, configuring, and extending one. Treat waterhole/core as the application and your Laravel app as the integration shell.

  1. Install into a fresh Laravel app (or the starter skeleton, if you want the reference wiring):
    • composer require waterhole/core
    • php artisan waterhole:install — publishes config, migrations, and assets, runs migrations, and walks you through creating the first admin account.
    • php artisan storage:link so uploads/avatars resolve.
  2. Look here first, in this order:
    • config/waterhole.php — feature toggles (gamification, reactions, tags, private discussions), route prefix/domain, uploads/disk, mail, search driver, SSO settings.
    • The package's routes/ and src/Http/ — this is the real public surface; read it before assuming an endpoint exists.
    • resources/views/ and the Alpine components — the frontend is Blade + Alpine + Tailwind, not a JS SPA, so customization is mostly server-rendered.
    • The Waterhole\Extend namespace — the supported hook registry. This is where "how do I add X to the discussion page" is answered.
  3. First working use case: boot the forum, create a group and a tag in the admin panel, post a discussion with an image, reply, react, and confirm the notification email lands in a mail catcher. That single loop exercises storage, queues, mail, permissions, and the asset pipeline — if all five work, your environment is correct.
  4. Then decide the identity model. Either users register directly in the forum, or you point Waterhole at your existing User model and authenticate via Socialite/your IdP. Decide this before seeding content; changing it later means reconciling users and authored posts.
  5. Read the extension docs before writing any code. The intended customization surface is extensions plus published views. If you find yourself editing vendor/waterhole/core, stop — you've taken a path that composer update will erase.

Implementation Patterns

Extension packages as the unit of customization. Every non-trivial change should be its own Composer package (local packages/ or extensions/ path repo) that registers with Waterhole's extend registry at boot. One extension per concern — "acme/waterhole-sso", "acme/waterhole-branding", "acme/waterhole-slack-notifications" — so you can disable or upgrade them independently.

// packages/acme/waterhole-branding/src/Extension.php
public function boot(): void
{
    Extend::assets()->add('acme-branding', __DIR__.'/../dist/branding.css');
    Extend::ui()->add('discussion.afterHeader', view('acme-branding::callout'));
}

Slot-based UI injection over view forking. The registry exposes named insertion points (page headers, post footers, sidebar, composer toolbar). Inject a Blade partial into the slot; only publish and override a full view when there is genuinely no slot for it. Slot injections survive upstream releases; forked views do not.

Publish narrowly, diff aggressively. vendor:publish --tag=waterhole-views dumps everything. Instead publish only the two or three views you actually need into resources/views/vendor/waterhole/, and keep a note of which upstream version each was copied from. On upgrade, diff those files against the new vendor copies — that diff is your upgrade risk.

Theming via CSS variables, not views. Waterhole's Tailwind layer is driven by CSS custom properties. A theme extension that ships a single CSS file overriding those properties gets you 90% of branding with near-zero upgrade cost. Reach for markup changes only for structural differences.

Use domain actions, not raw save(). Mutations go through the package's action/service classes so that events, denormalized counters (reply counts, last-activity), notifications, and the search index all stay consistent. If you write a bulk importer that touches models directly, you own re-indexing and counter recalculation afterward — plan for scout:import and any recalc commands in the same job.

Integrate by listening, not by patching. Subscribe to Waterhole's domain events (DiscussionStarted, PostCreated, UserRegistered, moderation events) from your host app to push to Slack, mirror into a CRM, award app-side perks, or feed analytics. Always implements ShouldQueue — a slow listener on PostCreated is a slow reply button.

SSO pattern. Register an OAuth provider via Socialite, map the IdP identity to the Waterhole user, and sync profile fields one-way from the IdP on each login. Keep the forum's username/email columns as a cache of the IdP, never the source of truth, or you'll get drift and duplicate accounts.

Permissions as data, not code. Define custom permissions in an extension's registration, assign them to groups in the admin panel, and check them through the policies/gates the package already consults. Hardcoding if ($user->isAdmin()) in an extension bypasses the whole group-permission model and confuses moderators.

Local workflow. Run a queue worker in dev (php artisan queue:work) — notifications, search indexing, and image processing are all queued, and a dead worker looks exactly like a broken feature. Use the package factories in tests with RefreshDatabase, and disable gamification/notifications in the test environment so feature tests don't fan out into mail assertions.

Gotchas and Tips

  • Never edit vendor/. It is the single most common way to lose a day. Every change belongs in an extension, a published view, or a config override.
  • License metadata is NOASSERTION. Read the actual LICENSE file before a commercial deployment; don't infer terms from the GitHub badge.
  • Pin exact versions. The core moves quickly and the extension registry is not a stability contract in the way the HTTP surface is. Upgrade deliberately: bump, run migrations, re-publish assets, load every page you've customized, then read the changelog for renamed slots.
  • Re-publish assets, not views. After an upgrade, vendor:publish --force for compiled assets is fine; doing it for views or config silently overwrites your customizations. Tag your publishes individually.
  • Config cache will bite you. Most waterhole.php values read env() at boot. php artisan config:cache freezes them — a changed .env with a cached config is the classic "why is search still using the old driver" bug. config:clear first when debugging anything config-shaped.
  • A broken extension takes down the whole site, because registration happens at boot. In development, scaffold new extensions with their boot() wrapped in a try/catch that logs and returns, so a typo yields a degraded forum rather than a 500 on every route.
  • Schema changes belong in your own migrations. Add columns and indexes with new migrations that ALTER the package tables; never edit the package's migration files. Be aware that major upgrades can rename columns your migration depends on.
  • Denormalized counters drift. Reply counts and last-activity timestamps are cached on the discussion row. Anything that writes posts outside the actions (imports, seeders, direct SQL) must trigger the package's recalculation; otherwise the index page slowly lies.
  • Queue and cache are load-bearing. Use Redis for cache and sessions in anything beyond a toy install — per-request permission and counter lookups are frequent. Set QUEUE_CONNECTION explicitly; the sync driver makes everything feel fine until it doesn't.
  • Uploads fail silently-ish. Verify both storage:link and the configured disk, and if you're on S3, set CORS for any direct-to-bucket flow. A wrong disk usually surfaces as broken image URLs, not an exception.
  • Timestamps cross the server/client boundary. Dates render both in Blade and in Alpine. Emit ISO-8601 into the markup and format client-side, or users in other timezones will see the wrong "5 minutes ago".
  • Debug with php artisan about, Telescope, and APP_DEBUG. For extension issues, confirm registration order and look for duplicate slot names — two extensions hooking the same slot produce confusing doubled markup rather than an error.
  • Watch the discussion index query. It's the hottest path in the app. Eager-load authors and tags, and cache per-group/per-tag listing fragments; a naive extra query there multiplies across every page view.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons