--- sidebar_label: "Wasp TS Config" comments: true last_checked_with_versions: Wasp: 0.24 --- # Migrating from the Wasp TS Config The first version of configuring Wasp in TypeScript used a **class-based API**: you created an `App` instance with `new App(...)` and registered declarations with mutating method calls like `app.page(...)` and `app.query(...)`. We called this the **TS Config**. Starting with Wasp 0.24, the TS Config is now retired in favor of the [Wasp Spec](../../general/spec.md): a **function-based API** where you call `app({ ... })` once and list everything in a `spec` property. :::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 ### Reference imports In the TS Config you could only reference your code with import objects (`{ import, from }`). The Wasp Spec also supports **reference imports**: import the value with the regular `import` syntax and pass it directly to a specification constructor. ```ts title="main.wasp.ts" const mainPage = app.page("MainPage", { component: { importDefault: "MainPage", from: "@src/MainPage" }, }); app.query("getTasks", { fn: { import: "getTasks", from: "@src/queries" }, }); ``` ```ts title="main.wasp.ts" import MainPage from "./src/MainPage" with { type: "ref" }; import { getTasks } from "./src/queries" with { type: "ref" }; export default app({ // ... spec: [ route("MainRoute", "/", page(MainPage)), query(getTasks), ], }); ``` Import objects still work through the `ref(...)` helper, so you can migrate gradually. See the [Wasp Spec documentation](../../general/spec.md#referencing-your-apps-code) for the supported patterns and their limitations. ### Multiple files The TS Config required your entire configuration to live in a single `main.wasp.ts`. 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 `cards.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 | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | Creating an app | `new App(name, { ... });` | `app({ name, ..., spec: [...] });` | | Configuring the app | `app.auth(...);`
`app.server(...);`
`app.client(...);`
`app.db(...);`
`app.emailSender(...);`
`app.webSocket(...);` |
app(\{
auth: ...,
server: ...,
client: ...,
db: ...,
emailSender: ...,
webSocket: ...,
});
| | Adding app specifications | `app.route(...);`
`app.query(...);`
`app.action(...);`
etc |
app(\{
spec: [
route(...),
query(...),
action(...),
]
});
| | Imports | `{ import, from }` | `import { ... } from "./src/..." with { type: "ref" };` | | Package name | `wasp-config` | `@wasp.sh/spec` | ### App and specifications ```ts title="main.wasp.ts" import { App } from "wasp-config"; const app = new App("todoApp", { title: "ToDo App", wasp: { version: "^0.24.0" }, }); const mainPage = app.page("MainPage", { component: { importDefault: "MainPage", from: "@src/MainPage" }, }); app.route("MainRoute", { path: "/", to: mainPage }); app.query("getTasks", { fn: { import: "getTasks", from: "@src/queries" }, entities: ["Task"], }); export default app; ``` ```ts title="main.wasp.ts" import { app, page, query, route } from "@wasp.sh/spec"; import MainPage from "./src/MainPage" with { type: "ref" }; import { getTasks } from "./src/queries" with { type: "ref" }; export default app({ name: "todoApp", title: "ToDo App", wasp: { version: "^0.24.0" }, spec: [ route("MainRoute", "/", page(MainPage)), query(getTasks, { entities: ["Task"] }), ], }); ``` ### API: `httpRoute` becomes positional arguments ```ts title="main.wasp.ts" app.apiNamespace("bar", { middlewareConfigFn: { import: "barNamespaceMiddlewareFn", from: "@src/apis" }, path: "/bar", }); app.api("barBaz", { fn: { import: "barBaz", from: "@src/apis" }, auth: false, entities: ["Task"], httpRoute: { method: "GET", route: "/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 ```ts title="main.wasp.ts" app.job("mySpecialJob", { executor: "PgBoss", perform: { fn: { import: "foo", from: "@src/jobs/bar" }, executorOptions: { pgBoss: { retryLimit: 1 } }, }, 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 ```ts title="main.wasp.ts" app.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 configured with mutating method calls. They are now keys of the `app({ ... })` object. ```ts title="main.wasp.ts" const app = new App("todoApp", { title: "ToDo App", wasp: { version: "^0.24.0" }, }); app.auth({ userEntity: "User", methods: { google: {} }, onAuthFailedRedirectTo: "/login", }); app.client({ rootComponent: { importDefault: "App", from: "@src/App" }, }); app.emailSender({ provider: "SMTP", defaultFrom: { email: "hi@example.com" }, }); export default app; ``` ```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 an old class-based Wasp TS Config to the new 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. Update your `package.json` with the new dependencies: ```json title="package.json" { // ... "devDependencies": { // ... "wasp-config": "file:.wasp/wasp-config" } } ``` ```json title="package.json" { // ... "devDependencies": { // ... "@types/node": "^24.0.0", "@wasp.sh/spec": "file:.wasp/spec" } } ``` Keep your existing dependencies, replace `wasp-config` with `@wasp.sh/spec`, and add `@types/node`. `@types/node` is required because the Wasp Spec runs in a Node.js environment. 2. Update your `tsconfig.wasp.json` and make sure it includes the following settings: ```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"] } ``` 3. Make sure your `tsconfig.src.json` excludes Wasp Spec files: ```json title="tsconfig.src.json" { // ... "include": ["src"], "exclude": ["**/*.wasp.ts"] } ``` 4. Run `wasp install`. 5. Rewrite `main.wasp.ts`: Replace `new App(...)` and the `app.*(...)` method calls with a single `app({ ... })` call whose `spec` property holds the specifications (see the [mapping above](#changes)), and update the import: ```ts title="main.wasp.ts" import { App } from "wasp-config"; const app = new App("myApp", { title: "My app", wasp: { version: "^0.24.0" }, }); ``` ```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.ts` 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. ::: 6. 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. ::: 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.