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. Starting 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 conversion below is mechanical, so you can let an LLM do the heavy lifting instead. The migration guide has a copyable prompt bundling this guide, the Wasp Spec docs, and the shared migration steps. Once your config is converted, return to the migration guide for the remaining shared steps.
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 specifications between them, so you can keep large apps 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, ..., spec: [...] }); |
| Configuring the app | app Name { | app({ |
| Adding app specifications | 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" },
spec: [
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({
// ...
spec: [
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({
// ...
spec: [
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({
// ...
spec: [
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({
// ...
spec: [
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" },
},
// ...
});
How to migrateโ
These steps convert a Wasp DSL config to the Wasp Spec. Before running wasp install below, make sure your app's Wasp version is ^0.24.0.
After finishing this guide, return to the migration guide if you still need to complete the shared Wasp 0.24 migration steps.
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": ["**/*.wasp.ts", ".wasp/out/types/spec"]} -
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: "myApp",title: "My app",wasp: { version: "^0.24.0" },head: ["<link rel='icon' href='/favicon.ico' />"],spec: [// ...],});noteWhile previously we accepted any
*.waspfile name, with the Wasp Spec the entry file must be namedmain.wasp.ts. You can still split the rest of your config across other*.wasp.tsfiles. -
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 thespecproperty. -
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.