Generate a complete CRUD feature with all related components in one workflow — Model, Controller, Views, Routes, Tests, Factory, Seeder, Resource. Use when user wants to build out an entire entity at once. Triggers include "full feature", "CRUD", "resource for", "complete setup", "everything for", "build out the X feature", "scaffold everything for", or mentions an entity name with the intent of creating all components (e.g., "I need a Product entity"). For creating a single component, use th...
Scanned 9/23/2026
Install to Claude Code
npx -y skills add gurenjs/guren --skill feature --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Feature?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/gurenjs-feature)More formats (shields.io, HTML) on the badges page.
---
name: feature
description: Generate a complete CRUD feature with all related components in one workflow — Model, Controller, Views, Routes, Tests, Factory, Seeder, Resource. Use when user wants to build out an entire entity at once. Triggers include "full feature", "CRUD", "resource for", "complete setup", "everything for", "build out the X feature", "scaffold everything for", or mentions an entity name with the intent of creating all components (e.g., "I need a Product entity"). For creating a single component, use the scaffold skill instead.
---
# Feature Skill
You are a full-feature scaffolding assistant for the Guren framework.
> When filling in generated code, follow the API rules in `__RULES_DIR__/` (orm-models, controllers-http, routes-codegen, testing) — they carry the verified signatures.
## Your Role
Generate all components needed for a complete CRUD feature in one workflow. This is the "batteries-included" approach — creating everything an entity needs to work end-to-end with full type safety.
## Workflow
When given a feature name (e.g., "Post", "Product"):
### 1. Generate all components
**Preferred:** Use `make:feature` which generates type-safe code with proper imports:
```bash
bunx guren make:feature <Name> --fields "title:string,body:text,published:boolean"
```
Add `--module <name>` to scaffold the feature inside an existing `modules/<name>/` directory instead of the project root — app/ files move under `modules/<name>/`, but pages stay top-level, namespaced by module name (`resources/js/pages/<name>/...`), not colocated.
This generates Validator, Resource, Controller, Views (Index/Show/New/Edit), and Model in one step with:
- Typed page props (no `any`)
- `route()` helper for all URLs
- `ApiRoutes` for form data types
- `RouteErrors` for validation error types
**Or generate individually:**
```bash
bunx guren make:model <Name>
bunx guren make:migration create_<names>_table
bunx guren make:controller <Name>
bunx guren make:view <names>/Index
bunx guren make:view <names>/Show
bunx guren make:view <names>/New
bunx guren make:view <names>/Edit
bunx guren make:route <names>
bunx guren make:test controllers/<Name>Controller --runner=vitest
bunx guren make:factory <Name> --model=<Name>
bunx guren make:seeder <Name>
bunx guren make:resource <Name> --model=<Name>
bunx guren make:validator <Name> --fields "<name:type,...>"
```
Don't hand-write the Validator — `make:validator` emits the same file `make:feature` does, so the schema names stay in sync with what the generated controller imports. Pass the same `--fields` you would pass `make:feature`; omit it to get an empty payload schema to fill in.
The Validator is needed for both controller validation and route body schema binding.
### 2. Register routes with body schemas
When registering routes in `routes/web.ts`, **always attach Zod body schemas** to mutation routes. This enables codegen to extract types for the frontend:
```typescript
import <Name>Controller from '../app/Http/Controllers/<Name>Controller.js'
import { <Name>PayloadSchema } from '../app/Http/Validators/<Name>Validator.js'
router.group('/<names>', (<names>) => {
<names>.get('/', [<Name>Controller, 'index']).name('<names>.index')
<names>.get('/new', [<Name>Controller, 'create']).name('<names>.create')
<names>.get('/:id', [<Name>Controller, 'show']).name('<names>.show')
<names>.get('/:id/edit', [<Name>Controller, 'edit']).name('<names>.edit')
<names>.post('/', { name: '<names>.store', body: <Name>PayloadSchema }, [<Name>Controller, 'store'])
<names>.put('/:id', { name: '<names>.update', body: <Name>PayloadSchema }, [<Name>Controller, 'update'])
})
```
Where `<Name>` is PascalCase singular (e.g., `Post`) and `<names>` is kebab-case plural (e.g., `posts`).
### 3. Run codegen
```bash
bunx guren codegen
```
This generates typed route helpers and API client types in `.guren/`.
### 4. Report created files and next steps
- Add table schema to `db/schema.ts`
- Run migration: `bun run db:migrate`
## Type Safety Patterns
Generated views follow these patterns for end-to-end type safety:
### Form pages (New/Edit) — derive types from ApiRoutes
```typescript
import type { ApiRoutes } from '@/.guren/api-client.gen'
import type { RouteBody, RouteErrors } from '@guren/inertia-client/typed-forms'
import { route } from '@/.guren/routes.gen'
type <Name>FormData = RouteBody<ApiRoutes, '<names>.store'>
// Form submission uses route() helper
form.post(route('<names>.store'))
form.put(route('<names>.update', { id: <name>.id }))
```
### List/Detail pages — typed props from Resource
```typescript
import type { PaginatedPageProps } from '@guren/core'
import type { <Name>ResourceData } from '@/app/Http/Resources/<Name>Resource'
import { route } from '@/.guren/routes.gen'
interface Props extends PaginatedPageProps<<Name>ResourceData> {}
// Navigation uses route() helper
<Link href={route('<names>.show', { id: <name>.id })}>
```
### Error types — RouteErrors with message field
```typescript
interface Props {
errors?: RouteErrors<<Name>FormData> & { message?: string }
}
```
### Models — define fillable for mass assignment protection
Always add `fillable` to generated models. This is the second defense layer after Zod validation — it prevents unintended fields from reaching the database even if the controller validation is bypassed or misconfigured:
```typescript
export class <Name> extends defineModel(<names>, {
fillable: ['title', 'body', 'authorId'], // only these fields pass to create()/update()
}) {}
```
Prefer the `defineModel` option over `static fillable = [...]` — the option is typed against the table's columns, so a typo is a compile error (a `static` declaration still works and shadows the option).
For User models, credential columns (`passwordHash`, `rememberToken`) are denied from
mass assignment by `AuthenticatableModel` itself — never list them in `fillable`:
```typescript
export class User extends defineModel(users, {
base: AuthenticatableModel,
optionalOnCreate: ['passwordHash'],
requireOnCreate: ['password'],
fillable: ['name', 'email', 'password'],
}) {}
```
## Generated Structure
For feature "Post":
```
app/
├── Http/Controllers/PostController.ts
├── Http/Resources/PostResource.ts
├── Http/Validators/PostValidator.ts
└── Models/Post.ts
db/
├── factories/PostFactory.ts
├── migrations/{timestamp}_create_posts_table.sql
└── seeders/PostSeeder.ts
resources/js/pages/posts/
├── Index.tsx ← typed props, route() links
├── Show.tsx ← typed props, route() links
├── New.tsx ← ApiRoutes form type, route() submit
└── Edit.tsx ← ApiRoutes form type, RouteErrors, route() submit
tests/controllers/PostController.test.ts
```
## Schema Example
```typescript
// db/schema.ts
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: varchar('title', { length: 255 }).notNull(),
content: text('content'),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow(),
})
```
On PostgreSQL, always give timestamp columns `{ withTimezone: true }`. A
`timestamp without time zone` stores a bare wall clock, so `defaultNow()`
records it in the database session's zone while the app reads it back as UTC,
and any client other than the app sees a different instant.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!