HasMultisite Trait
The HasMultisite trait enables WNCMS models to support multi-website scenarios, where the same model instance can belong to one or multiple websites depending on its website mode configuration.
Overview
WNCMS supports three website modes for each model:
| Mode | Description |
|---|---|
global | Not scoped to any website (shared across all) |
single | Belongs to exactly one website |
multi | Can belong to multiple websites simultaneously |
The mode is configured in config/wncms.php:
'models' => [
'post' => [
'class' => \Wncms\Models\Post::class,
'website_mode' => 'multi',
],
'page' => [
'class' => \Wncms\Models\Page::class,
'website_mode' => 'single',
],
],Legacy projects may still define a flat config map:
'model_website_modes' => [
'post' => 'multi',
'page' => 'single',
],WNCMS merges both config styles, then applies system setting override (model_website_modes) from database. Priority is:
- System setting in DB (
model_website_modes) config('wncms.models.*.website_mode')config('wncms.model_website_modes')(legacy)
Core Methods
If your model extends Wncms\Models\BaseModel, HasMultisite is already included by default. Do not re-add use HasMultisite; in the child model.
Get Website Mode
$mode = Post::getWebsiteMode();
// Returns: 'global', 'single', or 'multi'Access Website Relationship
// Get all associated websites (BelongsToMany)
$websites = $post->websites;
// Get first website (convenience attribute)
$website = $post->website;Scope Queries by Website
// Filter posts for current website
$posts = Post::forWebsite()->get();
// Filter posts for specific website
$posts = Post::forWebsite(5)->get();
// Using static helper
$query = Post::query();
Post::applyWebsiteScope($query, $websiteId);
$posts = $query->get();Binding and Unbinding Websites
Bind Websites
// Single website
$post->bindWebsites(1);
// Multiple websites
$post->bindWebsites([1, 2, 3]);Behavior:
- single mode: Only the first website ID is bound
- multi mode: All provided website IDs are synced without detaching
Unbind Websites
// Remove specific websites
$post->unbindWebsites([1, 2]);
// Remove all websites
$post->unbindAllWebsites();Bind All Websites
// Associate with all existing websites
$post->bindAllWebsites();Behavior:
- single mode: Binds only the first website
- multi mode: Binds all websites
Database Structure
The trait uses a polymorphic pivot table:
CREATE TABLE model_has_websites (
model_type VARCHAR(255),
model_id BIGINT,
website_id BIGINT,
PRIMARY KEY (model_type, model_id, website_id)
);Manager Integration
WNCMS managers automatically apply website scoping:
class PostManager extends ModelManager
{
protected function buildListQuery(array $options): mixed
{
$q = $this->query();
// Apply website filter
$this->applyWebsiteId($q, $options['website_id'] ?? null);
return $q;
}
}The applyWebsiteId() method internally uses applyWebsiteScope().
Usage Examples
Create Post for Specific Website
$post = Post::create([
'title' => 'Hello World',
'content' => 'Post content',
]);
// Bind to current website
$post->bindWebsites(wncms()->website()->id());Query Posts for Current Website
$posts = Post::forWebsite()->where('status', 'published')->get();Multi-Website Product
$product = Product::find(1);
// Assign to multiple websites
$product->bindWebsites([1, 2, 3]);
// Check which websites
foreach ($product->websites as $website) {
echo $website->domain;
}Global Content (Not Website-Scoped)
If website_mode is global:
// All queries return all records regardless of website
$banners = Banner::all();
// bindWebsites() has no effect
$banner->bindWebsites([1, 2]); // No-opController Example
class PostController extends BackendController
{
public function index()
{
$currentWebsiteId = wncms()->website()->id();
$posts = Post::forWebsite($currentWebsiteId)
->where('status', 'published')
->paginate(20);
return view('backend.posts.index', compact('posts'));
}
}Advanced: Manual Pivot Access
// Access pivot data
$website = $post->websites()->wherePivot('website_id', 1)->first();
// Attach with timestamp
$post->websites()->attach(1, ['created_at' => now()]);
// Sync specific websites
$post->websites()->sync([1, 2, 3]);
// Sync without detaching
$post->websites()->syncWithoutDetaching([4, 5]);Migration Example
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateModelHasWebsitesTable extends Migration
{
public function up()
{
Schema::create('model_has_websites', function (Blueprint $table) {
$table->string('model_type');
$table->unsignedBigInteger('model_id');
$table->unsignedBigInteger('website_id');
$table->primary(['model_type', 'model_id', 'website_id']);
$table->foreign('website_id')
->references('id')
->on('websites')
->onDelete('cascade');
});
}
}Best Practices
- Configure Mode Early - Set
website_modeinconfig/wncms.phpbefore creating records - Use Scopes - Always use
forWebsite()scope in multi-site scenarios - Bind After Creation - Create model first, then bind websites
- Manager Integration - Let managers handle website filtering automatically
- Check Mode - Use
getWebsiteMode()when behavior depends on configuration
Related Documentation
- Base Model
- ModelManager
- Link Manager - Website scoping example