stacks-new-feature

$npx mdskill add stacksjs/stacks/stacks-new-feature

Guides end-to-end feature development in Stacks applications.

  • Defines models, migrations, actions, routes, tests, and deployment order.
  • Depends on Stacks ORM, validation, and Bun runtime.
  • Recommends a fixed workflow based on model definition and feature traits.
  • Outputs step-by-step instructions and code snippets for each phase.

SKILL.md

.github/skills/stacks-new-featureView on GitHub ↗
---
name: stacks-new-feature
description: Use when adding a new feature end-to-end in a Stacks application — the complete workflow from model definition through migration, action, route, test, and deployment. Covers the recommended order of operations for building features.
license: MIT
compatibility: Bun >= 1.3.0, TypeScript
allowed-tools: Read Edit Write Bash Grep Glob
---

# Adding a New Feature to Stacks

Step-by-step guide for building features end-to-end.

## Workflow Overview

```
1. Model → 2. Migration → 3. Action → 4. Route → 5. Test → 6. Lint → 7. Deploy
```

## Step 1: Define the Model

```typescript
// Create or edit in storage/framework/models/
import { defineModel } from '@stacksjs/orm'
import { schema } from '@stacksjs/validation'

export default defineModel({
  name: 'Article',
  table: 'articles',
  primaryKey: 'id',
  autoIncrement: true,

  traits: {
    useTimestamps: true,
    useSearch: {
      searchable: ['title', 'content'],
      sortable: ['title', 'created_at'],
      filterable: ['status']
    },
    useApi: {
      uri: 'articles',
      routes: ['index', 'store', 'show', 'update', 'destroy']
    },
    useSeeder: { count: 10 },
    observe: true
  },

  belongsTo: ['User'],

  attributes: {
    title: {
      fillable: true,
      required: true,
      validation: { rule: schema.string().maxLength(200) },
      factory: (faker) => faker.lorem.sentence()
    },
    content: {
      fillable: true,
      required: true,
      validation: { rule: schema.string() },
      factory: (faker) => faker.lorem.paragraphs(3)
    },
    status: {
      fillable: true,
      default: 'draft',
      validation: { rule: schema.enum(['draft', 'published', 'archived']) },
      factory: () => 'draft'
    }
  },

  get: {
    excerpt: (attrs) => attrs.content?.substring(0, 150) + '...'
  }
} as const)
```

## Step 2: Generate & Run Migration

```bash
buddy generate:migrations      # generate migration SQL from model diffs
buddy migrate                  # apply migration
buddy seed                     # seed with factory data
```

## Step 3: Create Actions

```typescript
// app/Actions/CreateArticle.ts
export default {
  name: 'CreateArticle',
  description: 'Create a new article',

  async handle(request: any) {
    const title = request.get('title')
    const content = request.get('content')
    const user = await request.user()

    const article = await Article.create({
      title,
      content,
      user_id: user.id,
      status: 'draft'
    })

    return { success: true, data: article }
  }
}
```

## Step 4: Define Routes

```typescript
// routes/api.ts (add to existing)
route.group({ prefix: '/articles', middleware: ['auth'] }, () => {
  route.get('/', 'Actions/ListArticles')
  route.post('/', 'Actions/CreateArticle')
  route.get('/{id}', 'Actions/ShowArticle')
  route.put('/{id}', 'Actions/UpdateArticle')
  route.delete('/{id}', 'Actions/DeleteArticle')
})
```

Or rely on auto-generated routes from `useApi` trait — they're created automatically.

## Step 5: Add Event Listeners (Optional)

```typescript
// app/Events.ts — add to existing
{
  'article:created': ['NotifySubscribers'],
  'article:published': ['SendNewsletter', 'IndexInSearchEngine']
}
```

## Step 6: Write Tests

```typescript
// tests/feature/articles.test.ts
import { describe, test, expect, beforeAll } from 'bun:test'
import { setupDatabase, refreshDatabase } from '@stacksjs/testing'

describe('Articles', () => {
  beforeAll(async () => {
    await setupDatabase()
  })

  test('can create an article', async () => {
    const article = await Article.create({
      title: 'Test Article',
      content: 'Test content',
      status: 'draft'
    })
    expect(article.title).toBe('Test Article')
  })

  test('can list articles', async () => {
    const articles = await Article.all()
    expect(articles.length).toBeGreaterThan(0)
  })
})
```

## Step 7: Lint & Deploy

```bash
bunx --bun pickier . --fix    # lint and auto-fix
buddy test                     # run tests
buddy deploy                   # deploy to cloud
```

## Common Patterns

### Adding a Dashboard View
Models with `useApi` + `dashboard: { highlight: true }` auto-appear in the admin dashboard.

### Adding Email Notifications
```typescript
// app/Mail/ArticlePublished.ts
export async function sendArticlePublished({ to, article }) {
  const { html, text } = await template('article-published', {
    variables: { title: article.title, url: `${config.app.url}/articles/${article.id}` }
  })
  await mail.send({ to, subject: `New: ${article.title}`, html, text })
}
```

### Adding Background Processing
```typescript
// app/Jobs/IndexArticle.ts
export default new Job({
  name: 'IndexArticle',
  queue: 'search',
  tries: 3,
  async handle(payload: { articleId: number }) {
    const article = await Article.find(payload.articleId)
    await search.addDocuments('articles', [article])
  }
})
```

## Gotchas
- Models work directly via the dynamic ORM — no generation step needed before migrations
- The `useApi` trait auto-generates both routes AND dashboard views
- Model events (observe: true) emit `article:created`, `article:updated`, `article:deleted`
- Factories in model attributes are used by `buddy seed`
- Always lint after code generation: `bunx --bun pickier . --fix`
- Use conventional commits: `feat: add article management`

More from stacksjs/stacks

SkillDescription
stacks-actionsUse when working with Stacks server actions — creating actions in app/Actions/, auto-generated API actions from the useApi model trait, the 80+ default framework actions (auth, dashboard, commerce, content, deployment, jobs), action request/response handling, or action registration. Covers @stacksjs/actions and storage/framework/defaults/actions/.
stacks-aiUse when integrating AI capabilities into a Stacks application — using Anthropic/OpenAI/Ollama/AWS Bedrock drivers, image generation (DALL-E), vision analysis, RAG/vector search, embeddings, MCP (Model Context Protocol) clients, text summarization, sentiment analysis, content classification, personalization, or the buddy AI assistant. Covers @stacksjs/ai and config/ai.ts.
stacks-aliasUse when working with path aliases in a Stacks project — import resolution, module aliasing, or debugging import paths. Covers @stacksjs/alias which defines 260+ path mappings for the entire framework.
stacks-analyticsUse when adding analytics to a Stacks application — configuring Fathom or self-hosted analytics, generating tracking scripts, privacy-friendly analytics setup, or the analytics configuration. Covers @stacksjs/analytics and config/analytics.ts.
stacks-apiUse when building, modifying, or debugging API endpoints in a Stacks application — defining routes, handling requests, API middleware, working with the API server, HTTP client (fetcher), API resources, or OpenAPI generation. Covers both @stacksjs/api utilities and the stacks-api server implementation.
stacks-arraysUse when working with array utilities in Stacks — statistical operations (average, median, mode, standard deviation, z-score, percentile, covariance), array manipulation (unique, flatten, partition, shuffle, sample, move), containment checks, or the Arr facade. Covers @stacksjs/arrays.
stacks-authUse when implementing authentication, authorization, passkeys, TOTP/2FA, RBAC, gates, policies, session auth, token management, email verification, password resets, or rate limiting in a Stacks application. Covers the @stacksjs/auth package, config/auth.ts, app/Gates.ts, and app/Middleware/.
stacks-auto-importsUse when working with the Stacks auto-import system — understanding how browser and server auto-imports work, configuring auto-imported functions/models/composables, the auto-import manifests, type generation, or how globals are injected. Covers the auto-import pipeline at storage/framework/auto-imports/.
stacks-brandkitUse when defining or exploring a brand identity for a Stacks project and you want premium brand-kit REFERENCE IMAGES first - logo directions, palettes, typography specimens, identity boards, and application/mockup decks. Generates IMAGES ONLY, never code. Trained for minimalist, cinematic, editorial, dark-tech, luxury, cultural, security, gaming, developer-tool, and consumer-app brand systems with intentional logo concepting, sparse type, strong symbolic meaning, and disciplined palettes a coding step can build against.
stacks-browseUse for headless browser QA on Stacks applications — navigation, screenshots, responsive testing, console/network monitoring, and accessibility snapshots. Dependency-free: drives a system browser over the Chrome DevTools Protocol using only Bun (no Playwright/Puppeteer). Invoke with /stacks-browse.