Last checked with Wasp 0.24.
This guide depends on external libraries or services, so it may become outdated over time. We do our best to keep it up to date, but make sure to check their documentation for any changes.Migrating from the Wasp DSL
Wasp used to have its own configuration language, the Wasp DSL, which you wrote in a main.wasp file. Start with Wasp 0.24, the Wasp DSL is now retired in favor of the Wasp Spec: a main.wasp.ts file written in TypeScript.
The mapping below is mechanical. You can give the Wasp Spec reference and your main.wasp to the LLM of your choice and ask it to rewrite it following that reference. We had great results with this.
New features
Just TypeScript
The Wasp DSL was a custom language, so it needed its own IDE extension for highlighting and autocompletion, and you couldn't use the JavaScript ecosystem inside it. The Wasp Spec is just TypeScript, so:
- No special IDE extension is needed. You get type checking, autocompletion, and go-to-definition from your editor's regular TypeScript support.
- You can
importand use npm packages, environment variables, and your own helpers while building the config. - You can use normal language features (variables, functions, loops, conditionals) to remove repetition from your config.
Multiple files
The Wasp DSL kept your entire configuration in a single main.wasp. The Wasp Spec lets you split it across multiple *.wasp.ts files and import parts between them, so you can keep large configs organized (for example, a separate auth.wasp.ts or payments.wasp.ts next to the feature it configures).
See the Wasp Spec documentation for details.
Changes
Overview
| What | Before | After |
|---|---|---|
| File name | main.wasp | main.wasp.ts |
| Creating an app | app Name { ... } | app({ name, ..., parts: [] }) |
| Configuring the app | app Name { | app({ |
| Adding app declarations | route X { ... } | app({ |
| Referencing code | import { x } from "@src/..." inside a declaration | import { ... } from "./src/..." with { type: "ref" } at the top level |
| Entity references | Task (identifier) | 'Task' (string) |
App, routes, and pages
In the DSL, a route points to a page by name. In the Wasp Spec, route takes the page object directly.
- Wasp DSL
- Wasp Spec
app todoApp {
title: "ToDo App",
wasp: { version: "^0.24.0" }
}
route MainRoute { path: "/", to: MainPage }
page MainPage {
component: import { MainPage } from "@src/MainPage",
authRequired: true
}
import { app, page, route } from '@wasp.sh/spec'
import { MainPage } from './src/MainPage' with { type: "ref" }
export default app({
name: 'todoApp',
title: 'ToDo App',
wasp: { version: '^0.24.0' },
parts: [
route('MainRoute', '/', page(MainPage, { authRequired: true })),
],
})
Note that route no longer references a page by name (to: MainPage); it takes the page(...) object directly.
Queries and actions
- Wasp DSL
- Wasp Spec
query getTasks {
fn: import { getTasks } from "@src/queries",
entities: [Task]
}
action createTask {
fn: import { createTask } from "@src/actions",
entities: [Task]
}
import { action, app, query } from '@wasp.sh/spec'
import { getTasks } from './src/queries' with { type: "ref" }
import { createTask } from './src/actions' with { type: "ref" }
export default app({
// ...
parts: [
query(getTasks, { entities: ['Task'] }),
action(createTask, { entities: ['Task'] }),
],
})
APIs: httpRoute becomes positional arguments
The DSL's httpRoute: (GET, "/path") becomes the first two arguments of api.
- Wasp DSL
- Wasp Spec
apiNamespace bar {
middlewareConfigFn: import { barNamespaceMiddlewareFn } from "@src/apis",
path: "/bar"
}
api barBaz {
fn: import { barBaz } from "@src/apis",
auth: false,
entities: [Task],
httpRoute: (GET, "/bar/baz")
}
import { api, apiNamespace, app } from '@wasp.sh/spec'
import { barBaz, barNamespaceMiddlewareFn } from './src/apis' with { type: "ref" }
export default app({
// ...
parts: [
apiNamespace('/bar', {
middlewareConfigFn: barNamespaceMiddlewareFn,
}),
api('GET', '/bar/baz', barBaz, { auth: false, entities: ['Task'] }),
],
})
Jobs: perform is flattened
The DSL's perform: { fn, executorOptions } is flattened: fn becomes the first argument and executorOptions becomes performExecutorOptions.
- Wasp DSL
- Wasp Spec
job mySpecialJob {
executor: PgBoss,
perform: {
fn: import { foo } from "@src/jobs/bar",
executorOptions: { pgBoss: {=json { "retryLimit": 1 } json=} }
},
entities: [Task]
}
import { app, job } from '@wasp.sh/spec'
import { foo } from './src/jobs/bar' with { type: "ref" }
export default app({
// ...
parts: [
job(foo, {
executor: 'PgBoss',
entities: ['Task'],
performExecutorOptions: { pgBoss: { retryLimit: 1 } },
}),
],
})
CRUD
- Wasp DSL
- Wasp Spec
crud tasks {
entity: Task,
operations: {
getAll: {},
create: { overrideFn: import { createTask } from "@src/actions" }
}
}
import { app, crud } from '@wasp.sh/spec'
import { createTask } from './src/actions' with { type: "ref" }
export default app({
// ...
parts: [
crud('tasks', 'Task', {
getAll: {},
create: { overrideFn: createTask },
}),
],
})
Top-level config: auth, server, client, db, emailSender, webSocket
These were top-level fields of the app declaration's dictionary in the DSL. In the Wasp Spec they are keys of the app({ ... }) object.
- Wasp DSL
- Wasp Spec
app todoApp {
title: "ToDo App",
wasp: { version: "^0.24.0" },
auth: {
userEntity: User,
methods: { google: {} },
onAuthFailedRedirectTo: "/login"
},
client: {
rootComponent: import App from "@src/App"
},
emailSender: {
provider: SMTP,
defaultFrom: { email: "hi@example.com" }
}
}
import { app } from '@wasp.sh/spec'
import App from './src/App' with { type: "ref" }
export default app({
name: 'todoApp',
title: 'ToDo App',
wasp: { version: '^0.24.0' },
auth: {
userEntity: 'User',
methods: { google: {} },
onAuthFailedRedirectTo: '/login',
},
client: {
rootComponent: App,
},
emailSender: {
provider: 'SMTP',
defaultFrom: { email: 'hi@example.com' },
},
parts: [],
})
How to migrate
These steps assume your project is already on Wasp ^0.24.0. If it isn't, follow the migration guide first.
Wasp validates the Wasp Spec support files during migration, including the required package.json entries, tsconfig.wasp.json options, and tsconfig.src.json exclusions.
-
Rename
tsconfig.jsontotsconfig.src.jsonand make it exclude Wasp Spec files:tsconfig.src.json{// ..."include": ["src"],"exclude": ["**/*.wasp.ts"]} -
Create a new
tsconfig.jsonthat references the other two configs:tsconfig.json{"files": [],"references": [{ "path": "./tsconfig.src.json" },{ "path": "./tsconfig.wasp.json" }]} -
Create a
tsconfig.wasp.jsonwith the required compiler options and the Wasp Spec includes:tsconfig.wasp.json{"compilerOptions": {"target": "ES2022","module": "esnext","moduleResolution": "bundler","jsx": "preserve","strict": true,"isolatedModules": true,"moduleDetection": "force","skipLibCheck": true,"allowJs": true,"noEmit": true,"lib": ["ES2023"]},"include": ["main.wasp.ts", "**/*.wasp.ts"]} -
Add the required
devDependenciesto yourpackage.json:package.json{"devDependencies": {"@types/node": "^24.0.0","@wasp.sh/spec": "file:.wasp/spec"}}Keep your existing dependencies, and add these entries.
@types/nodeis required because the Wasp Spec runs in a Node.js environment, and@wasp.sh/specprovides the local Wasp Spec API package. -
Run
wasp install. -
Rename
main.wasptomain.wasp.old. -
Create a
main.wasp.tsfile with the following content:main.wasp.tsimport { app } from '@wasp.sh/spec'export default app({name: "myAppName",parts: []}) -
Rewrite your config:
You can use the mapping above. Top-level concerns (e.g.
auth,server,client,db,emailSender,webSocket) become keys of theapp({ ... })object; pages, routes, queries, actions, APIs, jobs, and CRUDs go into thepartsarray. -
Run your app with
wasp start. If everything is correct, your app should behave exactly as before.
At some points, when the Spec needs to be regenerated, Wasp will tell you to run wasp install before being able to start the app. Usually, this might happen when upgrading Wasp versions, running wasp clean, or removing the node_modules folder.
- Delete
main.wasp.oldonce you're sure the new config works.
See the full Wasp Spec reference for every option. Got stuck? Reach out on our Discord and we'll help.