Use when working with Stacks server actions - creating actions in app/Actions/, auto-generated API actions from the useApi model trait, the 670 default framework actions (auth, dashboard, commerce, content, deployment, jobs), action request/response handling, or action registration. Covers @stacksjs/actions and storage/framework/defaults/app/Actions/.
Install
npx skillscat add stacksjs/stacks/stacks-actions Install via the SkillsCat registry.
Stacks Actions
Server actions are reusable business logic units invoked from routes, events, or CLI commands.
Key Paths
- Core package:
storage/framework/core/actions/src/ - Application actions:
app/Actions/ - Default framework actions:
storage/framework/defaults/app/Actions/ - Framework actions (generated):
storage/framework/actions/
Creating an Action
// app/Actions/CreateWidget.ts
import { Action } from '@stacksjs/actions'
import { toSnakeCaseKeys } from '@stacksjs/orm'
import { response } from '@stacksjs/router'
export default new Action({
name: 'Create Widget',
description: 'Create a widget',
method: 'POST',
model: Widget,
async handle(request) {
await request.validate()
const widget = await Widget.create(toSnakeCaseKeys(request.all()))
return response.created(widget)
},
})Use the Action class, an explicit HTTP method, and response helpers. Store and
update actions should set model and call request.validate() before persisting
input. Import framework helpers explicitly, following the default actions.
Let handle(request) infer its request type. An explicit broad annotation loses
the model or validation field types that Action can derive. The typed Action
contract lives in core/actions/src/action.ts; the execution pipeline lives incore/router/src/stacks-router.ts.
Validation, lifecycle, and typed clients
An explicit validations object is inferred from its validators and runs
automatically before authorize, before, and handle. A model supplies
the rules used by an explicit request.validate() call; do not assume attaching
a model means every custom handler has already validated all its input.
import { Action } from '@stacksjs/actions'
import { schema } from '@stacksjs/validation'
export default new Action({
method: 'POST',
validations: {
title: { rule: schema.string().min(1).max(120) },
},
async handle(request) {
return { title: request.get('title') }
},
})authorize can return true, false, or an exact Response; false short-circuits
with 403. before can prepare context or return a Response early. Both run
after declared validation. Directly registered actions and action strings use
the same wrapAction pipeline. Register an imported action withcreateTypedRouter when its inferred request/response types should reach acreateTypedClient; lazy strings do not carry that response inference.
responses, responseHeaders, and requestHeaders enrich OpenAPI documentation.
They describe the contract and do not validate the actual response at runtime.
Test the endpoint's wire shape. apiResponse controls JSON formatting;
returning a raw Response owns the wire format and yields unknown client output.
dependencies lazily creates per-action dependencies exposed throughthis.deps. overrideDependencies and resetDependencies let a test replace
external boundaries without module-wide mocking. They do not justify mocking
every internal collaborator or replacing a real database test with a fake.
Precognition: true or ?_validate=1 requests a validation-only probe.Precognition-Validate-Only narrows explicit validation fields. Probes stop
before authorization/hooks/handler side effects, and do not magically run
custom request.validate() calls inside handle.
For event actions, declare invocation: 'event' and infer the payload fromvalidations when supplied. Event discovery calls handle(payload, event);
it does not hand an HTTP Request to an event handler.
Resource Action Contract
Show, update, and destroy actions must distinguish malformed identifiers,
missing records, invalid input, and operational failures:
- Read resource identifiers from
request.getParam('id'), never from the
request body. - Validate its actual identifier contract. Numeric resource IDs require a safe
positive integer; UUIDs or string keys need their own validation. The built-in
Commerce contract returns422for malformed numeric IDs. - Validate update input with the action model, then normalize persisted keys
withtoSnakeCaseKeys(request.all())when the service expects database
column names. - Core update services return
undefinedwhen the row does not exist. Core
destroy services returnfalse. They throw only for invalid domain input,
conflicts, or real operational failures. - Return
404when show or update returnsundefined, or destroy returnsfalse. Never return a successfulnull, an unconditional204, or a
generic500for an absent row. - Preserve domain status codes such as
409for uniqueness conflicts and422for relationship or state validation.
Share identifier and not-found response helpers inside a domain instead of
copying the contract across every resource. The built-in Commerce actions useActions/Commerce/commerce-action.ts as the reference implementation.
Auto-Generated API Actions (useApi Trait)
When a model defines useApi, the runtime registers native CRUD routes and
handlers from the merged model registry. It does not require generated action
files or a separate model-class generation step:
defineModel({
name: 'Product',
traits: {
useApi: {
uri: 'products',
routes: ['index', 'store', 'show', 'update', 'destroy']
}
}
})This generates:
GET /api/products→ Index action (list all)POST /api/products→ Store action (create)GET /api/products/{id}→ Show action (get one)PUT /api/products/{id}→ Update actionDELETE /api/products/{id}→ Destroy action
Default framework action families
Authentication Actions
LoginAction— POST /login (validates email + password, returns token + user)RegisterAction— POST /registerLogoutAction— POST /logout (auth required)RefreshTokenAction— POST /auth/refreshCreateTokenAction— POST /auth/tokenListTokensAction— GET /auth/tokens (auth required)RevokeTokenAction— DELETE /auth/tokens/{id}GetMeAction— GET /me (auth required)PasskeyRegistrationAction— passkey authentication options
Dashboard Settings Actions (40+)
For each settings category (AI, Analytics, App, Cache, Cloud, Database, DNS, Email, Environment, FileSystems, Hashing, Library, Logging, Notifications, Payment, Ports, Queue, SearchEngine, Security, Services, Storage, Team, UI):
Get{Category}SettingsAction— read current settingsUpdate{Category}SettingsAction— update settings
Commerce Actions
- CRUD actions for: Products, Orders, Customers, Payments, Coupons, GiftCards, Reviews, Shipping, DeliveryRoutes, TaxRates, LicenseKeys, etc.
Content Actions
- CRUD actions for: Posts, Pages, Authors, Categories, Tags, Comments
System Actions
HealthAction— GET /health (returns status, uptime, memory, PID, Bun version)GetUserCountAction— user count for dashboardGetSubscriberCountAction— subscriber count- Deployment CRUD actions
- Job monitoring actions
- Notification actions
- Request analytics actions
Action Handler Pattern
Actions receive the enhanced request object:
async handle(request: EnhancedRequest) {
const name = request.get('name') // input value
const email = request.input('email') // alias
const all = request.all() // all input
const user = await request.user() // authenticated user
return { success: true, data: { ... } }
}Using Actions in Routes
// String-based (auto-loaded)
route.post('/users', 'Actions/CreateUser')
// In events (app/Events.ts)
{ 'user:registered': ['SendWelcomeEmail'] } // action name as listenerCLI Commands
buddy make:action [name]— scaffold a new action
Gotchas
- Application actions go in
app/Actions/ - Framework default actions are in
storage/framework/defaults/app/Actions/ - The
handle()method is required — it receives the request object - Actions used as event listeners also have a
handle(event)method - The
useApimodel trait auto-generates CRUD actions + routes - Custom actions retain their own domain contract; generic CRUD also applies
model middleware, ownership, dashboard roles and per-row policy checks. - Actions are resolved dynamically at runtime via string names
- The HealthAction at
/healthis useful for container health checks - Login action returns
{ token: string, user: { id, email, name } } - All dashboard settings actions read/write from the corresponding config files