idoneo / cms-core
Multi-tenant CMS foundation with Teams support, Livewire components and Filament integration for Laravel.
Installs: 46
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/idoneo/cms-core
Requires
- php: ^8.2
- filament/filament: ^4.0
- filament/spatie-laravel-media-library-plugin: ^4.0
- filament/spatie-laravel-tags-plugin: ^4.0
- illuminate/contracts: ^11.0|^12.0
- laravel/jetstream: ^5.0
- laravel/sanctum: ^4.0
- livewire/livewire: ^3.0
- spatie/laravel-medialibrary: ^11.0
- spatie/laravel-package-tools: ^1.16
- spatie/laravel-permission: ^6.0
- spatie/laravel-tags: ^4.0
Requires (Dev)
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.1
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^3.0
- pestphp/pest-plugin-laravel: ^3.0
This package is auto-updated.
Last update: 2025-12-14 21:11:26 UTC
README
Multi-tenant CMS foundation with Teams support, Livewire components and Filament integration for Laravel.
Features
- BelongsToCurrentTeam trait for automatic team scoping
- TeamSwitcher Livewire component with alphabetical sorting
- CmsCorePlugin for Filament panels with user menu
- Spatie Laravel Tags integration for tagging and categorization
- Toggle teams feature via environment variable
Installation
Create New Project
To create a new Laravel project with CMS-Core from scratch:
composer create-project laravel/laravel my-project cd my-project composer require idoneo/cms-core php artisan cms-core:install --fresh --seed npm install && npm run build
This will:
- Create a new Laravel project
- Install CMS-Core package (with Jetstream, Filament, Livewire, and Spatie packages)
- Configure Jetstream with Livewire + Teams
- Install Filament panel (with Spanish locale)
- Register Jetstream routes automatically (
profile.show,teams.show,teams.create, etc.) - Publish Spatie Permission migrations
- Publish Spatie Tags migrations
- Publish Spatie Media Library migrations
- Publish Teams migrations (included in CMS-Core)
- Publish Post model
- Publish CMS-Core config files
- Configure authentication to use Filament login
- Set application locale to Spanish
- Run all migrations
- Create admin user (hola@humano.app / Simplicity!)
- Install and build npm assets
Install in Existing Project
If you already have a Laravel project, install the package:
composer require idoneo/cms-core
php artisan cms-core:install --fresh --seed
npm install && npm run build
Note: The --fresh flag will drop all existing tables. If you want to keep your data, use:
php artisan cms-core:install --seed php artisan migrate
Update Existing Installation
To update CMS-Core resources (migrations, config, views, translations) in an existing project:
composer update idoneo/cms-core php artisan cms-core:update php artisan migrate npm run build
Options:
--migrations: Only publish migrations--force: Force publish even if files exist
Manual Setup
If you need more control:
# Install dependencies composer require idoneo/cms-core # Install Jetstream manually php artisan jetstream:install livewire --teams # Publish CMS-Core assets php artisan vendor:publish --tag="cms-core" # Run migrations php artisan migrate # Build assets npm install && npm run build
Register the plugin in your AdminPanelProvider:
use Idoneo\CmsCore\Filament\CmsCorePlugin; public function panel(Panel $panel): Panel { return $panel ->plugin(CmsCorePlugin::make())
Tags System
This package includes Spatie Laravel Tags for tagging and categorization. See docs/TAGS.md for complete documentation.
Quick example:
use Spatie\Tags\HasTags; use Filament\Forms\Components\SpatieTagsInput; class Post extends Model { use HasTags; } // In Filament Resource SpatieTagsInput::make('tags') ->label('Tags') // For categorization SpatieTagsInput::make('categories') ->type('categories') // ... other configuration }
Default Credentials
After running with --seed flag:
| Field | Value |
|---|---|
hola@humano.app |
|
| Password | Simplicity! |
Configuration
Add to your .env file:
# Show/hide teams UI features (default: false) # Note: Users always have a personal team (Jetstream requirement) # This setting only controls UI visibility (team switcher, create team, team settings) APP_TEAMS=false
When APP_TEAMS=false:
- ✅ Users still have a personal team (required by Jetstream)
- ❌ Team switcher is hidden
- ❌ "Create New Team" option is hidden
- ❌ "Team Settings" option is hidden
When APP_TEAMS=true:
- ✅ All team features visible
- ✅ Users can create multiple teams
- ✅ Team switcher appears in user menu
Brand Logos
CMS-Core automatically configures brand logos for your Filament panel if you place logo files in public/custom/:
Supported files:
logo-light.svg- Logo for light modelogo-dark.svg- Logo for dark mode
Setup:
- Create the
public/custom/directory (if it doesn't exist) - Place your logo files:
public/ └── custom/ ├── logo-light.svg └── logo-dark.svg - The logos will be automatically detected and configured on the next page load
Note: Only SVG files are supported. The plugin automatically detects and configures the logos without any additional configuration needed.
Usage
BelongsToCurrentTeam Trait
Add to models that should be scoped by team:
use Idoneo\CmsCore\Traits\BelongsToCurrentTeam; class Project extends Model { use BelongsToCurrentTeam; }
This automatically:
- Filters queries by
current_team_id - Sets
team_idon create - Provides
forTeam()andwithoutTeamScope()methods
TeamSwitcher Component
Include in your Blade views:
@if(\Idoneo\CmsCore\CmsCore::teamsEnabled()) <livewire:cms-core::team-switcher /> @endif
Filament Plugin
Register in your Panel Provider:
use Idoneo\CmsCore\Filament\CmsCorePlugin; public function panel(Panel $panel): Panel { return $panel ->plugins([ CmsCorePlugin::make(), ]); }
Helper Methods
use Idoneo\CmsCore\CmsCore; // Check if teams are enabled CmsCore::teamsEnabled(); // Get configured models CmsCore::teamModel(); CmsCore::userModel();
Team Roles Configuration
After installation, configure team roles in app/Providers/JetstreamServiceProvider.php:
protected function configurePermissions(): void { Jetstream::defaultApiTokenPermissions(['read']); Jetstream::role('admin', 'Administrator', [ 'create', 'read', 'update', 'delete', ])->description('Administrator users can perform any action.'); Jetstream::role('member', 'Member', [ 'read', 'create', 'update', ])->description('Members have standard access to create and manage content.'); Jetstream::role('viewer', 'Viewer', [ 'read', ])->description('Viewers can only read and view content.'); }
These are suggested roles that work for most use cases. You can customize them:
- Rename roles (e.g., "member" → "employee", "student", "collaborator")
- Add new roles
- Modify permissions per role
Panel Access Control
By default, all authenticated users can access the Filament panel. To restrict access, implement FilamentUser in your User model:
use Filament\Models\Contracts\FilamentUser; use Filament\Panel; class User extends Authenticatable implements FilamentUser { public function canAccessPanel(Panel $panel): bool { // Allow all authenticated users (default behavior) return true; // Or restrict by email domain // return str_ends_with($this->email, '@yourdomain.com'); // Or use roles with Spatie Permission // return $this->hasRole('admin'); } }
API Documentation
CMS-Core provides a RESTful API for accessing posts with authentication via Sanctum tokens.
Authentication
All API endpoints require authentication using a Bearer token. You can generate a token in two ways:
Option 1: Generate Token via Artisan Command (Recommended)
Generate a token and add it to your .env file:
# Generate token for admin user (default) php artisan cms-core:api-token # Or specify a user email php artisan cms-core:api-token --email=hola@humano.app --name="API Token"
Output example:
API Token generated successfully!
Token Name: API Token
User: hola@humano.app
Add this to your .env file:
APP_TOKEN=1|abc123def456ghi789jkl012mno345pqr678stu901vwx234yz
⚠️ Save this token now! You won't be able to see it again.
Copy the token and add it to your .env file:
APP_TOKEN=1|abc123def456ghi789jkl012mno345pqr678stu901vwx234yz
Then use it in your curl requests:
# Replace YOUR_TOKEN with the token from .env curl -X GET "http://localhost:8000/api/posts" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Accept: application/json"
Option 2: Generate Token via User Profile
Generate a token in your user profile settings at /user/api-tokens or via Jetstream's API token management interface.
Headers:
Authorization: Bearer {your-token}
Content-Type: application/json
Accept: application/json
Troubleshooting
Teams Feature Not Showing
If you have APP_TEAMS=true in your .env but the teams options don't appear in the user menu, run the diagnostic command:
php artisan cms-core:diagnose
This will check:
- ✓ Config file exists
- ✓ Teams configuration (ENV, config, CmsCore helper)
- ✓ Jetstream routes registration
- ✓ Current user and team setup
Common solutions:
# 1. Clear config cache php artisan config:clear # 2. Verify .env has APP_TEAMS=true (no spaces) echo "APP_TEAMS=true" >> .env # 3. Republish config php artisan vendor:publish --tag=cms-core-config --force # 4. Clear cache again php artisan config:clear # 5. Restart server (if using php artisan serve)
Accept: application/json
**Note:** If you set `APP_TOKEN` in your `.env`, you can use that token directly. The system will automatically authenticate the user associated with that token.
#### Quick Start Example
1. **Generate a token:**
```bash
php artisan api:token --email=hola@humano.app
- Copy the output token to your
.envfile:
APP_TOKEN=1|abc123def456ghi789...
- Test the API with curl:
# List all published posts curl -X GET "http://localhost:8000/api/posts" \ -H "Authorization: Bearer 1|abc123def456ghi789..." \ -H "Accept: application/json" # Or use the token from .env (if you have it exported) curl -X GET "http://localhost:8000/api/posts" \ -H "Authorization: Bearer $APP_TOKEN" \ -H "Accept: application/json"
Endpoints
List Posts
Get a paginated list of posts with optional filters.
GET /api/posts
Query Parameters:
status(optional): Filter by status (draft,published,archived). Default:publishedcategory(optional): Filter by category nametag(optional): Filter by tag namesearch(optional): Search in title, excerpt, and contentper_page(optional): Number of items per page (1-100, default: 15)page(optional): Page number (default: 1)
Example Request:
# Using token from .env (APP_TOKEN) curl -X GET "http://localhost:8000/api/posts?status=published&category=Tutorials&per_page=10" \ -H "Authorization: Bearer $(php artisan tinker --execute='echo env(\"APP_TOKEN\");')" \ -H "Accept: application/json" # Or using a token directly curl -X GET "http://localhost:8000/api/posts?status=published&category=Tutorials&per_page=10" \ -H "Authorization: Bearer 1|your-token-here" \ -H "Accept: application/json" # Simple example (replace YOUR_TOKEN with your actual token) curl -X GET "http://localhost:8000/api/posts" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Accept: application/json"
Example Response:
{
"data": [
{
"id": 1,
"title": "My First Post",
"slug": "my-first-post",
"excerpt": "This is an excerpt...",
"content": "Full content here...",
"status": "published",
"published_at": "2024-01-15T10:00:00Z",
"created_at": "2024-01-15T09:00:00Z",
"updated_at": "2024-01-15T09:30:00Z",
"author": {
"id": 1,
"name": "John Doe",
"email": "john@example.com"
},
"featured_image": {
"url": "https://...",
"thumb": "https://...",
"web": "https://..."
},
"gallery": [
{
"url": "https://...",
"thumb": "https://...",
"web": "https://..."
}
],
"categories": [
{
"id": 1,
"name": "Tutorials",
"slug": "tutorials"
}
],
"tags": [
{
"id": 2,
"name": "Laravel",
"slug": "laravel"
}
]
}
],
"links": {
"first": "https://...",
"last": "https://...",
"prev": null,
"next": "https://..."
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 5,
"per_page": 10,
"to": 10,
"total": 50
}
}
Get Post by Slug
Get a single post by its slug.
GET /api/posts/{slug}
Example Request:
# Using token from .env (APP_TOKEN) curl -X GET "http://localhost:8000/api/posts/my-first-post" \ -H "Authorization: Bearer $(php artisan tinker --execute='echo env(\"APP_TOKEN\");')" \ -H "Accept: application/json" # Or using a token directly curl -X GET "http://localhost:8000/api/posts/my-first-post" \ -H "Authorization: Bearer 1|your-token-here" \ -H "Accept: application/json" # Simple example (replace YOUR_TOKEN with your actual token) curl -X GET "http://localhost:8000/api/posts/my-first-post" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Accept: application/json"
Example Response:
{
"data": {
"id": 1,
"title": "My First Post",
"slug": "my-first-post",
"excerpt": "This is an excerpt...",
"content": "Full content here...",
"status": "published",
"published_at": "2024-01-15T10:00:00Z",
"created_at": "2024-01-15T09:00:00Z",
"updated_at": "2024-01-15T09:30:00Z",
"author": {
"id": 1,
"name": "John Doe",
"email": "john@example.com"
},
"featured_image": {
"url": "https://...",
"thumb": "https://...",
"web": "https://..."
},
"gallery": [],
"categories": [],
"tags": []
}
}
Team Scoping
If teams are enabled (APP_TEAMS=true), the API automatically filters posts by the authenticated user's current team. Users can only access posts from their current team.
Error Responses
401 Unauthorized:
{
"message": "Unauthenticated."
}
404 Not Found:
{
"message": "No query results for model [App\\Models\\Post] {slug}"
}
422 Validation Error:
{
"message": "The given data was invalid.",
"errors": {
"status": ["The selected status is invalid."]
}
}
Complete cURL Examples
Here are ready-to-use curl examples (replace YOUR_TOKEN with your actual token):
List all published posts:
curl -X GET "http://localhost:8000/api/posts" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Accept: application/json"
List posts with filters:
curl -X GET "http://localhost:8000/api/posts?status=published&category=Tutorials&search=laravel&per_page=5" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Accept: application/json"
Get a specific post by slug:
curl -X GET "http://localhost:8000/api/posts/my-first-post" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Accept: application/json"
Filter by tag:
curl -X GET "http://localhost:8000/api/posts?tag=laravel" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Accept: application/json"
Pretty print JSON response (using jq):
curl -X GET "http://localhost:8000/api/posts" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Accept: application/json" | jq
Requirements
- PHP ^8.2
- Laravel ^11.0 | ^12.0
- Livewire ^3.0
- Laravel Jetstream with Teams (recommended)
Security Vulnerabilities
If you discover a security vulnerability, please send an e-mail to Diego Mascarenhas Goytía via diego.mascarenhas@icloud.com. All security vulnerabilities will be promptly addressed.
License
Licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).
Additional Terms
By deploying this software, you agree to notify the original author at diego.mascarenhas@icloud.com or by visiting linkedin.com/in/diego-mascarenhas. Any modifications or enhancements must be shared with the original author.