Inertia Blueprint is a powerful Laravel package that generates beautiful, fully-functional Inertia.js + React + TypeScript pages with shadcn/ui components from simple JSON file. Build complete CRUD interfaces in seconds.
- β‘ Rapid Development - Generate complete CRUD pages in seconds
- π¨ Beautiful UI - Pre-styled with shadcn/ui components
- π Smart Search - Debounced search with URL state management
- π± Responsive Design - Mobile-first, responsive layouts
- π‘οΈ Type Safety - Full TypeScript support with proper typing
- π Form Handling - Inertia.js
useFormintegration - βοΈ Flexible Configuration - Customize fields, routes, and behavior
- π― Customizable Stubs - Publish and modify templates to fit your needs
- Index - Tables with search and actions
- Create - Form pages with validation and error handling
- Edit - Pre-populated forms with validation and update functionality
- View - Detailed view pages with clean layouts
Install via Composer:
composer require sediqzada/inertia-blueprint:^0.2@beta --devMake sure you have the following set up in your Laravel project:
- Laravel 10+ with Inertia.js
- React 18+ with TypeScript support
- shadcn/ui components installed and configured
- Tailwind CSS for styling
For the best experience, ensure your project has:
# Install shadcn/ui if not already installed
npx shadcn-ui@latest init
# Install required shadcn/ui components
npx shadcn-ui@latest add button card input label textarea checkbox select dialog tableCreate a blueprint.json file in your project root:
{
"model": "Post",
"fields": [
{ "name": "title", "type": "string", "inputType": "text", "searchable": true },
{ "name": "content", "type": "text", "inputType": "textarea", "searchable": true },
{ "name": "published_at", "type": "datetime", "inputType": "text" },
{
"name": "category",
"fieldName": "category_id",
"type": "string",
"inputType": "select",
"options": "categories",
"valueField": "id",
"labelField": "name"
}
],
"pages": ["index", "create", "edit", "view"]
}php artisan blueprint:generateIf pages already exist, you'll be prompted to choose:
- ignore - Skip existing files, only generate new ones
- override - Replace existing files with new versions
Your pages are now available in resources/js/pages/Post/ with:
- Full TypeScript support
- shadcn/ui components
- Form validation
- Search functionality
- Responsive design
{
"model": "ModelName",
"fields": [...],
"routes": {...},
"language": "ts",
"pages": [...]
}Each field supports the following properties:
{
"name": "fieldName", // Required: Database field name
"type": "string|text|number|boolean|datetime|file|select", // Required: Data type
"inputType": "text|textarea|number|checkbox|file|select", // Required: Input component type
"searchable": true|false, // Optional: Include in search functionality
"options": {...} // Required for select fields
}| Type | Input Type | Generated Component | Description | Example |
|---|---|---|---|---|
string |
text |
Input |
Single-line text input | Name, email, slug |
string |
textarea |
Textarea |
Multi-line text area | Description, content, notes |
number |
number |
Input[type="number"] |
Numeric input with validation | Price, quantity, age |
boolean |
checkbox |
Checkbox |
Boolean checkbox | Is active, published, featured |
datetime |
text |
Input[type="text"] |
Date/time input | Created at, published at |
string |
file |
Input[type="file"] |
File upload input | Images, documents, attachments |
string |
select |
Select |
Dropdown selection | Categories, status, user roles |
Select fields require additional configuration for data binding:
If the options are provided by controller:
{
"name": "category",
"fieldName": "category_id",
"type": "string",
"inputType": "select",
"options": "categories",
"valueField": "id",
"labelField": "name"
}Or if the options are static:
{
"name": "status",
"type": "string",
"inputType": "select",
"options": [
{ "status": "draft", "label": "Draft" },
{ "status": "published", "label": "Published" },
{ "status": "cancelled", "label": "Cancelled" },
{ "status": "completed", "label": "Completed" }
],
"valueField": "status",
"labelField": "label"
}{
"fields": [
// Basic text field with search
{
"name": "title",
"type": "string",
"inputType": "text",
"searchable": true
},
// Large text area
{
"name": "description",
"type": "string",
"inputType": "textarea",
"searchable": true
},
// Numeric field
{
"name": "price",
"type": "number",
"inputType": "number"
},
// Boolean checkbox
{
"name": "is_featured",
"type": "boolean",
"inputType": "checkbox"
},
// File upload
{
"name": "featured_image",
"type": "string",
"inputType": "file"
},
// Select dropdown
{
"name": "status",
"type": "string",
"inputType": "select",
"options": "statuses",
"valueField": "value",
"labelField": "label"
}
]
}Routes will be generated from the model key, for example if the "model: Post" the generated routes will be:
posts.index, posts.create, posts.store, posts.show, posts.edit, posts.update, posts.destroy
You can also define your Laravel route names that correspond to your controller methods:
{
"routes": {
"index": "posts.index", // GET /posts - List all posts
"create": "posts.create", // GET /posts/create - Show create form
"store": "posts.store", // POST /posts - Store new post
"show": "posts.show", // GET /posts/{id} - Show single post
"edit": "posts.edit", // GET /posts/{id}/edit - Show edit form
"update": "posts.update", // PUT/PATCH /posts/{id} - Update post
"destroy": "posts.destroy" // DELETE /posts/{id} - Delete post
}
}Route Requirements by Page:
| Page | Required Routes | Optional Routes |
|---|---|---|
index |
index, destroy |
show, edit |
create |
store, index |
- |
edit |
update, index |
- |
view |
index |
edit |
Choose which pages to generate. Each page type serves a specific purpose:
{
"pages": ["index", "create", "edit", "view"]
}Available Page Types:
| Page | Purpose | Features |
|---|---|---|
index |
List/table view | Search, delete confirmation |
create |
New record form | Form validation, file uploads, select dropdowns |
edit |
Update existing record | Pre-populated form, validation, file handling |
view |
Read-only detail view | Clean layout, formatted data display |
Language Support:
- TypeScript (
ts) - Full type safety, interfaces, proper typing (default and currently only supported option)
Generate from specific blueprint files:
# Use a custom blueprint file
php artisan blueprint:generate custom-blueprint.json
# Generate multiple models
php artisan blueprint:generate posts.json
php artisan blueprint:generate users.json
php artisan blueprint:generate categories.jsonCustomize the generated code templates to match your project's needs:
# Publish stub files for customization
php artisan blueprint:publish-stubs
# Force overwrite existing stubs
php artisan blueprint:publish-stubs --forceThis publishes stub files to resources/inertia-blueprint-stubs/:
After publishing, you'll find these customizable templates:
resources/inertia-blueprint-stubs/
βββ react/
βββ Index.stub # List/table view template
βββ Create.stub # Creation form template
βββ Edit.stub # Edit form template
βββ View.stub # Detail view template
After running the command, your files will be organized as:
resources/js/pages/
βββ Post/
βββ Index.tsx # List view with search & actions
βββ Create.tsx # Creation form
βββ Edit.tsx # Edit form
βββ View.tsx # Detail view
Publish the configuration file to customize default settings:
php artisan vendor:publish --provider="Sediqzada\InertiaBlueprint\InertiaBluerintServiceProvider"This creates config/inertia-blueprint.php:
<?php
return [
'default_language' => 'ts', // TypeScript (only supported language)
];blog-posts.json
{
"model": "Post",
"fields": [
{
"name": "title",
"type": "string",
"inputType": "text",
"searchable": true
},
{
"name": "slug",
"type": "string",
"inputType": "text"
},
{
"name": "content",
"type": "string",
"inputType": "textarea",
"searchable": true
},
{
"name": "excerpt",
"type": "string",
"inputType": "textarea"
},
{
"name": "featured_image",
"type": "file",
"inputType": "file"
},
{
"name": "is_published",
"type": "boolean",
"inputType": "checkbox"
},
{
"name": "category",
"fieldName": "category_id",
"type": "string",
"inputType": "select",
"options": "categories",
"valueField": "id",
"labelField": "name"
}
],
"routes": {
"index": "admin.posts.index",
"create": "admin.posts.create",
"store": "admin.posts.store",
"show": "admin.posts.show",
"edit": "admin.posts.edit",
"update": "admin.posts.update",
"destroy": "admin.posts.destroy"
},
"pages": ["index", "create", "edit", "view"]
}products.json
{
"model": "Product",
"fields": [
{
"name": "name",
"type": "string",
"inputType": "text",
"searchable": true
},
{
"name": "sku",
"type": "string",
"inputType": "text",
"searchable": true
},
{
"name": "description",
"type": "text",
"inputType": "textarea"
},
{
"name": "price",
"type": "number",
"inputType": "number"
},
{
"name": "stock_quantity",
"type": "number",
"inputType": "number"
},
{
"name": "is_active",
"type": "boolean",
"inputType": "checkbox"
},
{
"name": "category",
"fieldName": "category_id",
"type": "string",
"inputType": "select",
"options": "categories",
"valueField": "id",
"labelField": "name"
},
{
"name": "brand",
"type": "string",
"inputType": "select",
"options": "brands",
"valueField": "id",
"labelField": "name"
},
{
"name": "product_image",
"type": "file",
"inputType": "file"
}
],
"routes": {
"index": "admin.products.index",
"create": "admin.products.create",
"store": "admin.products.store",
"show": "admin.products.show",
"edit": "admin.products.edit",
"update": "admin.products.update",
"destroy": "admin.products.destroy"
},
"pages": ["index", "create", "edit", "view"]
}users.json
{
"model": "User",
"fields": [
{
"name": "first_name",
"type": "string",
"inputType": "text",
"searchable": true
},
{
"name": "last_name",
"type": "string",
"inputType": "text",
"searchable": true
},
{
"name": "email",
"type": "string",
"inputType": "text",
"searchable": true
},
{
"name": "phone",
"type": "string",
"inputType": "text"
},
{
"name": "bio",
"type": "text",
"inputType": "textarea"
},
{
"name": "avatar",
"type": "file",
"inputType": "file"
},
{
"name": "status",
"type": "string",
"inputType": "select",
"options": [
{ "status": "draft", "label": "Draft" },
{ "status": "published", "label": "Published" },
{ "status": "cancelled", "label": "Cancelled" },
{ "status": "completed", "label": "Completed" }
],
"valueField": "status",
"labelField": "label"
}
],
"routes": {
"index": "admin.users.index",
"create": "admin.users.create",
"store": "admin.users.store",
"show": "admin.users.show",
"edit": "admin.users.edit",
"update": "admin.users.update",
"destroy": "admin.users.destroy"
},
"pages": ["index", "create", "edit", "view"]
}Your Laravel controllers should provide the expected data structure:
// Index method
public function index(Request $request)
{
$query = Post::query();
// Handle search
if ($request->search) {
$query->where('title', 'like', "%{$request->search}%")
->orWhere('content', 'like', "%{$request->search}%");
}
$posts = $query->get();
return inertia('Post/Index', [
'posts' => $posts,
'search' => $request->search,
'categories' => Category::all(['id', 'name']), // For select fields
]);
}
// Create method
public function create()
{
return inertia('Post/Create', [
'categories' => Category::all(['id', 'name']),
]);
}
// Edit method
public function edit(Post $post)
{
return inertia('Post/Edit', [
'post' => $post,
'categories' => Category::all(['id', 'name']),
]);
}
// Show method
public function show(Post $post)
{
return inertia('Post/Show', [
'post' => $post,
]);
}Contributions are welcome! Please feel free to submit a Pull Request.
- Clone the repository
- Install dependencies:
composer install - Run tests:
composer test
# Run tests
composer test
# Or
./vendor/bin/phpunit
# Run pint
./vendor/bin/pint --test
# Run rector
./vendor/bin/rector --dry-run
# Run phpstan
./vendor/bin/phpstan
The MIT License (MIT). Please see License File for more information.
- shadcn/ui - For the beautiful component library
- Inertia.js - For the seamless SPA experience
- Laravel - For the amazing framework
- React - For the powerful frontend library
If you discover any bugs, please create an issue on GitHub and DO NOT FORGET TO STAR THE REPO.
Built with β€οΈ for the Laravel community