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 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: a function-based API where you call app({ ... }) once and list everything in a spec property.
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โ
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 Config
- Wasp Spec
const mainPage = app.page("MainPage", {
component: { importDefault: "MainPage", from: "@src/MainPage" },
});
app.query("getTasks", {
fn: { import: "getTasks", from: "@src/queries" },
});
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 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 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({ |
| Adding app specifications | app.route(...); app.query(...); app.action(...); etc | app({ |
| Imports | { import, from } | import { ... } from "./src/..." with { type: "ref" }; |
| Package name | wasp-config | @wasp.sh/spec |
App and specificationsโ
- TS Config
- Wasp Spec
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;
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 Config
- Wasp Spec
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" },
});
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 Config
- Wasp Spec
app.job("mySpecialJob", {
executor: "PgBoss",
perform: {
fn: { import: "foo", from: "@src/jobs/bar" },
executorOptions: { pgBoss: { retryLimit: 1 } },
},
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โ
- TS Config
- Wasp Spec
app.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 configured with mutating method calls. They are now keys of the app({ ... }) object.
- TS Config
- Wasp Spec
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;
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 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.
-
Update your
package.jsonwith the new dependencies:- Before
- After
package.json{// ..."devDependencies": {// ..."wasp-config": "file:.wasp/wasp-config"}}package.json{// ..."devDependencies": {// ..."@types/node": "^24.0.0","@wasp.sh/spec": "file:.wasp/spec"}}Keep your existing dependencies, replace
wasp-configwith@wasp.sh/spec, and add@types/node.@types/nodeis required because the Wasp Spec runs in a Node.js environment. -
Update your
tsconfig.wasp.jsonand make sure it includes the following settings: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"]} -
Make sure your
tsconfig.src.jsonexcludes Wasp Spec files:tsconfig.src.json{// ..."include": ["src"],"exclude": ["**/*.wasp.ts"]} -
Run
wasp install. -
Rewrite
main.wasp.ts:Replace
new App(...)and theapp.*(...)method calls with a singleapp({ ... })call whosespecproperty holds the specifications (see the mapping above), and update the import:- Before
- After
main.wasp.tsimport { App } from "wasp-config";const app = new App("myApp", {title: "My app",wasp: { version: "^0.24.0" },});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
*.wasp.tsfile 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. -
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.
See the full Wasp Spec reference for every option. Got stuck? Reach out on our Discord and we'll help.