T
Tenanto
Documentation / README

README

Tenanto

Production-ready Laravel Multi-Tenant SaaS Boilerplate

A comprehensive Laravel 13 boilerplate for building multi-tenant SaaS applications with built-in billing, team management, and admin panels.

📖 New to Tenanto? Start with the Buyer User Guide for a step-by-step installation walkthrough, panel tours with screenshots, and a troubleshooting playbook.


Features

Multi-Tenancy

Authentication & Authorization

Billing & Subscriptions

Admin Panels (FilamentPHP 5)

API

Marketplace & Distribution

Internationalization (i18n)

Developer Experience

Quality & CI


Requirements


Quick Start

1. Clone the Repository

# Replace with your repository URL
git clone https://your-git-host.com/your-org/tenanto.git
cd tenanto

2. Start Docker Environment

docker compose up -d

3. Install Dependencies

docker compose exec app composer install

Frontend assets: No npm install on the host. The vite Docker service runs npm ci automatically on first start and serves assets with hot module replacement at http://localhost:5273. Blade/CSS/JS edits reload instantly. A production build (npm run build) is only required when deploying without the Vite service.

4. Configure Environment

cp .env.example .env
docker compose exec app php artisan key:generate

Edit .env with your settings:

5. Run Migrations & Seed

docker compose exec app php artisan migrate --seed

6. Configure Local DNS

Add to your hosts file:

127.0.0.1 tenanto.local
127.0.0.1 admin.tenanto.local
127.0.0.1 demo.tenanto.local

7. Access the Application

URL Redirects To Credentials
http://tenanto.local Landing page -
http://admin.tenanto.local → /admin (System Admin) [email protected] / ChangeMe-DemoPass!
http://demo.tenanto.local → /app (Tenant App) [email protected] / ChangeMe-DemoPass!
http://demo.tenanto.local/api/v1 API Bearer token
http://admin.tenanto.local/horizon Queue Dashboard System admin only
http://localhost:8025 Email Testing No auth required

Default credentials use the DEMO_DEFAULT_PASSWORD value from .env.example (ChangeMe-DemoPass!). Edit .env and re-run php artisan migrate --seed to seed with a different password, or run php artisan demo:reset-passwords after editing .env to rotate existing accounts in place. Always change the default before going public.

URL Structure:


Docker Services

The docker compose up -d command starts all 8 services automatically:

Service Container Port Description
app tenanto_app - PHP-FPM 8.4 application server
nginx tenanto_nginx 80, 443 Web server with SSL support
db tenanto_db 5432 PostgreSQL 16 database
redis tenanto_redis 6379 Cache, sessions, and queue broker
queue tenanto_queue - Laravel Horizon (background jobs)
scheduler tenanto_scheduler - Laravel task scheduler (cron jobs)
mailhog tenanto_mailhog 1025, 8025 SMTP server + Email testing UI
vite tenanto_vite 5273 Node 24 Vite dev server with hot module replacement (dev only)

Service Details

Useful Commands

# View all container logs
docker compose logs -f

# View specific service logs
docker compose logs -f queue

# Restart all services
docker compose restart

# Rebuild containers (after Dockerfile changes)
docker compose up -d --build

# Stop all services
docker compose down

Project Structure

app/
├── Domain/                 # Business logic (DDD-lite)
│   ├── Authorization/      # Roles & permissions
│   ├── Billing/            # Stripe/subscription logic
│   ├── Demo/               # Demo tenant automation
│   ├── ExampleApp/         # Demo module (Projects/Tasks)
│   ├── Licensing/          # License key management
│   ├── Onboarding/         # Customer onboarding wizard
│   ├── Support/            # Support ticket system
│   ├── Tenancy/            # Multi-tenant core
│   └── Updates/            # Version update notifications
├── Filament/
│   ├── Admin/              # System admin panel
│   └── Tenant/             # Per-tenant admin panel
└── Http/
    ├── Controllers/Api/    # API controllers
    └── Middleware/         # Tenant middleware

docs/
├── api.md                  # API documentation
├── authorization.md        # Roles & permissions guide
├── backup-restore.md       # Backup & recovery guide
├── buyer-guide.md          # Marketplace buyer guide (FAQ, licensing)
├── deployment.md           # Deployment guide
├── e2e-testing.md          # Playwright test suite documentation
├── example-module.md       # Module development guide
├── filament.md             # FilamentPHP customization
├── i18n.md                 # Internationalization guide
├── multi-tenancy.md        # Multi-tenancy architecture
├── performance.md          # Performance optimization
├── security.md             # Security checklist
├── troubleshooting.md      # Common issues and solutions
└── user-guide/             # Buyer-focused walkthroughs with screenshots
    ├── installation.md
    ├── admin-panel.md
    ├── tenant-panel.md
    ├── billing.md
    ├── api-quickstart.md
    ├── customization.md
    ├── support-policy.md
    ├── troubleshooting.md
    └── images/             # Screenshots of admin + tenant panels

tests/
├── Feature/                # Integration tests
└── Unit/                   # Unit tests

e2e/                        # Playwright E2E tests
├── admin/                  # Admin panel tests
├── auth/                   # Authentication tests
├── api/                    # API tests
├── errors/                 # Error page tests
├── helpers/                # Test utilities (auth, api)
├── isolation/              # Tenant isolation tests
├── navigation/             # Navigation tests
├── onboarding/             # Onboarding wizard tests
└── tenant/                 # Tenant panel tests

Getting Started for Developers

This section guides you through customizing Tenanto for your own SaaS product.

Step 1: Remove the Example Module

The ExampleApp module (Projects/Tasks) is included as a reference implementation. To remove it:

# Remove domain files
rm -rf app/Domain/ExampleApp

# Remove Filament resources
rm -rf app/Filament/Tenant/Resources/ProjectResource
rm -rf app/Filament/Tenant/Resources/TaskResource
rm -rf app/Filament/Tenant/Resources/ProjectResource.php
rm -rf app/Filament/Tenant/Resources/TaskResource.php
rm -rf app/Filament/Tenant/Widgets/ProjectStatsWidget.php
rm -rf app/Filament/Tenant/Widgets/TaskStatsWidget.php

# Remove API controllers
rm app/Http/Controllers/Api/V1/ProjectController.php
rm app/Http/Controllers/Api/V1/TaskController.php

# Remove migrations
rm database/migrations/*_create_projects_table.php
rm database/migrations/*_create_tasks_table.php

# Remove factories, seeders, and tests
rm database/factories/ProjectFactory.php
rm database/factories/TaskFactory.php
rm database/seeders/ExampleAppSeeder.php
rm -rf tests/Feature/Api/Project*
rm -rf tests/Feature/Api/Task*

# Clean up API routes
# Edit routes/api.php and remove Project/Task routes

Step 2: Create Your Own Module

Follow this pattern to add tenant-scoped models:

// 1. Create Model with BelongsToTenant trait
namespace App\Domain\YourModule\Models;

use App\Domain\Tenancy\Traits\BelongsToTenant;
use Illuminate\Database\Eloquent\Model;

class YourModel extends Model
{
    use BelongsToTenant;

    protected $fillable = ['tenant_id', 'name', ...];
}

// 2. Create Migration with tenant_id
Schema::create('your_models', function (Blueprint $table) {
    $table->id();
    $table->foreignId('tenant_id')->constrained()->cascadeOnDelete();
    $table->string('name');
    $table->timestamps();

    // Always add composite index for tenant queries
    $table->index(['tenant_id', 'created_at']);
});

// 3. Create Policy for authorization
namespace App\Policies;

use App\Domain\YourModule\Models\YourModel;
use App\Models\User;

class YourModelPolicy
{
    public function viewAny(User $user): bool
    {
        return $user->can('view your-models');
    }

    public function view(User $user, YourModel $model): bool
    {
        // Defense-in-depth: verify tenant ownership
        return $user->tenant_id === $model->tenant_id;
    }
}

// 4. Create Filament Resource
// See docs/example-module.md for complete guide

Step 3: Configure Your Plans

Edit config/billing.php to define your subscription tiers:

'plans' => [
    'starter' => [
        'name' => 'Starter',
        'stripe_price_id' => env('STRIPE_PRICE_STARTER'),
        'price_monthly' => (int) env('BILLING_PRICE_STARTER_MONTHLY', 29),
        'features' => [
            'users' => 3,
            'your_feature' => 100,
            'premium_feature' => false,
        ],
    ],
    // Add more plans...
],

Step 4: Customize Branding

Step 5: Set Up Stripe

  1. Create products/prices in Stripe Dashboard
  2. Copy Price IDs to .env:
    STRIPE_PRICE_STARTER=price_xxx
    STRIPE_PRICE_PRO=price_yyy
    BILLING_PRICE_STARTER_MONTHLY=29
    BILLING_PRICE_PRO_MONTHLY=79
    
  3. Set up webhooks pointing to /stripe/webhook
  4. Test with Stripe CLI: stripe listen --forward-to localhost/stripe/webhook

Key Concepts

Concept Location Purpose
BelongsToTenant app/Domain/Tenancy/Traits/ Auto-scopes models to current tenant
TenantScope app/Domain/Tenancy/Scopes/ Filters all queries by tenant_id
TenantMiddleware app/Http/Middleware/ Resolves tenant from subdomain/domain
PlanFeatureService app/Domain/Billing/Services/ Checks feature limits per plan

For detailed documentation, see docs/example-module.md.


Development Commands

The most common workflows, all run through docker compose exec app:

# Start development environment
docker compose up -d

# Run all PHP tests
docker compose exec app php artisan test

# Static analysis (PHPStan level 8)
docker compose exec app ./vendor/bin/phpstan analyse

# Code style fixing (Laravel Pint)
docker compose exec app ./vendor/bin/pint

# Fresh database with seeds
docker compose exec app php artisan migrate:fresh --seed

# Open a shell in the app container
docker compose exec app bash

API Overview

Base URL: /api/v1/

Authentication

POST /api/v1/auth/login
POST /api/v1/auth/logout
GET  /api/v1/auth/me

Resources

# Projects
GET    /api/v1/projects
POST   /api/v1/projects
GET    /api/v1/projects/{id}
PUT    /api/v1/projects/{id}
DELETE /api/v1/projects/{id}
POST   /api/v1/projects/{id}/archive
POST   /api/v1/projects/{id}/unarchive

# Tasks
GET    /api/v1/projects/{id}/tasks
POST   /api/v1/projects/{id}/tasks
GET    /api/v1/tasks/{id}
PUT    /api/v1/tasks/{id}
DELETE /api/v1/tasks/{id}
POST   /api/v1/tasks/{id}/complete

See docs/api.md for complete API documentation.


Testing

PHP Tests (PHPUnit)

# Run all PHP tests
docker compose exec app php artisan test

# Run with coverage
docker compose exec app php artisan test --coverage

# Run specific group
docker compose exec app php artisan test --group=tenant-isolation

Test Groups:

E2E Tests (Playwright)

# Install Playwright
npm install
npx playwright install chromium

# Run all E2E tests
npx playwright test --project=chromium

# Run specific test file
npx playwright test e2e/auth/login.spec.ts

# Run with UI mode
npx playwright test --ui

# View HTML report
npx playwright show-report

E2E Test Coverage:

Category Description
Auth Login, logout, register, password reset, email verification
Admin Panel Tenants, users, licenses, dashboard
Tenant Panel Projects, tasks, teams, users, profile, billing
API Authentication, CRUD operations
Isolation Tenant isolation, permission enforcement
Navigation Route testing, menu visibility
Onboarding Wizard flow
Errors Error pages (403, 404, 500)

Configuration: playwright.config.ts


Configuration

Environment Variables

Key variables in .env:

# Tenancy
TENANCY_MODE=subdomain
TENANCY_CENTRAL_DOMAINS=tenanto.local,admin.tenanto.local

# Billing
STRIPE_KEY=pk_test_xxx
STRIPE_SECRET=sk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx

# Plans
STRIPE_PRICE_BASIC=price_xxx
STRIPE_PRICE_PRO=price_xxx
STRIPE_PRICE_ENTERPRISE=price_xxx
BILLING_PRICE_BASIC_MONTHLY=29
BILLING_PRICE_PRO_MONTHLY=79
BILLING_PRICE_ENTERPRISE_MONTHLY=199

# Legal / Compliance (drives the /terms and /privacy pages)
LEGAL_OPERATOR_NAME="Acme SaaS s.r.o."
LEGAL_OPERATOR_ADDRESS="Main Street 1, 110 00 Prague, Czech Republic"
LEGAL_JURISDICTION="Czech Republic"
LEGAL_GOVERNING_LAW="Czech Republic"
LEGAL_VENUE="Czech Republic"
[email protected]
[email protected]
LEGAL_SUPERVISORY_AUTHORITY="the Office for Personal Data Protection (ÚOOÚ)"

See .env.example for all options with documentation.

The bundled Terms of Service and Privacy Policy pages show a "Template — customize before publishing" banner until the LEGAL_* variables are filled in, so you cannot accidentally expose unconfigured legal text to real users. Have the final rendered pages reviewed by qualified counsel in your jurisdiction before go-live — the templates are a solid starting point, not legal advice.

Subscription Plans

Configure in config/billing.php:

'plans' => [
    'basic' => [
        'name' => 'Basic',
        'stripe_price_id' => env('STRIPE_PRICE_BASIC'),
        'price_monthly' => (int) env('BILLING_PRICE_BASIC_MONTHLY', 29),
        'features' => [
            'users' => 5,
            'teams' => 1,
            'projects' => 10,
            'storage_gb' => 5,
            'api_requests_per_day' => 1000,
            'custom_domain' => false,
            'priority_support' => false,
        ],
    ],
    // ...
],

Deployment

See docs/deployment.md for production deployment instructions.

Quick Checklist


Security

Built-in Protections

See docs/security.md for security checklist.


Documentation

Document Description
User Guide Buyer-focused walkthroughs (installation, panels, billing, customization)
Marketplace Buyer Guide Marketplace-focused setup, FAQ, licensing, refund guidance
Multi-Tenancy Architecture Tenant isolation and scoping
Authorization & Permissions RBAC and policies
FilamentPHP Customization Admin panel configuration
API Documentation Complete API reference
Deployment Guide Production deployment
Security Checklist Security best practices
Example Module Building custom modules
Performance Guide Optimization strategies
i18n Guide Internationalization setup
Backup & Restore Backup strategies and recovery
E2E Testing Guide Playwright test suite documentation
Troubleshooting Common issues and solutions
UPGRADING Version upgrade instructions
CHANGELOG Version history and changes
Third-Party Licenses Dependency licenses

Tech Stack

Component Technology
Framework Laravel 13
PHP Version 8.3+ (8.4 recommended)
Admin Panel FilamentPHP 5
Frontend Livewire 4 + Tailwind CSS 4
Database PostgreSQL 16
Cache/Queue Redis 7
API Auth Laravel Sanctum
Billing Laravel Cashier (Stripe)
Permissions spatie/laravel-permission
Testing PHPUnit (600+ backend test methods)
E2E Testing Playwright (480+ scenarios across chromium, firefox, webkit + mobile viewports)
Static Analysis PHPStan Level 8
Code Style Laravel Pint

Support

Support Contact: Configure SUPPORT_EMAIL in .env before publishing buyer-facing support instructions.

Response Time:

What's Included:

What's Not Included:

Support is provided according to the support terms and marketplace policy you publish with the product listing.

See docs/buyer-guide.md for marketplace-focused buyer information. See docs/troubleshooting.md for common issues and solutions.


License

Tenanto is commercial software. See LICENSE for details.

Third-party dependencies are licensed under MIT, BSD-3-Clause, and Apache-2.0 licenses. See THIRD_PARTY_LICENSES.md for details.


Credits

Built with: