diff --git a/docs/architecture.md b/docs/architecture.md
new file mode 100644
index 0000000..a1e6547
--- /dev/null
+++ b/docs/architecture.md
@@ -0,0 +1,361 @@
+---
+title: Architecture
+description: Technical architecture of core-template - the starter template for Core PHP Framework applications
+updated: 2026-01-29
+---
+
+# Architecture
+
+core-template is the official starter template for building applications with the Core PHP Framework. It provides a pre-configured Laravel 12 application with the modular monolith architecture, Livewire 3, and Flux UI integration.
+
+## Overview
+
+```
+core-template/
+├── app/
+│ ├── Http/Controllers/ # Traditional controllers (rarely used)
+│ ├── Models/ # Application-wide Eloquent models
+│ ├── Mod/ # Feature modules (your code goes here)
+│ └── Providers/ # Service providers
+├── bootstrap/
+│ ├── app.php # Application bootstrap with Core providers
+│ └── providers.php # Additional providers
+├── config/
+│ └── core.php # Core framework configuration
+├── public/
+│ └── index.php # Web entry point
+├── resources/
+│ ├── css/app.css # Tailwind entry point
+│ ├── js/app.js # JavaScript entry point
+│ └── views/ # Global Blade views
+├── routes/
+│ ├── web.php # Fallback web routes
+│ ├── api.php # Fallback API routes
+│ └── console.php # Console command routes
+└── tests/
+ ├── Feature/ # HTTP/Livewire feature tests
+ └── Unit/ # Unit tests
+```
+
+## Bootstrap Process
+
+The application bootstrap (`bootstrap/app.php`) registers Core PHP Framework providers:
+
+```php
+return Application::configure(basePath: dirname(__DIR__))
+ ->withProviders([
+ \Core\LifecycleEventProvider::class, // Event system
+ \Core\Website\Boot::class, // Website components
+ \Core\Front\Boot::class, // Frontend (Livewire, Flux)
+ \Core\Mod\Boot::class, // Module discovery
+ ])
+ ->withRouting(...)
+ ->withMiddleware(function (Middleware $middleware) {
+ \Core\Front\Boot::middleware($middleware);
+ })
+ ->create();
+```
+
+### Provider Loading Order
+
+1. **LifecycleEventProvider** - Sets up the event-driven architecture
+2. **Website\Boot** - Registers website-level functionality
+3. **Front\Boot** - Configures Livewire and frontend middleware
+4. **Mod\Boot** - Discovers and loads modules from configured paths
+
+## Module System
+
+Modules are self-contained feature packages that register via lifecycle events. This is the core architectural pattern of the framework.
+
+### Module Paths
+
+Configured in `config/core.php`:
+
+```php
+'module_paths' => [
+ app_path('Core'), // Local framework overrides (EUPL-1.2)
+ app_path('Mod'), // Your application modules
+ app_path('Website'), // Website-specific code
+],
+```
+
+### Module Structure
+
+Each module lives in `app/Mod/{ModuleName}/` with a `Boot.php` entry point:
+
+```
+app/Mod/Blog/
+├── Boot.php # Event listeners (required)
+├── Models/
+│ └── Post.php # Eloquent models
+├── Routes/
+│ ├── web.php # Web routes
+│ └── api.php # API routes
+├── Views/
+│ └── posts/
+│ └── index.blade.php
+├── Livewire/
+│ └── PostListPage.php # Livewire components
+├── Migrations/
+│ └── 2025_01_01_create_posts_table.php
+└── Tests/
+ └── PostTest.php
+```
+
+### Boot.php Pattern
+
+The `Boot.php` class declares which lifecycle events it responds to:
+
+```php
+ 'onWebRoutes',
+ ApiRoutesRegistering::class => 'onApiRoutes',
+ AdminPanelBooting::class => ['onAdminPanel', 10], // With priority
+ ConsoleBooting::class => 'onConsole',
+ ];
+
+ public function onWebRoutes(WebRoutesRegistering $event): void
+ {
+ // Register routes
+ $event->routes(fn() => require __DIR__.'/Routes/web.php');
+
+ // Register view namespace (accessed as 'blog::view.name')
+ $event->views('blog', __DIR__.'/Views');
+ }
+
+ public function onApiRoutes(ApiRoutesRegistering $event): void
+ {
+ $event->routes(fn() => require __DIR__.'/Routes/api.php');
+ }
+
+ public function onAdminPanel(AdminPanelBooting $event): void
+ {
+ // Register admin navigation
+ $event->navigation('Blog', 'blog.admin.index', 'newspaper');
+
+ // Register admin resources
+ $event->resource('posts', PostResource::class);
+ }
+
+ public function onConsole(ConsoleBooting $event): void
+ {
+ // Register artisan commands
+ $event->commands([
+ ImportPostsCommand::class,
+ ]);
+ }
+}
+```
+
+### Lifecycle Events
+
+| Event | When Fired | Common Uses |
+|-------|------------|-------------|
+| `WebRoutesRegistering` | Web routes loading | Public routes, views |
+| `ApiRoutesRegistering` | API routes loading | REST endpoints |
+| `AdminPanelBooting` | Admin panel setup | Navigation, resources |
+| `ClientRoutesRegistering` | Authenticated SaaS routes | Dashboard, settings |
+| `ConsoleBooting` | Artisan bootstrapping | Commands, schedules |
+| `McpToolsRegistering` | MCP server setup | AI agent tools |
+
+### Lazy Loading
+
+Modules are discovered at boot time, but their `Boot` classes are only instantiated when the events they listen to are fired. This means:
+
+- Console commands don't load web routes
+- API requests don't load admin panel code
+- Unused modules have minimal overhead
+
+## Dependency Packages
+
+The template depends on four Core PHP Framework packages:
+
+| Package | Namespace | Purpose |
+|---------|-----------|---------|
+| `host-uk/core` | `Core\` | Foundation: events, modules, lifecycle |
+| `host-uk/core-admin` | `Core\Admin\` | Admin panel, Livewire modals, Flux UI |
+| `host-uk/core-api` | `Core\Api\` | REST API, scopes, rate limiting, webhooks |
+| `host-uk/core-mcp` | `Core\Mcp\` | Model Context Protocol for AI agents |
+
+These are loaded as Composer dependencies and provide the framework infrastructure.
+
+## Frontend Stack
+
+### Livewire 3
+
+Livewire components live within modules:
+
+```php
+// app/Mod/Blog/Livewire/PostListPage.php
+ Post::latest()->paginate(10),
+ ]);
+ }
+}
+```
+
+### Flux UI
+
+Flux Pro components are the standard UI library. Example usage:
+
+```blade
+
+ Edit Post
+
+
+ Save
+
+```
+
+### Asset Pipeline
+
+Vite handles asset compilation:
+
+- `resources/css/app.css` - Tailwind CSS entry point
+- `resources/js/app.js` - JavaScript entry point
+- Module assets are not automatically included; import them in the main files or use `@vite` directive
+
+## Configuration
+
+### Core Framework (`config/core.php`)
+
+```php
+return [
+ // Paths to scan for modules
+ 'module_paths' => [
+ app_path('Core'),
+ app_path('Mod'),
+ app_path('Website'),
+ ],
+
+ // Service configuration
+ 'services' => [
+ 'cache_discovery' => env('CORE_CACHE_DISCOVERY', true),
+ ],
+
+ // CDN configuration
+ 'cdn' => [
+ 'enabled' => env('CDN_ENABLED', false),
+ 'driver' => env('CDN_DRIVER', 'bunny'),
+ ],
+];
+```
+
+### Environment Variables
+
+Key Core-specific environment variables:
+
+| Variable | Default | Description |
+|----------|---------|-------------|
+| `CORE_CACHE_DISCOVERY` | `true` | Cache module discovery for performance |
+| `CDN_ENABLED` | `false` | Enable CDN for static assets |
+| `CDN_DRIVER` | `bunny` | CDN provider (bunny, cloudflare, etc.) |
+| `FLUX_LICENSE_KEY` | - | Flux Pro license key (optional) |
+
+## Testing
+
+Tests use Pest PHP and follow Laravel conventions:
+
+```php
+// tests/Feature/BlogTest.php
+count(3)->create();
+
+ $this->get('/blog')
+ ->assertOk()
+ ->assertSee($posts->first()->title);
+});
+
+it('requires authentication to create posts', function () {
+ $this->post('/blog', ['title' => 'Test'])
+ ->assertRedirect('/login');
+});
+```
+
+### Test Organisation
+
+- **Feature tests** - HTTP requests, Livewire components, integration tests
+- **Unit tests** - Services, utilities, isolated logic
+- **Module tests** - Can live within the module directory (`app/Mod/Blog/Tests/`)
+
+## Routing
+
+### Route Registration
+
+Routes are registered via module events, not the traditional `routes/` directory:
+
+```php
+// app/Mod/Blog/Routes/web.php
+name('blog.')->group(function () {
+ Route::get('/', PostListPage::class)->name('index');
+ Route::get('/{post:slug}', PostShowPage::class)->name('show');
+});
+```
+
+The `routes/web.php` and `routes/api.php` files are fallbacks for routes that don't belong to any module.
+
+### View Namespacing
+
+Module views are namespaced:
+
+```blade
+{{-- Accessing blog module views --}}
+@include('blog::partials.header')
+
+{{-- In a Livewire component --}}
+return view('blog::posts.index', [...]);
+```
+
+## Namespace Conventions
+
+| Path | Namespace | License |
+|------|-----------|---------|
+| `app/Core/` | `Core\` (local) | EUPL-1.2 |
+| `app/Mod/` | `App\Mod\` | Your choice |
+| `app/Website/` | `App\Website\` | Your choice |
+| `vendor/host-uk/core/` | `Core\` | EUPL-1.2 |
+
+The `app/Core/` directory is for local overrides of framework classes. Any class you place here will take precedence over the vendor package.
diff --git a/docs/getting-started.md b/docs/getting-started.md
new file mode 100644
index 0000000..8fab065
--- /dev/null
+++ b/docs/getting-started.md
@@ -0,0 +1,354 @@
+---
+title: Getting Started
+description: Quick start guide for creating a new Core PHP Framework application
+updated: 2026-01-29
+---
+
+# Getting Started
+
+This guide walks you through creating your first application with Core PHP Framework using the core-template.
+
+## Prerequisites
+
+- PHP 8.2 or higher
+- Composer 2.x
+- Node.js 18+ and npm
+- SQLite (default) or MySQL/PostgreSQL
+
+## Installation
+
+### 1. Clone the Template
+
+```bash
+git clone https://github.com/host-uk/core-template.git my-project
+cd my-project
+```
+
+Or use Composer create-project (once published):
+
+```bash
+composer create-project host-uk/core-template my-project
+```
+
+### 2. Install Dependencies
+
+```bash
+# PHP dependencies
+composer install
+
+# JavaScript dependencies
+npm install
+```
+
+### 3. Configure Environment
+
+```bash
+# Copy environment file
+cp .env.example .env
+
+# Generate application key
+php artisan key:generate
+
+# Create SQLite database
+touch database/database.sqlite
+
+# Run migrations
+php artisan migrate
+```
+
+### 4. Start Development Server
+
+```bash
+# In one terminal - PHP server
+php artisan serve
+
+# In another terminal - Vite dev server
+npm run dev
+```
+
+Visit http://localhost:8000 to see your application.
+
+## Creating Your First Module
+
+The Core PHP Framework uses a modular architecture. Features are organised as self-contained modules.
+
+### Using the Artisan Command
+
+```bash
+# Create a full-featured module
+php artisan make:mod Blog --all
+
+# Or select specific features
+php artisan make:mod Blog --web --api
+```
+
+### Manual Creation
+
+1. Create the module directory:
+
+```bash
+mkdir -p app/Mod/Blog/{Models,Routes,Views,Livewire,Migrations,Tests}
+```
+
+2. Create `app/Mod/Blog/Boot.php`:
+
+```php
+ 'onWebRoutes',
+ ];
+
+ public function onWebRoutes(WebRoutesRegistering $event): void
+ {
+ $event->routes(fn() => require __DIR__.'/Routes/web.php');
+ $event->views('blog', __DIR__.'/Views');
+ }
+}
+```
+
+3. Create `app/Mod/Blog/Routes/web.php`:
+
+```php
+name('blog.index');
+```
+
+4. Create `app/Mod/Blog/Views/index.blade.php`:
+
+```blade
+
+
+
+
+ Blog
+
+
+ Welcome to the Blog
+
+
+```
+
+Visit http://localhost:8000/blog to see your module in action.
+
+## Adding a Model
+
+Create `app/Mod/Blog/Models/Post.php`:
+
+```php
+ 'datetime',
+ ];
+ }
+}
+```
+
+Create a migration in `app/Mod/Blog/Migrations/`:
+
+```php
+id();
+ $table->string('title');
+ $table->string('slug')->unique();
+ $table->text('content');
+ $table->timestamp('published_at')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::dropIfExists('posts');
+ }
+};
+```
+
+Run the migration:
+
+```bash
+php artisan migrate
+```
+
+## Adding a Livewire Component
+
+Create `app/Mod/Blog/Livewire/PostListPage.php`:
+
+```php
+ Post::latest()->paginate(10),
+ ]);
+ }
+}
+```
+
+Update `app/Mod/Blog/Routes/web.php`:
+
+```php
+name('blog.index');
+```
+
+Create `app/Mod/Blog/Views/posts/index.blade.php`:
+
+```blade
+
+
Blog Posts
+
+ @forelse ($posts as $post)
+
+ {{ $post->title }}
+ {{ Str::limit($post->content, 200) }}
+
+ @empty
+
No posts yet.
+ @endforelse
+
+ {{ $posts->links() }}
+
+```
+
+## Writing Tests
+
+Create `app/Mod/Blog/Tests/PostTest.php`:
+
+```php
+get('/blog')
+ ->assertOk()
+ ->assertSee('Blog Posts');
+});
+
+it('shows posts on the index page', function () {
+ $post = Post::create([
+ 'title' => 'Test Post',
+ 'slug' => 'test-post',
+ 'content' => 'This is a test post.',
+ ]);
+
+ $this->get('/blog')
+ ->assertOk()
+ ->assertSee('Test Post');
+});
+```
+
+Run tests:
+
+```bash
+vendor/bin/pest
+```
+
+## Code Formatting
+
+Before committing, run Laravel Pint:
+
+```bash
+# Format changed files only
+vendor/bin/pint --dirty
+
+# Format all files
+vendor/bin/pint
+```
+
+## Next Steps
+
+- Read the [Architecture documentation](architecture.md) to understand the module system
+- Review [Security considerations](security.md) before deploying
+- Explore the [Core PHP Framework documentation](https://github.com/host-uk/core-php)
+- Add the Admin Panel with `host-uk/core-admin`
+- Build an API with `host-uk/core-api`
+
+## Common Commands
+
+```bash
+# Development
+php artisan serve # Start PHP server
+npm run dev # Start Vite with HMR
+npm run build # Build for production
+
+# Modules
+php artisan make:mod Name # Create a new module
+php artisan make:mod Name --all # With all features
+
+# Database
+php artisan migrate # Run migrations
+php artisan migrate:fresh # Reset and re-run migrations
+php artisan db:seed # Run seeders
+
+# Testing
+vendor/bin/pest # Run all tests
+vendor/bin/pest --filter=Name # Run specific test
+
+# Code Quality
+vendor/bin/pint # Format code
+vendor/bin/pint --test # Check formatting without changes
+```
diff --git a/docs/modules.md b/docs/modules.md
new file mode 100644
index 0000000..a3449ef
--- /dev/null
+++ b/docs/modules.md
@@ -0,0 +1,427 @@
+---
+title: Modules
+description: Creating and organising modules in Core PHP Framework applications
+updated: 2026-01-29
+---
+
+# Modules
+
+Modules are the building blocks of Core PHP Framework applications. Each module is a self-contained feature that registers itself via lifecycle events.
+
+## Module Structure
+
+A typical module follows this structure:
+
+```
+app/Mod/Blog/
+├── Boot.php # Entry point - event listeners
+├── Models/
+│ └── Post.php # Eloquent models
+├── Routes/
+│ ├── web.php # Public routes
+│ └── api.php # API routes
+├── Views/
+│ ├── index.blade.php
+│ └── posts/
+│ └── show.blade.php
+├── Livewire/
+│ ├── PostListPage.php
+│ └── PostShowPage.php
+├── Actions/
+│ └── CreatePost.php # Business logic
+├── Services/
+│ └── PostService.php
+├── Migrations/
+│ └── 2025_01_01_create_posts_table.php
+└── Tests/
+ ├── PostTest.php
+ └── CreatePostTest.php
+```
+
+## The Boot Class
+
+Every module requires a `Boot.php` file that declares its event listeners:
+
+```php
+ 'onWebRoutes',
+ ApiRoutesRegistering::class => 'onApiRoutes',
+ AdminPanelBooting::class => ['onAdminPanel', 10], // With priority
+ ConsoleBooting::class => 'onConsole',
+ ];
+
+ public function onWebRoutes(WebRoutesRegistering $event): void
+ {
+ // Register routes
+ $event->routes(fn() => require __DIR__.'/Routes/web.php');
+
+ // Register view namespace (accessed as 'blog::view.name')
+ $event->views('blog', __DIR__.'/Views');
+ }
+
+ public function onApiRoutes(ApiRoutesRegistering $event): void
+ {
+ $event->routes(fn() => require __DIR__.'/Routes/api.php');
+ }
+
+ public function onAdminPanel(AdminPanelBooting $event): void
+ {
+ // Register navigation item
+ $event->navigation('Blog', 'blog.admin.index', 'newspaper');
+
+ // Register admin resources
+ $event->resource('posts', PostResource::class);
+ }
+
+ public function onConsole(ConsoleBooting $event): void
+ {
+ // Register artisan commands
+ $event->commands([
+ ImportPostsCommand::class,
+ PublishScheduledPostsCommand::class,
+ ]);
+
+ // Register scheduled tasks
+ $event->schedule(function ($schedule) {
+ $schedule->command('blog:publish-scheduled')->hourly();
+ });
+ }
+}
+```
+
+## Lifecycle Events
+
+### WebRoutesRegistering
+
+Fired when web routes are being registered. Use for public-facing routes.
+
+```php
+public function onWebRoutes(WebRoutesRegistering $event): void
+{
+ // Register route file
+ $event->routes(fn() => require __DIR__.'/Routes/web.php');
+
+ // Register view namespace
+ $event->views('blog', __DIR__.'/Views');
+
+ // Register Blade components
+ $event->components('blog', __DIR__.'/Views/Components');
+
+ // Register middleware
+ $event->middleware('blog.auth', BlogAuthMiddleware::class);
+}
+```
+
+### ApiRoutesRegistering
+
+Fired when API routes are being registered. Routes are automatically prefixed with `/api`.
+
+```php
+public function onApiRoutes(ApiRoutesRegistering $event): void
+{
+ $event->routes(fn() => require __DIR__.'/Routes/api.php');
+
+ // Register API resources
+ $event->resource('posts', PostApiResource::class);
+}
+```
+
+### AdminPanelBooting
+
+Fired when the admin panel is being set up (requires `core-admin` package).
+
+```php
+public function onAdminPanel(AdminPanelBooting $event): void
+{
+ // Navigation item with icon
+ $event->navigation('Blog', 'blog.admin.index', 'newspaper');
+
+ // Navigation group with sub-items
+ $event->navigationGroup('Blog', [
+ ['Posts', 'blog.admin.posts', 'file-text'],
+ ['Categories', 'blog.admin.categories', 'folder'],
+ ['Tags', 'blog.admin.tags', 'tag'],
+ ], 'newspaper');
+
+ // Register admin resource
+ $event->resource('posts', PostResource::class);
+
+ // Register widget for dashboard
+ $event->widget(RecentPostsWidget::class);
+}
+```
+
+### ClientRoutesRegistering
+
+Fired for authenticated SaaS routes (dashboard, settings, etc.).
+
+```php
+public function onClientRoutes(ClientRoutesRegistering $event): void
+{
+ $event->routes(fn() => require __DIR__.'/Routes/client.php');
+}
+```
+
+### ConsoleBooting
+
+Fired when Artisan is bootstrapping.
+
+```php
+public function onConsole(ConsoleBooting $event): void
+{
+ // Register commands
+ $event->commands([
+ ImportPostsCommand::class,
+ ]);
+
+ // Register scheduled tasks
+ $event->schedule(function ($schedule) {
+ $schedule->command('blog:publish-scheduled')
+ ->hourly()
+ ->withoutOverlapping();
+ });
+}
+```
+
+### McpToolsRegistering
+
+Fired when the MCP server is being set up (requires `core-mcp` package).
+
+```php
+public function onMcpTools(McpToolsRegistering $event): void
+{
+ $event->tool('create_post', CreatePostTool::class);
+ $event->tool('list_posts', ListPostsTool::class);
+}
+```
+
+## Event Priorities
+
+You can specify a priority for event listeners. Higher numbers execute first:
+
+```php
+public static array $listens = [
+ AdminPanelBooting::class => ['onAdminPanel', 100], // High priority
+ WebRoutesRegistering::class => ['onWebRoutes', 10], // Normal priority
+];
+```
+
+Priorities are useful when:
+- Your module needs to register before/after other modules
+- You need to override routes from other modules
+- You need to modify admin navigation order
+
+## View Namespacing
+
+Views are namespaced by the identifier you provide:
+
+```php
+$event->views('blog', __DIR__.'/Views');
+```
+
+Access views using the namespace prefix:
+
+```blade
+{{-- In controllers/components --}}
+return view('blog::posts.index');
+
+{{-- In Blade templates --}}
+@include('blog::partials.sidebar')
+@extends('blog::layouts.main')
+```
+
+## Route Files
+
+### Web Routes (`Routes/web.php`)
+
+```php
+name('blog.')->group(function () {
+ Route::get('/', PostListPage::class)->name('index');
+ Route::get('/{post:slug}', PostShowPage::class)->name('show');
+});
+
+// With middleware
+Route::middleware(['auth'])->prefix('blog')->name('blog.')->group(function () {
+ Route::get('/my-posts', MyPostsPage::class)->name('my-posts');
+});
+```
+
+### API Routes (`Routes/api.php`)
+
+```php
+name('api.blog.')->group(function () {
+ Route::apiResource('posts', PostController::class);
+});
+```
+
+## Actions Pattern
+
+For complex business logic, use the Actions pattern:
+
+```php
+ $data['title'],
+ 'slug' => Str::slug($data['title']),
+ 'content' => $data['content'],
+ 'published_at' => $data['publish_now'] ? now() : null,
+ ]);
+ }
+}
+```
+
+Usage:
+
+```php
+$post = CreatePost::run([
+ 'title' => 'My Post',
+ 'content' => 'Content here...',
+ 'publish_now' => true,
+]);
+```
+
+## Module Discovery
+
+Modules are discovered automatically from paths configured in `config/core.php`:
+
+```php
+'module_paths' => [
+ app_path('Core'), // Framework overrides
+ app_path('Mod'), // Application modules
+ app_path('Website'), // Website-specific modules
+],
+```
+
+### Caching
+
+In production, module discovery is cached. Clear the cache when adding new modules:
+
+```bash
+php artisan cache:clear
+```
+
+Or disable caching during development:
+
+```env
+CORE_CACHE_DISCOVERY=false
+```
+
+## Creating Modules with Artisan
+
+The `make:mod` command scaffolds a new module:
+
+```bash
+# Full module with all features
+php artisan make:mod Blog --all
+
+# Web routes only
+php artisan make:mod Blog --web
+
+# API routes only
+php artisan make:mod Blog --api
+
+# Admin panel integration
+php artisan make:mod Blog --admin
+
+# Combination
+php artisan make:mod Blog --web --api --admin
+```
+
+## Module Dependencies
+
+If your module depends on another module, check for its presence:
+
+```php
+public function onWebRoutes(WebRoutesRegistering $event): void
+{
+ // Check if core-tenant is available
+ if (!class_exists(\Core\Tenant\Models\Workspace::class)) {
+ return;
+ }
+
+ $event->routes(fn() => require __DIR__.'/Routes/web.php');
+}
+```
+
+## Best Practices
+
+### Keep Modules Focused
+
+Each module should represent a single feature or domain:
+
+- `Blog` - Blog posts, categories, tags
+- `Shop` - Products, orders, cart
+- `Newsletter` - Subscribers, campaigns
+
+### Use Clear Naming
+
+- Module name: PascalCase singular (`Blog`, not `Blogs`)
+- Namespace: `App\Mod\{ModuleName}`
+- View namespace: lowercase (`blog::`, `shop::`)
+
+### Isolate Dependencies
+
+Keep inter-module dependencies minimal. If modules need to communicate:
+
+1. Use events (preferred)
+2. Use interfaces and dependency injection
+3. Use shared services in `app/Services/`
+
+### Test Modules in Isolation
+
+Write tests that don't depend on other modules being present:
+
+```php
+it('creates a blog post', function () {
+ $post = Post::create([
+ 'title' => 'Test',
+ 'slug' => 'test',
+ 'content' => 'Content',
+ ]);
+
+ expect($post)->toBeInstanceOf(Post::class);
+ expect($post->title)->toBe('Test');
+});
+```
diff --git a/docs/security.md b/docs/security.md
new file mode 100644
index 0000000..02494ea
--- /dev/null
+++ b/docs/security.md
@@ -0,0 +1,266 @@
+---
+title: Security
+description: Security considerations and audit notes for core-template
+updated: 2026-01-29
+---
+
+# Security
+
+This document covers security considerations for applications built with core-template. It includes both framework-provided protections and recommendations for hardening your application.
+
+## Built-in Protections
+
+### CSRF Protection
+
+Laravel's CSRF protection is enabled by default for all web routes. The template includes axios configuration that automatically attaches the CSRF token to AJAX requests:
+
+```javascript
+// resources/js/bootstrap.js
+window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
+```
+
+For forms, use the `@csrf` Blade directive:
+
+```blade
+
+```
+
+### XSS Protection
+
+Blade's `{{ }}` syntax automatically escapes output. Use `{!! !!}` only for trusted HTML content.
+
+### SQL Injection
+
+Eloquent ORM and Query Builder use parameterised queries by default. Avoid raw queries where possible:
+
+```php
+// Safe - parameterised
+User::where('email', $email)->first();
+
+// Dangerous - raw SQL
+DB::select("SELECT * FROM users WHERE email = '$email'"); // Don't do this
+```
+
+### Mass Assignment
+
+Models should define `$fillable` or `$guarded` properties to prevent mass assignment vulnerabilities:
+
+```php
+class Post extends Model
+{
+ protected $fillable = ['title', 'content', 'slug'];
+}
+```
+
+### Password Hashing
+
+The template configures bcrypt with 12 rounds by default (`BCRYPT_ROUNDS=12` in `.env.example`). This is appropriate for production use.
+
+## Recommendations
+
+### Security Headers
+
+Add security headers via middleware. Create `app/Http/Middleware/SecurityHeaders.php`:
+
+```php
+headers->set('X-Frame-Options', 'SAMEORIGIN');
+ $response->headers->set('X-Content-Type-Options', 'nosniff');
+ $response->headers->set('X-XSS-Protection', '1; mode=block');
+ $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
+
+ // Content Security Policy (adjust as needed)
+ $response->headers->set(
+ 'Content-Security-Policy',
+ "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';"
+ );
+
+ return $response;
+ }
+}
+```
+
+Register in `bootstrap/app.php`:
+
+```php
+->withMiddleware(function (Middleware $middleware) {
+ \Core\Front\Boot::middleware($middleware);
+ $middleware->web(append: [
+ \App\Http\Middleware\SecurityHeaders::class,
+ ]);
+})
+```
+
+### Session Security
+
+For production environments, update these settings in `.env`:
+
+```env
+SESSION_SECURE_COOKIE=true # Only send cookies over HTTPS
+SESSION_ENCRYPT=true # Encrypt session data
+SESSION_HTTP_ONLY=true # Prevent JavaScript access to session cookie
+SESSION_SAME_SITE=strict # Strict same-site policy
+```
+
+### HTTPS Enforcement
+
+Force HTTPS in production by adding to `AppServiceProvider`:
+
+```php
+public function boot(): void
+{
+ if ($this->app->environment('production')) {
+ URL::forceScheme('https');
+ }
+}
+```
+
+### Rate Limiting
+
+The default welcome route has no rate limiting. For production, add throttle middleware:
+
+```php
+Route::middleware('throttle:60,1')->group(function () {
+ Route::get('/', function () {
+ return view('welcome');
+ });
+});
+```
+
+Configure custom rate limiters in `AppServiceProvider`:
+
+```php
+use Illuminate\Cache\RateLimiting\Limit;
+use Illuminate\Support\Facades\RateLimiter;
+
+public function boot(): void
+{
+ RateLimiter::for('api', function (Request $request) {
+ return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
+ });
+
+ RateLimiter::for('login', function (Request $request) {
+ return Limit::perMinute(5)->by($request->ip());
+ });
+}
+```
+
+### APP_KEY Management
+
+The `APP_KEY` is critical for encryption. Never:
+
+- Commit it to version control
+- Share it between environments
+- Use predictable values
+
+Rotate the key only when necessary, understanding that:
+
+- Existing encrypted data becomes unreadable
+- Active sessions are invalidated
+- Signed URLs become invalid
+
+### Debug Mode
+
+Ensure `APP_DEBUG=false` in production. Debug mode exposes:
+
+- Stack traces with file paths
+- Environment variables
+- Database queries
+
+### Database Credentials
+
+Never commit database credentials. Use environment variables:
+
+```env
+DB_CONNECTION=mysql
+DB_HOST=127.0.0.1
+DB_PORT=3306
+DB_DATABASE=myapp
+DB_USERNAME=${DB_USERNAME}
+DB_PASSWORD=${DB_PASSWORD}
+```
+
+## Audit Checklist
+
+### Before Deployment
+
+- [ ] `APP_DEBUG=false`
+- [ ] `APP_ENV=production`
+- [ ] `APP_KEY` is set and unique
+- [ ] Session cookies are secure (`SESSION_SECURE_COOKIE=true`)
+- [ ] HTTPS is enforced
+- [ ] Security headers are configured
+- [ ] Rate limiting is in place for sensitive endpoints
+- [ ] `.env` is not accessible via web (check with `curl https://yoursite.com/.env`)
+- [ ] Storage directory is not web-accessible
+- [ ] Error pages don't leak sensitive information
+- [ ] Database credentials are environment-specific
+- [ ] Third-party API keys are not exposed in client-side code
+
+### Authentication (if using core-tenant)
+
+- [ ] Password reset tokens expire appropriately
+- [ ] Login attempts are rate limited
+- [ ] Account lockout is configured after failed attempts
+- [ ] Two-factor authentication is available for sensitive accounts
+- [ ] Session regeneration on login
+- [ ] Session invalidation on logout
+
+### API Security (if using core-api)
+
+- [ ] API keys are properly scoped
+- [ ] Rate limiting per API key
+- [ ] Webhook signatures are verified
+- [ ] CORS is configured appropriately
+- [ ] Sensitive endpoints require authentication
+
+## Dependencies
+
+Keep dependencies updated to receive security patches:
+
+```bash
+# Check for outdated packages
+composer outdated
+
+# Update dependencies
+composer update
+
+# Check for known vulnerabilities
+composer audit
+```
+
+The template includes Dependabot configuration (`.github/dependabot.yml`) to automate security updates.
+
+## Reporting Security Issues
+
+If you discover a security vulnerability in the Core PHP Framework:
+
+1. **Do not** create a public GitHub issue
+2. Email security concerns to the maintainers directly
+3. Include detailed steps to reproduce
+4. Allow reasonable time for a fix before public disclosure
+
+## Resources
+
+- [Laravel Security Documentation](https://laravel.com/docs/security)
+- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
+- [PHP Security Best Practices](https://www.php.net/manual/en/security.php)
+- [Snyk Security Advisories](https://security.snyk.io/)