| description | arguments | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
Scaffold a new Laravel 12 project with Vue 3, Inertia.js, TypeScript, Sail, and common packages |
|
Create a new Laravel 12 project named $ARGUMENTS.project_name inside $ARGUMENTS.parent_dir.
You MUST output clear progress messages throughout execution. Before each step, print a header. Before each sub-action, print what you're doing. After each sub-action, confirm completion. Use this exact format:
==> [Step X/16] <icon> Step Title
-> Installing package-name...
β
Installed.
-> Publishing config...
β
Published.
-> Running migrations...
β
Migrated.
At the very start, print a summary banner:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β π New Laravel Project: $ARGUMENTS.project_name
β π Directory: $ARGUMENTS.parent_dir/$ARGUMENTS.project_name
β π οΈ Stack: Laravel 12 + Vue 3 + Inertia.js + TypeScript + Sail
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
At the very end, print a completion banner with the access URLs.
Backend:
- Laravel 12 with PHP 8.5 (Sail container)
- Laravel Breeze (Vue 3 + TypeScript + Inertia.js, no SSR)
- Laravel Sail (MySQL 8.4, Redis, Mailpit)
PHP Production Packages:
- spatie/laravel-permission - role & permission management
- spatie/laravel-medialibrary - file/image uploads tied to models
PHP Dev Packages:
- laravel/telescope - debug dashboard (requests, queries, jobs, etc.)
- barryvdh/laravel-debugbar - in-page debug toolbar
- barryvdh/laravel-ide-helper - PHPStorm/IDE autocomplete generation
- larastan/larastan - static analysis (PHPStan for Laravel)
- laravel/pint - code style fixer (PSR-12 / Laravel preset)
- pestphp/pest + pest-plugin-laravel - modern testing framework
- laravel/pail - real-time log viewer
Frontend (NPM dev):
- pinia - Vue state management
- @vueuse/core - Vue composition utilities
- @headlessui/vue - accessible unstyled UI components (Dialog, Menu, Listbox, etc.)
- lucide-vue-next - icon library
- vue-sonner - toast notifications
- dayjs - lightweight date manipulation
- vee-validate + @vee-validate/zod + zod - client-side schema validation (complements Inertia's server-side validation)
- clsx + tailwind-merge - Tailwind class merging utilities
- concurrently - parallel dev process runner
cd "$ARGUMENTS.parent_dir"
composer create-project laravel/laravel "$ARGUMENTS.project_name"
cd "$ARGUMENTS.project_name"composer require laravel/breeze --dev
php artisan breeze:install vue --typescriptBreeze installs: Inertia.js v2, Vue 3, Ziggy, Tailwind CSS, TypeScript, axios, and scaffolds auth pages (login, register, password reset, email verification, profile).
Do NOT install these separately - Breeze already provides them:
- @inertiajs/vue3, vue, tailwindcss, axios, typescript, vue-tsc, ziggy
composer require laravel/sail --dev
php artisan sail:install --with=mysql,redis,mailpit
php artisan sail:publishThis creates compose.yaml with MySQL 8.4, Redis, and Mailpit services, and publishes the Docker files to docker/.
Install each, then configure:
spatie/laravel-permission:
composer require spatie/laravel-permission
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"This publishes config/permission.php and a migration for roles/permissions tables.
Then add HasRoles trait to app/Models/User.php:
- Add import:
use Spatie\Permission\Traits\HasRoles; - Update trait list:
use HasFactory, HasRoles, Notifiable;
spatie/laravel-medialibrary:
composer require spatie/laravel-medialibrary
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-migrations"
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-config"This publishes config/media-library.php and a migration for the media table.
laravel/telescope:
composer require laravel/telescope --dev
php artisan telescope:installThis publishes config/telescope.php, a migration, and the Telescope service provider.
Ensure Telescope only loads in local environment. In app/Providers/TelescopeServiceProvider.php, verify the gate method restricts access. In config/telescope.php, confirm 'enabled' => env('TELESCOPE_ENABLED', true).
barryvdh/laravel-debugbar:
composer require barryvdh/laravel-debugbar --devAuto-discovered, no publish needed. Only active when APP_DEBUG=true.
barryvdh/laravel-ide-helper:
composer require barryvdh/laravel-ide-helper --devAdd IDE helper commands to composer.json scripts.post-update-cmd array (append after the existing laravel-assets line):
"@php artisan ide-helper:generate",
"@php artisan ide-helper:meta"larastan/larastan:
composer require larastan/larastan --devCreate phpstan.neon.dist at project root:
includes:
- vendor/larastan/larastan/extension.neon
parameters:
paths:
- app/
level: 5
ignoreErrors:
excludePaths:Add a scripts.analyse entry to composer.json:
"analyse": [
"./vendor/bin/phpstan analyse --memory-limit=2G"
]laravel/pint is already installed by default. Add a scripts.format entry to composer.json:
"format": [
"./vendor/bin/pint"
]pestphp/pest:
composer require pestphp/pest pestphp/pest-plugin-laravel --dev
./vendor/bin/pest --initThis creates tests/Pest.php with the base TestCase binding. Pest runs existing PHPUnit tests too, so Breeze's auth tests still work.
Update composer.json scripts.test to use Pest:
"test": [
"@php artisan config:clear --ansi",
"./vendor/bin/pest"
]laravel/pail:
composer require laravel/pail --devNo configuration needed. Used via php artisan pail.
Run a single install command for all frontend packages:
npm install --save-dev pinia @vueuse/core @headlessui/vue lucide-vue-next vue-sonner dayjs vee-validate @vee-validate/zod zod clsx tailwind-merge concurrentlyAll packages are devDependencies since they're bundled by Vite.
Pinia setup:
Create resources/js/plugins/pinia.ts:
import { createPinia } from 'pinia';
const pinia = createPinia();
export default pinia;Update resources/js/app.ts to register Pinia. In the setup function, add .use(pinia) before .mount(el):
import pinia from './plugins/pinia';
// inside setup():
createApp({ render: () => h(App, props) })
.use(plugin)
.use(ZiggyVue)
.use(pinia)
.mount(el);Tailwind class merge utility:
Create resources/js/utils/cn.ts:
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]): string {
return twMerge(clsx(inputs));
}Day.js setup:
Create resources/js/plugins/dayjs.ts:
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import localizedFormat from 'dayjs/plugin/localizedFormat';
dayjs.extend(relativeTime);
dayjs.extend(localizedFormat);
export default dayjs;Vue Sonner setup:
Add the <Toaster /> component to the main layout. In resources/js/Layouts/AuthenticatedLayout.vue (or your root layout), add:
<script setup>
import { Toaster } from 'vue-sonner';
// ... existing imports
</script>
<template>
<!-- Add at the top of the template, before other content -->
<Toaster position="top-right" :duration="3000" />
<!-- ... rest of template -->
</template>Also add <Toaster /> to resources/js/Layouts/GuestLayout.vue the same way.
Update .env with the following values. The Laravel app runs on port 8080 (APP_PORT).
APP_URL=http://localhost:8080
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=<snake_case of $ARGUMENTS.project_name>
DB_USERNAME=sail
DB_PASSWORD=password
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis
CACHE_STORE=redis
REDIS_CLIENT=phpredis
REDIS_HOST=redis
MAIL_MAILER=smtp
MAIL_HOST=mailpit
MAIL_PORT=1025
APP_PORT=8080
VITE_PORT=15173
FORWARD_DB_PORT=13306
FORWARD_REDIS_PORT=16379
FORWARD_MAILPIT_PORT=11025
FORWARD_MAILPIT_DASHBOARD_PORT=18025
TELESCOPE_ENABLED=true
VITE_APP_NAME="${APP_NAME}"IMPORTANT: APP_PORT=8080 means the Laravel app is accessible at http://localhost:8080 (NOT port 80). APP_URL must match.
Copy .env to .env.example but remove the APP_KEY value (leave it empty).
The Vite dev server must bind to 0.0.0.0 inside the Docker container. Update vite.config.js:
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
server: {
host: '0.0.0.0',
port: 15173,
hmr: {
host: 'localhost',
},
},
plugins: [
laravel({
input: 'resources/js/app.ts',
refresh: true,
}),
vue({
template: {
transformAssetUrls: {
base: null,
includeAbsolute: false,
},
},
}),
],
});CRITICAL: server.port must match VITE_PORT in .env and the port mapping in compose.yaml.
In composer.json, add/replace these entries in the scripts section:
"scripts": {
"setup": [
"composer install",
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
"@php artisan key:generate",
"@php artisan migrate --force",
"@php artisan db:seed --force",
"npm install",
"npm run build"
],
"dev": [
"Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1 --timeout=0\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others"
],
"test": [
"@php artisan config:clear --ansi",
"./vendor/bin/pest"
],
"format": [
"./vendor/bin/pint"
],
"analyse": [
"./vendor/bin/phpstan analyse --memory-limit=2G"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force",
"@php artisan ide-helper:generate",
"@php artisan ide-helper:meta"
]
}Keep all other existing script entries (post-autoload-dump, post-root-package-install, post-create-project-cmd, pre-package-uninstall) unchanged.
Append to .gitignore:
_ide_helper.php
_ide_helper_models.php
.phpstorm.meta.php
.phpunit.cache
Create migration to add is_admin column to users table:
php artisan make:migration add_is_admin_to_users_table --table=usersEdit the generated migration file:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->boolean('is_admin')->default(false)->after('email');
});
}
};Create AdminUserSeeder:
php artisan make:seeder AdminUserSeederEdit database/seeders/AdminUserSeeder.php:
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
use Spatie\Permission\Models\Role;
class AdminUserSeeder extends Seeder
{
public function run(): void
{
$adminRole = Role::firstOrCreate(['name' => 'admin']);
$admin = User::firstOrCreate(
['email' => 'admin@example.com'],
[
'name' => 'Admin',
'password' => Hash::make('password'),
'is_admin' => true,
'email_verified_at' => now(),
]
);
$admin->assignRole($adminRole);
}
}Register in DatabaseSeeder:
Edit database/seeders/DatabaseSeeder.php to call the admin seeder:
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
$this->call([
AdminUserSeeder::class,
]);
}
}sail up -d
sail npm install
sail artisan migrate
sail artisan db:seed
sail composer ide-helper:generate
sail composer ide-helper:meta
sail npm run buildVerify the app loads at http://localhost:8080.
Verify Telescope at http://localhost:8080/telescope.
Verify admin login works at http://localhost:8080/login with admin@example.com / password.
git init
git add -A
git commit -m "initial setup: Laravel 12, Vue 3, Inertia, Sail, Spatie, Telescope, Pest, Larastan"Print completion banner:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
Project "$ARGUMENTS.project_name" created successfully!
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π Access URLs (after `sail up -d` and `sail npm run dev`):
π App: http://localhost:8080
β‘ Vite HMR: http://localhost:15173
π Telescope: http://localhost:8080/telescope
π¬ Mailpit: http://localhost:18025
ποΈ MySQL: localhost:13306
π΄ Redis: localhost:16379
π€ Admin credentials:
π§ Email: admin@example.com
π Password: password
π οΈ Useful commands:
sail up -d Start all containers
sail npm run dev Start Vite dev server with HMR
sail artisan migrate Run migrations
sail artisan db:seed Seed the database
sail composer test Run Pest tests
sail composer format Fix code style with Pint
sail composer analyse Run Larastan static analysis
sail artisan telescope Manage Telescope
sail artisan pail Tail logs in real-time
composer dev Local dev (no Sail): server + queue + logs + vite
- @inertiajs/vue3, inertiajs/inertia-laravel
- vue, @vitejs/plugin-vue, vue-tsc, typescript
- tailwindcss, @tailwindcss/forms, @tailwindcss/vite, postcss, autoprefixer
- axios
- tightenco/ziggy
- laravel/sanctum
- laravel/pint
- Auth scaffolding (login, register, password reset, email verify, profile)
- Inertia
useForm: standard form submissions with server-side Laravel validation (FormRequest). This is the default for most forms. - vee-validate + zod: client-side validation for instant feedback before submission, complex multi-step forms, or forms that need schema-based validation rules on the frontend.
- Always use
sailprefix for artisan/npm/composer when working inside containers - The
composer devscript is for NON-Sail local dev (runsphp artisan servedirectly). Inside Sail, usesail npm run devfor Vite only since Sail already runs the PHP server via supervisord - Telescope is dev-only. It loads conditionally and is gated by
TelescopeServiceProvider - Debugbar only appears when
APP_DEBUG=true - IDE helper files are gitignored - regenerate with
sail artisan ide-helper:generate - If Vite HMR doesn't connect, verify
VITE_PORTin.env,server.portinvite.config.js, and port mapping incompose.yamlall match