--- sidebar_label: "Wasp DSL" comments: true last_checked_with_versions: Wasp: 0.24 --- # 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](../../general/spec.md): a `main.wasp.ts` file written in TypeScript. :::tip Upgrading from Wasp 0.23 to 0.24? The conversion below is mechanical, so you can let an LLM do the heavy lifting instead. The [migration guide](../../migration-guide.md#use-an-agent-to-do-it-for-you) 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](../../migration-guide.md) 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 `import` and 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](../../general/spec.md#splitting-your-spec-into-multiple-files) 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 \{
auth: \{ ... },
server: \{ ... },
}
|
app(\{
auth: ...,
server: ...,
});
| | Adding app specifications |
route X \{ ... }
query X \{ ... }
action X \{ ... }
|
app(\{
spec: [
route(...),
query(...),
action(...),
]
});
| | 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 title="main.wasp" 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 } ``` ```ts title="main.wasp.ts" 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 title="main.wasp" query getTasks { fn: import { getTasks } from "@src/queries", entities: [Task] } action createTask { fn: import { createTask } from "@src/actions", entities: [Task] } ``` ```ts title="main.wasp.ts" 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 title="main.wasp" 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") } ``` ```ts title="main.wasp.ts" 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 title="main.wasp" job mySpecialJob { executor: PgBoss, perform: { fn: import { foo } from "@src/jobs/bar", executorOptions: { pgBoss: {=json { "retryLimit": 1 } json=} } }, entities: [Task] } ``` ```ts title="main.wasp.ts" 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 title="main.wasp" crud tasks { entity: Task, operations: { getAll: {}, create: { overrideFn: import { createTask } from "@src/actions" } } } ``` ```ts title="main.wasp.ts" 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 title="main.wasp" 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" } } } ``` ```ts title="main.wasp.ts" 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](../../migration-guide.md) 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. 1. Rename `tsconfig.json` to `tsconfig.src.json` and make it exclude Wasp Spec files: ```json title="tsconfig.src.json" { // ... "include": ["src"], "exclude": ["**/*.wasp.ts"] } ``` 2. Create a new `tsconfig.json` that references the other two configs: ```json title="tsconfig.json" { "files": [], "references": [ { "path": "./tsconfig.src.json" }, { "path": "./tsconfig.wasp.json" } ] } ``` 3. Create a `tsconfig.wasp.json` with the required compiler options and the Wasp Spec includes: ```json title="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"] } ``` 4. Add the required `devDependencies` to your `package.json`: ```json title="package.json" { // ... "devDependencies": { // ... // highlight-next-line "@types/node": "^24.0.0", // highlight-next-line "@wasp.sh/spec": "file:.wasp/spec" } } ``` Keep your existing dependencies, and add these entries. `@types/node` is required because the Wasp Spec runs in a Node.js environment, and `@wasp.sh/spec` provides the local Wasp Spec API package. 5. Run `wasp install`. 6. Rename `main.wasp` to `main.wasp.old`. 7. Create a `main.wasp.ts` file with the following content: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec"; export default app({ name: "myApp", title: "My app", wasp: { version: "^0.24.0" }, head: [""], spec: [ // ... ], }); ``` :::note While previously we accepted any `*.wasp` file name, with the Wasp Spec the entry file must be named `main.wasp.ts`. You can still split the rest of your config across other `*.wasp.ts` files. ::: 8. Rewrite your config: You can use the mapping above. Top-level concerns (e.g. `auth`, `server`, `client`, `db`, `emailSender`, `webSocket`) become keys of the `app({ ... })` object; pages, routes, queries, actions, APIs, jobs, and CRUDs go into the `spec` property. 9. Run your app with `wasp start`. If everything is correct, your app should behave exactly as before. :::note 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. ::: 10. Delete `main.wasp.old` once you're sure the new config works. See the full [Wasp Spec reference](../../general/spec.md#reference) for every option. Got stuck? Reach out on our [Discord](https://discord.gg/rzdnErX) and we'll help.