# Wasp 0.25 Full Documentation This is the full documentation for the latest version of Wasp. For other versions, see the links below. ## Full Documentation by Version - [latest (currently 0.25)](https://wasp.sh/llms-full.txt) - [0.25](https://wasp.sh/llms-full-0.25.txt) - [0.24](https://wasp.sh/llms-full-0.24.txt) - [0.23](https://wasp.sh/llms-full-0.23.txt) - [0.22](https://wasp.sh/llms-full-0.22.txt) - [0.21](https://wasp.sh/llms-full-0.21.txt) - [0.20](https://wasp.sh/llms-full-0.20.txt) - [0.19](https://wasp.sh/llms-full-0.19.txt) - [0.18](https://wasp.sh/llms-full-0.18.txt) - [0.17](https://wasp.sh/llms-full-0.17.txt) - [0.16](https://wasp.sh/llms-full-0.16.txt) - [0.15](https://wasp.sh/llms-full-0.15.txt) - [0.14](https://wasp.sh/llms-full-0.14.txt) - [0.13](https://wasp.sh/llms-full-0.13.txt) - [0.12](https://wasp.sh/llms-full-0.12.txt) - [0.11.8](https://wasp.sh/llms-full-0.11.8.txt) --- # Docs ## Getting Started / Introduction :::note If you are looking for the installation instructions, check out the [Quick Start](https://wasp.sh/docs/quick-start) section. ::: We will give a brief overview of what Wasp is, how it works on a high level and when to use it. ### Wasp is a tool to build modern web applications It is an opinionated way of building **full-stack web applications**. It takes care of all three major parts of a web application: **client** (front-end), **server** (back-end) and **database**. #### Works well with your existing stack Wasp is not trying to do everything at once but rather focuses on the complexity that arises from connecting all the parts of the stack (client, server, database, deployment). Wasp is using **React**, **Node.js** and **Prisma** under the hood and relies on them to define web components and server queries and actions. #### Wasp's secret sauce At the core is the Wasp compiler which takes the Wasp Spec and your Javascript code and outputs the client app, server app and deployment code. ![](https://wasp.sh/img/lp/wasp-compilation-diagram.png) How the magic happens ๐ŸŒˆ The cool thing about having a compiler that understands your code is that it can do a lot of things for you. Define your app in the Wasp file and get: - login and signup with Auth UI components, - full-stack type safety, - e-mail sending, - async processing jobs, - React Query powered data fetching, - security best practices, - and more. You don't need to write any code for these features, Wasp will take care of it for you ๐Ÿคฏ And what's even better, Wasp also maintains the code for you, so you don't have to worry about keeping up with the latest security best practices. As Wasp updates, so does your app. ### So what does the code look like? Let's say you want to build a web app that allows users to **create and share their favorite recipes**. Let's start with the `main.wasp.ts` file: it is the central spec file of your app, where you describe the app from the high level. Let's give our app a title and let's immediately turn on the full-stack authentication via username and password: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "RecipeApp", wasp: { version: "^0.25" }, title: "My Recipes", head: [""], auth: { methods: { usernameAndPassword: {} }, onAuthFailedRedirectTo: "/login", userEntity: "User", }, // ... }) ``` Let's then add the data models for your recipes. Wasp understands and uses the models from the `schema.prisma` file. We will want to have Users and Users can own Recipes: ```prisma title="schema.prisma" ... // Data models are defined using Prisma Schema Language. model User { id Int @id @default(autoincrement()) recipes Recipe[] } model Recipe { id Int @id @default(autoincrement()) title String description String? userId Int user User @relation(fields: [userId], references: [id]) } ``` Next, let's define how to do something with these data models! We do that by defining Operations, in this case, a Query `getRecipes` and Action `addRecipe`, which are in their essence Node.js functions that execute on the server and can, thanks to Wasp, very easily be called from the client. First, we define these Operations in our `main.wasp.ts` file, so Wasp knows about them and can "beef them up": ```ts title="main.wasp.ts" import { action, app, query } from "@wasp.sh/spec" import { getRecipes, addRecipe } from "./src/recipe/operations" with { type: "ref" } export default app({ // ... spec: [ // ... // Queries have automatic cache invalidation and are type-safe. query(getRecipes, { entities: ["Recipe"] }), // Actions are type-safe and can be used to perform side-effects. action(addRecipe, { entities: ["Recipe"] }), ], }) ``` ... and then implement them in our Javascript (or TypeScript) code (we show just the query here, using TypeScript): ```ts title="src/recipe/operations.ts" // Wasp generates the types for you. import { type GetRecipes } from "wasp/server/operations"; import { type Recipe } from "wasp/entities"; export const getRecipes: GetRecipes<{}, Recipe[]> = async (_args, context) => { return context.entities.Recipe.findMany( // Prisma query { where: { user: { id: context.user.id } } } ); }; export const addRecipe ... ``` Now we can very easily use these in our React components! For the end, let's create a home page of our app. First, we define it in `main.wasp.ts`: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { HomePage } from "./src/pages/HomePage" with { type: "ref" } export default app({ // ... spec: [ // ... route("HomeRoute", "/", page(HomePage, { authRequired: true, // Will send user to /login if not authenticated. }) ), ], }) ``` and then implement it as a React component in JS/TS (that calls the Operations we previously defined): ```tsx title="src/pages/HomePage.tsx" import { useQuery, getRecipes } from "wasp/client/operations" import { type User } from "wasp/entities" export function HomePage({ user }: { user: User }) { // Due to full-stack type safety, `recipes` will be of type `Recipe[]` here. const { data: recipes, isLoading } = useQuery(getRecipes) // Calling our query here! if (isLoading) { return
Loading...
} return (

Recipes

) } ``` And voila! We are listing all the recipes in our app ๐ŸŽ‰ This was just a quick example to give you a taste of what Wasp is. For step by step tour through the most important Wasp features, check out the [Todo App tutorial](https://wasp.sh/docs/tutorial/create). :::note Above we skipped defining `/login` and `/signup` pages to keep the example a bit shorter, but those are very simple to do by using Wasp's Auth UI feature. ::: ### When to use Wasp Wasp addresses the same core problems that typical web app frameworks are addressing, and it in big part [looks, swims and quacks](https://en.wikipedia.org/wiki/Duck_test) like a web app framework. #### Best used for - building full-stack web apps (like e.g. Airbnb or Asana) - quickly starting a web app with industry best practices - to be used alongside modern web dev stack (React and Node.js are currently supported) #### Avoid using Wasp for - building static/presentational websites - to be used as a no-code solution - to be a solve-it-all tool in a single language ### Wasp is a spec-driven framework Wasp does not match typical expectations of a web app framework: it is not just a set of libraries. You describe your app in the `main.wasp.ts` spec file, and the compiler uses that spec together with your React, Node.js, and Prisma code to generate the application structure and glue code. This spec-driven approach lets Wasp focus on one purpose: **building modern web applications with 10x less code and less stack-specific knowledge**. ## Getting Started / Quick Start ### Installation Welcome, new Waspeteer ๐Ÿ! Let's create and run our first Wasp app in 3 short steps: 1. **To install Wasp on Linux / OSX / WSL (Windows), open your terminal and run:** ```shell npm i -g @wasp.sh/wasp-cli@latest ``` โ„น๏ธ Wasp requires Node.js and npm, which are usually installed together: check below for [more details](#requirements). 2. **Then, create a new app by running:** ```shell wasp new ``` 3. **Finally, run the app:** ```shell cd wasp start ``` That's it ๐ŸŽ‰ You have successfully created a new full-stack web app at and Wasp is serving both frontend and backend for you. But don't stop there! Turn your coding agent into a Wasp framework expert in the next step. 4. **Install Agent Plugin / Skills:** **Claude Code:** ```bash claude plugin marketplace add wasp-lang/wasp-agent-plugins claude plugin install wasp@wasp-agent-plugins --scope project ``` **Other Agents (Cursor, Codex, Gemini, Copilot, OpenCode, etc.):** ```bash npx skills add wasp-lang/wasp-agent-plugins ``` **Initialize the plugin / skills:** Invoke the `/wasp-plugin-init` skill to add Wasp knowledge to your agent's memory file (e.g. `CLAUDE.md`, `AGENTS.md`): ```bash Run the '/wasp-plugin-init' skill. ``` For more info check out the [Wasp Agent Plugin / Skills](https://wasp.sh/docs/wasp-ai/coding-agent-plugin) page. :::note[Something Unclear?] Check [More Details](#more-details) section below if anything went wrong with the installation, or if you have additional questions. ::: :::tip[Having trouble running Wasp?] If you get stuck with a weird error while developing with Wasp, try running `wasp clean` - this is the Wasp equivalent of "turning it off and on again"! Do however let us know about the issue on our GitHub repo or Discord server. ::: #### What next? - [ ] ๐Ÿ‘‰ **Check out the [Todo App tutorial](https://wasp.sh/docs/tutorial/create), which will take you through all the core features of Wasp!** ๐Ÿ‘ˆ - [ ] [Setup your editor](https://wasp.sh/docs/editor-setup) for working with Wasp. - [ ] Join us on [Discord](https://discord.gg/rzdnErX)! Any feedback or questions you have, we are there for you. - [ ] Follow Wasp development by subscribing to our newsletter: . We usually send 1 per month, and [Matija](https://github.com/matijaSos) does his best to unleash his creativity to make them engaging and fun to read \:D! --- ### More details #### Requirements You must have Node.js (and NPM) installed on your machine and available in `PATH`. A version of Node.js must be >= 24.14.1. If you need it, we recommend using [nvm](https://github.com/nvm-sh/nvm) for managing your Node.js installation version(s). A quick guide on installing/using nvm Install nvm via your OS package manager (`apt`, `pacman`, `homebrew`, ...) or via the [nvm](https://github.com/nvm-sh/nvm#install--update-script) install script. Then, install a version of Node.js that you need: ```shell nvm install 24 ``` Finally, whenever you need to ensure a specific version of Node.js is used, run: ```shell nvm use 24 ``` to set the Node.js version for the current shell session. You can run ```shell node -v ``` to check the version of Node.js currently being used in this shell session. Check NVM repo for more details: . #### Installation {#detailed-installation} **Linux / macOS (npm)** Open your terminal and run: ```shell npm i -g @wasp.sh/wasp-cli@latest ``` :::note[Looking for the old installer script?] Check out the **[Legacy installer guide](https://wasp.sh/docs/guides/legacy/installer)** for instructions on how to keep using it or how to migrate to npm-based installation. ::: **Windows** With Wasp for Windows, we are almost there: Wasp is successfully compiling and running on Windows but there is a bug or two stopping it from fully working. Check it out [here](https://github.com/wasp-lang/wasp/issues/48) if you are interested in helping. In the meantime, the best way to start using Wasp on Windows is by using [WSL](https://learn.microsoft.com/en-us/windows/wsl/install). Once you set up Ubuntu on WSL, just follow Linux instructions for installing Wasp. You can refer to this [article](https://wasp.sh/blog/2023/11/21/guide-windows-development-wasp-wsl) if you prefer a step by step guide to using Wasp in WSL environment. If you need further help, reach out to us on [Discord](https://discord.gg/rzdnErX) - we have some community members using WSL that might be able to help you. :::caution If you are using WSL2, make sure that your Wasp project is not on the Windows file system, but instead on the Linux file system. Otherwise, Wasp won't be able to detect file changes, due to the [issue in WSL2](https://github.com/microsoft/WSL/issues/4739). If you use VS Code, install the [WSL extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-wsl) and open the project from inside WSL (run `code .` in the project directory). Without it, TypeScript support in VS Code won't work on WSL2. ::: **From source** If the other methods are not working for you or your OS is not supported, you can try building Wasp from the source. To install from source, you need to clone the [wasp repo](https://github.com/wasp-lang/wasp), install [Cabal](https://cabal.readthedocs.io/en/stable/getting-started.html) on your machine and then run `cabal install` from the `waspc/` dir. If you have never built Wasp before, this might take some time due to `cabal` downloading dependencies for the first time. Check [waspc/](https://github.com/wasp-lang/wasp/tree/main/waspc) for more details on building Wasp from the source. ## Getting Started / Editor Setup :::note This page assumes you have already installed Wasp. If you do not have Wasp installed yet, check out the [Quick Start](https://wasp.sh/docs/quick-start) guide. ::: Wasp Spec files are TypeScript files, so editor support comes from your editor's regular TypeScript tooling. ### TypeScript support Use any editor with TypeScript language service support (VS Code, Zed, etc.), this gives you: - type checking and diagnostics for `main.wasp.ts` - autocompletion for `@wasp.sh/spec` functions and options - go to definition for [reference imports](https://wasp.sh/docs/general/spec#reference-imports) - import path checks for files in `src` For `schema.prisma`, install the [Prisma extension for VS Code](https://marketplace.visualstudio.com/items?itemName=Prisma.prisma) or the equivalent Prisma extension for your editor. If your editor reports stale type or import errors after changing Wasp files, restart the TypeScript server. In VS Code, open the command palette and run *"TypeScript: Restart TS Server."* ## Tutorial / 1. Creating a New Project :::info You'll need to have the latest version of Wasp installed locally to follow this tutorial. If you haven't installed it yet, check out the [QuickStart](https://wasp.sh/docs/quick-start) guide! ::: In this section, we'll guide you through the process of creating a simple Todo app with Wasp. In the process, we'll take you through the most important and useful features of Wasp. ![How the Todo App will work once it is finished](https://wasp.sh/img/todo-app-tutorial-intro.gif) If you get stuck at any point (or just want to chat), reach out to us on [Discord](https://discord.gg/rzdnErX) and we will help you! You can find the complete code of the app we're about to build [here](https://github.com/wasp-lang/wasp/tree/release/examples/tutorials/TodoApp). ### Creating a Project To setup a new Wasp project, run the following command in your terminal: ```sh wasp new TodoApp -t minimal ``` We are using the `minimal` template because we're going to implement the app from scratch, instead of the more full-featured default template. Enter the newly created directory and start the development server: ```sh cd TodoApp wasp start ``` `wasp start` will take a bit of time to start the server the first time you run it in a new project. You will see log messages from the client, server, and database setting themselves up. When everything is ready, a new tab should open in your browser at `http://localhost:3000` with a simple placeholder page: ![Screenshot of the Wasp minimal starter app](https://wasp.sh/img/wasp-new-screenshot.png) Wasp has generated for you the full front-end and back-end code of the app! Next, we'll take a closer look at how the project is structured. ### A note on supported languages Wasp supports both JavaScript and TypeScript out of the box, but you are free to choose between or mix JavaScript and TypeScript as you see fit. We'll provide you with both JavaScript and TypeScript code in this tutorial. Code blocks will have a toggle to switch between vanilla JavaScript and TypeScript. Try it out: :::note[Welcome to TypeScript!] You are now reading the TypeScript version of the docs. The site will remember your preference as you switch pages. You'll have a chance to change the language on every code snippet - both the snippets and the text will update accordingly. ::: ## Tutorial / 2. Project Structure After creating a new Wasp project, your project should look like this: ```python . โ”œโ”€โ”€ AGENTS.md # Instructions for AI coding agents. โ”œโ”€โ”€ CLAUDE.md # Symlink to AGENTS.md. โ”œโ”€โ”€ main.wasp.ts # Your Wasp Spec goes here. โ”œโ”€โ”€ package.json # Your dependencies and project info go here. โ”œโ”€โ”€ public # Your static files (e.g., images, favicon) go here. โ”‚ย ย  โ””โ”€โ”€ favicon.ico โ”œโ”€โ”€ schema.prisma # Your database models go here. โ”œโ”€โ”€ src # Your source code (TS/React/Node.js) goes here. โ”‚ย ย  โ”œโ”€โ”€ Main.css โ”‚ย ย  โ”œโ”€โ”€ MainPage.tsx โ”‚ย ย  โ”œโ”€โ”€ assets โ”‚ย ย  โ”‚ย ย  โ””โ”€โ”€ wasp-logo-rounded.svg โ”‚ย ย  โ””โ”€โ”€ vite-env.d.ts โ”œโ”€โ”€ tsconfig.json โ”œโ”€โ”€ tsconfig.src.json โ”œโ”€โ”€ tsconfig.wasp.json โ””โ”€โ”€ vite.config.ts ``` By *your code*, we mean the *"the code you write"*, as opposed to the code generated by Wasp. Wasp allows you to organize and structure your code however you think is best - there's no need to separate client files and server files into different directories. We'd normally recommend organizing code by features (i.e., vertically). However, since this tutorial contains only a handful of files, there's no need for fancy organization. We'll keep it simple by placing everything in the root `src` directory. Many other files (e.g., `tsconfig.json`, `tsconfig.src.json`, `tsconfig.wasp.json`, `vite-env.d.ts`, etc.) help Wasp and the IDE improve your development experience with autocompletion, IntelliSense, and error reporting. The `vite.config.ts` file is used to configure [Vite](https://vitejs.dev/guide/), Wasp's build tool of choice. We won't be customizing the Vite setup in this tutorial, so you can safely ignore the file. Still, if you ever end up wanting more control over Vite, you'll find everything you need to know in [custom Vite config docs](https://wasp.sh/docs/project/custom-vite-config). The `schema.prisma` file is where you define your database schema using [Prisma](https://www.prisma.io/). We'll cover this a bit later in the tutorial. The most important file in the project is `main.wasp.ts`. Wasp uses the configuration within it to perform its magic. Based on what you write, it generates a bunch of code for your database, server-client communication, React routing, and more. Let's take a closer look at `main.wasp.ts` ### `main.wasp.ts` `main.wasp.ts` is your app's Wasp file. It defines the app's central components and helps Wasp to do a lot of the legwork for you. The file exports your app's top-level configuration and a collection of specifications. Each one defines a Route, Page, Query, Action, or other features provided by Wasp. The default `main.wasp.ts` file generated with `wasp new` on the previous page looks like this: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" // This is a reference to your MainPage component. // Read more about how Wasp references your code in the section below. import { MainPage } from "./src/MainPage" with { type: "ref" } export default app({ name: "TodoApp", // Pins the version of Wasp to use. wasp: { version: "^0.25" }, title: "TodoApp", // Used as the browser tab title. head: [""], // Add your specs here so Wasp knows to register them. spec: [ route("RootRoute", "/", page(MainPage)), ], }) ``` #### Referencing code from `src` When `main.wasp.ts` needs to point to your React components or Node.js functions, it uses imports like this: ```ts import { MainPage } from "./src/MainPage" with { type: "ref" } ``` Notice the `with { type: "ref" }` part at the end of the import statement. This tells Wasp to treat the import as a reference to your app's code, without running the imported code. For more details and examples, see [reference imports](https://wasp.sh/docs/general/spec#reference-imports). #### Specifications This Wasp app uses three specifications: - [**app**](https://wasp.sh/docs/api/@wasp.sh/spec/functions/app): Top-level configuration information about your app. - [**route**](https://wasp.sh/docs/api/@wasp.sh/spec/functions/route): Describes which path each page should be accessible from. - [**page**](https://wasp.sh/docs/api/@wasp.sh/spec/functions/page): Defines a web page and the React component that gets rendered when the page is loaded. In the next section, we'll explore how **route** and **page** work together to build your web app. ## Tutorial / 3. Pages & Routes In the default `main.wasp.ts` file created by `wasp new`, there is a **page** and a **route** spec: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { MainPage } from "./src/MainPage" with { type: "ref" } export default app({ // ... spec: [ // We specify that the React implementation of the page is exported from // `src/MainPage.tsx`. Reference imports must point to files inside `src`. route("RootRoute", "/", page(MainPage)), ], }) ``` Together, these specifications tell Wasp that when a user navigates to `/`, it should render the `MainPage` component from `src/MainPage.tsx`. ### The MainPage Component Let's take a look at the React component referenced by the page spec: ```tsx title="src/MainPage.tsx" import Logo from "./assets/wasp-logo-rounded.svg"; import "./Main.css"; export function MainPage() { // ... } ``` This is a regular functional React component. It also imports some CSS and a logo from the `assets` folder. That is all the code you need! Wasp takes care of everything else necessary to define, build, and run the web app. :::tip[Keep Wasp start running] `wasp start` automatically picks up the changes you make, regenerates the code, and restarts the app. So keep it running in the background. It also improves your experience by tracking the working directory and ensuring the generated code/types are up to date with your changes. ::: :::caution[LSP Problems] If you are using TypeScript, your editor may sometimes report type and import errors even while `wasp start` is running. This happens when the TypeScript Language Server gets out of sync with the current code. If you're using VS Code, you can manually restart the language server by opening the command palette and selecting *"TypeScript: Restart TS Server."* Open the command pallete with: - `Ctrl` + `Shift` + `P` if you're on Windows or Linux. - `Cmd` + `Shift` + `P` if you're on a Mac. ::: ### Adding a Second Page To add more pages, you can add another route to your spec. You can even add parameters to the URL path, using [dynamic segments](https://wasp.sh/docs/advanced/routing#dynamic-segments). Let's test this out by adding a new page: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { HelloPage } from "./src/HelloPage" with { type: "ref" } export default app({ // ... spec: [ route("HelloRoute", "/hello/:name", page(HelloPage)), ], }) ``` When a user visits `/hello/their-name`, Wasp renders the component exported from `src/HelloPage.tsx` and you can use the `useParams` hook from `react-router` to access the `name` parameter: ```tsx title="src/HelloPage.tsx" import { useParams } from "react-router"; export const HelloPage = () => { const { name } = useParams<"name">(); return
Here's {name}!
; }; ``` Now you can visit `/hello/johnny` and see "Here's johnny!" :::tip[Type-safe links] Since you are using Typescript, you can benefit from using Wasp's type-safe `Link` component and the `routes` object. Check out the [type-safe links docs](https://wasp.sh/docs/advanced/links) for more details. ::: ### Cleaning Up Now that you've seen how Wasp deals with Routes and Pages, it's finally time to build the Todo app. Start by cleaning up the starter project and removing unnecessary code and files. First, remove most of the code from the `MainPage` component: ```tsx title="src/MainPage.tsx" export const MainPage = () => { return
Hello world!
; }; ``` At this point, the main page should look like this: ![Todo App - Hello World](https://wasp.sh/img/todo-app-hello-world.png) You can now delete redundant files: `src/Main.css`, `src/assets/wasp-logo-rounded.svg`, and `src/HelloPage.tsx` (we won't need this page for the rest of the tutorial). Since `src/HelloPage.tsx` no longer exists, remove its route from the `main.wasp.ts` file. Your Wasp file should now look like this: ```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", wasp: { version: "^0.25" }, title: "TodoApp", head: [""], spec: [ route("RootRoute", "/", page(MainPage)), ], }) ``` Excellent work! You now have a basic understanding of Wasp and are ready to start building your TodoApp. We'll implement the app's core features in the following sections. ## Tutorial / 4. Database Entities Entities are one of the most important concepts in Wasp and are how you define what gets stored in the database. Wasp uses Prisma to talk to the database, and you define Entities by defining Prisma models in the `schema.prisma` file. Since our Todo app is all about tasks, we'll define a Task entity by adding a Task model in the `schema.prisma` file: ```prisma title="schema.prisma" // ... model Task { id Int @id @default(autoincrement()) description String isDone Boolean @default(false) } ``` :::note Read more about how Wasp Entities work in the [Entities](https://wasp.sh/docs/data-model/entities) section or how Wasp uses the `schema.prisma` file in the [Prisma Schema File](https://wasp.sh/docs/data-model/prisma-file) section. ::: To update the database schema to include this entity, stop the `wasp start` process, if it's running, and run: ```sh wasp db migrate-dev ``` You'll need to do this any time you change an entity's definition. It instructs Prisma to create a new database migration and apply it to the database. To take a look at the database and the new `Task` entity, run: ```sh wasp db studio ``` This will open a new page in your browser to view and edit the data in your database. ![Todo App - Db studio showing Task schema](https://wasp.sh/img/todo-app-db-studio-task-entity.png) Click on the `Task` entity and check out its fields! We don't have any data in our database yet, but we are about to change that. ## Tutorial / 5. Querying the Database We want to know which tasks we need to do, so let's list them! The primary way of working with Entities in Wasp is with [Queries and Actions](https://wasp.sh/docs/data-model/operations/overview), collectively known as ***Operations***. Queries are used to read an entity, while Actions are used to create, modify, and delete entities. Since we want to list the tasks, we'll want to use a Query. To list the tasks, you must: 1. Create a Query that fetches the tasks from the database. 2. Update the `MainPage.tsx` to use that Query and display the results. ### Defining the Query We'll create a new Query called `getTasks`. We'll need to declare the Query in the Wasp file and write its implementation in TS. #### Specifying a Query We need to add a **query** specification to `main.wasp.ts` so that Wasp knows it exists: ```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({ // ... spec: [ route("RootRoute", "/", page(MainPage)), // Tell Wasp that this query reads from the `Task` entity. Wasp will // automatically update the results of this query when tasks are modified. query(getTasks, { entities: ["Task"] }), ], }) ``` :::note To generate the types used in the next section, make sure that `wasp start` is still running. ::: #### Implementing a Query Next, create a new file called `src/queries.ts` and define the TypeScript function we've just imported in our `query` spec: ```ts title="src/queries.ts" import type { Task } from "wasp/entities"; import type { GetTasks } from "wasp/server/operations"; export const getTasks: GetTasks = async (args, context) => { return context.entities.Task.findMany({ orderBy: { id: "asc" }, }); }; ``` Wasp automatically generates the types `GetTasks` and `Task` based on the contents of `main.wasp.ts`: - `Task` is a type corresponding to the `Task` entity you defined in `schema.prisma`. - `GetTasks` is a generic type Wasp automatically generated based on the `getTasks` Query you defined in `main.wasp.ts`. You can use these types to specify the Query's input and output types. This Query doesn't expect any arguments (its input type is `void`), but it does return an array of tasks (its output type is `Task[]`). Annotating the Queries is optional, but highly recommended because doing so enables **full-stack type safety**. We'll see what this means in the next step. Query function parameters: - `args: object` The arguments the caller passes to the Query. - `context` An object with extra information injected by Wasp. Its type depends on the Query specification. Since the Query spec in `main.wasp.ts` says that the `getTasks` Query uses the `Task` entity, Wasp injected a [Prisma client](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/crud) for the `Task` entity as `context.entities.Task` - we used it above to fetch all the tasks from the database. :::info Queries and Actions are NodeJS functions executed on the server. ::: ### Invoking the Query On the Frontend While we implement Queries on the server, Wasp generates client-side functions that automatically take care of serialization, network calls, and cache invalidation, allowing you to call the server code like it's a regular function. This makes it easy for us to use the `getTasks` Query we just created in our React component: ```tsx title="src/MainPage.tsx" import type { Task } from "wasp/entities"; import { getTasks, useQuery } from "wasp/client/operations"; export const MainPage = () => { const { data: tasks, isLoading, error } = useQuery(getTasks); return (
{tasks && } {isLoading && "Loading..."} {error && "Error: " + error}
); }; const TaskView = ({ task }: { task: Task }) => { return (
{task.description}
); }; const TasksList = ({ tasks }: { tasks: Task[] }) => { if (!tasks?.length) return
No tasks
; return (
{tasks.map((task, idx) => ( ))}
); }; ``` Most of this code is regular React, the only exception being the three special `wasp` imports: - `getTasks` - The client-side Query function Wasp generated based on the `getTasks` spec in `main.wasp.ts`. - `useQuery` - Wasp's [useQuery](https://wasp.sh/docs/data-model/operations/queries#the-usequery-hook-1) React hook, which is based on [react-query](https://github.com/tannerlinsley/react-query)'s hook with the same name. - `Task` - The type for the Task entity defined in `schema.prisma`. Notice how you don't need to annotate the type of the Query's return value: Wasp uses the types you defined while implementing the Query for the generated client-side function. This is **full-stack type safety**: the types on the client always match the types on the server. We could have called the Query directly using `getTasks()`, but the `useQuery` hook makes it reactive: React will re-render the component every time the Query changes. Remember that Wasp automatically refreshes Queries whenever the data is modified. With these changes, you should be seeing the text "No tasks" on the screen: ![Todo App - No Tasks](https://wasp.sh/img/todo-app-no-tasks.png) We'll create a form to add tasks in the next step ๐Ÿช„ ## Tutorial / 6. Modifying Data In the previous section, you learned about using Queries to fetch data. Let's now learn about Actions so you can add and update tasks in the database. In this section, you will create: 1. A Wasp Action that creates a new task. 2. A React form that calls that Action when the user creates a task. ### Creating a New Action Creating an Action is very similar to creating a Query. #### Specifying an Action We must first declare the Action in `main.wasp.ts`: ```ts title="main.wasp.ts" import { action, app, page, query, route } from "@wasp.sh/spec" import { MainPage } from "./src/MainPage" with { type: "ref" } import { getTasks } from "./src/queries" with { type: "ref" } import { createTask } from "./src/actions" with { type: "ref" } export default app({ // ... spec: [ route("RootRoute", "/", page(MainPage)), query(getTasks, { entities: ["Task"] }), action(createTask, { entities: ["Task"] }), ], }) ``` #### Implementing an Action Let's now define a TypeScript function for our `createTask` Action: ```ts title="src/actions.ts" import type { Task } from "wasp/entities"; import type { CreateTask } from "wasp/server/operations"; type CreateTaskPayload = Pick; export const createTask: CreateTask = async ( args, context, ) => { return context.entities.Task.create({ data: { description: args.description }, }); }; ``` Once again, we've annotated the Action with the `CreateTask` and `Task` types generated by Wasp. Just like with queries, defining the types on the implementation makes them available on the frontend, giving us **full-stack type safety**. :::tip We put the function in a new file `src/actions.ts`, but we could have put it anywhere we wanted! There are no limitations here, as long as the reference import in `main.wasp.ts` points to it and the file is located within the `src` directory. ::: ### Invoking the Action on the Client Start by defining a form for creating new tasks. ```tsx title="src/MainPage.tsx" import type { FormEvent } from "react"; import type { Task } from "wasp/entities"; import { createTask, getTasks, useQuery, } from "wasp/client/operations"; // ... MainPage, TaskView, TasksList ... const NewTaskForm = () => { const handleSubmit = async (event: FormEvent) => { event.preventDefault(); try { const target = event.target as HTMLFormElement; const description = target.description.value; target.reset(); await createTask({ description }); } catch (err: any) { window.alert("Error: " + err.message); } }; return (
); }; ``` Unlike Queries, you can call Actions directly (without wrapping them in a hook) because they don't need reactivity. The rest is just regular React code. Finally, because we've previously annotated the Action's server implementation with the correct type, Wasp knows that the `createTask` Action expects a value of type `{ description: string }` (try changing the argument and reading the error message). Wasp also knows that a call to the `createTask` Action returns a `Task` but we are not using it in this example. All that's left now is adding this form to the page component: ```tsx title="src/MainPage.tsx" import type { FormEvent } from "react"; import type { Task } from "wasp/entities"; import { createTask, getTasks, useQuery } from "wasp/client/operations"; export const MainPage = () => { const { data: tasks, isLoading, error } = useQuery(getTasks); return (
{tasks && } {isLoading && "Loading..."} {error && "Error: " + error}
); }; // ... TaskView, TasksList, NewTaskForm ... ``` Great work! You now have a form for creating new tasks. Try creating a "Build a Todo App in Wasp" task and see it appear in the list below. The task is created on the server and saved in the database. Try refreshing the page or opening it in another browser. You'll see the tasks are still there! ![Todo App - creating new task](https://wasp.sh/img/todo-app-new-task.png) :::note[Automatic Query Invalidation] When you create a new task, the list of tasks is automatically updated to display the new task, even though you haven't written any code that does that! Wasp handles these automatic updates under the hood. When you declared the `getTasks` and `createTask` operations, you specified that they both use the `Task` entity. So when `createTask` is called, Wasp knows that the data `getTasks` fetches may have changed and automatically updates it in the background. This means that **out of the box, Wasp keeps all your queries in sync with any changes made through Actions**. This behavior is convenient as a default but can cause poor performance in large apps. While there is no mechanism for overriding this behavior yet, it is something that we plan to include in Wasp in the future. This feature is tracked [here](https://github.com/wasp-lang/wasp/issues/63). ::: ### A Second Action Our Todo app isn't finished if you can't mark a task as done. We'll create a new Action to update a task's status and call it from React whenever a task's checkbox is toggled. Since we've already created one task together, try to create this one yourself. It should be an Action named `updateTask` that receives the task's `id` and its `isDone` status. You can see our implementation below. Solution Declaring the Action in `main.wasp.ts`: ```ts title="main.wasp.ts" import { action, app, page, query, route } from "@wasp.sh/spec" import { MainPage } from "./src/MainPage" with { type: "ref" } import { getTasks } from "./src/queries" with { type: "ref" } import { createTask, updateTask } from "./src/actions" with { type: "ref" } export default app({ // ... spec: [ // ... existing routes and queries action(createTask, { entities: ["Task"] }), action(updateTask, { entities: ["Task"] }), ], }) ``` Implementing the Action on the server: ```ts title="src/actions.ts" import type { Task } from "wasp/entities"; import type { CreateTask, UpdateTask } from "wasp/server/operations"; // ... type UpdateTaskPayload = Pick; export const updateTask: UpdateTask = async ( { id, isDone }, context, ) => { return context.entities.Task.update({ where: { id }, data: { isDone: isDone, }, }); }; ``` You can now call `updateTask` from the React component: ```tsx title="src/MainPage.tsx" import type { FormEvent, ChangeEvent } from "react"; import type { Task } from "wasp/entities"; import { updateTask, createTask, getTasks, useQuery, } from "wasp/client/operations"; // ... MainPage ... const TaskView = ({ task }: { task: Task }) => { const handleIsDoneChange = async (event: ChangeEvent) => { try { await updateTask({ id: task.id, isDone: event.target.checked, }); } catch (error: any) { window.alert("Error while updating task: " + error.message); } }; return (
{task.description}
); }; // ... TasksList, NewTaskForm ... ``` Awesome! You can now mark this task as done. It's time to make one final addition to your app: supporting multiple users. ## Tutorial / 7. Adding Authentication Most modern apps need a way to create and authenticate users. Wasp makes this as easy as possible with its first-class auth support. To add users to your app, you must: - [ ] Create a `User` Entity. - [ ] Tell Wasp to use the *Username and Password* authentication. - [ ] Add login and signup pages. - [ ] Update the main page to require authentication. - [ ] Add a relation between `User` and `Task` entities. - [ ] Modify your Queries and Actions so users can only see and modify their tasks. - [ ] Add a logout button. ### Creating a User Entity Since Wasp manages authentication, it will create [the auth related entities](https://wasp.sh/docs/auth/entities) for you in the background. Nothing to do here! You must only add the `User` Entity to keep track of who owns which tasks: ```prisma title="schema.prisma" // ... model User { id Int @id @default(autoincrement()) } ``` ### Adding Auth to the Project Next, tell Wasp to use full-stack [authentication](https://wasp.sh/docs/auth/overview): ```ts title="main.wasp.ts" import { action, app, page, query, route } from "@wasp.sh/spec" import { MainPage } from "./src/MainPage" with { type: "ref" } import { getTasks } from "./src/queries" with { type: "ref" } import { createTask, updateTask } from "./src/actions" with { type: "ref" } export default app({ // ... auth: { // Tells Wasp which entity to use for storing users. userEntity: "User", methods: { // Enable username and password auth. usernameAndPassword: {}, }, // We'll see how this is used in a bit. onAuthFailedRedirectTo: "/login", }, spec: [ route("RootRoute", "/", page(MainPage)), query(getTasks, { entities: ["Task"] }), action(createTask, { entities: ["Task"] }), action(updateTask, { entities: ["Task"] }), ], }) ``` Don't forget to update the database schema by running: ```sh wasp db migrate-dev ``` By doing this, Wasp will create: - [Auth UI](https://wasp.sh/docs/auth/ui) with login and signup forms. - A `logout()` action. - A React hook `useAuth()`. - `context.user` for use in Queries and Actions. :::info Wasp also supports authentication using [Google](https://wasp.sh/docs/auth/social-auth/google), [GitHub](https://wasp.sh/docs/auth/social-auth/github), and [email](https://wasp.sh/docs/auth/email), with more on the way! ::: ### Adding Login and Signup Pages Wasp creates the login and signup forms for us, but we still need to define the pages to display those forms on. We'll start by declaring the pages in the Wasp file: ```ts title="main.wasp.ts" import { action, app, page, query, route } from "@wasp.sh/spec" import { MainPage } from "./src/MainPage" with { type: "ref" } import { SignupPage } from "./src/SignupPage" with { type: "ref" } import { LoginPage } from "./src/LoginPage" with { type: "ref" } import { getTasks } from "./src/queries" with { type: "ref" } import { createTask, updateTask } from "./src/actions" with { type: "ref" } export default app({ // ... spec: [ route("RootRoute", "/", page(MainPage)), route("SignupRoute", "/signup", page(SignupPage)), route("LoginRoute", "/login", page(LoginPage)), // ... existing queries and actions ], }) ``` Great, Wasp now knows these pages exist! Here's the React code for the pages you've just imported: ```tsx title="src/LoginPage.tsx" import { Link } from "react-router"; import { LoginForm } from "wasp/client/auth"; export const LoginPage = () => { return (

I don't have an account yet (go to signup).
); }; ``` The signup page is very similar to the login page: ```tsx title="src/SignupPage.tsx" import { Link } from "react-router"; import { SignupForm } from "wasp/client/auth"; export const SignupPage = () => { return (

I already have an account (go to login).
); }; ``` :::tip[Type-safe links] Since you are using Typescript, you can benefit from using Wasp's type-safe `Link` component and the `routes` object. Check out the [type-safe links docs](https://wasp.sh/docs/advanced/links) for more details. ::: ### Update the Main Page to Require Auth We don't want users who are not logged in to access the main page, because they won't be able to create any tasks. So let's make the page private by requiring the user to be logged in: ```ts title="main.wasp.ts" import { action, app, page, query, route } from "@wasp.sh/spec" import { MainPage } from "./src/MainPage" with { type: "ref" } import { SignupPage } from "./src/SignupPage" with { type: "ref" } import { LoginPage } from "./src/LoginPage" with { type: "ref" } import { getTasks } from "./src/queries" with { type: "ref" } import { createTask, updateTask } from "./src/actions" with { type: "ref" } export default app({ // ... spec: [ route("RootRoute", "/", page(MainPage, { authRequired: true, })), // ... existing routes, queries, and actions ], }) ``` Now that auth is required for this page, unauthenticated users will be redirected to `/login`, as we specified with `auth.onAuthFailedRedirectTo`. Additionally, when `authRequired` is `true`, the page's React component will be provided a `user` object as prop. ```tsx title="src/MainPage.tsx" import type { AuthUser } from "wasp/auth"; // ... existing imports export const MainPage = ({ user }: { user: AuthUser }) => { const { data: tasks, isLoading, error } = useQuery(getTasks); // ... }; ``` Ok, time to test this out. Navigate to the main page (`/`) of the app. You'll get redirected to `/login`, where you'll be asked to authenticate. Since we just added users, you don't have an account yet. Go to the signup page and create one. You'll be sent back to the main page where you will now be able to see the TODO list! Let's check out what the database looks like. Start the Prisma Studio: ```shell wasp db studio ``` ![Database demonstration - password hashing](https://wasp.sh/img/wasp_user_in_db.gif) You'll notice that we now have a `User` entity in the database alongside the `Task` entity. However, you will notice that if you try logging in as different users and creating some tasks, all users share the same tasks. That's because you haven't yet updated the queries and actions to have per-user tasks. Let's do that next. You might notice some extra Prisma models like `Auth`, `AuthIdentity` and `Session` that Wasp created for you. You don't need to care about these right now, but if you are curious, you can read more about them [here](https://wasp.sh/docs/auth/entities). ### Defining a User-Task Relation First, let's define a one-to-many relation between users and tasks (check the [Prisma docs on relations](https://www.prisma.io/docs/orm/prisma-schema/data-model/relations)): ```prisma title="schema.prisma" // ... model User { id Int @id @default(autoincrement()) tasks Task[] } model Task { id Int @id @default(autoincrement()) description String isDone Boolean @default(false) user User? @relation(fields: [userId], references: [id]) userId Int? } ``` As always, you must migrate the database after changing the Entities: ```sh wasp db migrate-dev ``` :::note We made `user` and `userId` in `Task` optional (via `?`) because that allows us to keep the existing tasks, which don't have a user assigned, in the database. This isn't recommended because it allows an unwanted state in the database (what is the purpose of the task not belonging to anybody?) and normally we would not make these fields optional. Instead, we would do a data migration to take care of those tasks, even if it means just deleting them all. However, for this tutorial, for the sake of simplicity, we will stick with this. ::: ### Updating Operations to Check Authentication Next, let's update the queries and actions to forbid access to non-authenticated users and to operate only on the currently logged-in user's tasks: ```ts title="src/queries.ts" import type { Task } from "wasp/entities"; import { HttpError } from "wasp/server"; import type { GetTasks } from "wasp/server/operations"; export const getTasks: GetTasks = async (args, context) => { if (!context.user) { throw new HttpError(401); } return context.entities.Task.findMany({ where: { user: { id: context.user.id } }, orderBy: { id: "asc" }, }); }; ``` ```ts title="src/actions.ts" import type { Task } from "wasp/entities"; import { HttpError } from "wasp/server"; import type { CreateTask, UpdateTask } from "wasp/server/operations"; type CreateTaskPayload = Pick; export const createTask: CreateTask = async ( args, context, ) => { if (!context.user) { throw new HttpError(401); } return context.entities.Task.create({ data: { description: args.description, user: { connect: { id: context.user.id } }, }, }); }; type UpdateTaskPayload = Pick; export const updateTask: UpdateTask< UpdateTaskPayload, { count: number } > = async (args, context) => { if (!context.user) { throw new HttpError(401); } return context.entities.Task.updateMany({ where: { id: args.id, user: { id: context.user.id } }, data: { isDone: args.isDone }, }); }; ``` :::note Due to how Prisma works, we had to convert `update` to `updateMany` in `updateTask` action to be able to specify the user id in `where`. ::: With these changes, each user should have a list of tasks that only they can see and edit. Try playing around, adding a few users and some tasks for each of them. Then open the DB studio: ```sh wasp db studio ``` ![Database demonstration](https://wasp.sh/img/wasp_db_demonstration.gif) You will see that each user has their tasks, just as we specified in our code! ### Logout Button Last, but not least, let's add the logout functionality: ```tsx title="src/MainPage.tsx" import type { AuthUser } from "wasp/auth"; import { logout } from "wasp/client/auth"; // ... existing imports export const MainPage = ({ user }: { user: AuthUser }) => { // ... return (
{/* ... */}
); }; ``` This is it, we have a working authentication system, and our Todo app is multi-user! ### What's Next? We did it ๐ŸŽ‰ You've followed along with this tutorial to create a basic Todo app with Wasp. You can find the complete code for the TS version of the tutorial [here](https://github.com/wasp-lang/wasp/tree/release/examples/tutorials/TodoAppTs). You should be ready to learn about more complicated features and go more in-depth with the features already covered. Scroll through the sidebar on the left side of the page to see every feature Wasp has to offer. Or, let your imagination run wild and start building your app! โœจ Looking for inspiration? - Get a jump start on your next project with [Starter Templates](https://wasp.sh/docs/project/starter-templates). - Check out our [official examples](https://github.com/wasp-lang/wasp/tree/release/examples). - Make a real-time app with [Web Sockets](https://wasp.sh/docs/advanced/web-sockets). :::note If you notice that some of the features you'd like to have are missing, or have any other kind of feedback, please write to us on [Discord](https://discord.gg/rzdnErX) or create an issue on [Github](https://github.com/wasp-lang/wasp), so we can learn which features to add/improve next ๐Ÿ™ If you would like to contribute or help to build a feature, let us know! You can find more details on contributing [here](https://wasp.sh/docs/contributing). ::: Oh, and do [**subscribe to our newsletter**](https://wasp.sh/#signup)! We usually send one per month, and Matija does his best to unleash his creativity to make them engaging and fun to read \:D! ## Data Model / Entities Entities are the foundation of your app's data model. In short, an Entity defines a model in your database. Wasp uses the excellent [Prisma ORM](https://www.prisma.io/) to implement all database functionality and occasionally enhances it with a thin abstraction layer. This means that you use the `schema.prisma` file to define your database models and relationships. Wasp understands the Prisma schema file and picks up all the models you define there. You can read more about this in the [Prisma Schema File](https://wasp.sh/docs/data-model/prisma-file) section of the docs. In your project, you'll find a `schema.prisma` file in the root directory: ``` . โ”œโ”€โ”€ main.wasp.ts ... โ”œโ”€โ”€ package.json โ”œโ”€โ”€ public โ”œโ”€โ”€ schema.prisma โ”œโ”€โ”€ src โ”œโ”€โ”€ tsconfig.json โ”œโ”€โ”€ tsconfig.src.json โ”œโ”€โ”€ tsconfig.wasp.json โ””โ”€โ”€ vite.config.ts ``` Prisma uses the *Prisma Schema Language*, a simple definition language explicitly created for defining models. The language is declarative and very intuitive. We'll also go through an example later in the text, so there's no need to go and thoroughly learn it right away. Still, if you're curious, look no further than Prisma's official documentation: - [Basic intro and examples](https://www.prisma.io/docs/orm/prisma-schema/overview) - [A more exhaustive language specification](https://www.prisma.io/docs/orm/reference/prisma-schema-reference) ### Defining an Entity A Prisma `model` declaration in the `schema.prisma` file represents a Wasp Entity. Entity vs Model You might wonder why we distinguish between a **Wasp Entity** and a **Prisma model** if they're essentially the same thing right now. While defining a Prisma model is currently the only way to create an Entity in Wasp, the Entity concept is a higher-level abstraction. We plan to expand on Entities in the future, both in terms of how you can define them and what you can do with them. So, think of an Entity as a Wasp concept and a model as a Prisma concept. For now, all Prisma models are Entities and vice versa, but this relationship might evolve as Wasp grows. Here's how you could define an Entity that represents a Task: ```prisma title="schema.prisma" model Task { id String @id @default(uuid()) description String isDone Boolean @default(false) } ``` The above Prisma `model` definition tells Wasp to create a table for storing Tasks where each task has three fields (i.e., the `tasks` table has three columns): - `id` - A string value serving as a primary key. The database automatically generates it by generating a random unique ID. - `description` - A string value for storing the task's description. - `isDone` - A boolean value indicating the task's completion status. If you don't set it when creating a new task, the database sets it to `false` by default. Wasp also exposes a type for working with the created Entity. You can import and use it like this: ```ts import { Task } from "wasp/entities" const task: Task = { ... } // You can also define functions for working with entities function getInfoMessage(task: Task): string { const isDoneText = task.isDone ? "is done" : "is not done" return `Task '${task.description}' is ${isDoneText}.` } ``` Using the `Task` type in `getInfoMessage`'s definition connects the argument's type with the `Task` entity. This coupling removes duplication and ensures the function keeps the correct signature even if you change the entity. Of course, the function might throw type errors depending on how you change it, but that's precisely what you want! Entity types are available everywhere, including the client code: ```ts import { Task } from "wasp/entities" export function ExamplePage() { const task: Task = { id: "some-uuid-1234", description: "Some random task", isDone: false, } return
{task.description}
} ``` The mentioned type safety mechanisms also apply here: changing the task entity in our `schema.prisma` file changes the imported type, which might throw a type error and warn us that our task definition is outdated. You'll learn even more about Entity types when you start using [them with operations](#using-entities-in-operations). #### Working with Entities Let's see how you can define and work with Wasp Entities: 1. Create/update some Entities in the `schema.prisma` file. 2. Run `wasp db migrate-dev`. This command syncs the database model with the Entity definitions the `schema.prisma` file. It does this by creating migration scripts. 3. Migration scripts are automatically placed in the `migrations/` folder. Make sure to commit this folder into version control. 4. Use Wasp's JavaScript API to work with the database when implementing Operations (we'll cover this in detail when we talk about [operations](https://wasp.sh/docs/data-model/operations/overview)). ##### Using Entities in Operations Most of the time, you will be working with Entities within the context of [Operations (Queries & Actions)](https://wasp.sh/docs/data-model/operations/overview). We'll see how that's done on the next page. ##### Using Entities directly If you need more control, you can directly interact with Entities by importing and using the [Prisma Client](https://www.prisma.io/docs/concepts/components/prisma-client/crud). We recommend sticking with conventional Wasp-provided mechanisms, only resorting to directly using the Prisma client only if you need a feature Wasp doesn't provide. You can only use the Prisma Client in your Wasp server code. You can import it like this: ```ts import { prisma } from "wasp/server" prisma.task.create({ description: "Read the Entities doc", isDone: true // almost :) }) ``` :::note[Available Prisma features in the client] While the Prisma Client is not available in your client code, you can still import Prisma there, for accessing type definitions (notably, `enum`s). You can see more information in the overview of [supported Prisma Schema features](https://wasp.sh/docs/data-model/prisma-file#the-enum-blocks). ::: #### Next steps Now that we've seen how to define Entities that represent Wasp's core data model, we'll see how to make the most of them in other parts of Wasp. Keep reading to learn all about Wasp Operations! ## Data Model / Operations / Overview While Entities enable you to define your app's data model and relationships, Operations are all about working with this data. There are two kinds of Operations: [Queries](https://wasp.sh/docs/data-model/operations/queries) and [Actions](https://wasp.sh/docs/data-model/operations/actions). As their names suggest, Queries are meant for reading data, and Actions are meant for changing it (either by updating existing entries or creating new ones). Keep reading to find out all there is to know about Operations in Wasp. ## Data Model / Operations / Queries We'll explain what Queries are and how to use them. If you're looking for a detailed API specification, skip ahead to the [API Reference](#api-reference). You can use Queries to fetch data from the server. They shouldn't modify the server's state. Fetching all comments on a blog post, a list of users that liked a video, information about a single product based on its ID... All of these are perfect use cases for a Query. :::tip Queries are fairly similar to Actions in terms of their API. Therefore, if you're already familiar with Actions, you might find reading the entire guide repetitive. We instead recommend skipping ahead and only reading [the differences between Queries and Actions](https://wasp.sh/docs/data-model/operations/actions#differences-between-queries-and-actions), and consulting the [API Reference](#api-reference) as needed. ::: ### Working with Queries You declare queries in the Wasp file and implement them using NodeJS. Wasp not only runs these queries within the server's context but also creates code that enables you to call them from any part of your codebase, whether it's on the client or server side. This means you don't have to build an HTTP API for your query, manage server-side request handling, or even deal with client-side response handling and caching. Instead, just concentrate on implementing the business logic inside your query, and let Wasp handle the rest! To create a Query, you must: 1. Declare the Query in Wasp using the `query` spec. 2. Define the Query's NodeJS implementation. After completing these two steps, you'll be able to use the Query from any point in your code. #### Specifying Queries To create a Query in Wasp, we begin with a `query` spec. Let's declare two Queries - one to fetch all tasks, and another to fetch tasks based on a filter, such as whether a task is done: ```ts title="main.wasp.ts" import { app, query } from "@wasp.sh/spec" import { getAllTasks, getFilteredTasks } from "./src/queries" with { type: "ref" } export default app({ // ... spec: [ query(getAllTasks), query(getFilteredTasks), ], }) ``` If you want to know about all supported options for the `query` spec, take a look at the [API Reference](#api-reference). :::note When `main.wasp.ts` needs to point to your code, it uses imports like this: ```ts import { MainPage } from "./src/MainPage" with { type: "ref" } ``` Notice the `with { type: "ref" }` part at the end of the import statement. This tells Wasp to treat the import as a reference to your app's code, without running the imported code. For more details and examples, see [reference imports](https://wasp.sh/docs/general/spec#reference-imports). ::: :::info You might have noticed that we told Wasp to import Query implementations that don't yet exist. Don't worry about that for now. We'll write the implementations imported from `queries.ts` in the next section. It's a good idea to start with the high-level concept (the Query spec in the Wasp file) and only then deal with the implementation details (the Query's implementation in JavaScript). ::: After declaring a Wasp Query, Wasp derives the Query's name from the function you pass to `query`. For example, `query(getFilteredTasks)` creates a Query named `getFilteredTasks`. Two important things then happen: - Wasp **generates a server-side NodeJS function** with the Query's name. - Wasp **generates a client-side JavaScript function** with the Query's name (e.g., `getFilteredTasks`). This function takes a single optional argument - an object containing any serializable data you wish to use inside the Query. Wasp will send this object over the network and pass it into the Query's implementation as its first positional argument (more on this when we look at the implementations). Such an abstraction works thanks to an HTTP API route handler Wasp generates on the server, which calls the Query's NodeJS implementation under the hood. Generating these two functions ensures a similar calling interface across the entire app (both client and server). #### Implementing Queries in Node Now that we've declared the Query, what remains is to implement it. We've instructed Wasp to look for the Queries' implementations in the file `src/queries.ts`, so that's where we should export them from. Here's how you might implement the previously declared Queries `getAllTasks` and `getFilteredTasks`: ```ts title="src/queries.ts" import { type GetAllTasks, type GetFilteredTasks } from "wasp/server/operations" type Task = { id: number description: string isDone: boolean } // our "database" const tasks: Task[] = [ { id: 1, description: "Buy some eggs", isDone: true }, { id: 2, description: "Make an omelette", isDone: false }, { id: 3, description: "Eat breakfast", isDone: false }, ] // You don't need to use the arguments if you don't need them export const getAllTasks: GetAllTasks = () => { return tasks } // The 'args' object is something sent by the caller (most often from the client) export const getFilteredTasks: GetFilteredTasks< Pick, Task[] > = (args) => { const { isDone } = args return tasks.filter((task) => task.isDone === isDone) } ``` :::info[Payload constraints] Wasp uses [superjson](https://github.com/flightcontrolhq/superjson) under the hood. This means you're not limited to only sending and receiving JSON payloads. Wasp will automatically handle the serialization and deserialization [for all the data types that superjson supports](https://github.com/flightcontrolhq/superjson#decimaljs--prismadecimal:~\:text=Superjson%20supports%20many%20extra%20types) (like `biging`, `Date`, `Map`, `Set`, etc.), and for [Prisma.Decimal](https://www.prisma.io/docs/orm/prisma-client/special-fields-and-types#working-with-decimal). As long as you're annotating your Operations with the correct automatically generated types, TypeScript ensures your payloads are valid (i.e., Wasp knows how to serialize and deserialize them). ::: ##### Type support for Queries Wasp automatically generates the types `GetAllTasks` and `GetFilteredTasks` based on your Wasp file's specs: - `GetAllTasks` is a generic type automatically generated by Wasp, based on the Query spec for `getAllTasks`. - `GetFilteredTasks` is also a generic type automatically generated by Wasp, based on the Query spec for `getFilteredTasks`. Use these types to type the Query's implementation. It's optional but very helpful since doing so properly types the Query's context. In this case, TypeScript will know the `context.entities` object must include the `Task` entity. TypeScript also knows whether the `context` object includes user information (it depends on whether your Query uses auth). The generated types are generic and accept two optional type arguments: `Input` and `Output`. 1. `Input` - The argument (the payload) received by the Query function. 2. `Output` - The Query function's return type. Use these type arguments to type the Query's inputs and outputs. Explanation for the example above The above code says that the Query `getAllTasks` doesn't expect any arguments (its input type is `void`), but it does return a list of tasks (its output type is `Task[]`). On the other hand, the Query `getFilteredTasks` expects an object of type `{ isDone: boolean }`. This type is derived from the `Task` entity type. If you don't care about typing the Query's inputs and outputs, you can omit both type arguments. TypeScript will then infer the most general types (`never` for the input and `unknown` for the output). Specifying `Input` or `Output` is completely optional, but we highly recommended it. Doing so gives you: - Type support for the arguments and the return value inside the implementation. - **Full-stack type safety**. We'll explore what this means when we discuss calling the Query from the client. Read more about type support for implementing Queries in the [API Reference](#implementing-queries). :::tip[Inferring the return type] If don't want to explicitly type the Query's return value, the `satisfies` keyword tells TypeScript to infer it automatically: ```typescript const getFoo = (async (_args, context) => { const foos = await context.entities.Foo.findMany() return { foos, message: "Here are some foos!", queriedAt: new Date(), } }) satisfies GetFoo ``` From the snippet above, TypeScript knows: 1. The correct type for `context`. 2. The Query's return type is `{ foos: Foo[], message: string, queriedAt: Date }`. If you don't need the context, you can skip specifying the Query's type (and arguments): ```typescript const getFoo = () => ({ name: "Foo", date: new Date() }) ``` ::: For a detailed explanation of the Query definition API (more precisely, its arguments and return values), check the [API Reference](#api-reference). #### Using Queries ##### Using Queries on the client To call a Query on the client, you can import it from `wasp/client/operations` and call it directly. The usage doesn't change depending on whether the Query is authenticated or not. Wasp authenticates the logged-in user in the background. ```ts import { getAllTasks, getFilteredTasks } from "wasp/client/operations" // TypeScript automatically infers the return values and type-checks // the payloads. const allTasks = await getAllTasks() const doneTasks = await getFilteredTasks({ isDone: true }) ``` Wasp supports **automatic full-stack type safety**. You only need to specify the Query's type in its server-side definition, and the client code will automatically know its API payload types. ##### Using Queries on the server Calling a Query on the server is similar to calling it on the client. Here's what you have to do differently: - Import Queries from `wasp/server/operations` instead of `wasp/client/operations`. - Make sure you pass in a `context` object with the `user` field to authenticated Queries. - Note that you don't have to pass other parts of the `context` object, like Entities, those will get injected automatically. ```ts import { getAllTasks, getFilteredTasks } from "wasp/server/operations" const user = // Get an AuthUser object, e.g., from context.user in an operation. // TypeScript automatically infers the return values and type-checks // the payloads. const allTasks = await getAllTasks({ user }) const doneTasks = await getFilteredTasks({ isDone: true }, { user }) ``` ##### The `useQuery` hook When using Queries on the client, you can make them reactive with the `useQuery` hook. This hook comes bundled with Wasp and is a thin wrapper around the `useQuery` hook from [*react-query*](https://github.com/tannerlinsley/react-query). The only difference is that you don't need to supply the key - Wasp handles this for you automatically. Here's an example of calling the Queries using the `useQuery` hook: ```tsx title="src/MainPage.tsx" import React from "react" import { type Task } from "wasp/entities" import { useQuery, getAllTasks, getFilteredTasks } from "wasp/client/operations" const MainPage = () => { // TypeScript automatically infers return values and type-checks payload types. const { data: allTasks, error: error1 } = useQuery(getAllTasks) const { data: doneTasks, error: error2 } = useQuery(getFilteredTasks, { isDone: true, }) if (error1 !== null || error2 !== null) { return
There was an error
} return (

All Tasks

{allTasks && allTasks.length > 0 ? allTasks.map((task) => ) : "No tasks"}

Finished Tasks

{doneTasks && doneTasks.length > 0 ? doneTasks.map((task) => ) : "No finished tasks"}
) } const Task = ({ description, isDone }: Task) => { return (

Description: {description}

Is done: {isDone ? "Yes" : "No"}

) } export default MainPage ``` Notice how you don't need to annotate the Query's return value type. Wasp automatically infers the from the Query's backend implementation. This is **full-stack type safety**: the types on the client always match the types on the server. For a detailed specification of the `useQuery` hook, check the [API Reference](#api-reference). #### Error Handling For security reasons, all exceptions thrown in the Query's NodeJS implementation are sent to the client as responses with the HTTP status code `500`, with all other details removed. Hiding error details by default helps against accidentally leaking possibly sensitive information over the network. If you do want to pass additional error information to the client, you can construct and throw an appropriate `HttpError` in your implementation: ```ts title="src/queries.ts" import { type GetAllTasks } from "wasp/server/operations" import { HttpError } from "wasp/server" export const getAllTasks: GetAllTasks = async (args, context) => { throw new HttpError( 403, // status code "You can't do this!", // message { foo: "bar" } // data ) } ``` If the status code is `4xx`, the client will receive a response object with the corresponding `message` and `data` fields, and it will rethrow the error (including these fields). To prevent information leakage, the server won't forward these fields for any other HTTP status codes. #### Using Entities in Queries In most cases, resources used in Queries will be [Entities](https://wasp.sh/docs/data-model/entities). To use an Entity in your Query, add it to the `query` spec in Wasp: ```ts title="main.wasp.ts" import { app, query } from "@wasp.sh/spec" import { getAllTasks, getFilteredTasks } from "./src/queries" with { type: "ref" } export default app({ // ... spec: [ query(getAllTasks, { entities: ["Task"] }), query(getFilteredTasks, { entities: ["Task"] }), ], }) ``` Wasp will inject the specified Entity into the Query's `context` argument, giving you access to the Entity's Prisma API: ```ts title="src/queries.ts" import { type Task } from "wasp/entities" import { type GetAllTasks, type GetFilteredTasks } from "wasp/server/operations" export const getAllTasks: GetAllTasks = async (args, context) => { return context.entities.Task.findMany({}) } export const getFilteredTasks: GetFilteredTasks< Pick, Task[] > = async (args, context) => { return context.entities.Task.findMany({ where: { isDone: args.isDone }, }) } ``` Again, annotating the Queries is optional, but greatly improves **full-stack type safety**. The object `context.entities.Task` exposes `prisma.task` from [Prisma's CRUD API](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/crud). ### API Reference #### Specifying Queries [API reference](https://wasp.sh/docs/api/@wasp.sh/spec/functions/query) #### [query ยป](https://wasp.sh/docs/api/@wasp.sh/spec/functions/query) [All the options for declaring a query in the Wasp spec.](https://wasp.sh/docs/api/@wasp.sh/spec/functions/query) Declaring a Query enables you to import and use it anywhere in your code (on the server or the client). For example, for a Query that we declared as `getFoo`, Wasp generates two functions with the same name that you can import and use: ```ts // Use it on the client import { getFoo } from "wasp/client/operations" // Use it on the server import { getFoo } from "wasp/server/operations" ``` It also creates a type you can import on the server: ```ts import { type GetFoo } from "wasp/server/operations" ``` #### Implementing Queries The Query's implementation is a NodeJS function that takes two arguments (it can be an `async` function if you need to use the `await` keyword). Since both arguments are positional, you can name the parameters however you want, but we'll stick with `args` and `context`: 1. `args` (type depends on the Query) An object containing the data **passed in when calling the query** (e.g., filtering conditions). Check [the usage examples](#using-queries) to see how to pass this object to the Query. 2. `context` (type depends on the Query) An additional context object **passed into the Query by Wasp**. This object contains user session information, as well as information about entities. Check the [section about using entities in Queries](#using-entities-in-queries) to see how to use the entities field on the `context` object, or the [auth section](https://wasp.sh/docs/auth/overview#using-the-contextuser-object) to see how to use the `user` object. After you [declare the query](#specifying-queries), Wasp generates a generic type you can use when defining its implementation. For the Query declared as `getSomething`, the generated type is called `GetSomething`: ```ts import { type GetSomething } from "wasp/server/operations" ``` It expects two (optional) type arguments: 1. `Input` The type of the `args` object (the Query's input payload). The default value is `never`. 2. `Output` The type of the Query's return value (the Query's output payload). The default value is `unknown`. The defaults were chosen to make the type signature as permissive as possible. If don't want your Query to take/return anything, use `void` as a type argument. ##### Example The following Query: ```ts import { app, query } from "@wasp.sh/spec" import { getFoo } from "./src/queries" with { type: "ref" } export default app({ // ... spec: [ query(getFoo, { entities: ["Foo"] }), ], }) ``` Expects to find a named export `getFoo` from the file `src/queries.ts` You can use the generated type `GetFoo` and specify the Query's inputs and outputs using its type arguments. ```ts title="src/queries.ts" import { type GetFoo } from "wasp/server/operations" type Foo = // ... export const getFoo: GetFoo<{ id: number }, Foo> = (args, context) => { // implementation }; ``` In this case, the Query expects to receive an object with an `id` field of type `number` (this is the type of `args`), and return a value of type `Foo` (this must match the type of the Query's return value). #### The `useQuery` Hook Wasp's `useQuery` hook is a thin wrapper around the `useQuery` hook from [*react-query*](https://github.com/tannerlinsley/react-query). One key difference is that Wasp doesn't expect you to supply the cache key - it takes care of it under the hood. Wasp's `useQuery` hook accepts three arguments: - `queryFn` required The client-side query function generated by Wasp based on a `query` spec in your Wasp file. - `queryFnArgs` The arguments object (payload) you wish to pass into the Query. The Query's NodeJS implementation will receive this object as its first positional argument. - `options` A *react-query* `options` object. Use this to change [the default behavior](https://react-query.tanstack.com/guides/important-defaults) for this particular Query. If you want to change the global defaults, you can do so in the [client setup function](https://wasp.sh/docs/project/client-config#overriding-default-behaviour-for-queries). For an example of usage, check [this section](#the-usequery-hook). ## Data Model / Operations / Actions We'll explain what Actions are and how to use them. If you're looking for a detailed API specification, skip ahead to the [API Reference](#api-reference). Actions are quite similar to [Queries](https://wasp.sh/docs/data-model/operations/queries), but with a key distinction: Actions are designed to modify and add data, while Queries are solely for reading data. Examples of Actions include adding a comment to a blog post, liking a video, or updating a product's price. Actions and Queries work together to keep data caches up-to-date. :::tip Actions are almost identical to Queries in terms of their API. Therefore, if you're already familiar with Queries, you might find reading the entire guide repetitive. We instead recommend skipping ahead and only reading [the differences between Queries and Actions](#differences-between-queries-and-actions), and consulting the [API Reference](#api-reference) as needed. ::: ### Working with Actions Actions are declared in Wasp and implemented in NodeJS. Wasp runs Actions within the server's context, but it also generates code that allows you to call them from anywhere in your code (either client or server) using the same interface. This means you don't have to worry about building an HTTP API for the Action, managing server-side request handling, or even dealing with client-side response handling and caching. Instead, just focus on developing the business logic inside your Action, and let Wasp handle the rest! To create an Action, you need to: 1. Declare the Action in Wasp using the `action` spec. 2. Implement the Action's NodeJS functionality. Once these two steps are completed, you can use the Action from anywhere in your code. #### Specifying Actions To create an Action in Wasp, we begin with an `action` spec. Let's declare two Actions - one for creating a task, and another for marking tasks as done: ```ts title="main.wasp.ts" import { action, app } from "@wasp.sh/spec" import { createTask, markTaskAsDone } from "./src/actions" with { type: "ref" } export default app({ // ... spec: [ action(createTask), action(markTaskAsDone), ], }) ``` If you want to know about all supported options for the `action` spec, take a look at the [API Reference](#api-reference). :::note When `main.wasp.ts` needs to point to your code, it uses imports like this: ```ts import { MainPage } from "./src/MainPage" with { type: "ref" } ``` Notice the `with { type: "ref" }` part at the end of the import statement. This tells Wasp to treat the import as a reference to your app's code, without running the imported code. For more details and examples, see [reference imports](https://wasp.sh/docs/general/spec#reference-imports). ::: :::info You might have noticed that we told Wasp to import Action implementations that don't yet exist. Don't worry about that for now. We'll write the implementations imported from `actions.ts` in the next section. It's a good idea to start with the high-level concept (the Action spec in the Wasp file) and only then deal with the implementation details (the Action's implementation in JavaScript). ::: After declaring a Wasp Action, Wasp derives the Action's name from the function you pass to `action`. For example, `action(markTaskAsDone)` creates an Action named `markTaskAsDone`. Two important things then happen: - Wasp **generates a server-side NodeJS function** with the Action's name. - Wasp **generates a client-side JavaScript function** with the Action's name (e.g., `markTaskAsDone`). This function takes a single optional argument - an object containing any serializable data you wish to use inside the Action. Wasp will send this object over the network and pass it into the Action's implementation as its first positional argument (more on this when we look at the implementations). Such an abstraction works thanks to an HTTP API route handler Wasp generates on the server, which calls the Action's NodeJS implementation under the hood. Generating these two functions ensures a similar calling interface across the entire app (both client and server). #### Implementing Actions in Node Now that we've declared the Action, what remains is to implement it. We've instructed Wasp to look for the Actions' implementations in the file `src/actions.ts`, so that's where we should export them from. Here's how you might implement the previously declared Actions `createTask` and `markTaskAsDone`: ```ts title="src/actions.ts" import { type CreateTask, type MarkTaskAsDone } from "wasp/server/operations" type Task = { id: number description: string isDone: boolean } // our "database" let nextId = 4 const tasks = [ { id: 1, description: "Buy some eggs", isDone: true }, { id: 2, description: "Make an omelette", isDone: false }, { id: 3, description: "Eat breakfast", isDone: false }, ] // You don't need to use the arguments if you don't need them export const createTask: CreateTask, Task> = ( args ) => { const newTask = { id: nextId, isDone: false, description: args.description, } nextId += 1 tasks.push(newTask) return newTask } // The 'args' object is something sent by the caller (most often from the client) export const markTaskAsDone: MarkTaskAsDone, void> = ( args ) => { const task = tasks.find((task) => task.id === args.id) if (!task) { // We'll show how to properly handle such errors later return } task.isDone = true } ``` :::info[Payload constraints] Wasp uses [superjson](https://github.com/flightcontrolhq/superjson) under the hood. This means you're not limited to only sending and receiving JSON payloads. Wasp will automatically handle the serialization and deserialization [for all the data types that superjson supports](https://github.com/flightcontrolhq/superjson#decimaljs--prismadecimal:~\:text=Superjson%20supports%20many%20extra%20types) (like `biging`, `Date`, `Map`, `Set`, etc.), and for [Prisma.Decimal](https://www.prisma.io/docs/orm/prisma-client/special-fields-and-types#working-with-decimal). As long as you're annotating your Operations with the correct automatically generated types, TypeScript ensures your payloads are valid (i.e., Wasp knows how to serialize and deserialize them). ::: ##### Type support for Actions Wasp automatically generates the types `CreateTask` and `MarkTaskAsDone` based on the specs in your Wasp file: - `CreateTask` is a generic type that Wasp automatically generated based on the Action spec for `createTask`. - `MarkTaskAsDone` is a generic type that Wasp automatically generated based on the Action spec for `markTaskAsDone`. Use these types to type the Action's implementation. It's optional but very helpful since doing so properly types the Action's context. In this case, TypeScript will know the `context.entities` object must include the `Task` entity. TypeScript also knows whether the `context` object includes user information (it depends on whether your Action uses auth). The generated types are generic and accept two optional type arguments: `Input` and `Output`. 1. `Input` - The argument (the payload) received by the Action function. 2. `Output` - The Action function's return type. Use these type arguments to type the Action's inputs and outputs. Explanation for the example above The above code says that the Action `createTask` expects an object with the new task's description (its input type is `Pick`) and returns the new task (its output type is `Task`). On the other hand, the Action `markTaskAsDone` expects an object of type `Pick`. This type is derived from the `Task` entity type. If you don't care about typing the Action's inputs and outputs, you can omit both type arguments. TypeScript will then infer the most general types (`never` for the input and `unknown` for the output). Specifying `Input` or `Output` is completely optional, but we highly recommended it. Doing so gives you: - Type support for the arguments and the return value inside the implementation. - **Full-stack type safety**. We'll explore what this means when we discuss calling the Action from the client. Read more about type support for implementing Actions in the [API Reference](#implementing-actions). :::tip[Inferring the return type] If don't want to explicitly type the Action's return value, the `satisfies` keyword tells TypeScript to infer it automatically: ```typescript const createFoo = (async (_args, context) => { const foo = await context.entities.Foo.create() return { newFoo: foo, message: "Here's your foo!", returnedAt: new Date(), } }) satisfies CreateFoo ``` From the snippet above, TypeScript knows: 1. The correct type for `context`. 2. The Action's return type is `{ newFoo: Foo, message: string, returnedAt: Date }`. If you don't need the context, you can skip specifying the Action's type (and arguments): ```typescript const createFoo = () => ({ name: "Foo", date: new Date() }) ``` ::: For a detailed explanation of the Action definition API (more precisely, its arguments and return values), check the [API Reference](#api-reference). #### Using Actions ##### Using Actions on the client To call an Action on the client, you can import it from `wasp/client/operations` and call it directly. The usage doesn't depend on whether the Action is authenticated or not. Wasp authenticates the logged-in user in the background. ```ts import { createTask, markTaskAsDone } from "wasp/client/operations" // TypeScript automatically infers the return values and type-checks // the payloads. const newTask = await createTask({ description: "Keep learning TypeScript" }) await markTaskAsDone({ id: 1 }) ``` Wasp supports **automatic full-stack type safety**. You only need to specify the Action's type in its server-side definition, and the client code will automatically know its API payload types. When using Actions on the client, you'll most likely want to use them inside a component: ```tsx title="src/pages/Task.tsx" import React from "react" import { useQuery, getTask, markTaskAsDone } from "wasp/client/operations" export const TaskPage = ({ id }: { id: number }) => { const { data: task } = useQuery(getTask, { id }) if (!task) { return

"Loading"

} const { description, isDone } = task return (

Description: {description}

Is done: {isDone ? "Yes" : "No"}

{isDone || ( )}
) } ``` Since Actions don't require reactivity, they are safe to use inside components without a hook. Still, Wasp provides comes with the `useAction` hook you can use to enhance actions. Read all about it in the [API Reference](#api-reference). ##### Using Actions on the server Calling an Action on the server is similar to calling it on the client. Here's what you have to do differently: - Import Actions from `wasp/server/operations` instead of `wasp/client/operations`. - Make sure you pass in a context object with the user to authenticated Actions. ```ts import { createTask, markTaskAsDone } from "wasp/server/operations" const user = // Get an AuthUser object, e.g., from context.user // TypeScript automatically infers the return values and type-checks // the payloads. const newTask = await createTask( { description: "Keep learning TypeScript" }, { user }, ) await markTaskAsDone({ id: 1 }, { user }) ``` #### Error Handling For security reasons, all exceptions thrown in the Action's NodeJS implementation are sent to the client as responses with the HTTP status code `500`, with all other details removed. Hiding error details by default helps against accidentally leaking possibly sensitive information over the network. If you do want to pass additional error information to the client, you can construct and throw an appropriate `HttpError` in your implementation: ```ts title="src/actions.ts" import { type CreateTask } from "wasp/server/operations" import { HttpError } from "wasp/server" export const createTask: CreateTask = async (args, context) => { throw new HttpError( 403, // status code "You can't do this!", // message { foo: "bar" } // data ) } ``` #### Using Entities in Actions In most cases, resources used in Actions will be [Entities](https://wasp.sh/docs/data-model/entities). To use an Entity in your Action, add it to the `action` spec in Wasp: ```ts title="main.wasp.ts" import { action, app } from "@wasp.sh/spec" import { createTask, markTaskAsDone } from "./src/actions" with { type: "ref" } export default app({ // ... spec: [ action(createTask, { entities: ["Task"] }), action(markTaskAsDone, { entities: ["Task"] }), ], }) ``` Wasp will inject the specified Entity into the Action's `context` argument, giving you access to the Entity's Prisma API. Wasp invalidates frontend Query caches by looking at the Entities used by each Action/Query. Read more about Wasp's smart cache invalidation [here](#cache-invalidation). ```ts title="src/actions.ts" import { type CreateTask, type MarkTaskAsDone } from "wasp/server/operations" import { type Task } from "wasp/entities" // The 'args' object is the payload sent by the caller (most often from the client) export const createTask: CreateTask, Task> = async ( args, context ) => { const newTask = await context.entities.Task.create({ data: { description: args.description, isDone: false, }, }) return newTask } export const markTaskAsDone: MarkTaskAsDone, void> = async ( args, context ) => { await context.entities.Task.update({ where: { id: args.id }, data: { isDone: true }, }) } ``` Again, annotating the Actions is optional, but greatly improves **full-stack type safety**. The object `context.entities.Task` exposes `prisma.task` from [Prisma's CRUD API](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/crud). ### Cache Invalidation One of the trickiest parts of managing a web app's state is making sure the data returned by the Queries is up to date. Since Wasp uses *react-query* for Query management, we must make sure to invalidate Queries (more specifically, their cached results managed by *react-query*) whenever they become stale. It's possible to invalidate the caches manually through several mechanisms *react-query* provides (e.g., refetch, direct invalidation). However, since manual cache invalidation quickly becomes complex and error-prone, Wasp offers a faster and a more effective solution to get you started: **automatic Entity-based Query cache invalidation**. Because Actions can (and most often do) modify the state while Queries read it, Wasp invalidates a Query's cache whenever an Action that uses the same Entity is executed. For example, if the Action `createTask` and Query `getTasks` both use the Entity `Task`, executing `createTask` may cause the cached result of `getTasks` to become outdated. In response, Wasp will invalidate it, causing `getTasks` to refetch data from the server and update it. In practice, this means that Wasp keeps the Queries "fresh" without requiring you to think about cache invalidation. On the other hand, this kind of automatic cache invalidation can become wasteful (some updates might not be necessary) and will only work for Entities. If that's an issue, you can use the mechanisms provided by *react-query* for now, and expect more direct support in Wasp for handling those use cases in a nice, elegant way. If you wish to optimistically set cache values after performing an Action, you can do so using [optimistic updates](https://stackoverflow.com/a/33009713). Configure them using Wasp's [useAction hook](#the-useaction-hook-and-optimistic-updates). This is currently the only manual cache invalidation mechanism Wasps supports natively. For everything else, you can always rely on *react-query*. ### Differences Between Queries and Actions Actions and Queries are two closely related concepts in Wasp. They might seem to perform similar tasks, but Wasp treats them differently, and each concept represents a different thing. Here are the key differences between Queries and Actions: 1. Actions can (and often should) modify the server's state, while Queries are only permitted to read it. Wasp relies on you adhering to this convention when performing cache invalidations, so it's crucial to follow it. 2. Actions don't need to be reactive, so you can call them directly. However, Wasp does provide a [`useAction` React hook](#the-useaction-hook-and-optimistic-updates) for adding extra behavior to the Action (like optimistic updates). 3. `action` specs in Wasp are mostly identical to `query` specs. The only difference lies in the spec's name. ### API Reference #### Specifying Actions in Wasp Spec [API reference](https://wasp.sh/docs/api/@wasp.sh/spec/functions/action) #### [action ยป](https://wasp.sh/docs/api/@wasp.sh/spec/functions/action) [All the options for declaring an action in the Wasp spec.](https://wasp.sh/docs/api/@wasp.sh/spec/functions/action) Declaring an Action enables you to import and use it anywhere in your code (on the server or the client). For example, for an Action that we declared as `createFoo`, Wasp generates two functions with the same name that you can import and use: ```ts // Use it on the client import { createFoo } from "wasp/client/operations" // Use it on the server import { createFoo } from "wasp/server/operations" ``` It also creates a type you can import on the server: ```ts import { type CreateFoo } from "wasp/server/operations" ``` #### Implementing Actions The Action's implementation is a NodeJS function that takes two arguments (it can be an `async` function if you need to use the `await` keyword). Since both arguments are positional, you can name the parameters however you want, but we'll stick with `args` and `context`: 1. `args` (type depends on the Action) An object containing the data **passed in when calling the Action** (e.g., filtering conditions). Check [the usage examples](#using-actions) to see how to pass this object to the Action. 2. `context` (type depends on the Action) An additional context object **passed into the Action by Wasp**. This object contains user session information, as well as information about entities. Check the [section about using entities in Actions](#using-entities-in-actions) to see how to use the entities field on the `context` object, or the [auth section](https://wasp.sh/docs/auth/overview#using-the-contextuser-object) to see how to use the `user` object. After you [declare the Action](#specifying-actions), Wasp generates a generic type you can use when defining its implementation. For the Action declared as `createSomething`, the generated type is called `CreateSomething`: ```ts import { type CreateSomething } from "wasp/server/operations" ``` It expects two (optional) type arguments: 1. `Input` The type of the `args` object (the Action's input payload). The default value is `never`. 2. `Output` The type of the Action's return value (the Action's output payload). The default value is `unknown`. The defaults were chosen to make the type signature as permissive as possible. If don't want your Action to take/return anything, use `void` as a type argument. ##### Example The following Action: ```ts import { action, app } from "@wasp.sh/spec" import { createFoo } from "./src/actions" with { type: "ref" } export default app({ // ... spec: [ action(createFoo, { entities: ["Foo"] }), ], }) ``` Expects to find a named export `createFoo` from the file `src/actions.ts` You can use the generated type `CreateFoo` and specify the Action's inputs and outputs using its type arguments. ```ts title="src/actions.ts" import { type CreateFoo } from "wasp/server/operations" type Foo = // ... export const createFoo: CreateFoo<{ bar: string }, Foo> = (args, context) => { // implementation }; ``` In this case, the Action expects to receive an object with a `bar` field of type `string` (this is the type of `args`), and return a value of type `Foo` (this must match the type of the Action's return value). #### The `useAction` Hook and Optimistic Updates Make sure you understand how [Queries](https://wasp.sh/docs/data-model/operations/queries) and [Cache Invalidation](#cache-invalidation) work before reading this chapter. When using Actions in components, you can enhance them with the help of the `useAction` hook. This hook comes bundled with Wasp, and is used for decorating Wasp Actions. In other words, the hook returns a function whose API matches the original Action while also doing something extra under the hood (depending on how you configure it). The `useAction` hook accepts two arguments: - `actionFn` required The Wasp Action (the client-side Action function generated by Wasp based on an Action spec) you wish to enhance. - `actionOptions` An object configuring the extra features you want to add to the given Action. While this argument is technically optional, there is no point in using the `useAction` hook without providing it (it would be the same as using the Action directly). The Action options object supports the following fields: - `optimisticUpdates` An array of objects where each object defines an [optimistic update](https://stackoverflow.com/a/33009713) to perform on the Query cache. To define an optimistic update, you must specify the following properties: - `getQuerySpecifier` required A function returning the Query specifier (a value used to address the Query you want to update). A Query specifier is an array specifying the query function and arguments. For example, to optimistically update the Query used with `useQuery(fetchFilteredTasks, {isDone: true }]`, your `getQuerySpecifier` function would have to return the array `[fetchFilteredTasks, { isDone: true}]`. Wasp will forward the argument you pass into the decorated Action to this function (you can use the properties of the added/changed item to address the Query). - `updateQuery` required The function used to perform the optimistic update. It should return the desired state of the cache. Wasp will call it with the following arguments: - `item` - The argument you pass into the decorated Action. - `oldData` - The currently cached value for the Query identified by the specifier. :::caution The `updateQuery` function must be a pure function. It must return the desired cache value identified by the `getQuerySpecifier` function and *must not* perform any side effects. Also, make sure you only update the Query caches affected by your Action causing the optimistic update (Wasp cannot yet verify this). Finally, your implementation of the `updateQuery` function should work correctly regardless of the state of `oldData` (e.g., don't rely on array positioning). If you need to do something else during your optimistic update, you can directly use *react-query*'s lower-level API (read more about it [here](#advanced-usage)). ::: Here's an example showing how to configure the Action `markTaskAsDone` that toggles a task's `isDone` status to perform an optimistic update: ```tsx title="src/pages/Task.tsx" import React from "react" import { useQuery, useAction, type OptimisticUpdateDefinition, getTask, markTaskAsDone, } from "wasp/client/operations" type TaskPayload = Pick; const TaskPage = ({ id }: { id: number }) => { const { data: task } = useQuery(getTask, { id }); // Typescript automatically type-checks the payload type. const markTaskAsDoneOptimistically = useAction(markTaskAsDone, { optimisticUpdates: [ { getQuerySpecifier: ({ id }) => [getTask, { id }], updateQuery: (_payload, oldData) => ({ ...oldData, isDone: true }), } as OptimisticUpdateDefinition, ], }); if (!task) { return

"Loading"

; } const { description, isDone } = task; return (

Description: {description}

Is done: {isDone ? "Yes" : "No"}

{isDone || ( )}
); }; export default TaskPage; ``` ##### Advanced usage The `useAction` hook currently only supports specifying optimistic updates. You can expect more features in future versions of Wasp. Wasp's optimistic update API is deliberately small and focuses exclusively on updating Query caches (as that's the most common use case). You might need an API that offers more options or a higher level of control. If that's the case, instead of using Wasp's `useAction` hook, you can use *react-query*'s `useMutation` hook and directly work with [their low-level API](https://tanstack.com/query/v4/docs/framework/react/guides/optimistic-updates). If you decide to use *react-query*'s API directly, you will need access to Query cache key. Wasp internally uses this key but abstracts it from the programmer. Still, you can easily obtain it by accessing the `queryCacheKey` property on any Query: ```ts import { getTasks } from "wasp/client/operations" const queryKey = getTasks.queryCacheKey ``` ## Data Model / Automatic CRUD If you have a lot of experience writing full-stack apps, you probably ended up doing some of the same things many times: listing data, adding data, editing it, and deleting it. Wasp makes handling these boring bits easy by offering a higher-level concept called Automatic CRUD. With a single spec, you can tell Wasp to automatically generate server-side logic (i.e., Queries and Actions) for creating, reading, updating and deleting [Entities](https://wasp.sh/docs/data-model/entities). As you update definitions for your Entities, Wasp automatically regenerates the backend logic. :::caution[Early preview] This feature is currently in early preview and we are actively working on it. Read more about [our plans](#future-of-crud-operations-in-wasp) for CRUD operations. ::: ### Overview Imagine we have a `Task` entity and we want to enable CRUD operations for it: ```prisma title="schema.prisma" model Task { id Int @id @default(autoincrement()) description String isDone Boolean } ``` We can then define a new `crud` called `Tasks`. We specify to use the `Task` entity and we enable the `getAll`, `get`, `create` and `update` operations (let's say we don't need the `delete` operation). ```ts title="main.wasp.ts" import { app, crud } from "@wasp.sh/spec" import { createTask } from "./src/tasks" with { type: "ref" } export default app({ // ... spec: [ crud("Tasks", "Task", { getAll: { isPublic: true, // by default only logged in users can perform operations }, get: {}, create: { overrideFn: createTask, }, update: {}, }), ], }) ``` 1. It uses default implementation for `getAll`, `get`, and `update`, 2. ... while specifying a custom implementation for `create`. 3. `getAll` will be public (no auth needed), while the rest of the operations will be private. Here's what it looks like when visualized: ![Automatic CRUD with Wasp](https://wasp.sh/img/crud_diagram.png) Visualization of the Tasks crud spec We can now use the CRUD queries and actions we just specified in our client code. Keep reading for an example of Automatic CRUD in action, or skip ahead for the [API Reference](#api-reference). ### Example: A Simple ToDo App Let's create a full-app example that uses automatic CRUD. We'll stick to using the `Task` entity from the previous example, but we'll add a `User` entity and enable [username and password](https://wasp.sh/docs/auth/username-and-pass) based auth. ![Automatic CRUD with Wasp](https://wasp.sh/img/crud-guide.gif) We are building a simple tasks app with username based auth #### Creating the App We can start by running `wasp new tasksCrudApp` and then adding the following to the `main.wasp.ts` file: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { LoginPage } from "./src/LoginPage" with { type: "ref" } import { MainPage } from "./src/MainPage" with { type: "ref" } import { SignupPage } from "./src/SignupPage" with { type: "ref" } export default app({ name: "tasksCrudApp", wasp: { version: "^0.25" }, title: "Tasks Crud App", head: [""], // We enabled auth and set the auth method to username and password auth: { userEntity: "User", methods: { usernameAndPassword: {}, }, onAuthFailedRedirectTo: "/login", }, spec: [ // Tasks app routes route("RootRoute", "/", page(MainPage, { authRequired: true, })), route("LoginRoute", "/login", page(LoginPage)), route("SignupRoute", "/signup", page(SignupPage)), ], }) ``` And let's define our entities in the `schema.prisma` file: ```prisma title="schema.prisma" model User { id Int @id @default(autoincrement()) tasks Task[] } // We defined a Task entity on which we'll enable CRUD later on model Task { id Int @id @default(autoincrement()) description String isDone Boolean userId Int user User @relation(fields: [userId], references: [id]) } ``` We can then run `wasp db migrate-dev` to create the database and run the migrations. #### Adding CRUD to the `Task` Entity โœจ Let's add the following `crud` spec to our `main.wasp.ts` file: ```ts title="main.wasp.ts" import { app, crud } from "@wasp.sh/spec" import { createTask } from "./src/tasks" with { type: "ref" } export default app({ // ... spec: [ crud("Tasks", "Task", { getAll: {}, create: { overrideFn: createTask, }, }), ], }) ``` You'll notice that we enabled only `getAll` and `create` operations. This means that only these operations will be available. We also overrode the `create` operation with a custom implementation. This means that the `create` operation will not be generated, but instead, the `createTask` function from `src/tasks.ts` will be used. #### Our Custom `create` Operation We need a custom `create` operation because we want to make sure that the task is connected to the user creating it. Automatic CRUD doesn't yet support this by default. Read more about the default implementations in the [`CrudOperations` API Reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/CrudOperations). Here's the `src/tasks.ts` file: ```ts title="src/tasks.ts" import { type Tasks } from "wasp/server/crud" import { type Task } from "wasp/entities" import { HttpError } from "wasp/server" type CreateTaskInput = { description: string; isDone: boolean } export const createTask: Tasks.CreateAction = async ( args, context ) => { if (!context.user) { throw new HttpError(401, "User not authenticated.") } const { description, isDone } = args const { Task } = context.entities return await Task.create({ data: { description, isDone, // Connect the task to the user that is creating it user: { connect: { id: context.user.id, }, }, }, }) } ``` Wasp automatically generates the `Tasks.CreateAction` type based on the CRUD spec in your Wasp file. Use it to type the CRUD action's implementation. The `Tasks.CreateAction` type works exactly like the types Wasp generates for [Queries](https://wasp.sh/docs/data-model/operations/queries#type-support-for-queries) and [Actions](https://wasp.sh/docs/data-model/operations/actions#type-support-for-actions). In other words, annotating the action with `Tasks.CreateAction` tells TypeScript about the type of the Action's `context` object, while the two type arguments allow you to specify the Action's inputs and outputs. Read more about type support for CRUD overrides in the [API reference](#defining-the-overrides). #### Using the Generated CRUD Operations on the Client And let's use the generated operations in our client code: ```tsx title="src/MainPage.tsx" import { Tasks } from "wasp/client/crud" import { useState } from "react" export const MainPage = () => { // Thanks to full-stack type safety, all payload types are inferred // automatically const { data: tasks, isLoading, error } = Tasks.getAll.useQuery() const createTask = Tasks.create.useAction() const [taskDescription, setTaskDescription] = useState("") function handleCreateTask() { createTask({ description: taskDescription, isDone: false }) setTaskDescription("") } if (isLoading) return
Loading...
if (error) return
Error: {error.message}
return (
setTaskDescription(e.target.value)} />
    {tasks.map((task) => (
  • {task.description}
  • ))}
) } ``` And here are the login and signup pages, where we are using Wasp's [Auth UI](https://wasp.sh/docs/auth/ui) components: ```tsx title="src/LoginPage.tsx" import { LoginForm } from "wasp/client/auth" import { Link } from "react-router" export function LoginPage() { return (
Create an account
) } ``` ```tsx title="src/SignupPage.tsx" import { SignupForm } from "wasp/client/auth" export function SignupPage() { return (
) } ``` That's it. You can now run `wasp start` and see the app in action. โšก๏ธ You should see a login page and a signup page. After you log in, you should see a page with a list of tasks and a form to create new tasks. ### Future of CRUD Operations in Wasp CRUD operations currently have a limited set of knowledge about the business logic they are implementing. - For example, they don't know that a task should be connected to the user that is creating it. This is why we had to override the `create` operation in the example above. - Another thing: they are not aware of the authorization rules. For example, they don't know that a user should not be able to create a task for another user. In the future, we will be adding role-based authorization to Wasp, and we plan to make CRUD operations aware of the authorization rules. - Another issue is input validation and sanitization. For example, we might want to make sure that the task description is not empty. CRUD operations are a mechanism for getting a backend up and running quickly, but it depends on the information it can get from the Wasp app. The more information that it can pick up from your app, the more powerful it will be out of the box. We plan on supporting CRUD operations and growing them to become the easiest way to create your backend. Follow along on [this GitHub issue](https://github.com/wasp-lang/wasp/issues/1253) to see how we are doing. ### API Reference #### Specifying CRUD Operations [API reference](https://wasp.sh/docs/api/@wasp.sh/spec/functions/crud) #### [crud ยป](https://wasp.sh/docs/api/@wasp.sh/spec/functions/crud) [All the options for declaring CRUD operations in the Wasp spec.](https://wasp.sh/docs/api/@wasp.sh/spec/functions/crud) #### Defining the overrides Like with actions and queries, you can define the implementation in a Javascript/Typescript file. The overrides are functions that take the following arguments: - `args` The arguments of the operation i.e. the data sent from the client. - `context` Context contains the `user` making the request and the `entities` object with the entity that's being operated on. You can also import types for each of the functions you want to override by importing the `{crud name}` from `wasp/server/crud`. The available types are: - `{crud name}.GetAllQuery` - `{crud name}.GetQuery` - `{crud name}.CreateAction` - `{crud name}.UpdateAction` - `{crud name}.DeleteAction` If you have a CRUD named `Tasks`, you would import the types like this: ```ts import { type Tasks } from "wasp/server/crud" // Each of the types is a generic type, so you can use it like this: export const getAllOverride: Tasks.GetAllQuery = async ( args, context ) => { // ... } ``` For a usage example, check the [example guide](https://wasp.sh/docs/data-model/crud#adding-crud-to-the-task-entity-). #### Using the CRUD operations in client code On the client, you import the CRUD operations from `wasp/client/crud` by import the `{crud name}` object. For example, if you have a CRUD called `Tasks`, you would import the operations like this: ```tsx title="SomePage.tsx" import { Tasks } from "wasp/client/crud" ``` You can then access the operations like this: ```tsx title="SomePage.tsx" const { data } = Tasks.getAll.useQuery() const { data } = Tasks.get.useQuery({ id: 1 }) const createAction = Tasks.create.useAction() const updateAction = Tasks.update.useAction() const deleteAction = Tasks.delete.useAction() ``` All CRUD operations are implemented with [Queries and Actions](https://wasp.sh/docs/data-model/operations/overview) under the hood, which means they come with all the features you'd expect (e.g., automatic SuperJSON serialization, full-stack type safety when using TypeScript) ## Data Model / Databases [Entities](https://wasp.sh/docs/data-model/entities), [Operations](https://wasp.sh/docs/data-model/operations/overview) and [Automatic CRUD](https://wasp.sh/docs/data-model/crud) together make a high-level interface for working with your app's data. Still, all that data has to live somewhere, so let's see how Wasp deals with databases. ### Supported Database Backends Wasp supports multiple database backends. We'll list and explain each one. #### SQLite The default database Wasp uses is [SQLite](https://www.sqlite.org/index.html). When you create a new Wasp project, the `schema.prisma` file will have SQLite as the default database provider: ```prisma title="schema.prisma" datasource db { provider = "sqlite" url = env("DATABASE_URL") } // ... ``` Read more about how Wasp uses the Prisma schema file in the [Prisma schema file](https://wasp.sh/docs/data-model/prisma-file) section. When you use the SQLite database, Wasp sets the `DATABASE_URL` environment variable for you. SQLite is a great way to get started with a new project because it doesn't require any configuration, but Wasp can only use it in development. Once you want to deploy your Wasp app to production, you'll need to switch to PostgreSQL and stick with it. Fortunately, migrating from SQLite to PostgreSQL is pretty simple, and we have [a guide](#migrating-from-sqlite-to-postgresql) to help you. #### PostgreSQL [PostgreSQL](https://www.postgresql.org/) is the most advanced open-source database and one of the most popular databases overall. It's been in active development for 20+ years. Therefore, if you're looking for a battle-tested database, look no further. To use PostgreSQL with Wasp, set the provider to `"postgresql"` in the `schema.prisma` file: ```prisma title="schema.prisma" datasource db { provider = "postgresql" url = env("DATABASE_URL") } // ... ``` Read more about how Wasp uses the Prisma schema file in the [Prisma schema file](https://wasp.sh/docs/data-model/prisma-file) section. You'll have to ensure a database instance is running during development to use PostgreSQL. Wasp needs access to your database for commands such as `wasp start` or `wasp db migrate-dev`. We cover all supported ways of connecting to a database in [the next section](#connecting-to-a-database). ### Connecting to a Database #### SQLite If you are using SQLite, you don't need to do anything special to connect to the database. Wasp will take care of it for you. #### PostgreSQL If you are using PostgreSQL, Wasp supports two ways of connecting to a database: 1. For managed experience, let Wasp spin up a ready-to-go development database for you. 2. For more control, you can specify a database URL and connect to an existing database that you provisioned yourself. ##### Using the Dev Database provided by Wasp The command `wasp start db` will start a default PostgreSQL dev database for you. Your Wasp app will automatically connect to it, just keep `wasp start db` running in the background. Also, make sure that: - You have [Docker installed](https://www.docker.com/get-started/) and it's available in your `PATH`. - The port `5432` isn't taken. :::tip In case you might want to connect to the dev database through the external tool like `psql` or [pgAdmin](https://www.pgadmin.org/), the credentials are printed in the console when you run `wasp db start`, at the very beginning. ::: ###### Customising the dev database {#custom-database} The Wasp development database uses the [PostgreSQL 18 Docker image](https://hub.docker.com/_/postgres/tags?name=18) by default, and will set up its data volumes according to their guidance. If you need to customise the development database, you can use the following options: - `--db-image`: Specify a custom Docker image Useful for PostgreSQL extensions or specific versions (for example, PostGIS, pgvector, etc.). - `--db-volume-mount-path`: Specify the volume mount path inside the container You only need to set this option if your custom `--db-image` is based on **PostgreSQL 17 or older** (check the `postgres:15` example below). If the volume mount path is incorrect, the data won't be persisted in your development database. Here are some examples of customising the development database: ```bash # Use default PostgreSQL image: wasp start db # Same as: wasp start db --db-image postgres:18 # Use PostgreSQL with PostGIS extension for geographic data: wasp start db --db-image postgis/postgis:18-3.6 # Use PostgreSQL with pgvector extension for AI embeddings: wasp start db --db-image pgvector/pgvector:pg18 # Use PostgreSQL version 15 (requires different volume path): wasp start db --db-image postgres:15 --db-volume-mount-path /var/lib/postgresql/data ``` :::note The custom Docker image you specify must use the `POSTGRES_DB`, `POSTGRES_USER`, and `POSTGRES_PASSWORD` environment variables when configuring the database. Wasp will use those values when connecting to the database. We recommend basing your image on the official [PostgreSQL Docker image](https://hub.docker.com/_/postgres), as it automatically uses these environment variables to set up the database name, user, and password. ::: ##### Connecting to an existing database If you want to spin up your own dev database (or connect to an external one), you can tell Wasp about it using the `DATABASE_URL` environment variable. Wasp will use the value of `DATABASE_URL` as a connection string. The easiest way to set the necessary `DATABASE_URL` environment variable is by adding it to the [.env.server](https://wasp.sh/docs/project/env-vars) file in the root dir of your Wasp project (if that file doesn't yet exist, create it): ```env title=".env.server" DATABASE_URL=postgresql://user:password@localhost:5432/mydb ``` Alternatively, you can set it inline when running `wasp` (this applies to all environment variables): ```bash DATABASE_URL= wasp ... ``` This trick is useful for running a certain `wasp` command on a specific database. For example, you could do: ```bash DATABASE_URL= wasp db seed myProductionSeed ``` This command seeds the data for a fresh staging or production database. Read more about [seeding the database](#seeding-the-database). ### Migrating from SQLite to PostgreSQL To run your Wasp app in production, you'll need to switch from SQLite to PostgreSQL. 1. Set the provider to `"postgresql"` in the `schema.prisma` file: ```prisma title="schema.prisma" datasource db { provider = "postgresql" url = env("DATABASE_URL") } // ... ``` 2. Delete all the old migrations, since they are SQLite migrations and can't be used with PostgreSQL, as well as the SQLite database by running [`wasp clean`](https://wasp.sh/docs/general/cli#project-commands): ```bash rm -r migrations/ wasp clean ``` 3. Ensure your new database is running (check the [section on connecting to a database](#connecting-to-a-database) to see how). Leave it running, since we need it for the next step. 4. In a different terminal, run `wasp db migrate-dev` to apply the changes and create a new initial migration. 5. That is it, you are all done! ### Seeding the Database **Database seeding** is a term used for populating the database with some initial data. Seeding is most commonly used for: 1. Getting the development database into a state convenient for working and testing. 2. Initializing any database (`dev`, `staging`, or `prod`) with essential data it requires to operate. For example, populating the Currency table with default currencies, or the Country table with all available countries. #### Writing a Seed Function You can define as many **seed functions** as you want in an array under the `db.seeds` field: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { devSeedSimple, prodSeed } from "./src/dbSeeds" with { type: "ref" } export default app({ name: "MyApp", // ... db: { seeds: [devSeedSimple, prodSeed], }, }) ``` Each seed function must be an async function that takes one argument, `prisma`, which is a [Prisma Client](https://www.prisma.io/docs/concepts/components/prisma-client/crud) instance used to interact with the database. This is the same Prisma Client instance that Wasp uses internally. Since a seed function falls under server-side code, it can import other server-side functions. This is convenient because you might want to seed the database using Actions. Here's an example of a seed function that imports an Action: ```ts import { createTask } from "./actions.js" import type { DbSeedFn } from "wasp/server" import { sanitizeAndSerializeProviderData } from "wasp/server/auth" import type { AuthUser } from "wasp/auth" import type { PrismaClient } from "wasp/server" export const devSeedSimple: DbSeedFn = async (prisma) => { const user = await createUser(prisma, { username: "RiuTheDog", password: "bark1234", }) await createTask( { description: "Chase the cat", isDone: false }, { user, entities: { Task: prisma.task } } ) }; async function createUser( prisma: PrismaClient, data: { username: string, password: string } ): Promise { const newUser = await prisma.user.create({ data: { auth: { create: { identities: { create: { providerName: "username", providerUserId: data.username, providerData: await sanitizeAndSerializeProviderData<"username">({ hashedPassword: data.password }), }, }, }, }, }, }) return newUser } ``` Wasp exports a type called `DbSeedFn` which you can use to easily type your seeding function. Wasp defines `DbSeedFn` like this: ```typescript type DbSeedFn = (prisma: PrismaClient) => Promise ``` Annotating the function `devSeedSimple` with this type tells TypeScript: - The seeding function's argument (`prisma`) is of type `PrismaClient`. - The seeding function's return value is `Promise`. #### Running seed functions Run the command `wasp db seed` and Wasp will ask you which seed function you'd like to run (if you've defined more than one). Alternatively, run the command `wasp db seed ` to choose a specific seed function right away, for example: ``` wasp db seed devSeedSimple ``` Check the [API Reference](#cli-commands-for-seeding-the-database) for more details on these commands. :::tip You'll often want to call `wasp db seed` right after you run `wasp db reset`, as it makes sense to fill the database with initial data after clearing it. ::: ### Customising the Prisma Client Wasp interacts with the database using the [Prisma Client](https://www.prisma.io/docs/orm/prisma-client). To customize the client, define a function in the `db.prismaSetupFn` field that returns a Prisma Client instance. This allows you to configure features like [logging](https://www.prisma.io/docs/orm/prisma-client/observability-and-logging/logging) or [client extensions](https://www.prisma.io/docs/orm/prisma-client/client-extensions): ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { setUpPrisma } from "./src/prisma" with { type: "ref" } export default app({ name: "MyApp", // ... db: { prismaSetupFn: setUpPrisma, }, }) ``` ```ts title="src/prisma.ts" import { PrismaClient } from "@prisma/client" export const setUpPrisma = () => { const prisma = new PrismaClient({ log: ["query"], }).$extends({ query: { task: { async findMany({ args, query }) { args.where = { ...args.where, description: { not: { contains: "hidden by setUpPrisma" } }, } return query(args) }, }, }, }) return prisma } ``` ### API Reference [API reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Db) #### [Db ยป](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Db) [All the options for the db field of the app spec.](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Db) #### CLI Commands for Seeding the Database Use one of the following commands to run the seed functions: - `wasp db seed` If you've only defined a single seed function, this command runs it. If you've defined multiple seed functions, it asks you to choose one interactively. - `wasp db seed ` This command runs the seed function with the specified name. Wasp derives this name from the imported function name you list in `db.seeds`. For example, to run the seed function `devSeedSimple` which was defined like this: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { devSeedSimple } from "./src/dbSeeds" with { type: "ref" } export default app({ name: "MyApp", // ... db: { seeds: [ // ... devSeedSimple, ], }, }) ``` Use the following command: ``` wasp db seed devSeedSimple ``` ## Data Model / Prisma Schema File Wasp uses [Prisma](https://www.prisma.io/) to interact with the database. Prisma is a "Next-generation Node.js and TypeScript ORM" that provides a type-safe API for working with your database. With Prisma, you define your application's data model in a `schema.prisma` file. Read more about how Wasp Entities relate to Prisma models on the [Entities](https://wasp.sh/docs/data-model/entities) page. In Wasp, the `schema.prisma` file is located in your project's root directory: ```c . โ”œโ”€โ”€ main.wasp.ts ... โ”œโ”€โ”€ package.json โ”œโ”€โ”€ public โ”œโ”€โ”€ schema.prisma โ”œโ”€โ”€ src โ”œโ”€โ”€ tsconfig.json โ”œโ”€โ”€ tsconfig.src.json โ”œโ”€โ”€ tsconfig.wasp.json โ””โ”€โ”€ vite.config.ts ``` Wasp uses the `schema.prisma` file to understand your app's data model and generate the necessary code to interact with the database. ### Wasp file and Prisma schema file Let's see how Wasp and Prisma files work together to define your application. Here's an example `schema.prisma` file where we defined some database options and two models (User and Task) with a one-to-many relationship: ```prisma title="schema.prisma" datasource db { provider = "postgresql" url = env("DATABASE_URL") } generator client { provider = "prisma-client-js" } model User { id Int @id @default(autoincrement()) tasks Task[] } model Task { id Int @id @default(autoincrement()) description String isDone Boolean @default(false) user User @relation(fields: [userId], references: [id]) userId Int } ``` Wasp reads this `schema.prisma` file and extracts the info about your database models and database config. The `datasource` block defines which database you want to use (PostgreSQL in this case) and some other options. The `generator` block defines how to generate the Prisma Client code that you can use in your application to interact with the database. ![Relationship between Wasp file and Prisma file](https://wasp.sh/img/data-model/prisma_in_wasp.png) Relationship between Wasp file and Prisma file Finally, Prisma models become Wasp Entities which can be then used in the `main.wasp.ts` file: ```ts title="main.wasp.ts" import { api, app, job, query } from "@wasp.sh/spec" import { fooBar } from "./src/apis" with { type: "ref" } import { getTasks } from "./src/queries" with { type: "ref" } import { foo } from "./src/workers/bar" with { type: "ref" } export default app({ // ... spec: [ // Using Wasp Entities in the Wasp file. query(getTasks, { entities: ["Task"] }), job(foo, { executor: "PgBoss", entities: ["Task"], }), api("GET", "/foo/bar/:email", fooBar, { entities: ["Task"], }), ], }) ``` In the implementation of the `getTasks` query, `Task` is a Wasp Entity that corresponds to the `Task` model defined in the `schema.prisma` file. The same goes for the `myJob` job and `fooBar` API, where `Task` is used as an Entity. To learn more about the relationship between Wasp Entities and Prisma models, check out the [Entities](https://wasp.sh/docs/data-model/entities) page. ### Wasp-specific Prisma configuration Wasp mostly lets you use the Prisma schema file as you would in any other JS/TS project. However, there are some Wasp-specific rules you need to follow. #### The `datasource` block ```prisma title="schema.prisma" datasource db { provider = "postgresql" url = env("DATABASE_URL") } ``` Wasp takes the `datasource` you write and use it as-is. There are some rules you need to follow: - You can only use `"postgresql"` or `"sqlite"` as the `provider` because Wasp only supports PostgreSQL and SQLite databases for now. - You must set the `url` field to `env("DATABASE_URL")` so that Wasp can work properly with your database. #### The `generator` blocks ```prisma title="schema.prisma" generator client { provider = "prisma-client-js" } ``` Wasp requires that there is a `generator` block with `provider = "prisma-client-js"` in the `schema.prisma` file. You can add additional generators if you need them in your project. #### The `model` blocks ```prisma title="schema.prisma" model User { id Int @id @default(autoincrement()) tasks Task[] } model Task { id Int @id @default(autoincrement()) description String isDone Boolean @default(false) user User @relation(fields: [userId], references: [id]) userId Int } ``` You can define your models in any way you like, if it's valid Prisma schema code, it will work with Wasp. #### The `enum` blocks As our applications grow in complexity, we might want to use [Prisma `enum`s](https://www.prisma.io/docs/orm/prisma-schema/data-model/models#defining-enums) to closely define our app's domain. For example, if we had a `Task` model with a boolean `isDone`, and we wanted to start to track whether it is in progress, we could migrate the field to a more expressive type: ```prisma title="schema.prisma" enum TaskStatus { NotStarted Doing Done } model Task { ... state TaskStatus @default(NotStarted) } ``` Make sure to check [Prisma's enum compatibility with your database](https://www.prisma.io/docs/orm/reference/database-features#misc). If it works with Prisma, it will work with Wasp. ##### How to use `enum`s in your code If you need to access your `enum` cases and their values from your server, you can import them directly from `@prisma/client`: ```ts title="src/queries.ts" import { TaskState } from "@prisma/client"; import { Task } from "wasp/entities"; import { type GetTasks } from "wasp/server/operations"; export const getTasks: GetTasks = async (args, context) => { return context.entities.Task.findMany({ orderBy: { id: "asc" }, where: { NOT: { state: TaskState.Done } }, }); }; ``` You can also access them from your client code: ```ts title="src/views/TaskList.tsx" import { TaskState } from "@prisma/client"; import { Task } from "wasp/entities"; const TaskRow = ({ task }: { task: Task }) => { return (
{task.description}
); }; ``` :::note[Triple slash comments] Wasp only supports `///` in the *leading* position: ```prisma title="schema.prisma" model User { /// The unique identifier for the user. id Int @id @default(autoincrement()) } ``` However, Wasp does not support `///` comments in the *trailing* position: ```prisma title="schema.prisma" model User { id Int @id @default(autoincrement()) /// This is not supported } ``` We are aware of this issue and are tracking it at [#3041](https://github.com/wasp-lang/wasp/issues/3041), let us know if this is something you need. ::: ### Prisma preview features Prisma is still in active development and some of its features are not yet stable. To enable various preview features in Prisma, you need to add the `previewFeatures` field to the `generator` block in the `schema.prisma` file. For example, one useful Prisma preview feature is PostgreSQL extensions support, which allows you to use PostgreSQL extensions like `pg_vector` or `pg_trgm` in your database schema: ```prisma title="schema.prisma" datasource db { provider = "postgresql" url = env("DATABASE_URL") extensions = [pgvector(map: "vector")] } generator client { provider = "prisma-client-js" previewFeatures = ["postgresqlExtensions"] } // ... ``` Read more about preview features in the Prisma docs [here](https://www.prisma.io/docs/orm/reference/preview-features/client-preview-features) or about using PostgreSQL extensions [here](https://www.prisma.io/docs/orm/prisma-schema/postgresql-extensions). ## Authentication / Overview Auth is an essential piece of any serious application. That's why Wasp provides authentication and authorization support out of the box. Enabling auth for your app is optional and can be done by configuring the `auth` field of your `app` spec: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "MyApp", wasp: { version: "^0.25" }, title: "My app", head: [""], auth: { userEntity: "User", methods: { usernameAndPassword: {}, // use this or email, not both email: {}, // use this or usernameAndPassword, not both google: {}, gitHub: {}, }, onAuthFailedRedirectTo: "/someRoute", }, // ... }) ``` Read more about the `auth` field options in the [API Reference](#api-reference) section. We will provide a quick overview of auth in Wasp and link to more detailed documentation for each auth method. ### Available auth methods Wasp supports the following auth methods: #### [Email ยป](https://wasp.sh/docs/auth/email) [Email verification, password reset, etc.](https://wasp.sh/docs/auth/email) #### [Username & Password ยป](https://wasp.sh/docs/auth/username-and-pass) [The simplest way to get started](https://wasp.sh/docs/auth/username-and-pass) #### [Google ยป](https://wasp.sh/docs/auth/social-auth/google) [Users sign in with their Google account](https://wasp.sh/docs/auth/social-auth/google) #### [Github ยป](https://wasp.sh/docs/auth/social-auth/github) [Users sign in with their Github account](https://wasp.sh/docs/auth/social-auth/github) #### [Keycloak ยป](https://wasp.sh/docs/auth/social-auth/keycloak) [Users sign in with their Keycloak account](https://wasp.sh/docs/auth/social-auth/keycloak) #### [Slack ยป](https://wasp.sh/docs/auth/social-auth/slack) [Users sign in with their Slack account](https://wasp.sh/docs/auth/social-auth/slack) #### [Discord ยป](https://wasp.sh/docs/auth/social-auth/discord) [Users sign in with their Discord account](https://wasp.sh/docs/auth/social-auth/discord) Click on each auth method for more details. Let's say we enabled the [Username & password](https://wasp.sh/docs/auth/username-and-pass) authentication. We get an auth backend with signup and login endpoints. We also get the `user` object in our [Operations](https://wasp.sh/docs/data-model/operations/overview) and we can decide what to do based on whether the user is logged in or not. We would also get the [Auth UI](https://wasp.sh/docs/auth/ui) generated for us. We can set up our login and signup pages where our users can **create their account** and **login**. We can then protect certain pages by setting `authRequired: true` for them. This will make sure that only logged-in users can access them. We will also have access to the `user` object in our frontend code, so we can show different UI to logged-in and logged-out users. For example, we can show the user's name in the header alongside a **logout button** or a login button if the user is not logged in. ### Different ways to use auth When you have decided which auth methods you want to support, you can also choose how you want to present the authorization flows to your users. ##### Generated components This is the fastest way to ship, with Wasp generating ready-made components for your app. They allow for some customization to make them consistent with your app. You don't need to implement any UI or logic, and they just work. #### [Email ยป](https://wasp.sh/docs/auth/email) #### [Username and password ยป](https://wasp.sh/docs/auth/username-and-pass) #### [Social Auth ยป](https://wasp.sh/docs/auth/social-auth/overview) ##### Make your own UI {#custom-auth-ui} Wasp is flexible enough to let you completely customize your login and signup interface. We give you the auth related functions, and you decide how and when to call them. This allows for total customization of the look-and-feel, and the interaction, but it needs a bit more work. #### [Email ยป](https://wasp.sh/docs/auth/email/create-your-own-ui) #### [Username and password ยป](https://wasp.sh/docs/auth/username-and-pass/create-your-own-ui) #### [Social Auth ยป](https://wasp.sh/docs/auth/social-auth/create-your-own-ui) :::tip You don't have to choose one *or* the other! Mix-and-match, and use what you need in each moment. For example, you can create a custom signup screen, but use Wasp's generated components for login. ::: ##### Custom login and signup actions The previously discussed options should cover the vast majority of cases. But, for the few instances where it is not enough, you can [create your own signup flows](https://wasp.sh/docs/auth/advanced/custom-auth-actions), with completely custom logic. This is not recommended, and reserved for advanced use cases. Please check first if other Wasp features (mainly [auth hooks](https://wasp.sh/docs/auth/auth-hooks)) can handle your requirements. ### Protecting a page with `authRequired` When declaring a page, you can set the `authRequired` property. If you set it to `true`, only authenticated users can access the page. Unauthenticated users are redirected to a route defined by the `auth.onAuthFailedRedirectTo` field. ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import Main from "./src/pages/Main" with { type: "ref" } export default app({ // ... spec: [ route("MainRoute", "/", page(Main, { authRequired: true })), ], }) ``` :::caution[Requires auth method] You can only use `authRequired` if your app uses one of the [available auth methods](#available-auth-methods). ::: If `authRequired` is set to `true`, the page's React component (passed as the first argument to `page(...)`) receives the `user` object as a prop. Read more about the `user` object in the [Accessing the logged-in user section](#accessing-the-logged-in-user). ### Logout action We provide an action for logging out the user. Here's how you can use it: ```tsx title="src/components/LogoutButton.tsx" import { logout } from "wasp/client/auth" const LogoutButton = () => { return } ``` ### Accessing the logged-in user You can get access to the `user` object both on the server and on the client. The `user` object contains the logged-in user's data. The `user` object has all the fields that you defined in your `User` entity. In addition to that, it will also contain all the auth-related fields that Wasp stores. This includes things like the `username` or the email verification status. For example, if you have a user that signed up using an email and password, the `user` object might look like this: ```ts const user = { // User data id: "cluqsex9500017cn7i2hwsg17", address: "Some address", // Auth methods specific data identities: { email: { id: "user@app.com", isEmailVerified: true, emailVerificationSentAt: "2024-04-08T10:06:02.204Z", passwordResetSentAt: null, }, }, } ``` You can read more about how the `User` is connected to the rest of the auth system and how you can access the user data in the [Accessing User Data](https://wasp.sh/docs/auth/entities) section of the docs. #### On the client There are two ways to access the `user` object on the client: - the `user` prop - the `useAuth` hook ##### Getting the `user` in authenticated routes If the page's spec sets `authRequired` to `true`, the page's React component receives the `user` object as a prop. This is the simplest way to access the user inside an authenticated page: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import Account from "./src/pages/Account" with { type: "ref" } export default app({ // ... spec: [ route("AccountRoute", "/account", page(Account, { authRequired: true })), ], }) ``` ```tsx title="src/pages/Account.tsx" import type { AuthUser } from "wasp/auth"; import Button from "./Button"; import { logout } from "wasp/client/auth"; const AccountPage = ({ user }: { user: AuthUser }) => { return (
{JSON.stringify(user, null, 2)}
); }; export default AccountPage; ``` ##### Getting the `user` in non-authenticated routes Wasp provides a React hook you can use in the client components - `useAuth`. This hook is a thin wrapper over Wasp's `useQuery` hook and returns data in the same format. ```tsx title="src/pages/MainPage.tsx" import { useAuth, logout } from "wasp/client/auth"; import { Link } from "react-router"; import Todo from "../Todo"; export function Main() { const { data: user } = useAuth(); if (!user) { return ( Please login or{" "} sign up. ); } else { return ( <> ); } } ``` #### On the server ##### Using the `context.user` object When authentication is enabled, all [queries and actions](https://wasp.sh/docs/data-model/operations/overview) have access to the `user` object through the `context` argument. `context.user` contains all User entity's fields and the auth identities connected to the user. We strip out the `hashedPassword` field from the identities for security reasons. ```ts title="src/actions.ts" import type { Task } from "wasp/entities"; import type { CreateTask } from "wasp/server/operations"; import { HttpError } from "wasp/server"; type CreateTaskPayload = Pick; export const createTask: CreateTask = async ( args, context, ) => { if (!context.user) { throw new HttpError(403); } const Task = context.entities.Task; return Task.create({ data: { description: args.description, user: { connect: { id: context.user.id }, }, }, }); }; ``` To implement access control in your app, each operation must check `context.user` and decide what to do. For example, if `context.user` is `undefined` inside a private operation, the user's access should be denied. When using WebSockets, the `user` object is also available on the `socket.data` object. Read more in the [WebSockets section](https://wasp.sh/docs/advanced/web-sockets#websocketfn). ### Sessions Wasp's auth uses sessions to keep track of the logged-in user. The session is stored in `localStorage` on the client and in the database on the server. Under the hood, Wasp uses the excellent [Lucia Auth v3](https://v3.lucia-auth.com/) library for session management. When users log in, Wasp creates a session for them and stores it in the database. The session is then sent to the client and stored in `localStorage`. When users log out, Wasp deletes the session from the database and from `localStorage`. ### User Entity #### Password Hashing If you are saving a user's password in the database, you should **never** save it as plain text. You can use Wasp's helper functions for serializing and deserializing provider data which will automatically hash the password for you: ```ts title="main.wasp.ts" import { action, app } from "@wasp.sh/spec" import { updatePassword } from "./src/auth" with { type: "ref" } export default app({ // ... spec: [ action(updatePassword), ], }) ``` ```ts title="src/auth.ts" import { createProviderId, findAuthIdentity, updateAuthIdentityProviderData, getProviderDataWithPassword, } from "wasp/server/auth"; import type { UpdatePassword } from "wasp/server/operations"; export const updatePassword: UpdatePassword< { email: string; password: string }, void > = async (args, context) => { const providerId = createProviderId("email", args.email); const authIdentity = await findAuthIdentity(providerId); if (!authIdentity) { throw new HttpError(400, "Unknown user"); } const providerData = getProviderDataWithPassword<"email">( authIdentity.providerData, ); // Updates the password and hashes it automatically. await updateAuthIdentityProviderData(providerId, providerData, { hashedPassword: args.password, }); }; ``` #### Default Validations When you are using the default authentication flow, Wasp validates the fields with some default validations. These validations run if you use Wasp's built-in [Auth UI](https://wasp.sh/docs/auth/ui) or if you use the provided auth actions. If you decide to create your [custom auth actions](https://wasp.sh/docs/auth/advanced/custom-auth-actions), you'll need to run the validations yourself. Default validations depend on the auth method you use. ##### Username & Password If you use [Username & password](https://wasp.sh/docs/auth/username-and-pass) authentication, the default validations are: - The `username` must not be empty - The `password` must not be empty, have at least 8 characters, and contain a number Note that `username`s are stored in a **case-insensitive** manner. ##### Email If you use [Email](https://wasp.sh/docs/auth/email) authentication, the default validations are: - The `email` must not be empty and a valid email address - The `password` must not be empty, have at least 8 characters, and contain a number Note that `email`s are stored in a **case-insensitive** manner. ### Customizing the Signup Process Sometimes you want to include **extra fields** in your signup process, like first name and last name and save them in the `User` entity. For this to happen: - you need to define the fields that you want saved in the database, - you need to customize the `SignupForm` (in the case of [Email](https://wasp.sh/docs/auth/email) or [Username & Password](https://wasp.sh/docs/auth/username-and-pass) auth) Other times, you might need to just add some **extra UI** elements to the form, like a checkbox for terms of service. In this case, customizing only the UI components is enough. Let's see how to do both. #### 1. Defining Extra Fields If we want to **save** some extra fields in our signup process, we need to tell our app they exist. We do that by defining an object where the keys represent the field name, and the values are functions that receive the data sent from the client\* and return the value of the field. \* We exclude the `password` field from this object to prevent it from being saved as plain-text in the database. The `password` field is handled by Wasp's auth backend. First, we add the `auth.methods.{authMethod}.userSignupFields` field in our `main.wasp.ts` file. The `{authMethod}` depends on the auth method you are using. For example, if you are using [Username & Password](https://wasp.sh/docs/auth/username-and-pass), you would add the `auth.methods.usernameAndPassword.userSignupFields` field: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { userSignupFields } from "./src/auth/signup" with { type: "ref" } export default app({ name: "myApp", // ... auth: { userEntity: "User", methods: { usernameAndPassword: { userSignupFields, }, }, onAuthFailedRedirectTo: "/login", }, // ... }) ``` ```prisma title="schema.prisma" model User { id Int @id @default(autoincrement()) address String? } ``` Then we'll define the `userSignupFields` object in the `src/auth/signup.ts` file: ```ts title="src/auth/signup.ts" import { defineUserSignupFields } from "wasp/server/auth"; export const userSignupFields = defineUserSignupFields({ address: async (data) => { const address = data.address; if (typeof address !== "string") { throw new Error("Address is required"); } if (address.length < 5) { throw new Error("Address must be at least 5 characters long"); } return address; }, }); ``` Read more about the `userSignupFields` object in the [API Reference](#signup-fields-customization). Keep in mind, that these field names need to exist on the `userEntity` you defined in your `main.wasp.ts` file e.g. `address` needs to be a field on the `User` entity you defined in the `schema.prisma` file. The field function will receive the data sent from the client and it needs to return the value that will be saved into the database. If the field is invalid, the function should throw an error. :::info[Using Validation Libraries] You can use any validation library you want to validate the fields. For example, you can use `zod` like this: Click to see the code ```ts title="src/auth/signup.ts" import { defineUserSignupFields } from "wasp/server/auth"; import * as z from "zod"; export const userSignupFields = defineUserSignupFields({ address: (data) => { const AddressSchema = z .string({ required_error: "Address is required", invalid_type_error: "Address must be a string", }) .min(10, "Address must be at least 10 characters long"); const result = AddressSchema.safeParse(data.address); if (result.success === false) { throw new Error(result.error.issues[0].message); } return result.data; }, }); ``` ::: Now that we defined the fields, Wasp knows how to: 1. Validate the data sent from the client 2. Save the data to the database Next, let's see how to customize [Auth UI](https://wasp.sh/docs/auth/ui) to include those fields. #### 2. Customizing the Signup Component :::tip[Using Custom Signup Component] If you are not using Wasp's Auth UI, you can skip this section. Just make sure to include the extra fields in your custom signup form. Read more about using the signup actions for: - [Email auth](https://wasp.sh/docs/auth/email/create-your-own-ui) - [Username & password auth](https://wasp.sh/docs/auth/username-and-pass/create-your-own-ui) ::: If you are using Wasp's Auth UI, you can customize the `SignupForm` component by passing the `additionalFields` prop to it. It can be either a list of extra fields or a render function. ##### Using a List of Extra Fields When you pass in a list of extra fields to the `SignupForm`, they are added to the form one by one, in the order you pass them in. Inside the list, there can be either **objects** or **render functions** (you can combine them): 1. Objects are a simple way to describe new fields you need, but a bit less flexible than render functions. 2. Render functions can be used to render any UI you want, but they require a bit more code. The render functions receive the `react-hook-form` object and the form state object as arguments. ```tsx title="src/SignupPage.tsx" import { SignupForm, FormError, FormInput, FormItemGroup, FormLabel, } from "wasp/client/auth"; export const SignupPage = () => { return ( { return ( Phone Number {form.formState.errors.phoneNumber && ( {form.formState.errors.phoneNumber.message} )} ); }, ]} /> ); }; ``` Read more about the extra fields in the [API Reference](#signupform-customization). ##### Using a Single Render Function Instead of passing in a list of extra fields, you can pass in a render function which will receive the `react-hook-form` object and the form state object as arguments. What ever the render function returns, will be rendered below the default fields. ```tsx title="src/SignupPage.tsx" import { SignupForm, FormItemGroup } from "wasp/client/auth"; export const SignupPage = () => { return ( { const username = form.watch("username"); return ( username && ( Hello there {username} ๐Ÿ‘‹ ) ); }} /> ); }; ``` Read more about the render function in the [API Reference](#signupform-customization). ### API Reference #### Auth Fields [API reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Auth) #### [Auth ยป](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Auth) [All the options for the auth field of the app spec.](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Auth) #### Signup Fields Customization If you want to add extra fields to the signup process, the server needs to know how to save them to the database. You do that by defining the `auth.methods.{authMethod}.userSignupFields` field in your `main.wasp.ts` file. ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { userSignupFields } from "./src/auth/signup" with { type: "ref" } export default app({ name: "myApp", // ... auth: { userEntity: "User", methods: { usernameAndPassword: { userSignupFields, }, }, onAuthFailedRedirectTo: "/login", }, // ... }) ``` Then we'll export the `userSignupFields` object from the `src/auth/signup.ts` file: ```ts title="src/auth/signup.ts" import { defineUserSignupFields } from "wasp/server/auth"; export const userSignupFields = defineUserSignupFields({ address: async (data) => { const address = data.address; if (typeof address !== "string") { throw new Error("Address is required"); } if (address.length < 5) { throw new Error("Address must be at least 5 characters long"); } return address; }, }); ``` The `userSignupFields` object is an object where the keys represent the field name, and the values are functions that receive the data sent from the client\* and return the value of the field. If the value that the function received is invalid, the function should throw an error. \* We exclude the `password` field from this object to prevent it from being saved as plain text in the database. The `password` field is handled by Wasp's auth backend. #### `SignupForm` Customization To customize the `SignupForm` component, you need to pass in the `additionalFields` prop. It can be either a list of extra fields or a render function. ```tsx title="src/SignupPage.tsx" import { SignupForm, FormError, FormInput, FormItemGroup, FormLabel, } from "wasp/client/auth"; export const SignupPage = () => { return ( { return ( Phone Number {form.formState.errors.phoneNumber && ( {form.formState.errors.phoneNumber.message} )} ); }, ]} /> ); }; ``` The extra fields can be either **objects** or **render functions** (you can combine them): 1. Objects are a simple way to describe new fields you need, but a bit less flexible than render functions. The objects have the following properties: - `name` required - the name of the field - `label` required - the label of the field (used in the UI) - `type` required - the type of the field, which can be `input` or `textarea` - `validations` - an object with the validation rules for the field. The keys are the validation names, and the values are the validation error messages. Read more about the available validation rules in the [react-hook-form docs](https://react-hook-form.com/api/useform/register#register). 2. Render functions receive the `react-hook-form` object and the form state as arguments, and they can use them to render arbitrary UI elements. The render function has the following signature: ```ts type AdditionalSignupFieldRenderFn = ( hookForm: UseFormReturn, formState: FormState ) => React.ReactNode ``` - `form` required The `react-hook-form` object, read more about it in the [react-hook-form docs](https://react-hook-form.com/api/useform). You need to use the `form.register` function to register your fields - `state` required The form state object, which has the following properties: - `isLoading: boolean` Whether the form is currently submitting ## Authentication / Auth UI To make using authentication in your app as easy as possible, Wasp generates the server-side code but also the client-side UI for you. It enables you to quickly get the login, signup, password reset and email verification flows in your app. Below we cover all of the available UI components and how to use them. ![Auth UI](https://wasp.sh/assets/images/all_screens-963b58d02191c4b4f7285a2160101a7b.gif) :::note Remember that if you need a more custom approach, you can always [create your own UI](https://wasp.sh/docs/auth/overview#custom-auth-ui). ::: ### Overview After Wasp generates the UI components for your auth, you can use it as is, or customize it to your liking. Based on the authentication providers you enabled in your `main.wasp.ts` file, the Auth UI will show the corresponding UI (form and buttons). For example, if you enabled e-mail authentication: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "MyApp", //... auth: { methods: { email: {}, }, // ... }, // ... }) ``` You'll get the following UI: ![Auth UI](https://wasp.sh/assets/images/login-8ea785a886771acd0b29543625c4f377.png) And then if you enable Google and Github: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "MyApp", //... auth: { methods: { email: {}, google: {}, gitHub: {}, }, // ... }, // ... }) ``` The form will automatically update to look like this: ![Auth UI](https://wasp.sh/assets/images/multiple_providers-7418552f1647875e5dd9495a27503ea8.png) Let's go through all of the available components and how to use them. ### Auth Components The following components are available for you to use in your app: - [Login form](#login-form) - [Signup form](#signup-form) - [Forgot password form](#forgot-password-form) - [Reset password form](#reset-password-form) - [Verify email form](#verify-email-form) #### Login Form Used with [Username & Password](https://wasp.sh/docs/auth/username-and-pass), [Email](https://wasp.sh/docs/auth/email), [Github](https://wasp.sh/docs/auth/social-auth/github), [Google](https://wasp.sh/docs/auth/social-auth/google), [Keycloak](https://wasp.sh/docs/auth/social-auth/keycloak), [Slack](https://wasp.sh/docs/auth/social-auth/slack) and [Discord](https://wasp.sh/docs/auth/social-auth/discord) authentication. ![Login form](https://wasp.sh/assets/images/login-8ea785a886771acd0b29543625c4f377.png) You can use the `LoginForm` component to build your login page: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { LoginPage } from "./src/LoginPage" with { type: "ref" } export default app({ // ... spec: [ route("LoginRoute", "/login", page(LoginPage)), ], }) ``` ```tsx title="src/LoginPage.tsx" import { LoginForm } from "wasp/client/auth" // Use it like this export function LoginPage() { return } ``` It will automatically show the correct authentication providers based on your `main.wasp.ts` file. #### Signup Form Used with [Username & Password](https://wasp.sh/docs/auth/username-and-pass), [Email](https://wasp.sh/docs/auth/email), [Github](https://wasp.sh/docs/auth/social-auth/github), [Google](https://wasp.sh/docs/auth/social-auth/google), [Keycloak](https://wasp.sh/docs/auth/social-auth/keycloak), [Slack](https://wasp.sh/docs/auth/social-auth/slack) and [Discord](https://wasp.sh/docs/auth/social-auth/discord) authentication. ![Signup form](https://wasp.sh/assets/images/signup-6d04c4e24d598ce3552a5603aebea0df.png) You can use the `SignupForm` component to build your signup page: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { SignupPage } from "./src/SignupPage" with { type: "ref" } export default app({ // ... spec: [ route("SignupRoute", "/signup", page(SignupPage)), ], }) ``` ```tsx title="src/SignupPage.tsx" import { SignupForm } from "wasp/client/auth" // Use it like this export function SignupPage() { return } ``` It will automatically show the correct authentication providers based on your `main.wasp.ts` file. Read more about customizing the signup process like adding additional fields or extra UI in the [Auth Overview](https://wasp.sh/docs/auth/overview#customizing-the-signup-process) section. #### Forgot Password Form Used with [Email](https://wasp.sh/docs/auth/email) authentication. If users forget their password, they can use this form to reset it. *(Inlined image: Forgot password form)* You can use the `ForgotPasswordForm` component to build your own forgot password page: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { ForgotPasswordPage } from "./src/ForgotPasswordPage" with { type: "ref" } export default app({ // ... spec: [ route( "RequestPasswordResetRoute", "/request-password-reset", page(ForgotPasswordPage) ), ], }) ``` ```tsx title="src/ForgotPasswordPage.tsx" import { ForgotPasswordForm } from "wasp/client/auth" // Use it like this export function ForgotPasswordPage() { return } ``` #### Reset Password Form Used with [Email](https://wasp.sh/docs/auth/email) authentication. After users click on the link in the email they receive after submitting the forgot password form, they will be redirected to this form where they can reset their password. ![Reset password form](https://wasp.sh/assets/images/reset_password-8bd14c7e859201528a18ecc773d51e1d.png) You can use the `ResetPasswordForm` component to build your reset password page: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { ResetPasswordPage } from "./src/ResetPasswordPage" with { type: "ref" } export default app({ // ... spec: [ route("PasswordResetRoute", "/password-reset", page(ResetPasswordPage)), ], }) ``` ```tsx title="src/ResetPasswordPage.tsx" import { ResetPasswordForm } from "wasp/client/auth" // Use it like this export function ResetPasswordPage() { return } ``` #### Verify Email Form Used with [Email](https://wasp.sh/docs/auth/email) authentication. After users sign up, they will receive an email with a link to this form where they can verify their email. ![Verify email form](https://wasp.sh/assets/images/email_verification-5643900333decabde676675f1a462648.png) You can use the `VerifyEmailForm` component to build your email verification page: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { VerifyEmailPage } from "./src/VerifyEmailPage" with { type: "ref" } export default app({ // ... spec: [ route("EmailVerificationRoute", "/email-verification", page(VerifyEmailPage)), ], }) ``` ```tsx title="src/VerifyEmailPage.tsx" import { VerifyEmailForm } from "wasp/client/auth" // Use it like this export function VerifyEmailPage() { return } ``` ### Customization ๐Ÿ’…๐Ÿป You customize all of the available forms by passing props to them. Props you can pass to all of the forms: 1. `appearance` - customize the form colors (via design tokens) 2. `logo` - path to your logo 3. `socialLayout` - layout of the social buttons, which can be `vertical` or `horizontal` #### 1. Customizing the Colors We use CSS variables in our styling so you can customize the styles by overriding the default theme tokens. :::info[List of all available tokens] See the [list of all available tokens](https://github.com/wasp-lang/wasp/blob/release/waspc/data/Generator/templates/sdk/wasp/auth/forms/types.ts) which you can override. ::: ```ts title="src/appearance.ts" import type { CustomizationOptions } from "wasp/client/auth" export const authAppearance: CustomizationOptions["appearance"] = { colors: { brand: "#5969b8", // blue brandAccent: "#de5998", // pink submitButtonText: "white", }, } ``` ```tsx title="src/LoginPage.tsx" import { LoginForm } from "wasp/client/auth" import { authAppearance } from "./appearance" export function LoginPage() { return ( ) } ``` We recommend defining your appearance in a separate file and importing it into your components. #### 2. Using Your Logo You can add your logo to the Auth UI by passing the `logo` prop to any of the components. ```tsx title="src/LoginPage.tsx" import { LoginForm } from "wasp/client/auth" import Logo from "./logo.png" export function LoginPage() { return ( ) } ``` #### 3. Social Buttons Layout You can change the layout of the social buttons by passing the `socialLayout` prop to any of the components. It can be either `vertical` or `horizontal` (default). If we pass in `vertical`: ```tsx title="src/LoginPage.tsx" import { LoginForm } from "wasp/client/auth" export function LoginPage() { return ( ) } ``` We get this: ![Vertical social buttons](https://wasp.sh/assets/images/vertical_social_buttons-0cab6b68a60bdb32e421bd99b96942f6.png) #### Let's Put Everything Together ๐Ÿช„ If we provide the logo and custom colors: ```ts title="src/appearance.ts" import type { CustomizationOptions } from "wasp/client/auth" export const appearance: CustomizationOptions["appearance"] = { colors: { brand: "#5969b8", // blue brandAccent: "#de5998", // pink submitButtonText: "white", }, } ``` ```tsx title="src/LoginPage.tsx" import { LoginForm } from "wasp/client/auth" import { authAppearance } from "./appearance" import todoLogo from "./todoLogo.png" export function LoginPage() { return } ``` We get a form looking like this: ![Custom login form](https://wasp.sh/img/authui/custom_login.gif) ## Authentication / Username & Password / Overview Wasp supports username & password authentication out of the box with login and signup flows. It provides you with the server-side implementation and the UI components for the client side. ### Setting Up Username & Password Authentication To set up username authentication we need to: 1. Enable username authentication in the Wasp file 2. Add the `User` entity 3. Add the auth routes and pages 4. Use Auth UI components in our pages Structure of the `main.wasp.ts` file we will end up with: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { SignupPage } from "./src/pages/auth" with { type: "ref" } // Configuring e-mail authentication export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { // ... }, spec: [ // Defining routes and pages route("SignupRoute", "/signup", page(SignupPage)), // ... ], }) ``` #### 1. Enable Username Authentication Let's start with adding the following to our `main.wasp.ts` file: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { // 1. Specify the user entity (we'll define it next) userEntity: "User", methods: { // 2. Enable username authentication usernameAndPassword: {}, }, onAuthFailedRedirectTo: "/login" }, // ... }) ``` Read more about the `usernameAndPassword` auth method options in the [`UsernameAndPasswordConfig` API Reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/UsernameAndPasswordConfig). #### 2. Add the User Entity The `User` entity can be as simple as including only the `id` field: ```prisma title="schema.prisma" // 3. Define the user entity model User { id Int @id @default(autoincrement()) // Add your own fields below // ... } ``` You can read more about how the `User` is connected to the rest of the auth system and how you can access the user data in the [Accessing User Data](https://wasp.sh/docs/auth/entities) section of the docs. #### 3. Add the Routes and Pages Next, we need to define the routes and pages for the authentication pages. Add the following to the `main.wasp.ts` file: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { LoginPage, SignupPage } from "./src/pages/auth" with { type: "ref" } export default app({ // ... spec: [ route("LoginRoute", "/login", page(LoginPage)), route("SignupRoute", "/signup", page(SignupPage)), ], }) ``` We'll define the React components for these pages in the `src/pages/auth.tsx` file below. #### 4. Create the Client Pages :::info We are using [Tailwind CSS](https://tailwindcss.com/) to style the pages. Read more about how to add it [here](https://wasp.sh/docs/guides/libraries/tailwind). ::: Let's create a `auth.tsx` file in the `src/pages` folder and add the following to it: ```tsx title="src/pages/auth.tsx" import { LoginForm, SignupForm } from "wasp/client/auth" import { Link } from "react-router" export function LoginPage() { return (
Don't have an account yet? go to signup.
) } export function SignupPage() { return (
I already have an account (go to login).
) } // A layout component to center the content export function Layout({ children }: { children: React.ReactNode }) { return (
{children}
) } ``` We imported the generated Auth UI components and used them in our pages. Read more about the Auth UI components [here](https://wasp.sh/docs/auth/ui). #### Conclusion That's it! We have set up username authentication in our app. ๐ŸŽ‰ Running `wasp db migrate-dev` and then `wasp start` should give you a working app with username authentication. If you want to put some of the pages behind authentication, read the [auth overview docs](https://wasp.sh/docs/auth/overview). :::caution[Using multiple auth identities for a single user] Wasp currently doesn't support multiple auth identities for a single user. This means, for example, that a user can't have both an email-based auth identity and a Google-based auth identity. This is something we will add in the future with the introduction of the [account merging feature](https://github.com/wasp-lang/wasp/issues/954). Account merging means that multiple auth identities can be merged into a single user account. For example, a user's email and Google identity can be merged into a single user account. Then the user can log in with either their email or Google account and they will be logged into the same account. ::: ### Using Auth To read more about how to set up the logout button and how to get access to the logged-in user in our client and server code, read the [auth overview docs](https://wasp.sh/docs/auth/overview). When you receive the `user` object [on the client or the server](https://wasp.sh/docs/auth/overview#accessing-the-logged-in-user), you'll be able to access the user's username like this: ```ts const usernameIdentity = user.identities.username // Username that the user used to sign up, e.g. "fluffyllama" usernameIdentity.id ``` Read more about accessing the user data in the [Accessing User Data](https://wasp.sh/docs/auth/entities#accessing-the-auth-fields) section of the docs. ### API Reference [API reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Auth) #### [Auth ยป](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Auth) [All the options for the auth field of the app spec, including userEntity.](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Auth) [API reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/UsernameAndPasswordConfig) #### [UsernameAndPasswordConfig ยป](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/UsernameAndPasswordConfig) [All the options for the usernameAndPassword auth method.](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/UsernameAndPasswordConfig) Read more about the `userSignupFields` function in the [Auth Overview docs](https://wasp.sh/docs/auth/overview#signup-fields-customization). ## Authentication / Username & Password / Create your own UI The login and signup flows are pretty standard: they allow the user to sign up and then log in with their username and password. The signup flow validates the username and password and then creates a new user entity in the database. :::tip Read more about the default email and password validation rules in the [auth overview docs](https://wasp.sh/docs/auth/overview#default-validations). ::: Even though Wasp offers premade [Auth UI](https://wasp.sh/docs/auth/ui) for your authentication flows, there are times where you might want more customization, so we also give you the option to create your own UI and call Wasp's auth actions on your own code, similar to how Auth UI does it under the hood. ### Example code Below you can find a starting point for making your own UI in the client code. You can customize any of its look and behaviour, just make sure to call the `signup()` or `login()` functions imported from `wasp/client/auth`. #### Sign-up ```tsx title="src/pages/auth.tsx" import { login, signup } from 'wasp/client/auth' import { useState } from 'react' import { useNavigate } from 'react-router' export function SignupPage() { const [username, setUsername] = useState('') const [password, setPassword] = useState('') const [error, setError] = useState(null) const navigate = useNavigate() async function handleSubmit(event: React.FormEvent) { event.preventDefault() setError(null) try { await signup({ username, password }) await login({ username, password }) navigate('/') } catch (error: unknown) { setError(error as Error) } } return (
{error &&

Error: {error.message}

} setUsername(e.target.value)} placeholder="Username" /> setPassword(e.target.value)} placeholder="Password" />
) } ``` #### Login ```tsx title="src/pages/auth.tsx" import { login } from 'wasp/client/auth' import { useState } from 'react' import { useNavigate } from 'react-router' export function LoginPage() { const [username, setUsername] = useState('') const [password, setPassword] = useState('') const [error, setError] = useState(null) const navigate = useNavigate() async function handleSubmit(event: React.FormEvent) { event.preventDefault() setError(null) try { await login({ username, password }) navigate('/') } catch (error: unknown) { setError(error as Error) } } return (
{error &&

Error: {error.message}

} setUsername(e.target.value)} placeholder="Username" /> setPassword(e.target.value)} placeholder="Password" />
) } ``` ### API Reference You can import the following functions from `wasp/client/auth`: #### `login()` An action for logging in the user. It takes one argument: - `data: object` required It has the following fields: - `username: string` required - `password: string` required :::note When using the exposed `login()` function, make sure to implement your redirect on success login logic (e.g. redirecting to home). ::: #### `signup()` An action for signing up the user. This action does not log in the user, you still need to call `login()`. It takes one argument: - `data: object` required It has the following fields: - `username: string` required - `password: string` required :::info By default, Wasp will only save the `username` and `password` fields. If you want to add extra fields to your signup process, read about [defining extra signup fields](https://wasp.sh/docs/auth/overview#customizing-the-signup-process). ::: ## Authentication / Email / Overview Wasp supports e-mail authentication out of the box, along with email verification and "forgot your password?" flows. It provides you with the server-side implementation and email templates for all of these flows. ![Auth UI](https://wasp.sh/assets/images/all_screens-963b58d02191c4b4f7285a2160101a7b.gif) :::caution[Using multiple auth identities for a single user] Wasp currently doesn't support multiple auth identities for a single user. This means, for example, that a user can't have both an email-based auth identity and a Google-based auth identity. This is something we will add in the future with the introduction of the [account merging feature](https://github.com/wasp-lang/wasp/issues/954). Account merging means that multiple auth identities can be merged into a single user account. For example, a user's email and Google identity can be merged into a single user account. Then the user can log in with either their email or Google account and they will be logged into the same account. ::: ### Setting Up Email Authentication We'll need to take the following steps to set up email authentication: 1. Enable email authentication in the Wasp file 2. Add the `User` entity 3. Add the auth routes and pages 4. Use Auth UI components in our pages 5. Set up the email sender Structure of the `main.wasp.ts` file we will end up with: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { SignupPage } from "./src/pages/auth" with { type: "ref" } // Configuring e-mail authentication export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { // ... }, emailSender: { // ... }, spec: [ // Defining routes and pages route("SignupRoute", "/signup", page(SignupPage)), // ... ], }) ``` #### 1. Enable Email Authentication in `main.wasp.ts` Let's start with adding the following to our `main.wasp.ts` file: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { // 1. Specify the user entity (we'll define it next) userEntity: "User", methods: { // 2. Enable email authentication email: { // 3. Specify the email from field fromField: { name: "My App Postman", email: "hello@itsme.com" }, // 4. Specify the email verification and password reset options (we'll talk about them later) emailVerification: { clientRoute: "EmailVerificationRoute", }, passwordReset: { clientRoute: "PasswordResetRoute", }, }, }, onAuthFailedRedirectTo: "/login", onAuthSucceededRedirectTo: "/" }, // ... }) ``` Read more about the `email` auth method options in the [`EmailAuthConfig` API Reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/EmailAuthConfig). #### 2. Add the User Entity The `User` entity can be as simple as including only the `id` field: ```prisma title="schema.prisma" // 5. Define the user entity model User { id Int @id @default(autoincrement()) // Add your own fields below // ... } ``` You can read more about how the `User` is connected to the rest of the auth system and how you can access the user data in the [Accessing User Data](https://wasp.sh/docs/auth/entities) section of the docs. #### 3. Add the Routes and Pages Next, we need to define the routes and pages for the authentication pages. Add the following to the `main.wasp.ts` file: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { LoginPage, SignupPage, RequestPasswordResetPage, PasswordResetPage, EmailVerificationPage, } from "./src/pages/auth" with { type: "ref" } export default app({ // ... spec: [ route("LoginRoute", "/login", page(LoginPage)), route("SignupRoute", "/signup", page(SignupPage)), route( "RequestPasswordResetRoute", "/request-password-reset", page(RequestPasswordResetPage) ), route("PasswordResetRoute", "/password-reset", page(PasswordResetPage)), route("EmailVerificationRoute", "/email-verification", page(EmailVerificationPage)), ], }) ``` We'll define the React components for these pages in the `src/pages/auth.tsx` file below. #### 4. Create the Client Pages :::info We are using [Tailwind CSS](https://tailwindcss.com/) to style the pages. Read more about how to add it [here](https://wasp.sh/docs/guides/libraries/tailwind). ::: Let's create a `auth.tsx` file in the `src/pages` folder and add the following to it: ```tsx title="src/pages/auth.tsx" import { LoginForm, SignupForm, VerifyEmailForm, ForgotPasswordForm, ResetPasswordForm, } from "wasp/client/auth" import { Link } from "react-router" export function LoginPage() { return (
Don't have an account yet? go to signup.
Forgot your password? reset it.
) } export function SignupPage() { return (
I already have an account (go to login).
) } export function EmailVerificationPage() { return (
If everything is okay, go to login
) } export function RequestPasswordResetPage() { return ( ) } export function PasswordResetPage() { return (
If everything is okay, go to login
) } // A layout component to center the content export function Layout({ children }: { children: React.ReactNode }) { return (
{children}
) } ``` We imported the generated Auth UI components and used them in our pages. Read more about the Auth UI components [here](https://wasp.sh/docs/auth/ui). #### 5. Set up an Email Sender To support e-mail verification and password reset flows, we need an e-mail sender. Luckily, Wasp supports several email providers out of the box. We'll use the `Dummy` provider to speed up the setup. It just logs the emails to the console instead of sending them. You can use any of the [supported email providers](https://wasp.sh/docs/advanced/email#providers). To set up the `Dummy` provider to send emails, add the following to the `main.wasp.ts` file: ```ts title="main.wasp.ts" export default app({ // ... // 7. Set up the email sender emailSender: { provider: "Dummy", }, }) ``` #### Conclusion That's it! We have set up email authentication in our app. ๐ŸŽ‰ Running `wasp db migrate-dev` and then `wasp start` should give you a working app with email authentication. If you want to put some of the pages behind authentication, read the [auth overview](https://wasp.sh/docs/auth/overview). ### Login and Signup Flows #### Login ![Auth UI](https://wasp.sh/assets/images/login-8ea785a886771acd0b29543625c4f377.png) #### Signup ![Auth UI](https://wasp.sh/assets/images/signup-6d04c4e24d598ce3552a5603aebea0df.png) Some of the behavior you get out of the box: 1. Rate limiting We are limiting the rate of sign-up requests to **1 request per minute** per email address. This is done to prevent spamming. 2. Preventing user email leaks If somebody tries to signup with an email that already exists and it's verified, we *pretend* that the account was created instead of saying it's an existing account. This is done to prevent leaking the user's email address. 3. Allowing registration for unverified emails If a user tries to register with an existing but **unverified** email, we'll allow them to do that. This is done to prevent bad actors from locking out other users from registering with their email address. 4. Password validation Read more about the default password validation rules and how to override them in [auth overview docs](https://wasp.sh/docs/auth/overview). ### Email Verification Flow :::info[Automatic email verification in development] In development mode, you can skip the email verification step by setting the `SKIP_EMAIL_VERIFICATION_IN_DEV` environment variable to `true` in your `.env.server` file: ```env title=".env.server" SKIP_EMAIL_VERIFICATION_IN_DEV=true ``` This is useful when you are developing your app and don't want to go through the email verification flow every time you sign up. It can be also useful when you are writing automated tests for your app. ::: By default, Wasp requires the e-mail to be verified before allowing the user to log in. This is done by sending a verification email to the user's email address and requiring the user to click on a link in the email to verify their email address. Our setup looks like this: ```ts title="main.wasp.ts" // ... emailVerification: { clientRoute: "EmailVerificationRoute", } ``` When the user receives an e-mail, they receive a link that goes to the client route specified in the `clientRoute` field. In our case, this is the `EmailVerificationRoute` route we defined in the `main.wasp.ts` file. The content of the e-mail can be customized, read more about it in the [`EmailFlowConfig` API Reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/EmailFlowConfig#getemailcontentfn). #### Email Verification Page We defined our email verification page in the `auth.tsx` file. ![Auth UI](https://wasp.sh/assets/images/email_verification-5643900333decabde676675f1a462648.png) ### Password Reset Flow Users can request a password and then they'll receive an e-mail with a link to reset their password. Some of the behavior you get out of the box: 1. Rate limiting We are limiting the rate of sign-up requests to **1 request per minute** per email address. This is done to prevent spamming. 2. Preventing user email leaks If somebody requests a password reset with an unknown email address, we'll give back the same response as if the user requested a password reset successfully. This is done to prevent leaking information. Our setup in `main.wasp.ts` looks like this: ```ts title="main.wasp.ts" // ... passwordReset: { clientRoute: "PasswordResetRoute", } ``` #### Request Password Reset Page Users request their password to be reset by going to the `/request-password-reset` route. We defined our request password reset page in the `auth.tsx` file. ![Request password reset page](https://wasp.sh/assets/images/forgot_password_after-df7770780bdd4b4e6ac59401757d42e0.png) #### Password Reset Page When the user receives an e-mail, they receive a link that goes to the client route specified in the `clientRoute` field. In our case, this is the `PasswordResetRoute` route we defined in the `main.wasp.ts` file. ![Request password reset page](https://wasp.sh/assets/images/reset_password_after-c61da3703e0b0dbebb8a725935da6044.png) Users can enter their new password there. The content of the e-mail can be customized, read more about it in the [`EmailFlowConfig` API Reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/EmailFlowConfig#getemailcontentfn). ##### Password - `ensurePasswordIsPresent(args)` Checks if the password is present and throws an error if it's not. - `ensureValidPassword(args)` Checks if the password is valid and throws an error if it's not. Read more about the validation rules [here](https://wasp.sh/docs/auth/overview#default-validations). ### Using Auth To read more about how to set up the logout button and how to get access to the logged-in user in our client and server code, read the [auth overview docs](https://wasp.sh/docs/auth/overview). When you receive the `user` object [on the client or the server](https://wasp.sh/docs/auth/overview#accessing-the-logged-in-user), you'll be able to access the user's email and other information like this: ```ts const emailIdentity = user.identities.email // Email address the user used to sign up, e.g. "fluffyllama@app.com". emailIdentity.id // `true` if the user has verified their email address. emailIdentity.isEmailVerified // Datetime when the email verification email was sent. emailIdentity.emailVerificationSentAt // Datetime when the last password reset email was sent. emailIdentity.passwordResetSentAt ``` Read more about accessing the user data in the [Accessing User Data](https://wasp.sh/docs/auth/entities#accessing-the-auth-fields) section of the docs. ### API Reference [API reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Auth) #### [Auth ยป](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Auth) [All the options for the auth field of the app spec, including userEntity.](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Auth) [API reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/EmailAuthConfig) #### [EmailAuthConfig ยป](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/EmailAuthConfig) [All the options for the email auth method.](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/EmailAuthConfig) Read more about the `userSignupFields` function in the [Auth Overview docs](https://wasp.sh/docs/auth/overview#signup-fields-customization). ## Authentication / Email / Create your own UI When using the email auth provider, users log in with their email address and a password. On signup, Wasp validates the data and sends a verification email. The user account is not active until the user clicks the link in the verification email. Also, the user can reset their password through a similar flow. :::tip Read more about the default email and password validation rules in the [auth overview docs](https://wasp.sh/docs/auth/overview#default-validations). ::: Even though Wasp offers premade [Auth UI](https://wasp.sh/docs/auth/ui) for your authentication flows, there are times when you might want more customization, so we also give you the option to create your own UI and call Wasp's auth actions from your own code, similar to how Auth UI does it under the hood. ### Example code Below you can find a starting point for making your own UI in the client code. This example has all the necessary components to handle login, signup, email verification, and the password reset flow. You can customize any of its look and behaviour, just make sure to call the functions imported from `wasp/client/auth`. ```tsx title="src/pages/auth.tsx" import { login, requestPasswordReset, resetPassword, signup, verifyEmail, } from 'wasp/client/auth' import { useState } from 'react' import { useNavigate } from 'react-router' // This will be shown when the user wants to log in export function LoginPage() { const [email, setEmail] = useState('') const [password, setPassword] = useState('') const [error, setError] = useState(null) const navigate = useNavigate() async function handleSubmit(event: React.FormEvent) { event.preventDefault() setError(null) try { await login({ email, password }) navigate('/') } catch (error: unknown) { setError(error as Error) } } return (
{error &&

Error: {error.message}

} setEmail(e.target.value)} placeholder="Email" /> setPassword(e.target.value)} placeholder="Password" />
) } // This will be shown when the user wants to sign up export function SignupPage() { const [email, setEmail] = useState('') const [password, setPassword] = useState('') const [error, setError] = useState(null) const [needsConfirmation, setNeedsConfirmation] = useState(false) async function handleSubmit(event: React.FormEvent) { event.preventDefault() setError(null) try { await signup({ email, password }) setNeedsConfirmation(true) } catch (error: unknown) { console.error('Error during signup:', error) setError(error as Error) } } if (needsConfirmation) { return (

Check your email for the confirmation link. If you don't see it, check spam/junk folder.

) } return (
{error &&

Error: {error.message}

} setEmail(e.target.value)} placeholder="Email" /> setPassword(e.target.value)} placeholder="Password" />
) } // This will be shown has clicked on the link in their // email to verify their email address export function EmailVerificationPage() { const [error, setError] = useState(null) const navigate = useNavigate() async function handleClick() { setError(null) try { // The token is passed as a query parameter const token = new URLSearchParams(window.location.search).get('token') if (!token) throw new Error('Token not found in URL') await verifyEmail({ token }) navigate('/') } catch (error: unknown) { console.error('Error during email verification:', error) setError(error as Error) } } return ( <> {error &&

Error: {error.message}

} ) } // This will be shown when the user wants to reset their password export function RequestPasswordResetPage() { const [email, setEmail] = useState('') const [error, setError] = useState(null) const [needsConfirmation, setNeedsConfirmation] = useState(false) async function handleSubmit(event: React.FormEvent) { event.preventDefault() setError(null) try { await requestPasswordReset({ email }) setNeedsConfirmation(true) } catch (error: unknown) { console.error('Error during requesting reset:', error) setError(error as Error) } } if (needsConfirmation) { return (

Check your email for the confirmation link. If you don't see it, check spam/junk folder.

) } return (
{error &&

Error: {error.message}

} setEmail(e.target.value)} placeholder="Email" />
) } // This will be shown when the user clicks on the link in their // email to reset their password export function PasswordResetPage() { const [error, setError] = useState(null) const [newPassword, setNewPassword] = useState('') const navigate = useNavigate() async function handleSubmit(event: React.FormEvent) { event.preventDefault() setError(null) try { // The token is passed as a query parameter const token = new URLSearchParams(window.location.search).get('token') if (!token) throw new Error('Token not found in URL') await resetPassword({ token, password: newPassword }) navigate('/') } catch (error: unknown) { console.error('Error during password reset:', error) setError(error as Error) } } return (
{error &&

Error: {error.message}

} setNewPassword(e.target.value)} placeholder="New password" />
) } ``` ### API Reference You can import the following functions from `wasp/client/auth`: #### `login()` An action for logging in the user. Make sure to do a redirect on success (e.g. to the main page of the app). It takes one argument: - `data: object` required It has the following fields: - `email: string` required - `password: string` required #### `signup()` An action for signing up the user and starting the email verification. The user will not be logged in after this, as they still need to verify their email. It takes one argument: - `data: object` required It has the following fields: - `email: string` required - `password: string` required :::info By default, Wasp will only save the `email` and `password` fields. If you want to add extra fields to your signup process, read about [defining extra signup fields](https://wasp.sh/docs/auth/overview#customizing-the-signup-process). ::: #### `verifyEmail()` An action for marking the email as valid and the user account as active. Make sure to do a redirect on success (e.g. to the login page). It takes one argument: - `data: object` required It has the following fields: - `token: string` required The token that was created when signing up. It will be set as a URL Query Parameter named `token`. #### `requestPasswordReset()` An action for asking for a password reset email. This doesn't immediately reset their password, just sends the email. It takes one argument: - `data: object` required It has the following fields: - `email: string` required #### `resetPassword()` An action for confirming a password reset and providing the new password. Make sure to do a redirect on success (e.g. to the login page). It takes one argument: - `data: object` required It has the following fields: - `token: string` required The token that was created when requesting the password reset. It will be set as a URL Query Parameter named `token`. - `password: string` required The new password for the user. ## Authentication / Social Auth / Overview Social login options (e.g., *Log in with Google*) are a great (maybe even the best) solution for handling user accounts. A famous old developer joke tells us *"The best auth system is the one you never have to make."* Wasp wants to make adding social login options to your app as painless as possible. Using different social providers gives users a chance to sign into your app via their existing accounts on other platforms (Google, GitHub, etc.). This page goes through the common behaviors between all supported social login providers and shows you how to customize them. It also gives an overview of Wasp's UI helpers - the quickest possible way to get started with social auth. ### Available Providers Wasp currently supports the following social login providers: #### [Google ยป](https://wasp.sh/docs/auth/social-auth/google) [Users sign in with their Google account.](https://wasp.sh/docs/auth/social-auth/google) #### [Github ยป](https://wasp.sh/docs/auth/social-auth/github) [Users sign in with their Github account.](https://wasp.sh/docs/auth/social-auth/github) #### [Keycloak ยป](https://wasp.sh/docs/auth/social-auth/keycloak) [Users sign in with their Keycloak account.](https://wasp.sh/docs/auth/social-auth/keycloak) #### [Slack ยป](https://wasp.sh/docs/auth/social-auth/slack) [Users sign in with their Slack account.](https://wasp.sh/docs/auth/social-auth/slack) #### [Discord ยป](https://wasp.sh/docs/auth/social-auth/discord) [Users sign in with their Discord account.](https://wasp.sh/docs/auth/social-auth/discord) #### [Microsoft ยป](https://wasp.sh/docs/auth/social-auth/microsoft) [Users sign in with their Microsoft account.](https://wasp.sh/docs/auth/social-auth/microsoft) Click on each provider for more details. ### User Entity Wasp requires you to declare a `userEntity` for all `auth` methods (social or otherwise). This field tells Wasp which Entity represents the user. Here's what the full setup looks like: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { userEntity: "User", methods: { google: {} }, onAuthFailedRedirectTo: "/login" }, // ... }) ``` ```prisma title="schema.prisma" model User { id Int @id @default(autoincrement()) } ``` ### Default Behavior When a user **signs in for the first time**, Wasp creates a new user account and links it to the chosen auth provider account for future logins. ### Overrides By default, Wasp doesn't store any information it receives from the social login provider. It only stores the user's ID specific to the provider. If you wish to store more information about the user, you can override the default behavior. You can do this by defining the `userSignupFields` and `configFn` fields in `main.wasp.ts` for each provider. You can create custom signup setups, such as allowing users to define a custom username after they sign up with a social provider. #### Example: Allowing User to Set Their Username If you want to modify the signup flow (e.g., let users choose their own usernames), you will need to go through three steps: 1. The first step is adding a `isSignupComplete` property to your `User` Entity. This field will signal whether the user has completed the signup process. 2. The second step is overriding the default signup behavior. 3. The third step is implementing the rest of your signup flow and redirecting users where appropriate. Let's go through both steps in more detail. ##### 1. Adding the `isSignupComplete` Field to the `User` Entity ```prisma title="schema.prisma" model User { id Int @id @default(autoincrement()) username String? @unique isSignupComplete Boolean @default(false) } ``` ##### 2. Overriding the Default Behavior Declare an import under `auth.methods.google.userSignupFields` (the example assumes you're using Google): ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { userSignupFields } from "./src/auth/google" with { type: "ref" } export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { userEntity: "User", methods: { google: { userSignupFields } }, onAuthFailedRedirectTo: "/login" }, // ... }) ``` And implement the imported function: ```ts title="src/auth/google.ts" import { defineUserSignupFields } from "wasp/server/auth"; export const userSignupFields = defineUserSignupFields({ isSignupComplete: () => false, }); ``` Wasp automatically generates the `defineUserSignupFields` function to help you correctly type your `userSignupFields` object. ##### 3. Showing the Correct State on the Client You can check the `isSignupComplete` flag on the `user` object. Authenticated pages come with the [`user` prop](https://wasp.sh/docs/auth/overview#getting-the-user-in-authenticated-routes) which gives you access to the current user. If the `user` prop is out of reach, fetch the current user with the [`useAuth()` hook](https://wasp.sh/docs/auth/overview#getting-the-user-in-non-authenticated-routes). Depending on the flag's value, you can redirect users to the appropriate signup step. For example: 1. When the user lands on the homepage, check the value of `user.isSignupComplete`. 2. If it's `false`, it means the user has started the signup process but hasn't yet chosen their username. Therefore, you can redirect them to `EditUserDetailsPage` where they can edit the `username` property. ```tsx title="src/HomePage.tsx" import { Navigate } from "react-router"; import type { AuthUser } from "wasp/auth"; export function HomePage({ user }: { user: AuthUser }) { if (user.isSignupComplete === false) { return ; } // ... } ``` The same general principle applies to more complex signup procedures, just change the boolean `isSignupComplete` property to a property like `currentSignupStep` that can hold more values. #### Using the User's Provider Account Details Account details are provider-specific. Each provider has their own rules for defining the `userSignupFields` and `configFn` fields: #### [Google ยป](https://wasp.sh/docs/auth/social-auth/google#overrides) [Users sign in with their Google account.](https://wasp.sh/docs/auth/social-auth/google#overrides) #### [Github ยป](https://wasp.sh/docs/auth/social-auth/github#overrides) [Users sign in with their Github account.](https://wasp.sh/docs/auth/social-auth/github#overrides) #### [Keycloak ยป](https://wasp.sh/docs/auth/social-auth/keycloak#overrides) [Users sign in with their Keycloak account.](https://wasp.sh/docs/auth/social-auth/keycloak#overrides) #### [Slack ยป](https://wasp.sh/docs/auth/social-auth/slack#overrides) [Users sign in with their Slack account.](https://wasp.sh/docs/auth/social-auth/slack#overrides) #### [Discord ยป](https://wasp.sh/docs/auth/social-auth/discord#overrides) [Users sign in with their Discord account.](https://wasp.sh/docs/auth/social-auth/discord#overrides) #### [Microsoft ยป](https://wasp.sh/docs/auth/social-auth/microsoft#overrides) [Users sign in with their Microsoft account.](https://wasp.sh/docs/auth/social-auth/microsoft#overrides) Click on each provider for more details. ### API Reference [API reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/SocialAuthConfig) #### [SocialAuthConfig ยป](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/SocialAuthConfig) [All the options for the social auth methods.](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/SocialAuthConfig) For more information on the provider-specific behavior of the `userSignupFields` and `configFn` functions, check the provider-specific API References: #### [Google ยป](https://wasp.sh/docs/auth/social-auth/google#api-reference) [Users sign in with their Google account.](https://wasp.sh/docs/auth/social-auth/google#api-reference) #### [Github ยป](https://wasp.sh/docs/auth/social-auth/github#api-reference) [Users sign in with their Github account.](https://wasp.sh/docs/auth/social-auth/github#api-reference) #### [Keycloak ยป](https://wasp.sh/docs/auth/social-auth/keycloak#api-reference) [Users sign in with their Keycloak account.](https://wasp.sh/docs/auth/social-auth/keycloak#api-reference) #### [Slack ยป](https://wasp.sh/docs/auth/social-auth/slack#api-reference) [Users sign in with their Slack account.](https://wasp.sh/docs/auth/social-auth/slack#api-reference) #### [Discord ยป](https://wasp.sh/docs/auth/social-auth/discord#api-reference) [Users sign in with their Discord account.](https://wasp.sh/docs/auth/social-auth/discord#api-reference) #### [Microsoft ยป](https://wasp.sh/docs/auth/social-auth/microsoft#api-reference) [Users sign in with their Microsoft account.](https://wasp.sh/docs/auth/social-auth/microsoft#api-reference) Click on each provider for more details. ## Authentication / Social Auth / GitHub Wasp supports GitHub Authentication out of the box. GitHub is a great external auth choice when you're building apps for developers, as most of them already have a GitHub account. Letting your users log in using their GitHub accounts turns the signup process into a breeze. Let's walk through enabling GitHub Authentication, explain some of the default settings, and show how to override them. ### Setting up GitHub Auth Enabling GitHub Authentication comes down to a series of steps: 1. Enabling GitHub authentication in the Wasp file. 2. Adding the `User` entity. 3. Creating a GitHub OAuth app. 4. Adding the necessary Routes and Pages 5. Using Auth UI components in our Pages. Here's a skeleton of what our `main.wasp.ts` should look like after we're done: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { LoginPage } from "./src/pages/auth" with { type: "ref" } // Configuring the social authentication export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { // ... }, spec: [ // Defining routes and pages route("LoginRoute", "/login", page(LoginPage)), ], }) ``` #### 1. Adding GitHub Auth to Your Wasp File Let's start by properly configuring the Auth object: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { // 1. Specify the User entity (we'll define it next) userEntity: "User", methods: { // 2. Enable GitHub Auth gitHub: {} }, onAuthFailedRedirectTo: "/login" }, // ... }) ``` #### 2. Add the User Entity Let's now define the `auth.userEntity` entity in the `schema.prisma` file: ```prisma title="schema.prisma" // 3. Define the user entity model User { id Int @id @default(autoincrement()) // Add your own fields below // ... } ``` #### 3. Creating a GitHub OAuth App To use GitHub as an authentication method, you'll first need to create a GitHub App and provide Wasp with your client key and secret. Here's how you do it: 1. Create a GitHub account if you do not already have one: . 2. Create and configure a new GitHub App: . ![GitHub App Screenshot 1](https://wasp.sh/assets/images/github-app-1-b18764253a237b41d18fa9a05ccc1ccc.png) 3. We have to fill out **App name** and **Homepage URL** fields. Additionally we will add our authorization **Callback URL**s: - For development, put: `http://localhost:3001/auth/github/callback`. - Once you know your production URL you can add it via the **Add Callback URL** button, e.g. `https://your-server-url.com/auth/github/callback`. ![GitHub App Screenshot 2](https://wasp.sh/assets/images/github-app-2-07c8b302bb0cc42d0c4298970f0dc20f.png) 4. We will turn off the **Active** checkbox under the Webhook title, and then we can click **Create GitHub App**. ![GitHub App Screenshot 3](https://wasp.sh/assets/images/github-app-3-9be6d7ad852130c50a57e4b66020070c.png) 5. Finally, we can click **Generate a new client secret** button and then copy both **Client ID** and **Client secret**. ![GitHub App Screenshot 4](https://wasp.sh/assets/images/github-app-4-5e29b5b45f7b781e36ce7d4089d624c9.png) #### 4. Adding Environment Variables Add these environment variables to the `.env.server` file at the root of your project (take their values from the previous step): ```bash title=".env.server" GITHUB_CLIENT_ID=your-github-client-id GITHUB_CLIENT_SECRET=your-github-client-secret ``` #### 5. Adding the Necessary Routes and Pages Let's define the necessary authentication Routes and Pages. Add the following code to your `main.wasp.ts` file: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { LoginPage } from "./src/pages/auth" with { type: "ref" } export default app({ // ... spec: [ route("LoginRoute", "/login", page(LoginPage)), ], }) ``` We'll define the React components for these pages in the `src/pages/auth.tsx` file below. #### 6. Creating the Client Pages :::info We are using [Tailwind CSS](https://tailwindcss.com/) to style the pages. Read more about how to add it [here](https://wasp.sh/docs/guides/libraries/tailwind). ::: Let's create a `auth.tsx` file in the `src/pages` folder and add the following to it: ```tsx title="src/pages/auth.tsx" import type { ReactNode } from "react"; import { LoginForm } from "wasp/client/auth"; export function LoginPage() { return ( ); } // A layout component to center the content export function Layout({ children }: { children: ReactNode }) { return (
{children}
); } ``` We imported the generated Auth UI components and used them in our pages. Read more about the Auth UI components [here](https://wasp.sh/docs/auth/ui). #### Conclusion Yay, we've successfully set up GitHub Auth! ๐ŸŽ‰ *(Inlined image: GitHub Auth)* Running `wasp db migrate-dev` and `wasp start` should now give you a working app with authentication. To see how to protect specific pages (i.e., hide them from non-authenticated users), read the docs on [using auth](https://wasp.sh/docs/auth/overview). ### Default Behaviour Add `gitHub: {}` to the `auth.methods` object to use it with default settings. ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { userEntity: "User", methods: { gitHub: {} }, onAuthFailedRedirectTo: "/login" }, // ... }) ``` When a user **signs in for the first time**, Wasp creates a new user account and links it to the chosen auth provider account for future logins. ### Overrides By default, Wasp doesn't store any information it receives from the social login provider. It only stores the user's ID specific to the provider. There are two mechanisms used for overriding the default behavior: - `userSignupFields` - `configFn` Let's explore them in more detail. #### Data Received From GitHub We are using GitHub's API and its `/user` and `/user/emails` endpoints to get the user data. :::info[We combine the data from the two endpoints] You'll find the emails in the `emails` property in the object that you receive in `userSignupFields`. This is because we combine the data from the `/user` and `/user/emails` endpoints **if the `user` or `user:email` scope is requested.** ::: The data we receive from GitHub on the `/user` endpoint looks something this: ```json { "login": "octocat", "id": 1, "name": "monalisa octocat", "avatar_url": "https://github.com/images/error/octocat_happy.gif", "gravatar_id": "" // ... } ``` And the data from the `/user/emails` endpoint looks something like this: ```json [ { "email": "octocat@github.com", "verified": true, "primary": true, "visibility": "public" } ] ``` The fields you receive will depend on the scopes you requested. By default we don't specify any scopes. If you want to get the emails, you need to specify the `user` or `user:email` scope in the `configFn` function. For an up to date info about the data received from GitHub, please refer to the [GitHub API documentation](https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28#get-the-authenticated-user). #### Using the Data Received From GitHub When a user logs in using a social login provider, the backend receives some data about the user. Wasp lets you access this data inside the `userSignupFields` getters. For example, the User entity can include a `displayName` field which you can set based on the details received from the provider. Wasp also lets you customize the configuration of the providers' settings using the `configFn` function. Let's use this example to show both fields in action: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { getConfig, userSignupFields } from "./src/auth/github" with { type: "ref" } export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { userEntity: "User", methods: { gitHub: { configFn: getConfig, userSignupFields } }, onAuthFailedRedirectTo: "/login" }, // ... }) ``` ```prisma title="schema.prisma" model User { id Int @id @default(autoincrement()) username String @unique displayName String } // ... ``` ```ts title="src/auth/github.ts" import { defineUserSignupFields } from "wasp/server/auth"; export const userSignupFields = defineUserSignupFields({ username: () => "hardcoded-username", displayName: (data: any) => data.profile.name, }); export function getConfig() { return { scopes: ["user"], }; } ``` Wasp automatically generates the `defineUserSignupFields` function to help you correctly type your `userSignupFields` object. ### Using Auth To read more about how to set up the logout button and get access to the logged-in user in both client and server code, read the docs on [using auth](https://wasp.sh/docs/auth/overview). When you receive the `user` object [on the client or the server](https://wasp.sh/docs/auth/overview#accessing-the-logged-in-user), you'll be able to access the user's GitHub ID like this: ```ts const githubIdentity = user.identities.github // GitHub User ID for example "12345678" githubIdentity.id ``` Read more about accessing the user data in the [Accessing User Data](https://wasp.sh/docs/auth/entities#accessing-the-auth-fields) section of the docs. ### API Reference [API reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/SocialAuthConfig) #### [SocialAuthConfig ยป](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/SocialAuthConfig) [All the options for the gitHub auth method.](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/SocialAuthConfig) For the provider-specific behavior of the `configFn` and `userSignupFields` functions, check the [Overrides section](#overrides). For behavior common to all providers, check the [Social Auth Overview](https://wasp.sh/docs/auth/social-auth/overview). ## Authentication / Social Auth / Google Wasp supports Google Authentication out of the box. Google Auth is arguably the best external auth option, as most users on the web already have Google accounts. Enabling it lets your users log in using their existing Google accounts, greatly simplifying the process and enhancing the user experience. Let's walk through enabling Google authentication, explain some of the default settings, and show how to override them. ### Setting up Google Auth Enabling Google Authentication comes down to a series of steps: 1. Enabling Google authentication in the Wasp file. 2. Adding the `User` entity. 3. Creating a Google OAuth app. 4. Adding the necessary Routes and Pages 5. Using Auth UI components in our Pages. Here's a skeleton of what our `main.wasp.ts` should look like after we're done: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { LoginPage } from "./src/pages/auth" with { type: "ref" } // Configuring the social authentication export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { // ... }, spec: [ // Defining routes and pages route("LoginRoute", "/login", page(LoginPage)), ], }) ``` #### 1. Adding Google Auth to Your Wasp File Let's start by properly configuring the Auth object: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { // 1. Specify the User entity (we'll define it next) userEntity: "User", methods: { // 2. Enable Google Auth google: {} }, onAuthFailedRedirectTo: "/login" }, // ... }) ``` `userEntity` is explained in [the social auth overview](https://wasp.sh/docs/auth/social-auth/overview#user-entity). #### 2. Adding the User Entity Let's now define the `auth.userEntity` entity in the `schema.prisma` file: ```prisma title="schema.prisma" // 3. Define the user entity model User { id Int @id @default(autoincrement()) // Add your own fields below // ... } ``` #### 3. Creating a Google OAuth App To use Google as an authentication method, you'll first need to create a Google project and provide Wasp with your client key and secret. Here's how you do it: 1. Create a Google Cloud Platform account if you do not already have one: 2. Create and configure a new Google project here: ![Google Console Screenshot 1](https://wasp.sh/assets/images/integrations-google-v2-1-008e52a4561d115c2c26ce49639dcc4f.png) ![Google Console Screenshot 2](https://wasp.sh/assets/images/integrations-google-v2-2-6c2d6c95b88834d5c80530ff77d9cf4f.png) 3. Search for **Google Auth** in the top bar (1), click on **Google Auth Platform** (2). Then click on **Get Started** (3). ![Google Console Screenshot 3](https://wasp.sh/assets/images/integrations-google-v2-3-2fa867d7b04d692b187f89ee055f714f.png) ![Google Console Screenshot 4](https://wasp.sh/assets/images/integrations-google-v2-4-31ffaaab723f19ff4cf4988130529fa9.png) 4. Fill out you app information. For the **Audience** field, we will go with **External**. When you're done, click **Create**. ![Google Console Screenshot 5](https://wasp.sh/assets/images/integrations-google-v2-5-39a95a68d8e4b8015f66262299f65118.png) ![Google Console Screenshot 6](https://wasp.sh/assets/images/integrations-google-v2-6-e8b77a601c7340ccb4f2705c2eff216f.png) 5. You should now be in the **OAuth Overview** page. Click on **Create OAuth Client** (1). ![Google Console Screenshot 7](https://wasp.sh/assets/images/integrations-google-v2-7-907a0e615b62fce43066bd6d6b23b8c8.png) 6. Fill out the form. These are the values for a typical Wasp application: | # | Field | Value | | - | ------------------------ | -------------------------------------------- | | 1 | Application type | Web application | | 2 | Name | (your wasp app name) | | 3 | Authorized redirect URIs | `http://localhost:3001/auth/google/callback` | :::note Once you know on which URL(s) your API server will be deployed, also add those URL(s) to the **Authorized redirect URIs**.\ For example: `https://your-server-url.com/auth/google/callback` ::: ![Google Console Screenshot 8](https://wasp.sh/assets/images/integrations-google-v2-8-cbd285263686de2dfa533a835ae192e8.png) Then click on **Create** (4). 7. You will see a box saying **OAuth client created**. Click on **OK**. ![Google Console Screenshot 9](https://wasp.sh/assets/images/integrations-google-v2-9-0f12f8e9bb2831e3e6fb8358b3944511.png) 8. Click on the name of your newly-created app. ![Google Console Screenshot 10](https://wasp.sh/assets/images/integrations-google-v2-10-d4633d3d242d614939fa801e8eb0c83a.png) 9. On the right-hand side, you will see your **Client ID** (1) and **Client secret** (2). **Copy them somewhere safe, as you will need them for your app.** ![Google Console Screenshot 11](https://wasp.sh/assets/images/integrations-google-v2-11-bcfdd0934e0c2168680783681bf2b257.png) :::info These are the credentials your app will use to authenticate with Google. Do not share them anywhere publicly, as anyone with these credentials can impersonate your app and access user data. ::: 10. Click on **Data Access** (1) in the left-hand menu, then click on **Add or remove scopes** (2). You should select `userinfo.profile` (3), and optionally `userinfo.email` (4), or any other scopes you want to use. Remember to click **Update** and **Save** when done. ![Google Console Screenshot 12](https://wasp.sh/assets/images/integrations-google-v2-12-57982749bd48d7aaccca7f5d6ec739fe.png) ![Google Console Screenshot 13](https://wasp.sh/assets/images/integrations-google-v2-13-5cc7e5507e1f15ad3e5eabf222bccd49.png) 11. Go to **Audience** (1) in the left-hand menu, and add any test users you want (2). This is useful for testing your app before going live. You can add any email addresses you want to test with. ![Google Console Screenshot 14](https://wasp.sh/assets/images/integrations-google-v2-14-6d1477a0463aed2de449c286bbf0f9df.png) 12. Finally, you can go to **Branding** (1) in the left-hand menu, and customize your app's branding in the Google login page. *This is optional*, but recommended if you want to make your app look more professional. ![Google Console Screenshot 15](https://wasp.sh/assets/images/integrations-google-v2-15-35bb5e712b887128265630a9af3e07d8.png) #### 4. Adding Environment Variables Add these environment variables to the `.env.server` file at the root of your project (take their values from the previous step): ```bash title=".env.server" GOOGLE_CLIENT_ID=your-google-client-id GOOGLE_CLIENT_SECRET=your-google-client-secret ``` #### 5. Adding the Necessary Routes and Pages Let's define the necessary authentication Routes and Pages. Add the following code to your `main.wasp.ts` file: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { LoginPage } from "./src/pages/auth" with { type: "ref" } export default app({ // ... spec: [ route("LoginRoute", "/login", page(LoginPage)), ], }) ``` We'll define the React components for these pages in the `src/pages/auth.tsx` file below. #### 6. Create the Client Pages :::info We are using [Tailwind CSS](https://tailwindcss.com/) to style the pages. Read more about how to add it [here](https://wasp.sh/docs/guides/libraries/tailwind). ::: Let's create a `auth.tsx` file in the `src/pages` folder and add the following to it: ```tsx title="src/pages/auth.tsx" import type { ReactNode } from "react"; import { LoginForm } from "wasp/client/auth"; export function LoginPage() { return ( ); } // A layout component to center the content export function Layout({ children }: { children: ReactNode }) { return (
{children}
); } ``` We imported the generated Auth UI components and used them in our pages. Read more about the Auth UI components [here](https://wasp.sh/docs/auth/ui). #### Conclusion Yay, we've successfully set up Google Auth! ๐ŸŽ‰ *(Inlined image: Google Auth)* Running `wasp db migrate-dev` and `wasp start` should now give you a working app with authentication. To see how to protect specific pages (i.e., hide them from non-authenticated users), read the docs on [using auth](https://wasp.sh/docs/auth/overview). ### Default Behaviour Add `google: {}` to the `auth.methods` object to use it with default settings: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { userEntity: "User", methods: { google: {} }, onAuthFailedRedirectTo: "/login" }, // ... }) ``` When a user **signs in for the first time**, Wasp creates a new user account and links it to the chosen auth provider account for future logins. ### Overrides By default, Wasp doesn't store any information it receives from the social login provider. It only stores the user's ID specific to the provider. There are two mechanisms used for overriding the default behavior: - `userSignupFields` - `configFn` Let's explore them in more detail. #### Data Received From Google We are using Google's API and its `/userinfo` endpoint to fetch the user's data. The data received from Google is an object which can contain the following fields: ```json [ "name", "given_name", "family_name", "email", "email_verified", "aud", "exp", "iat", "iss", "locale", "picture", "sub" ] ``` The fields you receive depend on the scopes you request. The default scope is set to `profile` only. If you want to get the user's email, you need to specify the `email` scope in the `configFn` function. For an up to date info about the data received from Google, please refer to the [Google API documentation](https://developers.google.com/identity/openid-connect/openid-connect#an-id-tokens-payload). #### Using the Data Received From Google When a user logs in using a social login provider, the backend receives some data about the user. Wasp lets you access this data inside the `userSignupFields` getters. For example, the User entity can include a `displayName` field which you can set based on the details received from the provider. Wasp also lets you customize the configuration of the providers' settings using the `configFn` function. Let's use this example to show both fields in action: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { getConfig, userSignupFields } from "./src/auth/google" with { type: "ref" } export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { userEntity: "User", methods: { google: { configFn: getConfig, userSignupFields } }, onAuthFailedRedirectTo: "/login" }, // ... }) ``` ```prisma title="schema.prisma" model User { id Int @id @default(autoincrement()) username String @unique displayName String } // ... ``` ```ts title="src/auth/google.ts" import { defineUserSignupFields } from "wasp/server/auth"; export const userSignupFields = defineUserSignupFields({ username: () => "hardcoded-username", displayName: (data: any) => data.profile.name, }); export function getConfig() { return { scopes: ["profile", "email"], }; } ``` Wasp automatically generates the `defineUserSignupFields` function to help you correctly type your `userSignupFields` object. ### Using Auth To read more about how to set up the logout button and get access to the logged-in user in both client and server code, read the docs on [using auth](https://wasp.sh/docs/auth/overview). When you receive the `user` object [on the client or the server](https://wasp.sh/docs/auth/overview#accessing-the-logged-in-user), you'll be able to access the user's Google ID like this: ```ts const googleIdentity = user.identities.google // Google User ID for example "123456789012345678901" googleIdentity.id ``` Read more about accessing the user data in the [Accessing User Data](https://wasp.sh/docs/auth/entities#accessing-the-auth-fields) section of the docs. ### API Reference [API reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/SocialAuthConfig) #### [SocialAuthConfig ยป](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/SocialAuthConfig) [All the options for the google auth method.](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/SocialAuthConfig) For the provider-specific behavior of the `configFn` and `userSignupFields` functions, check the [Overrides section](#overrides). For behavior common to all providers, check the [Social Auth Overview](https://wasp.sh/docs/auth/social-auth/overview). ## Authentication / Social Auth / Keycloak Wasp supports Keycloak Authentication out of the box. [Keycloak](https://www.keycloak.org/) is an open-source identity and access management solution for modern applications and services. Keycloak provides both SAML and OpenID protocol solutions. It also has a very flexible and powerful administration UI. Let's walk through enabling Keycloak authentication, explain some of the default settings, and show how to override them. ### Setting up Keycloak Auth Enabling Keycloak Authentication comes down to a series of steps: 1. Enabling Keycloak authentication in the Wasp file. 2. Adding the `User` entity. 3. Creating a Keycloak client. 4. Adding the necessary Routes and Pages 5. Using Auth UI components in our Pages. Here's a skeleton of what our `main.wasp.ts` should look like after we're done: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { LoginPage } from "./src/pages/auth" with { type: "ref" } // Configuring the social authentication export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { // ... }, spec: [ // Defining routes and pages route("LoginRoute", "/login", page(LoginPage)), ], }) ``` #### 1. Adding Keycloak Auth to Your Wasp File Let's start by properly configuring the Auth object: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { // 1. Specify the User entity (we'll define it next) userEntity: "User", methods: { // 2. Enable Keycloak Auth keycloak: {} }, onAuthFailedRedirectTo: "/login" }, // ... }) ``` `userEntity` is explained in [the social auth overview](https://wasp.sh/docs/auth/social-auth/overview#user-entity). #### 2. Adding the User Entity Let's now define the `auth.userEntity` entity in the `schema.prisma` file: ```prisma title="schema.prisma" // 3. Define the user entity model User { id Int @id @default(autoincrement()) // Add your own fields below // ... } ``` #### 3. Creating a Keycloak Client 1. Log into your Keycloak admin console. 2. Under **Clients**, click on **Create Client**. ![Keycloak Screenshot 1](https://wasp.sh/assets/images/1-keycloak-b0390d5092c134084709858a87f87073.png) 1. Fill in the **Client ID** and choose a name for the client. ![Keycloak Screenshot 2](https://wasp.sh/assets/images/2-keycloak-c0406518fa25f94ccfdbe868736b70b9.png) 1. In the next step, enable **Client Authentication**. ![Keycloak Screenshot 3](https://wasp.sh/assets/images/3-keycloak-8840e9161762ffce794c83d5cf5b4474.png) 1. Under **Valid Redirect URIs**, add `http://localhost:3001/auth/keycloak/callback` for local development. ![Keycloak Screenshot 4](https://wasp.sh/assets/images/4-keycloak-33ed7897f5a7bd8a8666ffc302888abd.png) - Once you know on which URL(s) your API server will be deployed, also add those URL(s). - For example: `https://my-server-url.com/auth/keycloak/callback`. 1. Click **Save**. 2. In the **Credentials** tab, copy the **Client Secret** value, which we'll use in the next step. ![Keycloak Screenshot 5](https://wasp.sh/assets/images/5-keycloak-bfc17f1cb42a9bf9c4bdc6108ddd86c2.png) #### 4. Adding Environment Variables Add these environment variables to the `.env.server` file at the root of your project (take their values from the previous step): ```bash title=".env.server" KEYCLOAK_CLIENT_ID=your-keycloak-client-id KEYCLOAK_CLIENT_SECRET=your-keycloak-client-secret KEYCLOAK_REALM_URL=https://your-keycloak-url.com/realms/master ``` We assumed in the `KEYCLOAK_REALM_URL` env variable that you are using the `master` realm. If you are using a different realm, replace `master` with your realm name. #### 5. Adding the Necessary Routes and Pages Let's define the necessary authentication Routes and Pages. Add the following code to your `main.wasp.ts` file: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { LoginPage } from "./src/pages/auth" with { type: "ref" } export default app({ // ... spec: [ route("LoginRoute", "/login", page(LoginPage)), ], }) ``` We'll define the React components for these pages in the `src/pages/auth.tsx` file below. #### 6. Create the Client Pages :::info We are using [Tailwind CSS](https://tailwindcss.com/) to style the pages. Read more about how to add it [here](https://wasp.sh/docs/guides/libraries/tailwind). ::: Let's create a `auth.tsx` file in the `src/pages` folder and add the following to it: ```tsx title="src/pages/auth.tsx" import type { ReactNode } from "react"; import { LoginForm } from "wasp/client/auth"; export function LoginPage() { return ( ); } // A layout component to center the content export function Layout({ children }: { children: ReactNode }) { return (
{children}
); } ``` We imported the generated Auth UI components and used them in our pages. Read more about the Auth UI components [here](https://wasp.sh/docs/auth/ui). #### Conclusion Yay, we've successfully set up Keycloak Auth! Running `wasp db migrate-dev` and `wasp start` should now give you a working app with authentication. To see how to protect specific pages (i.e., hide them from non-authenticated users), read the docs on [using auth](https://wasp.sh/docs/auth/overview). ### Default Behaviour Add `keycloak: {}` to the `auth.methods` object to use it with default settings: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { userEntity: "User", methods: { keycloak: {} }, onAuthFailedRedirectTo: "/login" }, // ... }) ``` When a user **signs in for the first time**, Wasp creates a new user account and links it to the chosen auth provider account for future logins. ### Overrides By default, Wasp doesn't store any information it receives from the social login provider. It only stores the user's ID specific to the provider. There are two mechanisms used for overriding the default behavior: - `userSignupFields` - `configFn` Let's explore them in more detail. #### Data Received From Keycloak We are using Keycloak's API and its `/userinfo` endpoint to fetch the user's data. ```ts title="Keycloak user data" { sub: "5adba8fc-3ea6-445a-a379-13f0bb0b6969", email_verified: true, name: "Test User", preferred_username: "test", given_name: "Test", family_name: "User", email: "test@example.com" } ``` The fields you receive will depend on the scopes you requested. The default scope is set to `profile` only. If you want to get the user's email, you need to specify the `email` scope in the `configFn` function. For up-to-date info about the data received from Keycloak, please refer to the [Keycloak API documentation](https://www.keycloak.org/docs-api/23.0.7/javadocs/org/keycloak/representations/UserInfo.html). #### Using the Data Received From Keycloak When a user logs in using a social login provider, the backend receives some data about the user. Wasp lets you access this data inside the `userSignupFields` getters. For example, the User entity can include a `displayName` field which you can set based on the details received from the provider. Wasp also lets you customize the configuration of the providers' settings using the `configFn` function. Let's use this example to show both fields in action: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { getConfig, userSignupFields } from "./src/auth/keycloak" with { type: "ref" } export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { userEntity: "User", methods: { keycloak: { configFn: getConfig, userSignupFields } }, onAuthFailedRedirectTo: "/login" }, // ... }) ``` ```prisma title="schema.prisma" model User { id Int @id @default(autoincrement()) username String @unique displayName String } // ... ``` ```ts title="src/auth/keycloak.ts" import { defineUserSignupFields } from "wasp/server/auth"; export const userSignupFields = defineUserSignupFields({ username: () => "hardcoded-username", displayName: (data: any) => data.profile.name, }); export function getConfig() { return { scopes: ["profile", "email"], }; } ``` Wasp automatically generates the `defineUserSignupFields` function to help you correctly type your `userSignupFields` object. ### Using Auth To read more about how to set up the logout button and get access to the logged-in user in both client and server code, read the docs on [using auth](https://wasp.sh/docs/auth/overview). When you receive the `user` object [on the client or the server](https://wasp.sh/docs/auth/overview#accessing-the-logged-in-user), you'll be able to access the user's Keycloak ID like this: ```ts const keycloakIdentity = user.identities.keycloak // Keycloak User ID for example "12345678-1234-1234-1234-123456789012" keycloakIdentity.id ``` Read more about accessing the user data in the [Accessing User Data](https://wasp.sh/docs/auth/entities#accessing-the-auth-fields) section of the docs. ### API Reference [API reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/SocialAuthConfig) #### [SocialAuthConfig ยป](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/SocialAuthConfig) [All the options for the keycloak auth method.](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/SocialAuthConfig) For the provider-specific behavior of the `configFn` and `userSignupFields` functions, check the [Overrides section](#overrides). For behavior common to all providers, check the [Social Auth Overview](https://wasp.sh/docs/auth/social-auth/overview). ## Authentication / Social Auth / Slack Wasp supports Slack Authentication out of the box. Using Slack Authentication is perfect when you build a control panel for a Slack app. Let's walk through enabling Slack Authentication, explain some quirks, explore default settings and show how to override them. ### Setting up Slack Auth Enabling Slack Authentication comes down to a series of steps: 1. Enabling Slack authentication in the Wasp file. 2. Adding the `User` entity. 3. Creating Slack App. 4. Adding the necessary Routes and Pages 5. Using Auth UI components in our Pages. Here's a skeleton of what our `main.wasp.ts` should look like after we're done: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { LoginPage } from "./src/pages/auth" with { type: "ref" } // Configuring the social authentication export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { // ... }, spec: [ // Defining routes and pages route("LoginRoute", "/login", page(LoginPage)), ], }) ``` #### 1. Enabling Slack authentication in the Wasp file. Now let's properly configure the Auth object: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { // 1. Specify the User entity (we'll define it next) userEntity: "User", methods: { // 2. Enable Slack Auth slack: {} }, onAuthFailedRedirectTo: "/login" }, // ... }) ``` #### 2. Add the User Entity Let's now define the `auth.userEntity` entity in the `schema.prisma` file: ```prisma title="schema.prisma" // 3. Define the user entity model User { id Int @id @default(autoincrement()) // Add your own fields below // ... } ``` #### 3. Creating a Slack App To use Slack as an authentication method, you'll first need to create a Slack App and provide Wasp with your client key and secret. Here's how you do it: 1. Log into your Slack account and navigate to: . 2. Select **Create New App**. 3. Click "From scratch" 4. Enter App Name and select workspace that should host your app. ![Slack Applications Screenshot](https://wasp.sh/img/integrations-slack-1.png) 4. Go to the **OAuth & Permissions** tab on the sidebar and click **Add New Redirect URL**. - Enter the value `https://.local.lt/auth/slack/callback`, where `` is your selected localtunnel subdomain. - Slack requires us to use HTTPS even when developing, [read below](#slack-https) how to set it up. 5. Hit **Save URLs**. 6. Go to **Basic Information** tab 7. Hit **Show** next to **Client Secret** 8. Copy your Client ID and Client Secret as you'll need them in the next step. :::tip Be precise with your redirect URL. Slackโ€™s redirect URLs are case-sensitive and sensitive to trailing slashes. For example, `https://your-app.loca.lt/auth/slack/callback` and `https://your-app.loca.lt/auth/slack/callback/` are **not** the same. ::: #### 4. Adding Environment Variables Add these environment variables to the `.env.server` file at the root of your project (take their values from the previous step): ```bash title=".env.server" SLACK_CLIENT_ID=your-slack-client-id SLACK_CLIENT_SECRET=your-slack-client-secret ``` #### 5. Adding the Necessary Routes and Pages Let's define the necessary authentication Routes and Pages. Add the following code to your `main.wasp.ts` file: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { LoginPage } from "./src/pages/auth" with { type: "ref" } export default app({ // ... spec: [ route("LoginRoute", "/login", page(LoginPage)), ], }) ``` We'll define the React components for these pages in the `src/pages/auth.tsx` file below. #### 6. Creating the Client Pages :::info We are using [Tailwind CSS](https://tailwindcss.com/) to style the pages. Read more about how to add it [here](https://wasp.sh/docs/guides/libraries/tailwind). ::: Let's create a `auth.tsx` file in the `src/pages` folder and add the following to it: ```tsx title="src/pages/auth.tsx" import type { ReactNode } from "react"; import { LoginForm } from "wasp/client/auth"; export function LoginPage() { return ( ); } // A layout component to center the content export function Layout({ children }: { children: ReactNode }) { return (
{children}
); } ``` We imported the generated Auth UI components and used them in our pages. Read more about the Auth UI components [here](https://wasp.sh/docs/auth/ui). #### Conclusion Yay, we've successfully set up Slack Auth! ๐ŸŽ‰ ![Slack Auth](https://wasp.sh/assets/images/slack-a021290efd4071dd47a4f132790fd48b.png) Running `wasp db migrate-dev` and `wasp start` should now give you a working app with authentication. To see how to protect specific pages (i.e., hide them from non-authenticated users), read the docs on [using auth](https://wasp.sh/docs/auth/overview). ### Developing with Slack auth and HTTPS {#slack-https} Unlike most OAuth providers, Slack **requires HTTPS and publicly accessible URL for the OAuth redirect URL**. This means that we can't simply use `localhost:3001` as a base host for redirect urls. Instead, we need to configure Wasp server to be publicly available under HTTPS, even in the local development environment. Fortunately, there are quite a few free and convenient tools available to simplify the process, such as [localtunnel.me](https://localtunnel.me/) (free) and [ngrok.com](https://ngrok.com) (lots of features, but free tier is limited). Using localtunnel Install localtunnel globally with `npm install -g localtunnel`. Start a tunnel with `lt --port 3001 -s `, where `` is a unique subdomain you would like to have. :::info[Subdomain option] Usually localtunnel will assign you a random subdomain on each start, but you can specify it with the `-s` flag. Doing it this way will make it easier to remember the URL and will also make it easier to set up the redirect URL in Slack app settings. ::: After starting the tunnel, you will see your tunnel URL in the terminal. Go to that URL to unlock the tunnel by entering your IP address in a field that appears on the page the first time you open it in the browser. This is a basic anti-abuse mechanism. If you're not sure what your IP is, you can find it by running `curl ifconfig.me` or going to [ifconfig.me](https://ifconfig.me). Now that your server is exposed to the public, we need to configure Wasp to use the new public domain. This needs to be done in two places: server and client configuration. To configure client, add this line to your `.env.client` file (create it if doesn't exist): ```bash title=".env.client" REACT_APP_API_URL=https://.loca.lt ``` Similarly, to configure the server, add this line to your `.env.server`: ```bash title=".env.server" WASP_SERVER_URL=https://.loca.lt ``` ### Default Behaviour Add `slack: {}` to the `auth.methods` object to use it with default settings. ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { userEntity: "User", methods: { slack: {} }, onAuthFailedRedirectTo: "/login" }, // ... }) ``` When a user **signs in for the first time**, Wasp creates a new user account and links it to the chosen auth provider account for future logins. ### Overrides By default, Wasp doesn't store any information it receives from the social login provider. It only stores the user's ID specific to the provider. There are two mechanisms used for overriding the default behavior: - `userSignupFields` - `configFn` Let's explore them in more detail. #### Data Received From Slack We are using Slack's API and its `/openid.connect.userInfo` endpoint to get the user data. The data we receive from Slack on the `/openid.connect.userInfo` endpoint looks something like this: ```json { "ok": true, "sub": "U0R7JM", "https://slack.com/user_id": "U0R7JM", "https://slack.com/team_id": "T0R7GR", "email": "krane@slack-corp.com", "email_verified": true, "date_email_verified": 1622128723, "name": "krane", "picture": "https://secure.gravatar.com/....png", "given_name": "Bront", "family_name": "Labradoodle", "locale": "en-US", "https://slack.com/team_name": "kraneflannel", "https://slack.com/team_domain": "kraneflannel", "https://slack.com/user_image_24": "...", "https://slack.com/user_image_32": "...", "https://slack.com/user_image_48": "...", "https://slack.com/user_image_72": "...", "https://slack.com/user_image_192": "...", "https://slack.com/user_image_512": "...", "https://slack.com/team_image_34": "...", "https://slack.com/team_image_44": "...", "https://slack.com/team_image_68": "...", "https://slack.com/team_image_88": "...", "https://slack.com/team_image_102": "...", "https://slack.com/team_image_132": "...", "https://slack.com/team_image_230": "...", "https://slack.com/team_image_default": true } ``` The fields you receive depend on the scopes you request. In the example above, the scope includes `email`, `profile` and `openid`. By default, only `openid` is requested. See below for instructions on how to request additional scopes. For an up to date info about the data received from Slack, please refer to the [Slack API documentation](https://api.slack.com/methods/openid.connect.userInfo). #### Using the Data Received From Slack When a user logs in using a social login provider, the backend receives some data about the user. Wasp lets you access this data inside the `userSignupFields` getters. For example, the User entity can include a `displayName` field which you can set based on the details received from the provider. Wasp also lets you customize the configuration of the providers' settings using the `configFn` function. Let's use this example to show both fields in action: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { config, userSignupFields } from "./src/auth/slack" with { type: "ref" } export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { userEntity: "User", methods: { slack: { configFn: config, userSignupFields } }, onAuthFailedRedirectTo: "/login" }, // ... }) ``` ```prisma title="schema.prisma" model User { id Int @id @default(autoincrement()) username String @unique avatarUrl String } // ... ``` ```ts title="src/auth/slack.ts" import { defineUserSignupFields } from "wasp/server/auth"; export function config() { console.log("Inside user-supplied Slack config"); return { scopes: ["openid", "email", "profile"], }; } export const userSignupFields = defineUserSignupFields({ username: (data: any) => data.profile.name, avatarUrl: (data: any) => data.profile.picture, }); ``` Wasp automatically generates the `defineUserSignupFields` function to help you correctly type your `userSignupFields` object. ### Using Auth To read more about how to set up the logout button and get access to the logged-in user in both client and server code, read the docs on [using auth](https://wasp.sh/docs/auth/overview). When you receive the `user` object [on the client or the server](https://wasp.sh/docs/auth/overview#accessing-the-logged-in-user), you'll be able to access the user's Slack ID like this: ```ts const slackIdentity = user.identities.slack // Discord User ID for example "80351110224678912" slackIdentity.id ``` Read more about accessing the user data in the [Accessing User Data](https://wasp.sh/docs/auth/entities#accessing-the-auth-fields) section of the docs. ### API Reference [API reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/SocialAuthConfig) #### [SocialAuthConfig ยป](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/SocialAuthConfig) [All the options for the slack auth method.](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/SocialAuthConfig) For the provider-specific behavior of the `configFn` and `userSignupFields` functions, check the [Overrides section](#overrides). For behavior common to all providers, check the [Social Auth Overview](https://wasp.sh/docs/auth/social-auth/overview). ## Authentication / Social Auth / Discord Wasp supports Discord Authentication out of the box. Letting your users log in using their Discord accounts turns the signup process into a breeze. Let's walk through enabling Discord Authentication, explain some of the default settings, and show how to override them. ### Setting up Discord Auth Enabling Discord Authentication comes down to a series of steps: 1. Enabling Discord authentication in the Wasp file. 2. Adding the `User` entity. 3. Creating a Discord App. 4. Adding the necessary Routes and Pages 5. Using Auth UI components in our Pages. Here's a skeleton of what our `main.wasp.ts` should look like after we're done: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { LoginPage } from "./src/pages/auth" with { type: "ref" } // Configuring the social authentication export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { // ... }, spec: [ // Defining routes and pages route("LoginRoute", "/login", page(LoginPage)), ], }) ``` #### 1. Adding Discord Auth to Your Wasp File Let's start by properly configuring the Auth object: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { // 1. Specify the User entity (we'll define it next) userEntity: "User", methods: { // 2. Enable Discord Auth discord: {} }, onAuthFailedRedirectTo: "/login" }, // ... }) ``` #### 2. Add the User Entity Let's now define the `auth.userEntity` entity in the `schema.prisma` file: ```prisma title="schema.prisma" // 3. Define the user entity model User { id Int @id @default(autoincrement()) // Add your own fields below // ... } ``` #### 3. Creating a Discord App To use Discord as an authentication method, you'll first need to create a Discord App and provide Wasp with your client key and secret. Here's how you do it: 1. Log into your Discord account and navigate to: . 2. Select **New Application**. 3. Supply required information. ![Discord Applications Screenshot](https://wasp.sh/img/integrations-discord-1.png) 4. Go to the **OAuth2** tab on the sidebar and click **Add Redirect** - For development, put: `http://localhost:3001/auth/discord/callback`. - Once you know on which URL your API server will be deployed, you can create a new app with that URL instead e.g. `https://your-server-url.com/auth/discord/callback`. 4. Hit **Save Changes**. 5. Hit **Reset Secret**. 6. Copy your Client ID and Client secret as you'll need them in the next step. #### 4. Adding Environment Variables Add these environment variables to the `.env.server` file at the root of your project (take their values from the previous step): ```bash title=".env.server" DISCORD_CLIENT_ID=your-discord-client-id DISCORD_CLIENT_SECRET=your-discord-client-secret ``` #### 5. Adding the Necessary Routes and Pages Let's define the necessary authentication Routes and Pages. Add the following code to your `main.wasp.ts` file: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { LoginPage } from "./src/pages/auth" with { type: "ref" } export default app({ // ... spec: [ route("LoginRoute", "/login", page(LoginPage)), ], }) ``` We'll define the React components for these pages in the `src/pages/auth.tsx` file below. #### 6. Creating the Client Pages :::info We are using [Tailwind CSS](https://tailwindcss.com/) to style the pages. Read more about how to add it [here](https://wasp.sh/docs/guides/libraries/tailwind). ::: Let's create a `auth.tsx` file in the `src/pages` folder and add the following to it: ```tsx title="src/pages/auth.tsx" import type { ReactNode } from "react"; import { LoginForm } from "wasp/client/auth"; export function LoginPage() { return ( ); } // A layout component to center the content export function Layout({ children }: { children: ReactNode }) { return (
{children}
); } ``` We imported the generated Auth UI components and used them in our pages. Read more about the Auth UI components [here](https://wasp.sh/docs/auth/ui). #### Conclusion Yay, we've successfully set up Discord Auth! ๐ŸŽ‰ *(Inlined image: Discord Auth)* Running `wasp db migrate-dev` and `wasp start` should now give you a working app with authentication. To see how to protect specific pages (i.e., hide them from non-authenticated users), read the docs on [using auth](https://wasp.sh/docs/auth/overview). ### Default Behaviour Add `discord: {}` to the `auth.methods` object to use it with default settings. ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { userEntity: "User", methods: { discord: {} }, onAuthFailedRedirectTo: "/login" }, // ... }) ``` When a user **signs in for the first time**, Wasp creates a new user account and links it to the chosen auth provider account for future logins. ### Overrides By default, Wasp doesn't store any information it receives from the social login provider. It only stores the user's ID specific to the provider. There are two mechanisms used for overriding the default behavior: - `userSignupFields` - `configFn` Let's explore them in more detail. #### Data Received From Discord We are using Discord's API and its `/users/@me` endpoint to get the user data. The data we receive from Discord on the `/users/@me` endpoint looks something like this: ```json { "id": "80351110224678912", "username": "Nelly", "discriminator": "1337", "avatar": "8342729096ea3675442027381ff50dfe", "verified": true, "flags": 64, "banner": "06c16474723fe537c283b8efa61a30c8", "accent_color": 16711680, "premium_type": 1, "public_flags": 64, "avatar_decoration_data": { "sku_id": "1144058844004233369", "asset": "a_fed43ab12698df65902ba06727e20c0e" } } ``` The fields you receive will depend on the scopes you requested. The default scope is set to `identify` only. If you want to get the email, you need to specify the `email` scope in the `configFn` function. For an up to date info about the data received from Discord, please refer to the [Discord API documentation](https://discord.com/developers/docs/resources/user#user-object-user-structure). #### Using the Data Received From Discord When a user logs in using a social login provider, the backend receives some data about the user. Wasp lets you access this data inside the `userSignupFields` getters. For example, the User entity can include a `displayName` field which you can set based on the details received from the provider. Wasp also lets you customize the configuration of the providers' settings using the `configFn` function. Let's use this example to show both fields in action: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { getConfig, userSignupFields } from "./src/auth/discord" with { type: "ref" } export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { userEntity: "User", methods: { discord: { configFn: getConfig, userSignupFields } }, onAuthFailedRedirectTo: "/login" }, // ... }) ``` ```prisma title="schema.prisma" model User { id Int @id @default(autoincrement()) username String @unique displayName String } // ... ``` ```ts title="src/auth/discord.ts" import { defineUserSignupFields } from "wasp/server/auth"; export const userSignupFields = defineUserSignupFields({ username: (data: any) => data.profile.global_name, avatarUrl: (data: any) => data.profile.avatar, }); export function getConfig() { return { scopes: ["identify"], }; } ``` Wasp automatically generates the `defineUserSignupFields` function to help you correctly type your `userSignupFields` object. ### Using Auth To read more about how to set up the logout button and get access to the logged-in user in both client and server code, read the docs on [using auth](https://wasp.sh/docs/auth/overview). When you receive the `user` object [on the client or the server](https://wasp.sh/docs/auth/overview#accessing-the-logged-in-user), you'll be able to access the user's Discord ID like this: ```ts const discordIdentity = user.identities.discord // Discord User ID for example "80351110224678912" discordIdentity.id ``` Read more about accessing the user data in the [Accessing User Data](https://wasp.sh/docs/auth/entities#accessing-the-auth-fields) section of the docs. ### API Reference [API reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/SocialAuthConfig) #### [SocialAuthConfig ยป](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/SocialAuthConfig) [All the options for the discord auth method.](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/SocialAuthConfig) For the provider-specific behavior of the `configFn` and `userSignupFields` functions, check the [Overrides section](#overrides). For behavior common to all providers, check the [Social Auth Overview](https://wasp.sh/docs/auth/social-auth/overview). ## Authentication / Social Auth / Microsoft Wasp supports Microsoft Authentication out of the box. Microsoft Auth uses [Microsoft Entra ID](https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id) as the identity provider. This lets your users sign in with their Microsoft accounts โ€” including personal Microsoft accounts, work/school (Microsoft 365) accounts, or both, depending on your configuration. Let's walk through enabling Microsoft authentication, explain some of the default settings, and show how to override them. ### Understanding tenants When setting up Microsoft Authentication, you'll encounter the concept of "tenants". A tenant represents an organization in Microsoft Entra ID. Depending on the supported account types you choose during app registration, your app will be associated with a specific tenant, or be multi-tenant. When planning out your Microsoft integration, you have to decide which kind of accounts will be able to sign in. This will determine the tenant ID you use in your configuration, and the users that can sign in to your app. | Supported Account Types | Tenant ID Value | Description | Best for | | ----------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------- | | Tenant-specific | Your Tenant ID (generated by Microsoft) | Only users in your Microsoft Entra ID tenant can sign in. | Internal-only apps for a specific organization. | | Organization accounts | `organizations` | Any user with a "work or school" Microsoft account can sign in, but not personal accounts. | B2B apps targeting users in other organizations. | | Personal accounts | `consumers` | Only personal Microsoft accounts (e.g., Outlook.com, Hotmail, Xbox) can sign in. | Consumer-facing apps targeting individual users. | | All of the above | `common` | Both organizational accounts and personal Microsoft accounts can sign in. | Apps that can be used by personal, work, and school accounts. | We recommend choosing the least-permissive option that fits your use case, as Microsoft might require you to submit to a verification process if you want to use the higher-privileged tenant types in production. ### Setting up Microsoft Auth Enabling Microsoft Authentication comes down to a series of steps: 1. Enabling Microsoft authentication in the Wasp file. 2. Adding the `User` entity. 3. Creating a Microsoft Entra ID app registration. 4. Adding the necessary Routes and Pages 5. Using Auth UI components in our Pages. Here's a skeleton of what our `main.wasp.ts` should look like after we're done: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { LoginPage } from "./src/pages/auth" with { type: "ref" } // Configuring the social authentication export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { // ... }, spec: [ // Defining routes and pages route("LoginRoute", "/login", page(LoginPage)), ], }) ``` #### 1. Adding Microsoft Auth to Your Wasp File Let's start by properly configuring the Auth object: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { // 1. Specify the User entity (we'll define it next) userEntity: "User", methods: { // 2. Enable Microsoft Auth microsoft: {} }, onAuthFailedRedirectTo: "/login" }, // ... }) ``` `userEntity` is explained in [the social auth overview](https://wasp.sh/docs/auth/social-auth/overview#user-entity). #### 2. Adding the User Entity Let's now define the `auth.userEntity` entity in the `schema.prisma` file: ```prisma title="schema.prisma" // 3. Define the user entity model User { id Int @id @default(autoincrement()) // Add your own fields below // ... } ``` #### 3. Creating a Microsoft Entra ID App Registration To use Microsoft as an authentication method, you'll first need to register an application in the Microsoft Entra ID portal and provide Wasp with your client ID, client secret, and tenant ID. 1. Go to the [Microsoft Entra ID portal](https://entra.microsoft.com/). Login or sign up if necessary. 2. In the left sidebar, click on "App registrations" **(1)** and then "New registration" **(2)**. ![Microsoft Entra console screenshot 1](https://wasp.sh/assets/images/integrations-microsoft-1-a5bb446a4665f24129535fe9ef05ca5e.png) 3. Fill out the form. These are the values for a typical Wasp application: | # | Field | Value | | - | ------------------------ | ---------------------------------------------------------------- | | 1 | Name | (your wasp app name) | | 1 | Supported account types | Read through **[Understanding tenants](#understanding-tenants)** | | 3 | Authorized redirect URIs | Web: `http://localhost:3001/auth/microsoft/callback` | :::note Once you know on which URL(s) your API server will be deployed, also add those URL(s) in the **Authentication** section.\ For example: `https://your-server-url.com/auth/microsoft/callback` ::: ![Microsoft Entra console screenshot 2](https://wasp.sh/assets/images/integrations-microsoft-2-f4ea0d2da49cb869d7581a37342ebdeb.png) 4. You should now see your app registration's overview page. Take note of the **Application (client) ID (1)** and the **Directory (tenant) ID (2)**, as you'll need them in the next steps. ![Microsoft Entra console screenshot 3](https://wasp.sh/assets/images/integrations-microsoft-3-fd704126830d71b193cb8106e60228c6.png) 5. Next, go to the "Certificates & secrets" **(1)** page from the left sidebar, and create a new client secret **(2)**. You can fill out the name of your Wasp app as the client secret's name **(3)**. ![Microsoft Entra console screenshot 4](https://wasp.sh/assets/images/integrations-microsoft-4-a50974743a4745063d5ab3d3046104a3.png) 6. Finally, take note of the client secret's value, as you'll need it in the next steps. Make sure to copy it somewhere safe, as you won't be able to see it again! ![Microsoft Entra console screenshot 5](https://wasp.sh/assets/images/integrations-microsoft-5-228dc5331884e6613a72c15430b2b1a1.png) :::info The keys you copied are the credentials your app will use to authenticate with Microsoft. Do not share them anywhere publicly, as anyone with these credentials can impersonate your app and access user data. ::: #### 4. Adding Environment Variables Add these environment variables to the `.env.server` file at the root of your project (take their values from the previous step): ```bash title=".env.server" MICROSOFT_TENANT_ID=your-microsoft-tenant-id MICROSOFT_CLIENT_ID=your-microsoft-client-id MICROSOFT_CLIENT_SECRET=your-microsoft-client-secret ``` The `MICROSOFT_TENANT_ID` should be set based on the supported account types you chose, as described in the [Understanding Tenant IDs](#understanding-tenants) section above. #### 5. Adding the Necessary Routes and Pages Let's define the necessary authentication Routes and Pages. Add the following code to your `main.wasp.ts` file: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { LoginPage } from "./src/pages/auth" with { type: "ref" } export default app({ // ... spec: [ route("LoginRoute", "/login", page(LoginPage)), ], }) ``` We'll define the React components for these pages in the `src/pages/auth.tsx` file below. #### 6. Create the Client Pages :::info We are using [Tailwind CSS](https://tailwindcss.com/) to style the pages. Read more about how to add it [here](https://wasp.sh/docs/guides/libraries/tailwind). ::: Let's create a `auth.tsx` file in the `src/pages` folder and add the following to it: ```tsx title="src/pages/auth.tsx" import type { ReactNode } from "react"; import { LoginForm } from "wasp/client/auth"; export function LoginPage() { return ( ); } // A layout component to center the content export function Layout({ children }: { children: ReactNode }) { return (
{children}
); } ``` We imported the generated Auth UI components and used them in our pages. Read more about the Auth UI components [here](https://wasp.sh/docs/auth/ui). #### Conclusion Yay, we've successfully set up Microsoft Auth! ๐ŸŽ‰ ![Microsoft Auth](https://wasp.sh/assets/images/microsoft-e1d888c7437f6c8de3c8adb15e4cbf07.jpeg) Running `wasp db migrate-dev` and `wasp start` should now give you a working app with authentication. To see how to protect specific pages (i.e., hide them from non-authenticated users), read the docs on [using auth](https://wasp.sh/docs/auth/overview). ### Default Behaviour Add `microsoft: {}` to the `auth.methods` object to use it with default settings: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { userEntity: "User", methods: { microsoft: {} }, onAuthFailedRedirectTo: "/login" }, // ... }) ``` When a user **signs in for the first time**, Wasp creates a new user account and links it to the chosen auth provider account for future logins. ### Overrides By default, Wasp doesn't store any information it receives from the social login provider. It only stores the user's ID specific to the provider. There are two mechanisms used for overriding the default behavior: - `userSignupFields` - `configFn` Let's explore them in more detail. #### Data Received From Microsoft We are using Microsoft's Graph API and its `/oidc/userinfo` endpoint to fetch the user's data. The data received from Microsoft is an object which can contain the following fields: ```json { "sub": "OLu859SGc2Sr9ZsqbkG-QbeLgJlb41KcdiPoLYNpSFA", "name": "Mikah Ollenburg", // all names require the โ€œprofileโ€ scope. "family_name": " Ollenburg", "given_name": "Mikah", "picture": "https://graph.microsoft.com/v1.0/me/photo/$value", "email": "mikoll@contoso.com" // requires the โ€œemailโ€ scope. } ``` The fields you receive depend on the scopes you request. The default scopes are set to `openid`, `profile`, and `email`. For up-to-date info about the data received from Microsoft, please refer to the [Microsoft Graph API documentation](https://learn.microsoft.com/en-us/entra/identity-platform/userinfo). #### Using the Data Received From Microsoft When a user logs in using a social login provider, the backend receives some data about the user. Wasp lets you access this data inside the `userSignupFields` getters. For example, the User entity can include a `displayName` field which you can set based on the details received from the provider. Wasp also lets you customize the configuration of the providers' settings using the `configFn` function. Let's use this example to show both fields in action: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { getConfig, userSignupFields } from "./src/auth/microsoft" with { type: "ref" } export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { userEntity: "User", methods: { microsoft: { configFn: getConfig, userSignupFields } }, onAuthFailedRedirectTo: "/login" }, // ... }) ``` ```prisma title="schema.prisma" model User { id Int @id @default(autoincrement()) username String @unique displayName String } // ... ``` ```ts title="src/auth/microsoft.ts" import { defineUserSignupFields } from "wasp/server/auth"; export const userSignupFields = defineUserSignupFields({ username: () => "hardcoded-username", displayName: (data: any) => data.profile.name, }); export function getConfig() { return { scopes: ["openid", "profile", "email"], }; } ``` Wasp automatically generates the `defineUserSignupFields` function to help you correctly type your `userSignupFields` object. ### Using Auth To read more about how to set up the logout button and get access to the logged-in user in both client and server code, read the docs on [using auth](https://wasp.sh/docs/auth/overview). When you receive the `user` object [on the client or the server](https://wasp.sh/docs/auth/overview#accessing-the-logged-in-user), you'll be able to access the user's Microsoft ID like this: ```ts const microsoftIdentity = user.identities.microsoft // Microsoft User ID for example "a1b2c3d4-e5f6-7890-abcd-ef1234567890" microsoftIdentity.id ``` Read more about accessing the user data in the [Accessing User Data](https://wasp.sh/docs/auth/entities#accessing-the-auth-fields) section of the docs. ### API Reference [API reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/SocialAuthConfig) #### [SocialAuthConfig ยป](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/SocialAuthConfig) [All the options for the microsoft auth method.](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/SocialAuthConfig) For the provider-specific behavior of the `configFn` and `userSignupFields` functions, check the [Overrides section](#overrides). For behavior common to all providers, check the [Social Auth Overview](https://wasp.sh/docs/auth/social-auth/overview). ## Authentication / Social Auth / Create your own UI [Auth UI](https://wasp.sh/docs/auth/ui) is a common name for all high-level auth forms that come with Wasp. These include fully functional auto-generated login and signup forms with working social login buttons. If you're looking for the fastest way to get your auth up and running, that's where you should look. The UI helpers described below are lower-level and are useful for creating your custom login links. Wasp provides sign-in buttons and URLs for each of the supported social login providers. ```tsx title="src/LoginPage.tsx" import { GoogleSignInButton, googleSignInUrl, GitHubSignInButton, githubSignInUrl, } from "wasp/client/auth"; export const LoginPage = () => { return ( <> {/* or */} Sign in with Google Sign in with GitHub ); }; ``` ## Authentication / Accessing User Data First, we'll check out the most practical info: **how to access the user's data in your app**. Then, we'll dive into the details of the **auth entities** that Wasp creates behind the scenes to store the user's data. For auth each method, Wasp needs to store different information about the user. For example, username for [Username & password](https://wasp.sh/docs/auth/username-and-pass) auth, email verification status for [Email](https://wasp.sh/docs/auth/email) auth, and so on. We'll also show you how you can use these entities to create a custom signup action. ### Accessing the Auth Fields When you receive the `user` object [on the client or the server](https://wasp.sh/docs/auth/overview#accessing-the-logged-in-user), it will contain all the user fields you defined in the `User` entity in the `schema.prisma` file. In addition to that, it will also contain all the auth-related fields that Wasp stores. This includes things like the `username` or the email verification status. In Wasp, this data is called the `AuthUser` object. #### `AuthUser` Object Fields All the `User` fields you defined will be present at the top level of the `AuthUser` object. The auth-related fields will be on the `identities` object. For each auth method you enable, there will be a separate data object in the `identities` object. The `AuthUser` object will change depending on which auth method you have enabled in the Wasp file. For example, if you enabled the email auth and Google auth, it would look something like this: **User Signed Up with Google** If the user has only the Google identity, the `AuthUser` object will look like this: ```ts const user = { // User data id: "cluqs9qyh00007cn73apj4hp7", address: "Some address", // Auth methods specific data identities: { email: null, google: { id: "1117XXXX1301972049448", }, }, } ``` **User Signed Up with Email** If the user has only the email identity, the `AuthUser` object will look like this: ```ts const user = { // User data id: "cluqsex9500017cn7i2hwsg17", address: "Some address", // Auth methods specific data identities: { email: { id: "user@app.com", isEmailVerified: true, emailVerificationSentAt: "2024-04-08T10:06:02.204Z", passwordResetSentAt: null, }, google: null, }, } ``` In the examples above, you can see the `identities` object contains the `email` and `google` objects. The `email` object contains the email-related data and the `google` object contains the Google-related data. :::info[Make sure to check if the data exists] Before accessing some auth method's data, you'll need to check if that data exists for the user and then access it: ```ts if (user.identities.google !== null) { const userId = user.identities.google.id // ... } ``` You need to do this because if a user didn't sign up with some auth method, the data for that auth method will be `null`. ::: Let's look at the data for each of the available auth methods: - [Username & password](https://wasp.sh/docs/auth/username-and-pass) data ```ts const usernameIdentity = user.identities.username // Username that the user used to sign up, e.g. "fluffyllama" usernameIdentity.id ``` - [Email](https://wasp.sh/docs/auth/email) data ```ts const emailIdentity = user.identities.email // Email address the user used to sign up, e.g. "fluffyllama@app.com". emailIdentity.id // `true` if the user has verified their email address. emailIdentity.isEmailVerified // Datetime when the email verification email was sent. emailIdentity.emailVerificationSentAt // Datetime when the last password reset email was sent. emailIdentity.passwordResetSentAt ``` - [Google](https://wasp.sh/docs/auth/social-auth/google) data ```ts const googleIdentity = user.identities.google // Google User ID for example "123456789012345678901" googleIdentity.id ``` - [GitHub](https://wasp.sh/docs/auth/social-auth/github) data ```ts const githubIdentity = user.identities.github // GitHub User ID for example "12345678" githubIdentity.id ``` - [Keycloak](https://wasp.sh/docs/auth/social-auth/keycloak) data ```ts const keycloakIdentity = user.identities.keycloak // Keycloak User ID for example "12345678-1234-1234-1234-123456789012" keycloakIdentity.id ``` - [Discord](https://wasp.sh/docs/auth/social-auth/discord) data ```ts const discordIdentity = user.identities.discord // Discord User ID for example "80351110224678912" discordIdentity.id ``` If you support multiple auth methods, you'll need to find which identity exists for the user and then access its data: ```ts if (user.identities.email !== null) { const email = user.identities.email.id // ... } else if (user.identities.google !== null) { const googleId = user.identities.google.id // ... } ``` #### `getFirstProviderUserId` Helper The `getFirstProviderUserId` method returns the first user ID that it finds for the user. For example if the user has signed up with email, it will return the email. If the user has signed up with Google, it will return the Google ID. This can be useful if you support multiple authentication methods and you need *any* ID that identifies the user in your app. ```tsx title="src/MainPage.tsx" import { type AuthUser } from "wasp/auth" const MainPage = ({ user }: { user: AuthUser }) => { const userId = user.getFirstProviderUserId() // ... } ``` ```ts title="src/tasks.ts" export const createTask: CreateTask<...> = async (args, context) => { const userId = context.user.getFirstProviderUserId() // ... } ``` \* Multiple identities per user will be possible in the future and then the `getFirstProviderUserId` method will return the ID of the first identity that it finds without any guarantees about which one it will be. ### Including the User with Other Entities Sometimes, you might want to include the user's data when fetching other entities. For example, you might want to include the user's data with the tasks they have created. We'll mention the `auth` and the `identities` relations which we will explain in more detail later in the [Entities Explained](#entities-explained) section. :::caution[Be careful about sensitive data] You'll need to include the `auth` and the `identities` relations to get the full auth data about the user. However, you should keep in mind that the `providerData` field in the `identities` can contain sensitive data like the user's hashed password (in case of email or username auth), so you will likely want to exclude it if you are returning those values to the client. ::: You can include the full user's data with other entities using the `include` option in the Prisma queries: ```ts title="src/tasks.ts" export const getAllTasks = (async (args, context) => { return context.entities.Task.findMany({ orderBy: { id: "desc" }, select: { id: true, title: true, user: { include: { auth: { include: { identities: { // Including only the `providerName` and `providerUserId` fields select: { providerName: true, providerUserId: true, }, }, }, }, }, }, }, }) }) satisfies tasks.GetAllQuery<{}, {}> ``` If you have some **piece of the auth data that you want to access frequently** (for example the `username`), it's best to store it at the top level of the `User` entity. For example, save the `username` or `email` as a property on the `User` and you'll be able to access it without including the `auth` and `identities` fields. We show an example in the [Defining Extra Fields on the User Entity](https://wasp.sh/docs/auth/overview#1-defining-extra-fields) section of the docs. #### Getting Auth Data from the User Object When you have the `user` object with the `auth` and `identities` fields, it can be a bit tedious to obtain the auth data (like username or Google ID) from it: ```tsx title="src/MainPage.tsx" function MainPage() { // ... return (
{tasks.map((task) => (
{task.title} by {task.user.auth?.identities[0].providerUserId}
))}
) } ``` Wasp offers a few helper methods to access the user's auth data when you retrieve the `user` like this. They are `getUsername`, `getEmail` and `getFirstProviderUserId`. They can be used both on the client and the server. ##### `getUsername` It accepts the `user` object and if the user signed up with the [Username & password](https://wasp.sh/docs/auth/username-and-pass) auth method, it returns the username or `null` otherwise. The `user` object needs to have the `auth` and the `identities` relations included. ```tsx title="src/MainPage.tsx" import { getUsername } from "wasp/auth" function MainPage() { // ... return (
{tasks.map((task) => (
{task.title} by {getUsername(task.user)}
))}
) } ``` ##### `getEmail` It accepts the `user` object and if the user signed up with the [Email](https://wasp.sh/docs/auth/email) auth method, it returns the email or `null` otherwise. The `user` object needs to have the `auth` and the `identities` relations included. ```tsx title="src/MainPage.tsx" import { getEmail } from "wasp/auth" function MainPage() { // ... return (
{tasks.map((task) => (
{task.title} by {getEmail(task.user)}
))}
) } ``` ##### `getFirstProviderUserId` It returns the first user ID that it finds for the user. For example if the user has signed up with email, it will return the email. If the user has signed up with Google, it will return the Google ID. The `user` object needs to have the `auth` and the `identities` relations included. ```tsx title="src/MainPage.tsx" import { getFirstProviderUserId } from "wasp/auth" function MainPage() { // ... return (
{tasks.map((task) => (
{task.title} by {getFirstProviderUserId(task.user)}
))}
) } ``` ### Entities Explained To store user's auth information, Wasp does a few things behind the scenes. Wasp takes your `schema.prisma` file and combines it with additional entities to create the final `schema.prisma` file that is used in your app. In this section, we will explain which entities are created and how they are connected. #### User Entity When you want to add authentication to your app, you need to specify the `userEntity` field. For example, you might set it to `User`: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { userEntity: "User", // ... }, // ... }) ``` And define the `User` in the `schema.prisma` file: ```prisma title="schema.prisma" model User { id Int @id @default(autoincrement()) // Any other fields you want to store about the user } ``` The `User` entity is a "business logic user" which represents a user of your app. You can use this entity to store any information about the user that you want to store. For example, you might want to store the user's name or address. You can also use the user entity to define the relations between users and other entities in your app. For example, you might want to define a relation between a user and the tasks that they have created. You **own** the user entity and you can modify it as you wish. You can add new fields to it, remove fields from it, or change the type of the fields. You can also add new relations to it or remove existing relations from it. ![Auth Entities in a Wasp App](https://wasp.sh/img/auth-entities/model.png) Auth Entities in a Wasp App On the other hand, the `Auth`, `AuthIdentity` and `Session` entities are created behind the scenes and are used to store the user's login credentials. You as the developer don't need to care about this entity most of the time. Wasp **owns** these entities. In the case you want to create a custom signup action, you will need to use the `Auth` and `AuthIdentity` entities directly. #### Example App Model Let's imagine we created a simple tasks management app: - The app has email and Google-based auth. - Users can create tasks and see the tasks that they have created. Let's look at how would that look in the database: ![Example of Auth Entities](https://wasp.sh/img/auth-entities/model-example.png) Example of Auth Entities If we take a look at an example user in the database, we can see: - The business logic user, `User` is connected to multiple `Task` entities. - In this example, "Example User" has two tasks. - The `User` is connected to exactly one `Auth` entity. - Each `Auth` entity can have multiple `AuthIdentity` entities. - In this example, the `Auth` entity has two `AuthIdentity` entities: one for the email-based auth and one for the Google-based auth. - Each `Auth` entity can have multiple `Session` entities. - In this example, the `Auth` entity has one `Session` entity. :::caution[Using multiple auth identities for a single user] Wasp currently doesn't support multiple auth identities for a single user. This means, for example, that a user can't have both an email-based auth identity and a Google-based auth identity. This is something we will add in the future with the introduction of the [account merging feature](https://github.com/wasp-lang/wasp/issues/954). Account merging means that multiple auth identities can be merged into a single user account. For example, a user's email and Google identity can be merged into a single user account. Then the user can log in with either their email or Google account and they will be logged into the same account. ::: #### `Auth` Entity internal {#auth-entity-} Wasp's internal `Auth` entity is used to connect the business logic user, `User` with the user's login credentials. ```prisma model Auth { id String @id @default(uuid()) userId Int? @unique // Wasp injects this relation on the User entity as well user User? @relation(fields: [userId], references: [id], onDelete: Cascade) identities AuthIdentity[] sessions Session[] } ``` The `Auth` fields: - `id` is a unique identifier of the `Auth` entity. - `userId` is a foreign key to the `User` entity. - It is used to connect the `Auth` entity with the business logic user. - `user` is a relation to the `User` entity. - This relation is injected on the `User` entity as well. - `identities` is a relation to the `AuthIdentity` entity. - `sessions` is a relation to the `Session` entity. #### `AuthIdentity` Entity internal {#authidentity-entity-} The `AuthIdentity` entity is used to store the user's login credentials for various authentication methods. ```prisma model AuthIdentity { providerName String providerUserId String providerData String @default("{}") authId String auth Auth @relation(fields: [authId], references: [id], onDelete: Cascade) @@id([providerName, providerUserId]) } ``` The `AuthIdentity` fields: - `providerName` is the name of the authentication provider. - For example, `email` or `google`. - `providerUserId` is the user's ID in the authentication provider. - For example, the user's email or Google ID. - `providerData` is a JSON string that contains additional data about the user from the authentication provider. - For example, for password based auth, this field contains the user's hashed password. - This field is a `String` and not a `Json` type because [Prisma doesn't support the `Json` type for SQLite](https://github.com/prisma/prisma/issues/3786). - `authId` is a foreign key to the `Auth` entity. - It is used to connect the `AuthIdentity` entity with the `Auth` entity. - `auth` is a relation to the `Auth` entity. #### `Session` Entity internal {#session-entity-} The `Session` entity is used to store the user's session information. It is used to keep the user logged in between page refreshes. ```prisma model Session { id String @id @unique expiresAt DateTime userId String auth Auth @relation(references: [id], fields: [userId], onDelete: Cascade) @@index([userId]) } ``` The `Session` fields: - `id` is a unique identifier of the `Session` entity. - `expiresAt` is the date when the session expires. - `userId` is a foreign key to the `Auth` entity. - It is used to connect the `Session` entity with the `Auth` entity. - `auth` is a relation to the `Auth` entity. ### Custom Signup Action Let's take a look at how you can use the `Auth` and `AuthIdentity` entities to create custom login and signup actions. For example, you might want to create a custom signup action that creates a user in your app and also creates a user in a third-party service. :::info[Custom Signup Examples] In the Advanced section you can see an example for [Email](https://wasp.sh/docs/auth/advanced/custom-auth-actions#email) or [Username and password](https://wasp.sh/docs/auth/advanced/custom-auth-actions#username-and-password) authentication. ::: Below is a simplified version of a custom signup action which you probably wouldn't use in your app but it shows you how you can use the `Auth` and `AuthIdentity` entities to create a custom signup action. ```ts title="main.wasp.ts" import { action, app } from "@wasp.sh/spec" import { customSignup } from "./src/auth/signup" with { type: "ref" } export default app({ // ... spec: [ action(customSignup, { entities: ["User"] }), ], }) ``` ```ts title="src/auth/signup.ts" import { createProviderId, sanitizeAndSerializeProviderData, createUser, } from "wasp/server/auth" import type { CustomSignup } from "wasp/server/operations" type CustomSignupInput = { username: string password: string } type CustomSignupOutput = { success: boolean message: string } export const customSignup: CustomSignup< CustomSignupInput, CustomSignupOutput > = async (args, { entities: { User } }) => { try { // Provider ID is a combination of the provider name and the provider user ID // And it is used to uniquely identify the user in your app const providerId = createProviderId("username", args.username) // sanitizeAndSerializeProviderData hashes the password and returns a JSON string const providerData = await sanitizeAndSerializeProviderData<"username">({ hashedPassword: args.password, }) await createUser( providerId, providerData, // Any additional data you want to store on the User entity {} ) // This is equivalent to: // await User.create({ // data: { // auth: { // create: { // identities: { // create: { // providerName: "username", // providerUserId: args.username // providerData, // }, // }, // } // }, // } // }) } catch (e) { return { success: false, message: e.message, } } // Your custom code after sign-up. // ... return { success: true, message: "User created successfully", } } ``` You can use whichever method suits your needs better: either the `createUser` function or Prisma's `User.create` method. The `createUser` function is a bit more convenient to use because it hides some of the complexity. On the other hand, the `User.create` method gives you more control over the data that is stored in the `Auth` and `AuthIdentity` entities. ## Authentication / Auth Hooks Auth hooks allow you to "hook into" the auth process at various stages and run your custom code. For example, if you want to forbid certain emails from signing up, or if you wish to send a welcome email to the user after they sign up, auth hooks are the way to go. ### Supported hooks The following auth hooks are available in Wasp: - [`onBeforeSignup`](#executing-code-before-the-user-signs-up) - [`onAfterSignup`](#executing-code-after-the-user-signs-up) - [`onAfterEmailVerified`](#executing-code-after-a-user-verifies-their-email) - [`onBeforeOAuthRedirect`](#executing-code-before-the-oauth-redirect) - [`onBeforeLogin`](#executing-code-before-the-user-logs-in) - [`onAfterLogin`](#executing-code-after-the-user-logs-in) We'll go through each of these hooks in detail. But first, let's see how the hooks fit into the auth flows: ![Signup Flow with Hooks](https://wasp.sh/img/auth-hooks/signup_flow_with_hooks.png) Signup Flow with Hooks ![Login Flow with Hooks](https://wasp.sh/img/auth-hooks/login_flow_with_hooks.png) Login Flow with Hooks \* \* When using the OAuth auth providers, the login hooks are both called before the session is created but the session is created quickly afterward, so it shouldn't make any difference in practice. Users registering with [email](https://wasp.sh/docs/auth/email) must verify it before they can log in. This verification triggers the Email verification flow: ![Email Verification Flow with Hooks](https://wasp.sh/img/auth-hooks/email_verification_flow_with_hooks.png) Email Verification Flow with Hooks Users signing in with [OAuth](https://wasp.sh/docs/auth/social-auth/overview) must authorize access before completing login. This authorization triggers the OAuth consent flow: ![OAuth Flow with Hooks](https://wasp.sh/img/auth-hooks/oauth_flow_with_hooks.png) OAuth Flow with Hooks ### Using hooks To use auth hooks, you must first declare them in the Wasp file: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { onBeforeSignup, onAfterSignup, onAfterEmailVerified, onBeforeOAuthRedirect, onBeforeLogin, onAfterLogin, } from "./src/auth/hooks" with { type: "ref" } export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { userEntity: "User", methods: { // ... }, onBeforeSignup, onAfterSignup, onAfterEmailVerified, onBeforeOAuthRedirect, onBeforeLogin, onAfterLogin, }, // ... }) ``` If the hooks are defined as async functions, Wasp *awaits* them. This means the auth process waits for the hooks to finish before continuing. Wasp ignores the hooks' return values. The only exception is the `onBeforeOAuthRedirect` hook, whose return value affects the OAuth redirect URL. We'll now go through each of the available hooks. #### Executing code before the user signs up Wasp calls the `onBeforeSignup` hook before the user is created. The `onBeforeSignup` hook can be useful if you want to reject a user based on some criteria before they sign up. Works with [Email](https://wasp.sh/docs/auth/email) [Username & Password](https://wasp.sh/docs/auth/username-and-pass) [Slack](https://wasp.sh/docs/auth/social-auth/slack) [Discord](https://wasp.sh/docs/auth/social-auth/discord) [Github](https://wasp.sh/docs/auth/social-auth/github) [Google](https://wasp.sh/docs/auth/social-auth/google) [Keycloak](https://wasp.sh/docs/auth/social-auth/keycloak) ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { onBeforeSignup } from "./src/auth/hooks" with { type: "ref" } export default app({ // ... auth: { // ... onBeforeSignup, }, // ... }) ``` ```ts title="src/auth/hooks.ts" import { HttpError } from "wasp/server" import type { OnBeforeSignupHook } from "wasp/server/auth" export const onBeforeSignup: OnBeforeSignupHook = async ({ providerId, prisma, req, }) => { const count = await prisma.user.count() console.log("number of users before", count) console.log("provider name", providerId.providerName) console.log("provider user ID", providerId.providerUserId) if (count > 100) { throw new HttpError(403, "Too many users") } if ( providerId.providerName === "email" && providerId.providerUserId === "some@email.com" ) { throw new HttpError(403, "This email is not allowed") } } ``` Read more about the data the `onBeforeSignup` hook receives in the [API Reference](#the-onbeforesignup-hook). #### Executing code after the user signs up Wasp calls the `onAfterSignup` hook after the user is created. The `onAfterSignup` hook can be useful if you want to send the user a welcome email or perform some other action after the user signs up like syncing the user with a third-party service. Since the `onAfterSignup` hook receives the OAuth tokens, you can use this hook to store the OAuth access token and/or [refresh token](#refreshing-the-oauth-access-token) in your database. Works with [Email](https://wasp.sh/docs/auth/email) [Username & Password](https://wasp.sh/docs/auth/username-and-pass) [Slack](https://wasp.sh/docs/auth/social-auth/slack) [Discord](https://wasp.sh/docs/auth/social-auth/discord) [Github](https://wasp.sh/docs/auth/social-auth/github) [Google](https://wasp.sh/docs/auth/social-auth/google) [Keycloak](https://wasp.sh/docs/auth/social-auth/keycloak) ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { onAfterSignup } from "./src/auth/hooks" with { type: "ref" } export default app({ // ... auth: { // ... onAfterSignup, }, // ... }) ``` ```ts title="src/auth/hooks.ts" import type { OnAfterSignupHook } from "wasp/server/auth" export const onAfterSignup: OnAfterSignupHook = async ({ providerId, user, oauth, prisma, req, }) => { const count = await prisma.user.count() console.log("number of users after", count) console.log("user object", user) // If this is an OAuth signup, you have access to the OAuth tokens and the uniqueRequestId if (oauth) { console.log("accessToken", oauth.tokens.accessToken) console.log("uniqueRequestId", oauth.uniqueRequestId) const id = oauth.uniqueRequestId const data = someKindOfStore.get(id) if (data) { console.log("saved data for the ID", data) } someKindOfStore.delete(id) } } ``` Read more about the data the `onAfterSignup` hook receives in the [API Reference](#the-onaftersignup-hook). #### Executing code after a user verifies their email Wasp calls the `onAfterEmailVerified` hook exactly once, after the user verifies their email. The `onAfterEmailVerified` hook is useful for triggering actions in response to the verification event โ€” such as sending a welcome email or syncing user data with a third-party service. The `onAfterEmailVerified` hook receives an `email` string and `user` object, this makes it easy to perform personalized actions upon email verification. Works with [Email](https://wasp.sh/docs/auth/email) ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { onAfterEmailVerified } from "./src/auth/hooks" with { type: "ref" } export default app({ // ... auth: { // ... onAfterEmailVerified, }, // ... }) ``` ```ts title="src/auth/hooks.ts" import type { OnAfterEmailVerifiedHook } from "wasp/server/auth" import { emailSender } from "wasp/server/email" export const onAfterEmailVerified: OnAfterEmailVerifiedHook = async ({ email, }) => { const info = await emailSender.send({ from: { name: "John Doe", email: "john@doe.com", }, to: email, subject: "Thank you for verifying your email!", text: `Your email ${email} has been successfully verified!`, }) // ... } ``` Read more about the data the `onAfterEmailVerified` hook receives in the [API Reference](#the-onafteremailverified-hook). #### Executing code before the OAuth redirect Wasp calls the `onBeforeOAuthRedirect` hook after the OAuth redirect URL is generated but before redirecting the user. This hook can access the request object sent from the client at the start of the OAuth process. The `onBeforeOAuthRedirect` hook can be useful if you want to save some data (e.g. request query parameters) that you can use later in the OAuth flow. You can use the `uniqueRequestId` parameter to reference this data later in the `onAfterSignup` or `onAfterLogin` hooks. Works with [Discord](https://wasp.sh/docs/auth/social-auth/discord) [Github](https://wasp.sh/docs/auth/social-auth/github) [Google](https://wasp.sh/docs/auth/social-auth/google) [Keycloak](https://wasp.sh/docs/auth/social-auth/keycloak) ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { onBeforeOAuthRedirect } from "./src/auth/hooks" with { type: "ref" } export default app({ // ... auth: { // ... onBeforeOAuthRedirect, }, // ... }) ``` ```ts title="src/auth/hooks.ts" import type { OnBeforeOAuthRedirectHook } from "wasp/server/auth" export const onBeforeOAuthRedirect: OnBeforeOAuthRedirectHook = async ({ url, oauth, prisma, req, }) => { console.log("query params before oAuth redirect", req.query) // Saving query params for later use in onAfterSignup or onAfterLogin hooks const id = oauth.uniqueRequestId someKindOfStore.set(id, req.query) return { url } } ``` This hook's return value must be an object that looks like this: `{ url: URL }`. Wasp uses the URL to redirect the user to the OAuth provider. Read more about the data the `onBeforeOAuthRedirect` hook receives in the [API Reference](#the-onbeforeoauthredirect-hook). #### Executing code before the user logs in Wasp calls the `onBeforeLogin` hook before the user is logged in. The `onBeforeLogin` hook can be useful if you want to reject a user based on some criteria before they log in. Works with [Email](https://wasp.sh/docs/auth/email) [Username & Password](https://wasp.sh/docs/auth/username-and-pass) [Slack](https://wasp.sh/docs/auth/social-auth/slack) [Discord](https://wasp.sh/docs/auth/social-auth/discord) [Github](https://wasp.sh/docs/auth/social-auth/github) [Google](https://wasp.sh/docs/auth/social-auth/google) [Keycloak](https://wasp.sh/docs/auth/social-auth/keycloak) ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { onBeforeLogin } from "./src/auth/hooks" with { type: "ref" } export default app({ // ... auth: { // ... onBeforeLogin, }, // ... }) ``` ```ts title="src/auth/hooks.ts" import { HttpError } from "wasp/server" import type { OnBeforeLoginHook } from "wasp/server/auth" export const onBeforeLogin: OnBeforeLoginHook = async ({ providerId, user, prisma, req, }) => { if ( providerId.providerName === "email" && providerId.providerUserId === "some@email.com" ) { throw new HttpError(403, "You cannot log in with this email") } } ``` Read more about the data the `onBeforeLogin` hook receives in the [API Reference](#the-onbeforelogin-hook). #### Executing code after the user logs in Wasp calls the `onAfterLogin` hook after the user logs in. The `onAfterLogin` hook can be useful if you want to perform some action after the user logs in, like syncing the user with a third-party service. Since the `onAfterLogin` hook receives the OAuth tokens, you can use it to update the OAuth access token for the user in your database. You can also use it to [refresh the OAuth access token](#refreshing-the-oauth-access-token) if the provider supports it. Works with [Email](https://wasp.sh/docs/auth/email) [Username & Password](https://wasp.sh/docs/auth/username-and-pass) [Discord](https://wasp.sh/docs/auth/social-auth/discord) [Github](https://wasp.sh/docs/auth/social-auth/github) [Google](https://wasp.sh/docs/auth/social-auth/google) [Keycloak](https://wasp.sh/docs/auth/social-auth/keycloak) ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { onAfterLogin } from "./src/auth/hooks" with { type: "ref" } export default app({ // ... auth: { // ... onAfterLogin, }, // ... }) ``` ```ts title="src/auth/hooks.ts" import type { OnAfterLoginHook } from "wasp/server/auth" export const onAfterLogin: OnAfterLoginHook = async ({ providerId, user, oauth, prisma, req, }) => { console.log("user object", user) // If this is an OAuth signup, you have access to the OAuth tokens and the uniqueRequestId if (oauth) { console.log("accessToken", oauth.tokens.accessToken) console.log("uniqueRequestId", oauth.uniqueRequestId) const id = oauth.uniqueRequestId const data = someKindOfStore.get(id) if (data) { console.log("saved data for the ID", data) } someKindOfStore.delete(id) } } ``` Read more about the data the `onAfterLogin` hook receives in the [API Reference](#the-onafterlogin-hook). #### Refreshing the OAuth access token Some OAuth providers support refreshing the access token when it expires. To refresh the access token, you need the OAuth **refresh token**. Wasp exposes the OAuth refresh token in the `onAfterSignup` and `onAfterLogin` hooks. You can store the refresh token in your database and use it to refresh the access token when it expires. Import the provider object with the OAuth client from the `wasp/server/auth` module. For example, to refresh the Google OAuth access token, import the `google` object from the `wasp/server/auth` module. You use the `refreshAccessToken` method of the OAuth client to refresh the access token. Here's an example of how you can refresh the access token for Google OAuth: ```ts title="src/auth/hooks.ts" import type { OnAfterLoginHook } from "wasp/server/auth" import { google } from "wasp/server/auth" export const onAfterLogin: OnAfterLoginHook = async ({ oauth }) => { if (oauth.provider === "google" && oauth.tokens.refreshToken !== null) { const newTokens = await google.oAuthClient.refreshAccessToken( oauth.tokens.refreshToken ) log("new tokens", newTokens) } } ``` Google exposes the `accessTokenExpiresAt` field in the `oauth.tokens` object. You can use this field to determine when the access token expires. If you want to refresh the token periodically, use a [Wasp Job](https://wasp.sh/docs/advanced/jobs). ### API Reference ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { onBeforeSignup, onAfterSignup, onAfterEmailVerified, onBeforeOAuthRedirect, onBeforeLogin, onAfterLogin, } from "./src/auth/hooks" with { type: "ref" } export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { userEntity: "User", methods: { // ... }, onBeforeSignup, onAfterSignup, onAfterEmailVerified, onBeforeOAuthRedirect, onBeforeLogin, onAfterLogin, }, // ... }) ``` #### Common hook input The following properties are available in all auth hooks: - `prisma: PrismaClient` The Prisma client instance which you can use to query your database. - `req: Request` The [Express request object](https://expressjs.com/en/api.html#req) from which you can access the request headers, cookies, etc. #### The `onBeforeSignup` hook ```ts title="src/auth/hooks.ts" import type { OnBeforeSignupHook } from "wasp/server/auth" export const onBeforeSignup: OnBeforeSignupHook = async ({ providerId, prisma, req, }) => { // Hook code goes here } ``` The hook receives an object as **input** with the following properties: - [`providerId: ProviderId`](#providerid-fields) - Plus the [common hook input](#common-hook-input) Wasp ignores this hook's **return value**. #### The `onAfterSignup` hook ```ts title="src/auth/hooks.ts" import type { OnAfterSignupHook } from "wasp/server/auth" export const onAfterSignup: OnAfterSignupHook = async ({ providerId, user, oauth, prisma, req, }) => { // Hook code goes here } ``` The hook receives an object as **input** with the following properties: - [`providerId: ProviderId`](#providerid-fields) - `user: User` The user object that was created. - [`oauth?: OAuthFields`](#oauth-fields) - Plus the [common hook input](#common-hook-input) Wasp ignores this hook's **return value**. #### The `onAfterEmailVerified` hook ```ts title="src/auth/hooks.ts" import type { OnAfterEmailVerifiedHook } from "wasp/server/auth" export const onAfterEmailVerified: OnAfterEmailVerifiedHook = async ({ email, user, prisma, req, }) => { // Hook code goes here } ``` The hook receives an object as **input** with the following properties: - `email: string` The user's veriried email address. - `user: User` The user who completed email verification. - Plus the [common hook input](#common-hook-input) Wasp ignores this hook's **return value**. #### The `onBeforeOAuthRedirect` hook ```ts title="src/auth/hooks.ts" import type { OnBeforeOAuthRedirectHook } from "wasp/server/auth" export const onBeforeOAuthRedirect: OnBeforeOAuthRedirectHook = async ({ url, oauth, prisma, req, }) => { // Hook code goes here return { url } } ``` The hook receives an object as **input** with the following properties: - `url: URL` Wasp uses the URL for the OAuth redirect. - `oauth: { uniqueRequestId: string }` The `oauth` object has the following fields: - `uniqueRequestId: string` The unique request ID for the OAuth flow (you might know it as the `state` parameter in OAuth.) You can use the unique request ID to save data (e.g. request query params) that you can later use in the `onAfterSignup` or `onAfterLogin` hooks. - Plus the [common hook input](#common-hook-input) This hook's return value must be an object that looks like this: `{ url: URL }`. Wasp uses the URL to redirect the user to the OAuth provider. #### The `onBeforeLogin` hook ```ts title="src/auth/hooks.ts" import type { OnBeforeLoginHook } from "wasp/server/auth" export const onBeforeLogin: OnBeforeLoginHook = async ({ providerId, prisma, req, }) => { // Hook code goes here } ``` The hook receives an object as **input** with the following properties: - [`providerId: ProviderId`](#providerid-fields) - `user: User` The user that is trying to log in. - Plus the [common hook input](#common-hook-input) Wasp ignores this hook's **return value**. #### The `onAfterLogin` hook ```ts title="src/auth/hooks.ts" import type { OnAfterLoginHook } from "wasp/server/auth" export const onAfterLogin: OnAfterLoginHook = async ({ providerId, user, oauth, prisma, req, }) => { // Hook code goes here } ``` The hook receives an object as **input** with the following properties: - [`providerId: ProviderId`](#providerid-fields) - `user: User` The logged-in user's object. - [`oauth?: OAuthFields`](#oauth-fields) - Plus the [common hook input](#common-hook-input) Wasp ignores this hook's **return value**. #### ProviderId fields The `providerId` object represents the user for the current authentication method. Wasp passes it to the `onBeforeSignup`, `onAfterSignup`, `onBeforeLogin`, and `onAfterLogin` hooks. It has the following fields: - `providerName: string` The provider's name (e.g. `"email"`, `"google"`, `"github"`) - `providerUserId: string` The user's unique ID in the provider's system (e.g. email, Google ID, GitHub ID) #### OAuth fields Wasp passes the `oauth` object to the `onAfterSignup` and `onAfterLogin` hooks only when the user is authenticated with [Social Auth](https://wasp.sh/docs/auth/social-auth/overview). It has the following fields: - `providerName: string` The name of the OAuth provider the user authenticated with (e.g. `"google"`, `"github"`). - `tokens: Tokens` You can use the OAuth tokens to make requests to the provider's API on the user's behalf. Depending on the OAuth provider, the `tokens` object might have different fields. For example, Google has the fields `accessToken`, `refreshToken`, `idToken`, and `accessTokenExpiresAt`. To access the provider-specific fields, you must first narrow down the `oauth.tokens` object type to the specific OAuth provider type. ```ts if (oauth && oauth.providerName === "google") { console.log(oauth.tokens.accessToken) // ^ Google specific tokens are available here console.log(oauth.tokens.refreshToken) console.log(oauth.tokens.idToken) console.log(oauth.tokens.accessTokenExpiresAt) } ``` - `uniqueRequestId: string` The unique request ID for the OAuth flow (you might know it as the `state` parameter in OAuth.) You can use the unique request ID to get the data that was saved in the `onBeforeOAuthRedirect` hook. ## Authentication / Advanced / Custom sign-up actions If you need to deeply hook into the sign-up process, you can create your own sign-up action and customize the code to, for example, add extra validation, store more data, or otherwise call custom code at registration time. :::danger Custom sign-up actions are complex, and we don't recommend creating a custom sign-up action unless you have a good reason to do so. They also require you to be careful, as any small mistake will compromise the security of your app. Before using custom actions, check if our support for [custom auth UI](https://wasp.sh/docs/auth/overview#custom-auth-ui) and for [auth hooks](https://wasp.sh/docs/auth/auth-hooks) could fit well with you requirements. ::: You are not able to use Wasp UI with custom sign-up actions, so you're expected to implemented your own UI and call the custom actions you create from it. ### Example code Below you will find a starting point for creating your own actions. The given implementation is similar to what Wasp does under the hood, and it is up to you to customize it. #### Email ```ts title="main.wasp.ts" import { action, app } from "@wasp.sh/spec" import { onBeforeSignup } from "./src/auth/hooks" with { type: "ref" } import { customSignup } from "./src/auth/signup" with { type: "ref" } export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [""], auth: { // ... onBeforeSignup, }, spec: [ action(customSignup), ], }) ``` ```ts title="src/auth/hooks.ts" import { HttpError } from "wasp/server"; // This disables Wasp's default sign-up action export const onBeforeSignup = async () => { throw new HttpError(403, "This sign-up method is disabled"); }; ``` ```ts title="src/auth/signup.ts" import type { CustomSignup } from "wasp/server/operations"; import { HttpError } from "wasp/server"; import { createEmailVerificationLink, createProviderId, createUser, ensurePasswordIsPresent, ensureValidEmail, ensureValidPassword, findAuthIdentity, getProviderData, sanitizeAndSerializeProviderData, sendEmailVerificationEmail, } from "wasp/server/auth"; type CustomSignupInput = { email: string; password: string; }; type CustomSignupOutput = { success: boolean; message: string; }; export const customSignup: CustomSignup< CustomSignupInput, CustomSignupOutput > = async (args, _context) => { ensureValidEmail(args); ensurePasswordIsPresent(args); ensureValidPassword(args); try { const providerId = createProviderId("email", args.email); const existingAuthIdentity = await findAuthIdentity(providerId); let providerData; if (existingAuthIdentity) { // User already exists, handle accordingly // For example, throw an error or return a message throw new HttpError(400, "Email already exists."); // Or, another example, you can check if the user is already // verified and re-send the verification email if not providerData = getProviderData<"email">( existingAuthIdentity.providerData, ); if (providerData.isEmailVerified) throw new HttpError(400, "Email already verified."); } if (!providerData) { providerData = await sanitizeAndSerializeProviderData<"email">({ // The provider will hash the password for us, so we don't need to do it here. hashedPassword: args.password, isEmailVerified: false, emailVerificationSentAt: null, passwordResetSentAt: null, }); await createUser( providerId, providerData, // Any additional data you want to store on the User entity {}, ); } // Verification link links to a client route e.g. /email-verification const verificationLink = await createEmailVerificationLink( args.email, "/email-verification", ); try { await sendEmailVerificationEmail(args.email, { from: { name: "My App Postman", email: "hello@itsme.com", }, to: args.email, subject: "Verify your email", text: `Click the link below to verify your email: ${verificationLink}`, html: `

Click the link below to verify your email

Verify email `, }); } catch (e: unknown) { console.error("Failed to send email verification email:", e); throw new HttpError(500, "Failed to send email verification email."); } } catch (e: any) { return { success: false, message: e.message, }; } // Your custom code after sign-up. // ... return { success: true, message: "User created successfully", }; }; ``` #### Username and password ```ts title="main.wasp.ts" import { action, app } from "@wasp.sh/spec" import { onBeforeSignup } from "./src/auth/hooks" with { type: "ref" } import { customSignup } from "./src/auth/signup" with { type: "ref" } export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", auth: { // ... onBeforeSignup, }, spec: [ action(customSignup), ], }) ``` ```ts title="src/auth/hooks.ts" import { HttpError } from "wasp/server"; // This disables Wasp's default sign-up action export const onBeforeSignup = async () => { throw new HttpError(403, "This sign-up method is disabled"); }; ``` ```ts title="src/auth/signup.ts" import type { CustomSignup } from "wasp/server/operations"; import { createProviderId, createUser, ensurePasswordIsPresent, ensureValidPassword, ensureValidUsername, sanitizeAndSerializeProviderData, } from "wasp/server/auth"; type CustomSignupInput = { username: string; password: string; }; type CustomSignupOutput = { success: boolean; message: string; }; export const customSignup: CustomSignup< CustomSignupInput, CustomSignupOutput > = async (args, _context) => { ensureValidUsername(args); ensurePasswordIsPresent(args); ensureValidPassword(args); try { const providerId = createProviderId("username", args.username); const providerData = await sanitizeAndSerializeProviderData<"username">({ // The provider will hash the password for us, so we don't need to do it here. hashedPassword: args.password, }); await createUser(providerId, providerData, {}); } catch (e: any) { console.error("Error creating user:", e); return { success: false, message: e.message, }; } return { success: true, message: "User created successfully", }; }; ``` ### Validators API Reference We suggest using the built-in field validators for your authentication flow. You can import them from `wasp/server/auth`. These are the same validators that Wasp uses internally for the default authentication flow. ##### Username - `ensureValidUsername(args)` Checks if the username is valid and throws an error if it's not. Read more about the validation rules [here](https://wasp.sh/docs/auth/overview#default-validations). ##### Email - `ensureValidEmail(args)` Checks if the email is valid and throws an error if it's not. Read more about the validation rules [here](https://wasp.sh/docs/auth/overview#default-validations). ##### Password - `ensurePasswordIsPresent(args)` Checks if the password is present and throws an error if it's not. - `ensureValidPassword(args)` Checks if the password is valid and throws an error if it's not. Read more about the validation rules [here](https://wasp.sh/docs/auth/overview#default-validations). ## Project Setup / Starter Templates We created a few starter templates to help you get started with Wasp. Check out the list [below](#available-templates). ### Using a Template Run `wasp new` to run the interactive mode for creating a new Wasp project. It will ask you for the project name, and then for the template to use: ``` $ wasp new Enter the project name (e.g. my-project) โ–ธ MyFirstProject Choose a starter template [1] basic (default) A basic starter template designed to help you get up and running quickly. It features examples covering the most common use cases. [2] minimal A minimal starter template that features just a single page. [3] saas Everything a SaaS needs! Comes with Auth, ChatGPT API, Tailwind, Stripe payments and more. Check out https://opensaas.sh/ for more details. โ–ธ 1 ๐Ÿ --- Creating your project from the "basic" template... ------------------------- Created new Wasp app in ./MyFirstProject directory! To run your new app, do: cd MyFirstProject wasp db migrate-dev wasp start ``` ### Available Templates When you have a good idea for a new product, you don't want to waste your time on setting up common things like authentication, database, etc. That's why we created a few starter templates to help you get started with Wasp. #### OpenSaaS.sh template ![SaaS Template](https://wasp.sh/assets/images/open-saas-banner-0e2c9194bba58b98451afda04a638cb3.png) Everything a SaaS needs! Comes with Auth, ChatGPT API, Tailwind, Stripe payments and more. Check out for more details. **Features:** Stripe Payments, OpenAI GPT API, Google Auth, SendGrid, Tailwind, & Cron Jobs Use this template: ``` wasp new -t saas ``` #### Minimal Template A minimal starter template that features just a single page. Perfect for starting from scratch with the bare essentials. Use this template: ``` wasp new -t minimal ``` ## Project Setup / Customizing the App Each Wasp project can have only one `app` spec. It is used to configure your app and its components. ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "todoApp", wasp: { version: "^0.25" }, title: "ToDo App", head: [ "", "", ], // ... }) ``` We'll go through some common customizations you might want to do to your app. For more details on each of the fields, check out the [API Reference](#api-reference). #### Changing the App Title You may want to change the title of your app, which appears in the browser tab, next to the favicon. You can change it by changing the `title` field of your `app` spec: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "BookFace", // ... }) ``` #### Adding Additional Lines to the Head If you are looking to add additional style sheets or scripts to your app, you can do so by adding them to the `head` field of your `app` spec. An example of adding extra style sheets and scripts: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "myApp", wasp: { version: "^0.25" }, title: "My App", head: [ // optional "", "", "", "", ], // ... }) ``` ### API Reference [API reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/App) #### [App ยป](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/App) [All the options for the app spec.](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/App) ## Project Setup / Client Config You can configure the client using the `client` field inside the `app` spec: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import Root from "./src/Root" with { type: "ref" } import mySetupFunction from "./src/myClientSetupCode" with { type: "ref" } export default app({ name: "MyApp", client: { rootComponent: Root, setupFn: mySetupFunction, }, // ... }) ``` ### Root Component Wasp gives you the option to define a "wrapper" component for your React app. It can be used for a variety of purposes, but the most common ones are: - Defining a common layout for your application. - Setting up various providers that your application needs. #### Defining a Common Layout Let's define a common layout for your application: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import Root from "./src/Root" with { type: "ref" } export default app({ name: "MyApp", client: { rootComponent: Root, }, // ... }) ``` ```tsx title="src/Root.tsx" import { Outlet } from "react-router" export default function Root() { return (

My App

My App footer

) } ``` You need to import the [`Outlet`](https://reactrouter.com/8.0.1/api/components/Outlet) component from `react-router` and put it where you want the current page to be rendered. #### Setting up a Provider This is how to set up various providers that your application needs: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import Root from "./src/Root" with { type: "ref" } export default app({ name: "MyApp", client: { rootComponent: Root, }, // ... }) ``` ```tsx title="src/Root.tsx" import { Outlet } from "react-router" import store from "./store" import { Provider } from "react-redux" export default function Root() { return ( ) } ``` As long as you render the `Outlet` component, you can put what ever you want in the root component. For the full description of the `rootComponent` field, check the [`Client` API Reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Client#rootcomponent). ### Setup Function `setupFn` declares a Typescript function that Wasp executes on the client before everything else. :::caution[The setup function can also run on the server] The setup function can also run during server-side rendering, like when [prerendering](https://wasp.sh/docs/advanced/prerendering) pages, where browser APIs like `window` are not available. See [Running Code Only on the Client](#running-code-only-on-the-client). ::: #### Running Some Code We can run any code we want in the setup function. For example, here's a setup function that logs a message every hour: ```ts title="src/myClientSetupCode.ts" export default async function mySetupFunction(): Promise { let count = 1 setInterval( () => console.log(`You have been online for ${count++} hours.`), 1000 * 60 * 60 ) } ``` #### Running Code Only on the Client If your app uses [prerendering](https://wasp.sh/docs/advanced/prerendering), Wasp also executes the setup function while rendering your pages on the server. There, browser APIs like `window`, `document`, or `localStorage` don't exist, so using them would crash the prerender. Side effects like timers or event listeners would also run in the Node.js process. To run some code only in the browser, check Vite's [`import.meta.env.SSR`](https://vite.dev/guide/env-and-mode.html#env-variables) flag, which is `true` during server-side rendering and `false` on the client: ```ts title="src/myClientSetupCode.ts" export default async function mySetupFunction(): Promise { if (import.meta.env.SSR) { // We're rendering on the server, skip the browser-only setup. return; } window.addEventListener("online", () => console.log("You are back online!")); } ``` #### Overriding Default Behaviour for Queries :::info You can change the options for a **single** Query using the `options` object, as described [here](https://wasp.sh/docs/data-model/operations/queries#the-usequery-hook-1). ::: Wasp's `useQuery` hook uses `react-query`'s `useQuery` hook under the hood. Since `react-query` comes configured with aggressive but sane default options, you most likely won't have to change those defaults for all Queries. If you do need to change the global defaults, you can do so inside the client setup function. Wasp exposes a `configureQueryClient` hook that lets you configure *react-query*'s `QueryClient` object: ```ts title="src/myClientSetupCode.ts" import { configureQueryClient } from "wasp/client/operations" export default async function mySetupFunction(): Promise { // ... some setup configureQueryClient({ defaultOptions: { queries: { staleTime: Infinity, }, }, }) // ... some more setup } ``` Make sure to pass in an object expected by the `QueryClient`'s constructor, as explained in [react-query's docs](https://tanstack.com/query/v4/docs/reference/QueryClient). For the full description of the `setupFn` field, check the [`Client` API Reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Client#setupfn). ### Base Directory If you need to serve the client from a subdirectory, you can use the `baseDir` option: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "MyApp", client: { baseDir: "/my-app", }, // ... }) ``` This means that if you serve your app from `https://example.com/my-app`, the router will work correctly, and all the assets will be served from `https://example.com/my-app`. :::caution[Setting the correct env variable] If you set the `baseDir` option, make sure that the `WASP_WEB_CLIENT_URL` env variable also includes that base directory. For example, if you are serving your app from `https://example.com/my-app`, the `WASP_WEB_CLIENT_URL` should be also set to `https://example.com/my-app`, and not just `https://example.com`. ::: ### API Reference [API reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Client) #### [Client ยป](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Client) [All the options for the client field of the app spec.](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Client) ## Project Setup / Server Config You can configure the behavior of the server via the `server` field of `app` spec: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { myMiddlewareConfigFn, mySetupFunction } from "./src/myServerSetupCode" with { type: "ref" } export default app({ name: "MyApp", server: { setupFn: mySetupFunction, middlewareConfigFn: myMiddlewareConfigFn, }, // ... }) ``` ### Setup Function `setupFn` declares a Typescript function that will be executed on server start. #### Adding a Custom Route As an example, adding a custom route would look something like: ```ts title="src/myServerSetupCode.ts" import { ServerSetupFn } from "wasp/server" import { Application } from "express" export const mySetupFunction: ServerSetupFn = async ({ app }) => { addCustomRoute(app) } function addCustomRoute(app: Application) { app.get("/customRoute", (_req, res) => { res.send("I am a custom route") }) } ``` #### Storing Some Values for Later Use In case you want to store some values for later use, or to be accessed by the [Operations](https://wasp.sh/docs/data-model/operations/overview) you do that in the `setupFn` function. Dummy example of such function and its usage: ```ts title="src/myServerSetupCode.ts" import { type ServerSetupFn } from "wasp/server" let someResource = undefined export const mySetupFunction: ServerSetupFn = async () => { // Let's pretend functions setUpSomeResource and startSomeCronJob // are implemented below or imported from another file. someResource = await setUpSomeResource() startSomeCronJob() } export const getSomeResource = () => someResource ``` ```ts title="src/queries.ts" import { type SomeQuery } from "wasp/server/operations" import { getSomeResource } from "./myServerSetupCode.js" ... export const someQuery: SomeQuery<...> = async (args, context) => { const someResource = getSomeResource() return queryDataFromSomeResource(args, someResource) } ``` :::note The recommended way is to put the variable in the same module where you defined the setup function and then expose additional functions for reading those values, which you can then import directly from Operations and use. This effectively turns your module into a singleton whose construction is performed on server start. ::: For the full description of the `setupFn` field, check the [`Server` API Reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Server#setupfn). ### Middleware Config Function You can configure the global middleware via the `middlewareConfigFn`. This will modify the middleware stack for all operations and APIs. Read more in the [configuring middleware section](https://wasp.sh/docs/advanced/middleware-config#1-customize-global-middleware). ### API Reference [API reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Server) #### [Server ยป](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Server) [All the options for the server field of the app spec.](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Server) ## Project Setup / Static Asset Handling ### Importing an Asset as URL Importing a static asset (e.g. an image) will return its URL. For example: ```jsx title="src/App.tsx" import imgUrl from './img.png' function App() { return img } ``` For example, `imgUrl` will be `/img.png` during development, and become `/assets/img.2d8efhg.png` in the production build. This is what you want to use most of the time, as it ensures that the asset file exists and is included in the bundle. We are using Vite under the hood, read more about importing static assets in Vite's [docs](https://vitejs.dev/guide/assets.html#importing-asset-as-url). ### The `public` Directory If you have assets that are: - Never referenced in source code (e.g. robots.txt) - Must retain the exact same file name (without hashing) - ...or you simply don't want to have to import an asset first just to get its URL Then you can place the asset in the `public` directory at the root of your project: ``` . โ””โ”€โ”€ public โ”œโ”€โ”€ favicon.ico โ””โ”€โ”€ robots.txt ``` Assets in this directory will be served at root path `/` during development and copied to the root of the dist directory as-is. For example, if you have a file `favicon.ico` in the `public` directory, and your app is hosted at `https://myapp.com`, it will be made available at `https://myapp.com/favicon.ico`. :::info[Usage in client code] Note that: - You should always reference public assets using root absolute path - for example, `public/icon.png` should be referenced in source code as `/icon.png`. - Assets in the `public` directory **cannot be imported** from TypeScript. ::: ## Project Setup / Env Variables **Environment variables** are used to configure projects based on the context in which they run. This allows them to exhibit different behaviors in different environments, such as development, staging, or production. For instance, *during development*, you may want your project to connect to a local development database running on your machine, but *in production*, you want it to connect to the production database. Similarly, in development, you may want to use a test Stripe account, while in production, your app should use a real Stripe account. While some env vars are required by Wasp, such as the database connection or secrets for social auth, you can also define your env vars for any other useful purposes, and then access them in the code. Let's go over the available env vars in Wasp, how to define them, and how to use them in your project. ### Client Env Vars Client environment variables are injected into the client Javascript code during the build process, making them public and readable by anyone. Therefore, you should **never store secrets in them** (such as secret API keys, you should store secrets in the server env variables). :::caution[Client Env Var Prefix] Client env vars must be prefixed with `REACT_APP_`, for example: `REACT_APP_SOME_VAR_NAME=...` for security reasons. Wasp will only inject env vars that start with this prefix into the client code to prevent accidental exposure of sensitive information. ::: You can read them from the client code like this: ```ts title="src/App.ts" import { env } from "wasp/client" console.log(env.REACT_APP_SOME_VAR_NAME) ``` Read more about the `env` object in the [API reference](#client-env-vars-api). #### Wasp Client Env Vars Here are the client env vars that Wasp defines: ##### General Configuration {#client-general-configuration} These are some general env variables used for various Wasp features: | Name | Type | Notes | | ------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `REACT_APP_API_URL` | URLrequired | The client uses this as the server URL. This app is required in production but defaults to `http://localhost:3001` in development. | ### Server Env Vars You can store secret values (e.g. secret API keys) in the server env variables since they are not publicly readable. You can define them without any special prefix, such as `SOME_VAR_NAME=...`. You can read the env vars from server code like this: ```ts import { env } from "wasp/server" console.log(env.SOME_VAR_NAME) ``` Read more about the `env` object in the [API reference](#server-env-vars-1). #### Wasp Server Env Vars ##### General Configuration {#server-general-configuration} These are some general env variables used for various Wasp features: | Name | Type | Notes | | --------------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `DATABASE_URL` | Stringrequired | The URL of the PostgreSQL database you want your app to use. | | `WASP_WEB_CLIENT_URL` | URLrequired | Server uses this value as your client URL in various features e.g. linking to your app in e-mails. Defaults to `http://localhost:3000` in development. | | `WASP_SERVER_URL` | URLrequired | Server uses this value as your server URL in various features e.g. to redirect users when logging in with OAuth providers like Google or GitHub. Defaults to `http://localhost:3001` in development. | | `JWT_SECRET` | Stringrequired | A random string of at least 32 characters. Needed to generate secure tokens. Defaults to `DEVJWTSECRET` in development. Generate secret | | `PORT` | IntegeroptionalDefault: `3001` | This is where the server listens for requests. | ##### SMTP Email Sender If you are using `SMTP` as your email sender, you need to provide the following environment variables: | Name | Type | Notes | | --------------- | --------------- | ------------------------- | | `SMTP_HOST` | Stringrequired | The SMTP server host. | | `SMTP_PORT` | Integerrequired | The SMTP server port. | | `SMTP_USERNAME` | Stringrequired | The SMTP server username. | | `SMTP_PASSWORD` | Stringrequired | The SMTP server password. | ##### SendGrid Email Sender If you are using `SendGrid` as your email sender, you need to provide the following environment variables: | Name | Type | Notes | | ------------------ | -------------- | --------------------- | | `SENDGRID_API_KEY` | Stringrequired | The SendGrid API key. | ##### Mailgun Email Sender If you are using `Mailgun` as your email sender, you need to provide the following environment variables: | Name | Type | Notes | | ----------------- | -------------- | ----------------------------------------------------------------------------- | | `MAILGUN_API_KEY` | Stringrequired | The Mailgun API key. | | `MAILGUN_DOMAIN` | Stringrequired | The Mailgun domain. | | `MAILGUN_API_URL` | URLoptional | Useful if you want to use the EU API endpoint (`https://api.eu.mailgun.net`). | ##### Resend Email Sender If you are using `Resend` as your email sender, you need to provide the following environment variables: | Name | Type | Notes | | ---------------- | -------------- | ------------------- | | `RESEND_API_KEY` | Stringrequired | The Resend API key. | ##### OAuth Providers If you are using OAuth, you need to provide the following environment variables: | Name | Type | Notes | | ------------------------------- | -------------- | ------------------------------------------------- | | `_CLIENT_ID` | Stringrequired | The client ID provided by the OAuth provider. | | `_CLIENT_SECRET` | Stringrequired | The client secret provided by the OAuth provider. | \* `` is the uppercase name of the provider you are using. For example, if you are using Google OAuth, you need to provide the `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` environment variables. If you are using [Keycloak](https://wasp.sh/docs/auth/social-auth/keycloak), you'll need to provide one extra environment variable: | Name | Type | Notes | | -------------------- | ----------- | ------------------------------ | | `KEYCLOAK_REALM_URL` | URLrequired | The URL of the Keycloak realm. | ##### Jobs | Name | Type | Notes | | --------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `PG_BOSS_NEW_OPTIONS` | Stringoptional | A [JSON env var](#json-env-vars). Enables you to provide [custom config](https://wasp.sh/docs/advanced/jobs#pg_boss_new_options) for PgBoss. | ##### Development We provide some helper env variables in development: | Name | Type | Notes | | -------------------------------- | ------------------------------- | -------------------------------------------------------------------------- | | `SKIP_EMAIL_VERIFICATION_IN_DEV` | BooleanoptionalDefault: `false` | If set to true, automatically sets user emails as verified in development. | ### Defining Env Vars in Development During development (`wasp start`), there are two ways to provide env vars to your Wasp project: 1. Using `.env` files. **(recommended)** 2. Using shell. (useful for overrides) #### 1. Using .env (dotenv) Files {#dotenv-files} ![Env vars usage in development](https://wasp.sh/assets/images/prod_dev_fade-e4097e7d9b64c62ca95bfde692e5115d.svg) This is the recommended method for providing env vars to your Wasp project during development. In the root of your Wasp project you can create two distinct files: - `.env.server` for env vars that will be provided to the server. Variables are defined in these files in the form of `NAME=VALUE`, for example: ```shell title=".env.server" DATABASE_URL=postgresql://localhost:5432 SOME_VAR_NAME=somevalue ``` - `.env.client` for env vars that will be provided to the client. Variables are defined in these files in the form of `NAME=VALUE`, for example: ```shell title=".env.client" REACT_APP_SOME_VAR_NAME=somevalue ``` :::caution[Client Env Var Prefix] Client env vars must be prefixed with `REACT_APP_`, for example: `REACT_APP_SOME_VAR_NAME=...` for security reasons. Wasp will only inject env vars that start with this prefix into the client code to prevent accidental exposure of sensitive information. ::: `.env.server` should not be committed to version control as it can contain secrets, while `.env.client` can be versioned as it must not contain any secrets. By default, in the `.gitignore` file that comes with a new Wasp app, we ignore all dotenv files. #### 2. Using Shell If you set environment variables in the shell where you run your Wasp commands (e.g., `wasp start`), Wasp will recognize them. You can set environment variables in the `.profile` or a similar file, which will set them permanently, or you can set them temporarily by defining them at the start of a command (`SOME_VAR_NAME=SOMEVALUE wasp start`). This is not specific to Wasp and is simply how environment variables can be set in the shell. Defining environment variables in this way can be cumbersome even for a single project and even more challenging to manage if you have multiple Wasp projects. Therefore, we do not recommend this as a default method for providing environment variables to Wasp projects during development, you should use .env files instead. However, it can be useful for occasionally **overriding** specific environment variables because environment variables set this way **take precedence over those defined in `.env` files**. ### Defining Env Vars in Production Defining env variables in production will depend on where you are deploying your Wasp project. In general, you will define them via mechanisms that your hosting provider provides. We talk about how to define env vars for each deployment option in the [deployment section](https://wasp.sh/docs/deployment/env-vars). ### JSON Env Vars Some of the environment variables you pass to Wasp are parsed as JSON values. This is useful for features needing more in-depth configuration, but it comes with the caveat of ensuring that the JSON syntax is valid. The main issue comes in the form of escaping quotes, and the different ways to do it depending on where you are defining the env var. ##### In `.env` files In `.env` files, you don't need to quote the full value, so you don't need to escape the quotes. For example, you can define a JSON object like this: ```shell title=".env.server" PG_BOSS_NEW_OPTIONS={"connectionString":"...db url...","jobExpirationInSeconds":60,"maxRetries":3} ``` ##### In the shell In the shell, you need to quote the full value and escape the quotes inside the JSON object. For example, you can define a JSON object like this: ```shell PG_BOSS_NEW_OPTIONS="{\"connectionString\":\"...db url...\",\"jobExpirationInSeconds\":60,\"maxRetries\":3}" ``` As an alternative, you can use single quotes to avoid escaping the quotes inside the JSON object: ```shell PG_BOSS_NEW_OPTIONS='{"connectionString":"...db url...","jobExpirationInSeconds":60,"maxRetries":3}' ``` ### Custom Env Var Validations If your code requires some environment variables, you usually want to ensure that they are correctly defined. In Wasp, you can define your environment variables validation by defining a [Zod object schema](https://zod.dev/?id=basic-usage) and telling Wasp to use it. :::info[What is Zod?] [Zod](https://zod.dev/) is a library that lets you define what you expect from your data. For example, you can use Zod to define that: - A value should be a string that's a valid email address. - A value should be a number between 0 and 100. - ... and much more. ::: Take a look at an example of defining env vars validation: ```ts title="src/env.ts" import * as z from "zod" import { defineEnvValidationSchema } from "wasp/env" export const serverEnvValidationSchema = defineEnvValidationSchema( z.object({ STRIPE_API_KEY: z.string({ required_error: "STRIPE_API_KEY is required.", }), }) ) export const clientEnvValidationSchema = defineEnvValidationSchema( z.object({ REACT_APP_NAME: z.string().default("TODO App"), }) ) ``` The `defineEnvValidationSchema` function ensures your Zod schema is type-checked. ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { clientEnvValidationSchema, serverEnvValidationSchema } from "./src/env" with { type: "ref" } export default app({ name: "myApp", client: { envValidationSchema: clientEnvValidationSchema, }, server: { envValidationSchema: serverEnvValidationSchema, }, // ... }) ``` You defined schemas for both the client and the server env vars and told Wasp to use them. Wasp merges your env validation schemas with the built-in env vars validation schemas when it validates the `process.env` object on the server and the `import.meta.env` object on the client. This means you can use the `env` object to access **your env vars** like this: ```ts title="src/stripe.ts" import { env } from "wasp/server" const stripeApiKey = env.STRIPE_API_KEY ``` Read more about the env object in the [API Reference](#api-reference). ### API Reference There are **Wasp-defined** and **user-defined** env vars. Wasp already comes with built-in validation for Wasp-defined env vars. For your env vars, you can define your own validation. #### Client Env Vars {#client-env-vars-api} ##### User-defined env vars validation You can define your client env vars validation like this: ```ts title="src/env.ts" import * as z from "zod" import { defineEnvValidationSchema } from "wasp/env" export const envValidationSchema = defineEnvValidationSchema( z.object({ REACT_APP_ANALYTICS_ID: z.string({ required_error: "REACT_APP_ANALYTICS_ID is required.", }), }) ) ``` The `defineEnvValidationSchema` function ensures your Zod schema is type-checked. ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { envValidationSchema } from "./src/env" with { type: "ref" } export default app({ name: "myApp", client: { envValidationSchema, }, // ... }) ``` Wasp merges your env validation schemas with the built-in env vars validation schemas when it validates the `import.meta.env` object. ##### Accessing env vars in client code You can access both **Wasp-defined** and **user-defined** client env vars in your client code using the `env` object: ```ts title="src/App.ts" import { env } from "wasp/client" // Wasp-defined const apiUrl = env.REACT_APP_API_URL // User-defined const analyticsId = env.REACT_APP_ANALYTICS_ID ``` You can use `import.meta.env.REACT_APP_SOME_VAR_NAME` directly in your code. We don't recommend this since `import.meta.env` isn't validated and missing env vars can cause runtime errors. #### Server Env Vars ##### User-defined env vars validation You can define your env vars validation like this: ```ts title="src/env.ts" import * as z from "zod" import { defineEnvValidationSchema } from "wasp/env" export const envValidationSchema = defineEnvValidationSchema( z.object({ STRIPE_API_KEY: z.string({ required_error: "STRIPE_API_KEY is required.", }), }) ) ``` The `defineEnvValidationSchema` function ensures your Zod schema is type-checked. ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { envValidationSchema } from "./src/env" with { type: "ref" } export default app({ name: "myApp", server: { envValidationSchema, }, // ... }) ``` Wasp merges your env validation schemas with the built-in env vars validation schemas when it validates the `process.env` object. ##### Accessing env vars in server code You can access both **Wasp-defined** and **user-defined** client env vars in your client code using the `env` object: ```ts title="src/stripe.ts" import { env } from "wasp/server" // Wasp-defined const serverUrl = env.WASP_SERVER_URL // User-defined const stripeApiKey = env.STRIPE_API_KEY ``` You can use `process.env.SOME_SECRET` directly in your code. We don't recommend this since `process.env` isn't validated and missing env vars can cause runtime errors. ## Project Setup / Testing :::info Wasp is in beta, so keep in mind there might be some kinks / bugs, and possibly some changes with testing support in the future. If you encounter any issues, reach out to us on [Discord](https://discord.gg/rzdnErX) and we will make sure to help you out! ::: ### Testing Your React App Wasp enables you to quickly and easily write both unit tests and React component tests for your frontend code. Because Wasp uses [Vite](https://vitejs.dev/), we support testing web apps through [Vitest](https://vitest.dev/). Make sure your `devDependencies` include the Vitest dependency. Wasp starters come with Vitest included: ```json title="package.json" { "devDependencies": { "vitest": "^4.1.9" } } ``` Testing Libraries [`vitest`](https://www.npmjs.com/package/vitest): Unit test framework with native Vite support. [`@vitest/ui`](https://www.npmjs.com/package/@vitest/ui): A nice UI for seeing your test results. [`jsdom`](https://www.npmjs.com/package/jsdom): A web browser test environment for Node.js. [`@testing-library/react`](https://www.npmjs.com/package/@testing-library/react) / [`@testing-library/jest-dom`](https://www.npmjs.com/package/@testing-library/jest-dom): Testing helpers. [`msw`](https://www.npmjs.com/package/msw): A server mocking library. #### Writing Tests For Wasp to pick up your tests, they should be placed within the `src` directory and use an extension that matches [these glob patterns](https://vitest.dev/config#include). Some of the file names that Wasp will pick up as tests: - `yourFile.test.ts` - `YourComponent.spec.jsx` Within test files, you can import your other source files as usual. For example, if you have a component `Counter.jsx`, you test it by creating a file in the same directory called `Counter.test.jsx` and import the component with `import Counter from './Counter'`. #### Running Tests Running `wasp test client` will start Vitest in watch mode and recompile your Wasp project when changes are made. - If you want to see a real-time UI, pass `--ui` as an option. - To run the tests just once, use `wasp test client run`. All arguments after `wasp test client` are passed directly to the Vitest CLI, so check out [their documentation](https://vitest.dev/guide/cli.html) for all of the options. :::warning[Be Careful] You should not run `wasp test` while `wasp start` is running. Both will try to compile your project to `.wasp/out`. ::: #### React Testing Helpers Wasp provides several functions to help you write React tests: - `renderInContext`: Takes a React component, wraps it inside a `QueryClientProvider` and `Router`, and renders it. This is the function you should use to render components in your React component tests. ```js import { renderInContext } from "wasp/client/test"; renderInContext(); ``` - `mockServer`: Sets up the mock server and returns an object containing the `mockQuery` and `mockApi` utilities. This should be called outside of any test case, in each file that wants to use those helpers. ```js import { mockServer } from "wasp/client/test"; const { mockQuery, mockApi } = mockServer(); ``` - `mockQuery`: Takes a Wasp [query](https://wasp.sh/docs/data-model/operations/queries) to mock and the JSON data it should return. ```js import { getTasks } from "wasp/client/operations"; mockQuery(getTasks, []); ``` - Helpful when your component uses `useQuery`. - Behind the scenes, Wasp uses [`msw`](https://npmjs.com/package/msw) to create a server request handle that responds with the specified data. - Mock are cleared between each test. - `mockApi`: Similar to `mockQuery`, but for [APIs](https://wasp.sh/docs/advanced/apis). Instead of a Wasp query, it takes a route containing an HTTP method and a path. ```js import { HttpMethod } from "wasp/client"; mockApi({ method: HttpMethod.Get, path: "/foor/bar" }, { res: "hello" }); ``` ### Testing Your Server-Side Code Wasp currently does not provide a way to test your server-side code, but we will be adding support soon. You can track the progress at [this GitHub issue](https://github.com/wasp-lang/wasp/issues/110) and express your interest by commenting. ### Examples You can see some tests in a Wasp project [here](https://github.com/wasp-lang/wasp/blob/release/waspc/examples/todoApp/src/pages/auth/helpers.test.ts). #### Client Unit Tests ```ts title="src/helpers.ts" import { type Task } from "wasp/entities"; export function areThereAnyTasks(tasks: Task[]): boolean { return tasks.length !== 0; } ``` ```ts title="src/helpers.test.ts" import { test, expect } from "vitest"; import { areThereAnyTasks } from "./helpers"; test("areThereAnyTasks", () => { expect(areThereAnyTasks([])).toBe(false); }); ``` #### React Component Tests ```tsx title="src/Todo.tsx" import { useQuery, getTasks } from "wasp/client/operations"; const Todo = (_props: {}) => { const { data: tasks } = useQuery(getTasks); return (
    {tasks && tasks.map((task) => (
  • {task.description}
  • ))}
); }; ``` ```tsx title="src/Todo.test.tsx" import { test, expect } from "vitest"; import { screen } from "@testing-library/react"; import { mockServer, renderInContext } from "wasp/client/test"; import { getTasks } from "wasp/client/operations"; import Todo from "./Todo"; const { mockQuery } = mockServer(); const mockTasks = [ { id: 1, description: "test todo 1", isDone: true, userId: 1, }, ]; test("handles mock data", async () => { mockQuery(getTasks, mockTasks); renderInContext(); await screen.findByText("test todo 1"); expect(screen.getByRole("checkbox")).toBeChecked(); screen.debug(); }); ``` #### Testing With Mocked APIs ```tsx title="src/Todo.tsx" import { type Task } from "wasp/entities"; import { api } from "wasp/client/api"; const Todo = (_props: {}) => { const [tasks, setTasks] = useState([]); useEffect(() => { api.get("/tasks").json() .then((tasks) => setTasks(tasks)) .catch((err) => window.alert(err)); }); return (
    {tasks && tasks.map((task) => (
  • {task.description}
  • ))}
); }; ``` ```tsx title="src/Todo.test.tsx" import { test, expect } from "vitest"; import { screen } from "@testing-library/react"; import { mockServer, renderInContext } from "wasp/client/test"; import Todo from "./Todo"; const { mockApi } = mockServer(); const mockTasks = [ { id: 1, description: "test todo 1", isDone: true, userId: 1, }, ]; test("handles mock data", async () => { mockApi("/tasks", mockTasks); renderInContext(); await screen.findByText("test todo 1"); expect(screen.getByRole("checkbox")).toBeChecked(); screen.debug(); }); ``` ## Project Setup / Dependencies In a Wasp project, dependencies are defined in a standard way for JavaScript projects: using the [package.json](https://docs.npmjs.com/cli/configuring-npm/package-json) file, located at the root of your project. You can list your dependencies under the `dependencies` or `devDependencies` fields. #### Adding a New Dependency To add a new package, like `date-fns` (a great date handling library), you use `npm`: ```bash npm install date-fns ``` This command will add the package in the `dependencies` section of your `package.json` file. You will notice that there are some other packages in the `dependencies` section, like `react` and `wasp`. These are the packages that Wasp uses internally, and you should not modify or remove them. #### Using Packages that are Already Used by Wasp Internally Wasp internally uses certain dependencies (e.g. React, Prisma, Vite) with specific versions. By default, you cannot specify a different version for these packages - if you try, you'll get an error telling you which version Wasp requires. ##### Overriding Wasp's Dependencies (Advanced) If you need to use a different version of a Wasp-managed dependency, you can override it using the `wasp.overriddenDeps` field in your `package.json`. This is an advanced feature intended for: - Testing newer versions of dependencies before Wasp officially supports them - Working around bugs in a specific dependency version - Using older versions for compatibility reasons :::caution This functionality is intended to give you control when absolutely necessary, but it comes with risks. We recommend that you **don't** use override in production projects. We don't test Wasp with different versions of our dependencies, and we don't guarantee functionality or stability. Incompatibilities might be big and obvious, but they can also be subtle and indirect. When you override dependencies, it's up to you to test your app thoroughly and validate that it works as expected. If issues arise from using overridden versions, it's also up to you to deal with them. ::: :::tip If you find the need to override any dependency, we'd appreciate for you to [post an issue on GitHub](https://github.com/wasp-lang/wasp/issues/new/choose), or a [message on our Discord](https://discord.gg/rzdnErX), explaining your usecase. This will make us aware of your needs, and helps us prioritize giving you a supported solution faster. ::: To override a dependency, add the `wasp` field to your `package.json` with an `overriddenDeps` object. The keys are the package names, and the values are **what Wasp currently requires** (not your desired version): **Before** ```json title="package.json" { "dependencies": { "react": "19.2.1", "react-dom": "19.2.1" } } ``` **After** ```json title="package.json" { "dependencies": { "react": "18.2.0", "react-dom": "18.2.0" }, "wasp": { "overriddenDeps": { "react": "19.2.1", "react-dom": "19.2.1" } } } ``` In this example: - You want to use React 18.2.0 (specified in `dependencies`) - Wasp requires React 19.2.1 (specified in `overriddenDeps`) - By declaring this, you acknowledge you're deviating from Wasp's tested version When Wasp updates its requirements in a new release, you'll need to update your `overriddenDeps` values to match. This ensures you consciously acknowledge each change. :::note If you need the override to apply to transitive dependencies as well (dependencies of your dependencies), you can use npm's built-in [`overrides`](https://docs.npmjs.com/cli/v11/configuring-npm/package-json#overrides) feature alongside `wasp.overriddenDeps`. ::: #### Supply Chain Protection New Wasp projects include an `.npmrc` file with [`min-release-age`](https://docs.npmjs.com/cli/v11/using-npm/config#min-release-age) set to **7 days** by default. This prevents npm from installing any package version that was published less than 7 days ago, which helps protect against [supply chain attacks](https://en.wikipedia.org/wiki/Supply_chain_attack). Malicious packages are typically detected and removed within hours of publication, so by adding a short delay there's a much smaller chance of being targeted by these attacks. If you need to install a recently published package, you can temporarily override this by passing the flag directly: ```bash npm install some-package --min-release-age=0 ``` Or you can adjust the value in your project's `.npmrc` file. ## Project Setup / Custom Vite Config Wasp uses [Vite](https://vitejs.dev/) to serve the client during development and bundling it for production. If you want to customize the Vite config, you can do that by editing the `vite.config.ts` file in your project root directory. ### Required Configuration You have **full control** over your `vite.config.ts` file. Wasp doesn't manage this file internally. Instead, you must import and use the `wasp()` plugin from `wasp/client/vite` in your Vite configuration. This plugin provides all the essential Wasp features: - Configuration required for Wasp full-stack apps to work. - Environment variables validation. - Prevention of server imports in client code. - TypeScript type checking during production builds. Here's the minimal required configuration: ```ts title="vite.config.ts" import { wasp } from 'wasp/client/vite' import { defineConfig } from 'vite' export default defineConfig({ plugins: [wasp()], }) ``` :::warning[Plugin order] The `wasp()` plugin must be the **first** plugin in the `plugins` array. Any other plugins (like Tailwind CSS) should be added after it. ::: ### Enforced Options The `wasp()` plugin enforces certain Vite config values that Wasp needs to function correctly. If you set any of these in your `vite.config.ts`, Wasp will throw an error asking you to remove them. | Option | Internal value | Why you can't customize it | | -------------- | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | | `base` | Based on the [`client.baseDir`](https://wasp.sh/docs/project/client-config#base-directory) option | Wasp sets the React Router's `basename` to the same value. | | `envPrefix` | `"REACT_APP_"` | Wasp's environment variable validation depends on this prefix. | | `build.outDir` | `".wasp/out/web-app/build"` | Build artifacts must go to the location Wasp expects for deployment. | ### Customization You can add additional configuration and plugins as needed. The `wasp()` plugin will use your config and merge it with the built-in defaults. Vite config customization can be useful for things like: - Adding additional Vite plugins. - Customizing the dev server behavior. - Customizing the build process. ### Plugin Options The `wasp()` plugin accepts options allowing you to customize the underlying React plugin behavior if needed: ```ts title="vite.config.ts" import { wasp } from "wasp/client/vite"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [ wasp({ reactOptions: { // Pass any @vitejs/plugin-react options here }, }), ], }); ``` ### Examples Below are some examples of how you can customize the Vite config. #### Changing the Dev Server Behaviour If you want to stop Vite from opening the browser automatically when you run `wasp start`, you can do that by customizing the `open` option. ```ts title="vite.config.ts" import { wasp } from "wasp/client/vite"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [wasp()], server: { open: false, }, }); ``` #### Custom Dev Server Port You have access to all of the [Vite dev server options](https://vitejs.dev/config/server-options.html) in your custom Vite config. You can change the **client** dev server port by setting the `port` option. To change the Wasp **server** port, see the [`PORT` server env var](https://wasp.sh/docs/project/env-vars#server-general-configuration). ```ts title="vite.config.ts" import { wasp } from "wasp/client/vite"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [wasp()], server: { port: 4000, }, }); ``` ```env title=".env.server" WASP_WEB_CLIENT_URL=http://localhost:4000 ``` :::warning[Changing the client dev server port] Be careful when changing the client dev server port, you'll need to update the `WASP_WEB_CLIENT_URL` env var in your `.env.server` file. ::: #### Editing from the Chrome DevTools {#devtools-workspace} Chrome DevTools support [mapping a page's resources to a folder](https://developer.chrome.com/docs/devtools/workspaces), so any changes you make in the browser are reflected back to your files. To enable it, you can use their Vite plugin: [`vite-plugin-devtools-json`](https://github.com/ChromeDevTools/vite-plugin-devtools-json). 1. Install the plugin as a **dev dependency**: ```bash npm i -D vite-plugin-devtools-json ``` 2. Extend your `vite.config.{ts,js}`: ```ts title="vite.config.ts" import { wasp } from "wasp/client/vite"; import { defineConfig } from "vite"; import devtoolsJson from "vite-plugin-devtools-json"; export default defineConfig({ plugins: [wasp(), devtoolsJson({ root: import.meta.dirname })], }); ``` 3. Start your app with `wasp start`, open **Chrome DevTools โ†’ Sources โ†’ Workspace** and you should see your project automatically mapped. Changes you make in DevTools now save to disk and Vite's HMR updates the browser instantly! :::tip[Path normalisation] The latest version of `vite-plugin-devtools-json` includes Windows, WSL and Docker Desktop path fixes contributed by the Wasp community โ€“ make sure you are on version 0.4.0 or greater. ::: ### API Reference ```ts title="vite.config.ts" import { wasp } from "wasp/client/vite"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [ wasp({ reactOptions: { // ... }, }), ], }); ``` The `wasp()` plugin accepts the following options: - ##### `reactOptions: ReactOptions` optional {#reactoptions-reactoptions-} Object to customize the underlying [`@vitejs/plugin-react`](https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react) plugin. This allows you to configure React-specific options like Babel plugins, Fast Refresh settings, and JSX configuration. ## Deployment / Introduction After developing your app locally on your machine, the next step is to deploy it to the web so that others can access it. In this section, we'll walk you through the steps to deploy your Wasp app. #### Wasp app structure Before we start, let's understand what Wasp generates when it builds your app. What we call a "Wasp app" consists of three different parts: - **Client app** - It's a single-page application (SPA), built using [React](https://react.dev/). It's what the user sees and interacts with. - It's usually served by some static file server or you can host it on a CDN like Cloudflare or Netlify. - **Server app**: - The backend of your app, built using [Express](https://expressjs.com/) on Node.js. - It handles requests from the client app, interacts with the database, and returns responses. - It comes with a ready-to-use `Dockerfile` so you can easily package it and deploy it anywhere where Docker is supported. - **Database**: - Wasp uses [PostgreSQL](https://www.postgresql.org/) as its production database. - You can host the database on your own server or use a cloud service. ![Wasp app structure](https://wasp.sh/img/deploying/wasp-app-flow.gif) Data flow in a typical deployed Wasp app where all three parts are deployed separately The thing to take away from this: the client app and server app are separate applications that communicate with each other over HTTP. This means you can deploy them on the same or different servers, depending on your needs. We'll show you different ways of how deploy your app in the [deployment methods](https://wasp.sh/docs/deployment/deployment-methods/overview) section. Server needs to be able to communicate with the database, we'll show you how to set that up using [env variables](https://wasp.sh/docs/deployment/env-vars). #### Deploying your app In the following sections, we'll go through all the different things you need to know about deployment: - How [env variables](https://wasp.sh/docs/deployment/env-vars) work in production - they are different than using .env files in development. - Production [database setup](https://wasp.sh/docs/deployment/database) - how migrations work, how to connect to the database, etc. - Different deployment methods (using [Wasp's CLI](https://wasp.sh/docs/deployment/deployment-methods/wasp-deploy/overview), [cloud services](https://wasp.sh/docs/deployment/deployment-methods/cloud-providers), [self-hosting](https://wasp.sh/docs/deployment/deployment-methods/self-hosted), etc.) - How to [set up CI/CD](https://wasp.sh/docs/deployment/ci-cd) for your app - automatically deploy your app when you push to your Git repository. - Some [extras](https://wasp.sh/docs/deployment/extras) like custom domains, CDN, etc. ## Deployment / Env Variables We talked about environment variables in the [project setup section](https://wasp.sh/docs/project/env-vars). If you haven't read it, make sure to check it out first. In this section, we'll talk about environment variables in the context of deploying the app. While developing our app on our machine, we had the option of using `.env.client` and `.env.server` files which made it easy to define and manage env vars. However, when we are deploying our app, **`.env.client` and `.env.server` files will be ignored, and we need to provide env vars differently.** ![Env vars usage in development and production](https://wasp.sh/assets/images/prod_dev_fade_2-d0ff1e438a29011a68bcf630a9470254.svg) #### Client Env Vars During the build process, client env vars are injected into the client Javascript code, making them public and readable by anyone. Therefore, you should **never store secrets in them** (such as secret API keys). When building for production, the `.env.client` file will be ignored, since it is meant to be used only during development. Instead, you should provide the production client env vars directly to the build command that turns client code into static files. Make sure to check the [required client env vars](https://wasp.sh/docs/project/env-vars#client-general-configuration) and set them when building for production, the build will fail if any required env vars are missing. ```shell REACT_APP_API_URL= REACT_APP_SOME_OTHER_VAR_NAME=someothervalue npx vite build ``` Also, notice **that you can't and shouldn't provide client env vars to the client code by setting them on the hosting provider** (unlike providing server env vars to the server app, in that case this is how you should do it). Your client code will ignore those, as at that point client code is just static files. :::info[How it works] What happens behind the scenes is that Wasp will replace all occurrences of `import.meta.env.REACT_APP_SOME_VAR_NAME` in your client code with the env var value you provided. This is done during the build process, so the value is injected into the static files produced from the client code. Read more about it in Vite's [docs](https://vitejs.dev/guide/env-and-mode.html#production-replacement). ::: #### Server Env Vars When building your Wasp app for production `.env.server` will be ignored, since it is meant to be used only during development. You can provide production env vars to your server code in production by defining them and making them available on the server where your server code is running. :::caution[Set the required env vars] Make sure to go through [all the required server env vars](https://wasp.sh/docs/project/env-vars#server-general-configuration) like `DATABASE_URL`, `WASP_WEB_CLIENT_URL`, `WASP_SERVER_URL` etc. and set them up in your production environment. While some env vars like `WASP_WEB_CLIENT_URL` and `WASP_SERVER_URL` have default values in development, they are **required in production** and must be explicitly set. **If you are using the [Wasp CLI](https://wasp.sh/docs/deployment/deployment-methods/wasp-deploy/overview)** deployment method, Wasp will set the general configuration env vars for you, but you will need to set the rest of the env vars yourself (like the ones for OAuth auth methods or any other custom env vars you might have defined). ::: Setting server env variables up will highly depend on where you are deploying your server, but in general it comes down to defining the env vars via mechanisms that your hosting provider provides. For example, if you deploy your server to [Fly](https://fly.io), you can define them using the `fly` CLI tool: ```shell fly secrets set SOME_VAR_NAME=somevalue ``` We talk about specific providers in the [Cloud Providers section](https://wasp.sh/docs/deployment/deployment-methods/cloud-providers) or the [self-hosted deployment section](https://wasp.sh/docs/deployment/deployment-methods/self-hosted). ## Deployment / Database In this section, we'll discuss what happens with the database when your app goes live. When you develop your app locally, you probably use a local dev database (started with `wasp start db` or some other way). However, when it's time to deploy your app, you'll need to set up a production database. #### Production database requirements The server app that Wasp generates uses a PostgreSQL database. The only requirement from Wasp's point of view is that the database is accessible from the server via the `DATABASE_URL` server env variable. It can be a PostgreSQL database running on the same server as the server app, or it can be a managed PostgreSQL database service like [Fly Postgres](https://fly.io/docs/postgres/), [AWS RDS](https://aws.amazon.com/rds/), or some other service. ### Migrations Every time you make a change in your [Prisma schema](https://wasp.sh/docs/data-model/prisma-file) e.g. adding a new model, changing a field type, etc., you need to create a migration. Migrations are some code that describes the change you made in the schema, and they are used to apply the change to the database. The benefit of migrations is that you can apply the same change to multiple databases. If there are multiple people working on the project, they can all apply the same changes to their local databases. When you deploy the app to production, the same chaanges are applied to the production database. #### Creating migrations After you made a change in the Prisma schema, you can create a migration by running the following command: ```bash wasp db migrate-dev ``` This command will create a new migration in the `migrations` directory. The migration is a set of SQL commands that describe the change you made in the schema. #### Applying migrations **In development**, the migrations are applied as soon as you run the `wasp start` command. **In production**, the server app first checks if there are any new migrations that need to be applied, and if there are, it applies them before starting the server. This way, the database schema is always in sync with the Prisma schema. :::note[How it works] In the built server app, there are two npm scripts: `start` and `start-production`. The `start` script is used in development, and the `start-production` script is used in production. The `start-production` script first applies any pending migrations before starting the server. ::: The migrations might fail to apply if there is a conflict with the existing data in the database. In that case, you'll need to fix the migration and try again. #### Debugging failed migrations If a migration fails to apply, the server app will log the error message and stop. You should then connect to the production database and see what went wrong. If you check the `_prisma_migrations` table, you'll see the failed migration there. You can try resolving the erorr e.g. if you tried adding a `@unique` constraint to a field that already has duplicate values: 1. Remove any duplicate values from the database 2. Remove the failed migration from the `_prisma_migrations` table 3. Try applying the migration again by restarting the server app :::tip[Viewing the _prisma_migrations table] You can't use the `wasp db studio` command to view the `_prisma_migrations` table in the production database, but you can use a database management tool like [DBeaver](https://dbeaver.io/) or [pgAdmin](https://www.pgadmin.org/). ::: ### Connect to the production database **In development**, you can use the `wasp db studio` command to open a web-based database management tool that allows you to inspect the database. You can use the same tool to inspect the **production database**, but you'll need to set the `DATABASE_URL` env variable to point to the production database. Set the `DATABASE_URL` env variable in your terminal before running the `wasp db studio` command: ```bash DATABASE_URL="postgresql://user:password@host:port/dbname" wasp db studio ``` :::caution[Be careful with the DATABASE_URL env variable] Setting the `DATABASE_URL` env variable in the `.env.server` file to point to your production database also works, but then you might forget to remove it and you could accidentally make changes to the production database when you run `wasp start` in development. That's why we recommend setting the `DATABASE_URL` env variable in the terminal to avoid this. ::: If you are looking how to connect to a Fly.io production database, we wrote a guide on how to do that: [Guide](https://wasp.sh/docs/guides/debugging/db-studio-fly-io) #### [Database Studio with Fly.io ยป](https://wasp.sh/docs/guides/debugging/db-studio-fly-io) [Connect to your Fly.io production database and run wasp db studio](https://wasp.sh/docs/guides/debugging/db-studio-fly-io) ## Deployment / Testing the build locally `wasp build start` lets you test your production build locally before deployment, ensuring everything works correctly before going live. This command takes the output of `wasp build` and starts a local server to run it. That means that you can test using the same optimized code that would be deployed to production. You also configure it with the same environment variables you'd use in production, which helps you catch configuration issues before deploying. While it's not identical to a real production environment, it's the closest you can get to testing your deployed app without actually deploying it. :::warning[This is not a deployment command] `wasp build start` is only intended for testing your `wasp build` output locally, and is not designed for serving your app in production. For that, check out our [deployment guide](https://wasp.sh/docs/deployment/intro). ::: ### Usage ```bash # Start a local database, copy the connection URL wasp start db # Start the local production build server # (this is an example, you'll probably need to add more environment variables) wasp build start --server-env DATABASE_URL= --server-env JWT_SECRET= ``` :::tip For `JWT_SECRET`, you can generate a random secret here: Generate secret. You might need to pass other environment variables as well, depending on your app's configuration. Check our [Environment variables reference](https://wasp.sh/docs/project/env-vars) for more details. ::: This command will: - Start a local server serving your production build (the output of `wasp build`). - Use only the environment variables you set explicitly. - Use the same bundled assets that would be deployed. - Run in production mode with optimizations enabled. ### Why? The main reason for using `wasp build start` is to catch dependencies on your local development environment that might not work in production. For example, your app might rely on environment variables that are set in your local `.env` files. `wasp start` by default will read these files and use them. While this makes it easy to develop your app locally, it also makes it easy to lose track of which environment variables your app actually needs in production. `wasp build start` forces you to explicitly specify the environment variables your app needs to run in production. This helps you double-check which ones you also need to set in your deployment environment for the app to work correctly. Your code might also depend on some development-only features in your libraries, such as React development or strict mode. `wasp build start` runs your app as they would for your users, which means that these features are disabled. This helps you catch issues that might only appear in production. You should treat this command as the last check before deploying your app, confirming that you know all required environment variables and that integrations behave as expected with production settings on. It is also the best way to reproduce issues that only appear in production, which can be very useful for debugging. ### Differences from `wasp start` | Aspect | `wasp start` | `wasp build start` | | ---------------------------------------- | ------------------- | ---------------------------------------------------------------------------- | | Runs your app for general production use | **No** | **No** (check our [deployment guide](https://wasp.sh/docs/deployment/intro)) | | Intended for | Local development | Local production testing | | Server environment | Node.js | Node.js in a Docker container | | Client environment | Static server | Static server | | Assets | Served individually | Bundled and minified | | React dev mode | Enabled | Disabled | | Hot reload | Enabled | Disabled | | Source maps | Enabled | Disabled | | Debugging support | Full | Limited | | Performance | Slower | Normal | ### Passing environment variables You must manually specify any environment variables that your app needs to run in production. This is crucial because the production build may require different configurations from the development build. This helps you take note of which ones you also need to set in your deployment environment for the app to work correctly. Environment variables include database URLs, API keys, and any other configuration settings necessary for your app to function correctly. You can usually check out your [`.env` files](https://wasp.sh/docs/project/env-vars#dotenv-files) to see what environment variables your app expects. You can read more about environment variables in Wasp in the [environment variables guide](https://wasp.sh/docs/project/env-vars). The only exception is the environment variables that configure your app's client and server URLs (`WASP_WEB_CLIENT_URL`, `WASP_SERVER_URL`, and `REACT_APP_API_URL`). Because `wasp build start` knows that it's running the app on your local workstation, it can fill them out for you automatically. #### Which values should I use when testing? - Do not use real production secrets or endpoints. - Prefer staging/sandbox credentials and services that mirror production (e.g., Stripe test keys, a staging DB, or an isolated local DB with realistic data). - Keep config parity with production: same feature flags, callbacks/redirect URLs, and optional vars set/unset as in prod. Example: ```bash wasp build start --server-env-file .env.staging --client-env-file .env.client.staging ``` #### Server environment variables Use `--server-env` to specify environment variables for the server: ```bash wasp build start --server-env DATABASE_URL=postgresql://localhost:5432/myapp ``` You can specify multiple server environment variables: ```bash wasp build start --server-env DATABASE_URL=postgresql://localhost:5432/myapp --server-env JWT_SECRET=my-secret-key ``` You can also point to an `.env` file to load environment variables: ```bash wasp build start --server-env-file .env.production ``` :::warning Do not commit your `.env` files with sensitive information to your version control system. Use `.gitignore` to exclude them. ::: #### Client environment variables Use `--client-env` to specify environment variables for the client: ```bash wasp build start --client-env REACT_APP_GOOGLE_ANALYTICS_ID=GA-123456 ``` Multiple client environment variables: ```bash wasp build start --client-env REACT_APP_GOOGLE_ANALYTICS_ID=GA-123456 --client-env REACT_APP_PLAUSIBLE_ID=PLAUSIBLE-123456 ``` You can also point to an `.env` file for client variables: ```bash wasp build start --client-env-file .env.client.production ``` :::warning Do not commit your `.env` files with sensitive information to your version control system. Use `.gitignore` to exclude them. ::: ## Deployment / Deployment Methods / Overview Wasp apps are full-stack apps that consist of: - A Node.js server. - A static client. - A PostgreSQL database. To make deploying as smooth as possible, Wasp also offers a single-command deployment called **Wasp Deploy**. [Documentation](https://wasp.sh/docs/deployment/deployment-methods/wasp-deploy/overview) #### [Wasp Deploy ยป](https://wasp.sh/docs/deployment/deployment-methods/wasp-deploy/overview) [One-command deployment & redeployment](https://wasp.sh/docs/deployment/deployment-methods/wasp-deploy/overview) But even when not using Wasp Deploy, you can deploy each part **anywhere** where you can usually deploy Node.js apps or static apps. For example, you can deploy your client on [Netlify](https://www.netlify.com/), the server on [Fly.io](https://fly.io/), and the database on [Neon](https://neon.tech/). You can read our guides on how to deploy your Wasp app to different platforms, both from cloud providers and on your own infrastructure: [Documentation](https://wasp.sh/docs/deployment/deployment-methods/cloud-providers) #### [Cloud Providers ยป](https://wasp.sh/docs/deployment/deployment-methods/cloud-providers) [Deploy your Wasp app to various cloud platforms](https://wasp.sh/docs/deployment/deployment-methods/cloud-providers) [Documentation](https://wasp.sh/docs/deployment/deployment-methods/self-hosted) #### [Self-hosted ยป](https://wasp.sh/docs/deployment/deployment-methods/self-hosted) [Use your own servers to host your app](https://wasp.sh/docs/deployment/deployment-methods/self-hosted) Regardless of how you choose to deploy your app (i.e., manually or using the Wasp CLI), you'll need to know about some common patterns covered below. :::tip[Deployed? Get some swag! ๐Ÿ‘•๐Ÿ] Do you have a Wasp app running in production? If yes, we'd love to send some swag your way! All you need to do is fill [this form](https://e44cy1h4s0q.typeform.com/to/EPJCwsMi) out and we'll make it happen. ::: ### Customizing the Dockerfile By default, Wasp generates a multi-stage Dockerfile. This file is used to build and run a Docker image with the Wasp-generated server code. It also runs any pending migrations. You can **add extra steps to this multi-stage `Dockerfile`** by creating your own `Dockerfile` in the project's root directory. If Wasp finds a Dockerfile in the project's root, it appends its contents at the *bottom* of the default multi-stage Dockerfile. Since the last definition in a Dockerfile wins, you can override or continue from any existing build stages. You can also choose not to use any of our build stages and have your own custom Dockerfile used as-is. A few things to keep in mind: - If you override an intermediate build stage, no later build stages will be used unless you reproduce them below. - The generated Dockerfile's content is dynamic and depends on which features your app uses. The content can also change in future releases, so please verify it from time to time. - Make sure to supply `ENTRYPOINT` in your final build stage. Your changes won't have any effect if you don't. Read more in the official Docker docs on [multi-stage builds](https://docs.docker.com/build/building/multi-stage/). To see what your project's (potentially combined) Dockerfile will look like, run: ```shell wasp dockerfile ``` Join our [Discord](https://discord.gg/rzdnErX) if you have any questions, or if you need more customization than this hook provides. ## Deployment / Deployment Methods / Wasp Deploy / Overview Wasp CLI can deploy your full-stack application with a single command. The command automates the manual deployment process and is the recommended way of deploying Wasp apps. It looks like this: ```shell wasp deploy launch my-wasp-app ``` The `wasp deploy` command sets up all the necessary services on the provider, builds your Wasp app, and deploys it. #### Supported Providers Wasp Deploy supports automated deployment to the following providers: #### [Fly.io ยป](https://wasp.sh/docs/deployment/deployment-methods/wasp-deploy/fly) #### [Railway ยป](https://wasp.sh/docs/deployment/deployment-methods/wasp-deploy/railway) Click on each provider for more details. ## Deployment / Deployment Methods / Wasp Deploy / Fly.io [Fly.io](https://fly.io/) is a platform for running containerized apps and microservices on servers around the world. It makes deploying and managing your apps straightforward with minimal setup. ### Prerequisites To deploy to Fly.io using Wasp CLI: 1. Create a [Fly.io](https://fly.io/) account 2. Fly requires you to add a payment method before you can deploy more than two Fly apps. To deploy Wasp apps, you need three Fly apps: the client, the server, and the database. 3. Install the [`fly` CLI](https://fly.io/docs/hands-on/install-flyctl/) on your machine. ### Deploying Using the Wasp CLI, you can easily deploy a new app to [Fly.io](https://fly.io) with just a single command: ```shell wasp deploy fly launch my-wasp-app dfw ``` Please do not CTRL-C or exit your terminal while the commands are running. Two things to keep in mind: 1. Your app name (for example `my-wasp-app`) must be **unique** across all of Fly or deployment will fail. 2. If your account is a member of **more than one organization** on Fly.io, you will need to specify under which one you want to execute the command. To do that, provide an additional `--org ` option. You can find out the names (slugs) of your organizations by running `fly orgs list`. The `launch` command uses the app basename `my-wasp-app` and deploy it to the `dfw` region (`dfw` is short for *Dallas, Texas (US)*). Read more about Fly.io regions [here](#flyio-regions). The basename is used to create all three app tiers, resulting in three separate apps in your Fly dashboard: - `my-wasp-app-client` - `my-wasp-app-server` - `my-wasp-app-db` You'll notice that Wasp creates two new files in your project root directory: - `fly-server.toml` - `fly-client.toml` You should include these files in your version control so that you can deploy your app with a single command in the future. When you run the `launch` command, Wasp CLI knows how to connect different parts of your Wasp app together, so it sets up the required environment variables for your server app: 1. `WASP_WEB_CLIENT_URL` and `WASP_SERVER_URL` which are required to connect your client and server apps. 2. `DATABASE_URL` which is required to connect your server app to the database. 3. `JWT_SECRET` which is required for authentication to work. If your app requires any additional environment variables, use the `wasp deploy fly cmd secrets set` command. Read more in the [API Reference](#flyio-cli-environment-variables) section. If you want to automate the deployment process, check out the [CI/CD Deployment](https://wasp.sh/docs/deployment/deployment-methods/wasp-deploy/ci-cd) page to re-deploy your app on each commit as part of your CI/CD pipeline. ### Using a Custom Domain For Your App {#custom-domain} Setting up a custom domain is a three-step process: 1. You need to add your domain to your Fly client app. You can do this by running: ```shell wasp deploy fly cmd --context client certs create mycoolapp.com ``` :::note[Use Your Domain] Make sure to replace `mycoolapp.com` with your domain in all of the commands mentioned in this section. ::: This command will output the instructions to add the DNS records to your domain. It will look something like this: ```shell-session You can direct traffic to mycoolapp.com by: 1: Adding an A record to your DNS service which reads A @ 66.241.1XX.154 You can validate your ownership of mycoolapp.com by: 2: Adding an AAAA record to your DNS service which reads: AAAA @ 2a09:82XX:1::1:ff40 ``` 2. You need to add the DNS records for your domain: *This will depend on your domain provider, but it should be a matter of adding an A record for `@` and an AAAA record for `@` with the values provided by the previous command.* 3. You need to set your domain as the `WASP_WEB_CLIENT_URL` environment variable for your server app: ```shell wasp deploy fly cmd --context server secrets set WASP_WEB_CLIENT_URL=https://mycoolapp.com ``` We need to do this to keep our CORS configuration up to date. That's it, your app should be available at `https://mycoolapp.com`! #### Adding a `www` Subdomain If you'd also like to access your app at `https://www.mycoolapp.com`, you can generate certificates for the `www` subdomain. ```shell wasp deploy fly cmd --context client certs create www.mycoolapp.com ``` Once you do that, you will need to add another DNS record for your domain. It should be a CNAME record for `www` with the value of your root domain. Here's an example: | Type | Name | Value | TTL | | ----- | ---- | ------------- | ---- | | CNAME | www | mycoolapp.com | 3600 | With the CNAME record (Canonical name), you are assigning the `www` subdomain as an alias to the root domain. Your app should now be available both at the root domain `https://mycoolapp.com` and the `www` sub-domain `https://www.mycoolapp.com`. :::caution[CORS Configuration] Using the `www` and `non-www` domains at the same time will require you to update your CORS configuration to allow both domains. You'll need to provide [custom CORS configuration](https://gist.github.com/infomiho/5ca98e5e2161df4ea78f76fc858d3ca2) in your server app to allow requests from both domains. ::: ### Environment Variables {#flyio-cli-environment-variables} #### Server Secrets If your app requires any other server-side environment variables (like social auth secrets), you can set them: 1. Initially, in the `launch` or `setup` commands with the [`--server-secret` option](#fly-launch-environment-variables) 2. After the app has already been deployed by using the `secrets set` command: ``` wasp deploy fly cmd secrets set GOOGLE_CLIENT_ID=<...> GOOGLE_CLIENT_SECRET=<...> --context=server ``` #### Client Environment Variables If you've added any [client-side environment variables](https://wasp.sh/docs/project/env-vars#client-env-vars) to your app, pass them to the terminal session before running a deployment command, for example: ```shell REACT_APP_ANOTHER_VAR=somevalue wasp deploy fly launch my-wasp-app dfw ``` or ```shell REACT_APP_ANOTHER_VAR=somevalue wasp deploy fly deploy ``` Please note that you should do this for **every deployment**, not just the first time you set up the variables. One way to make sure you don't forget to add them is to create a `deploy` script in your `package.json` file: ```json title="package.json" { "scripts": { "deploy": "REACT_APP_ANOTHER_VAR=somevalue wasp deploy fly deploy" } } ``` Then you can run `npm run deploy` to deploy your app. ### Fly.io Regions > Fly.io runs applications physically close to users: in datacenters around the world, on servers we run ourselves. You can currently deploy your apps in 34 regions, connected to a global Anycast network that makes sure your users hit our nearest server, whether theyโ€™re in Tokyo, Sรฃo Paolo, or Frankfurt. Read more on Fly regions [here](https://fly.io/docs/reference/regions/). You can find the list of all available Fly regions by running: ```shell fly platform regions ``` ### Multiple Fly.io Organizations If you have multiple organizations, you can specify a `--org` option. For example: ```shell wasp deploy fly launch my-wasp-app dfw --org hive ``` ### Building Locally Fly.io offers support for both **locally** built Docker containers and **remotely** built ones. However, for simplicity and reproducibility, the CLI defaults to the use of a remote Fly.io builder. If you want to build locally, supply the `--build-locally` option to `wasp deploy fly launch` or `wasp deploy fly deploy`. ##### Using a custom PostgreSQL database By default, Wasp uses the standard PostgreSQL Docker image provided by Fly.io when creating a new database for your app. However, if you have a need for a custom Docker image, e.g., your application requires specific PostgreSQL extensions (e.g., PostGIS), you can specify a Docker image with a custom PostgreSQL installation, with the `--db-image ` flag. Your custom PostgreSQL image must be compatible with Fly.io, as their platform has some requirements to work properly. Since these requirements are not readily documented, an easy way to ensure compatibility is to base your custom image off the official Fly.io PostgreSQL image: [`flyio/postgres-flex`](https://hub.docker.com/r/flyio/postgres-flex). We have crafted a small guide on [how to create a custom Docker image with PostGIS or pgvector for Fly.io](https://gist.github.com/cprecioso/e19e883138241c1a446f48d6187aae75). You can also use it as a starting point to create your own images with other extensions. :::tip You only need to specify the Docker image once, when first creating the app with any of these commands: ```shell wasp deploy fly create-db --db-image wasp deploy fly setup --db-image wasp deploy fly launch --db-image ``` ::: ### API Reference #### `launch` `launch` is a convenience command that runs `setup`, `create-db`, and `deploy` in sequence. ```shell wasp deploy fly launch ``` It accepts the following arguments: - `` required The name of your app. - `` required The region where your app will be deployed. Read how to find the available regions [here](#flyio-regions). Running `wasp deploy fly launch` is the same as running the following commands: ```shell wasp deploy fly setup wasp deploy fly create-db wasp deploy fly deploy ``` ##### Environment Variables {#fly-launch-environment-variables} ###### Server If you are deploying an app that requires any other environment variables (like social auth secrets), you can set them with the `--server-secret` option: ``` wasp deploy fly launch my-wasp-app dfw --server-secret GOOGLE_CLIENT_ID=<...> --server-secret GOOGLE_CLIENT_SECRET=<...> ``` ###### Client If you've added any [client-side environment variables](https://wasp.sh/docs/project/env-vars#client-env-vars) to your app, pass them to the terminal session before running the `launch` command, for example: ```shell REACT_APP_ANOTHER_VAR=somevalue wasp deploy fly launch my-wasp-app dfw ``` ##### Custom Server URL If you want your client to connect to a different server URL (for example, if you're using a custom domain for your server), use the `--custom-server-url` option: ```shell wasp deploy fly launch my-wasp-app dfw --custom-server-url https://api.myapp.com ``` #### `setup` The `setup` command registers your client and server apps on Fly, and sets up needed environment variables. It only needs to be run once, when initially creating the app. It does *not* trigger a deploy for the client or server apps. ```shell wasp deploy fly setup ``` It accepts the following arguments: - `` required The name of your app. - `` required The region where your app will be deployed. Read how to find the available regions [here](#flyio-regions). After running `setup`, Wasp creates two new files in your project root directory: `fly-server.toml` and `fly-client.toml`. You should include these files in your version control. You **can edit the `fly-server.toml` and `fly-client.toml` files** to further configure your Fly deployments. Wasp will use the TOML files when you run `deploy`. If you want to maintain multiple apps, you can add the `--fly-toml-dir ` option to point to different directories, like "dev" or "staging". :::caution[Execute Only Once] You should only run `setup` once per app. If you run it multiple times, it creates unnecessary apps on Fly. ::: #### `create-db` The `create-db` command creates a new database for your app. ```shell wasp deploy fly create-db ``` It accepts the following arguments: - `` required The region where your app will be deployed. Read how to find the available regions [here](#flyio-regions). :::caution[Execute Only Once] You should only run `create-db` once per app. If you run it multiple times, it creates multiple databases, but your app needs only one. ::: #### `deploy` ```shell wasp deploy fly deploy ``` The `deploy` command pushes your built client and server live. Run this command whenever you want to **update your deployed app** with the latest changes: ```shell wasp deploy fly deploy ``` If you've added any [client-side environment variables](https://wasp.sh/docs/project/env-vars#client-env-vars) to your app, pass them to the terminal session before running the `deploy` command, for example: ```shell REACT_APP_ANOTHER_VAR=somevalue wasp deploy fly deploy ``` You must specify your client-side environment variables every time you redeploy with the above command [to ensure they are included in the build process](https://wasp.sh/docs/deployment/env-vars#client-env-vars). ##### Custom Server URL {#custom-server-url} If you want your client to connect to a different server URL (for example, if you're using a custom domain for your server), use the `--custom-server-url` option: ```shell wasp deploy fly deploy --custom-server-url https://api.myapp.com ``` #### `cmd` If you want to run arbitrary Fly commands (for example `fly secrets list` for your server app), here's how to do it: ```shell wasp deploy fly cmd secrets list --context server ``` ## Deployment / Deployment Methods / Wasp Deploy / Railway [Railway](https://railway.com/?utm_medium=integration\&utm_source=docs\&utm_campaign=wasp) is a cloud development platform that streamlines building and deploying applications with built-in support for databases and services. It offers an intuitive interface and automates infrastructure. ### Prerequisites To deploy to Railway using Wasp CLI: 1. Create a [Railway](https://railway.com/?utm_medium=integration\&utm_source=docs\&utm_campaign=wasp) account, 2. Install the [`railway` CLI](https://docs.railway.com/guides/cli?utm_medium=integration\&utm_source=docs\&utm_campaign=wasp#installing-the-cli) on your machine. ### Deploying Using the Wasp CLI, you can easily deploy a new app to Railway with a single command: ```shell wasp deploy railway launch my-wasp-app ``` Please do not CTRL-C or exit your terminal while the commands are running. Keep in mind that: 1. Your project name (for example `my-wasp-app`) must be unique across all your Railway projects or deployment will fail (this is a current limitation of the Wasp CLI and Railway integration [#2926](https://github.com/wasp-lang/wasp/issues/2926)). 2. If you are a member of multiple Railway organizations, the CLI will prompt you to select the organization under which you want to deploy your app. The project name is used as a base for your server and client service names on Railway: - `my-wasp-app-client` - `my-wasp-app-server` Railway doesn't allow setting the database service name using the Railway CLI. It will always be named `Postgres`. This also applies when using the `--db-image` flag. When you run the `launch` command, Wasp CLI knows how to connect different parts of your Wasp app together, so it sets up the required environment variables for your server app: 1. `WASP_WEB_CLIENT_URL` and `WASP_SERVER_URL` which are required to connect your client and server apps. 2. `DATABASE_URL` which is required to connect your server app to the database. 3. `JWT_SECRET` which is required for authentication to work. If you have any additional environment variables that your app needs, read how to set them in the [API Reference](#railway-environment-variables) section. If you want to automate the deployment process, check out the [CI/CD Deployment](https://wasp.sh/docs/deployment/deployment-methods/wasp-deploy/ci-cd) page to re-deploy your app on each commit as part of your CI/CD pipeline. ### Using a Custom Domain For Your App {#custom-domain} Setting up a custom domain is a three-step process: 1. Add your domain to the Railway client service: - Go into the [Railway dashboard](https://railway.com/dashboard?utm_medium=integration\&utm_source=docs\&utm_campaign=wasp). - Select your project (for example `my-wasp-app`). - Click on the client service (for example `my-wasp-app-client`). - Go to the **Settings** tab and click **Custom Domain**. - Enter your domain name (for example `mycoolapp.com`) and port `8080`. - Click **Add Domain**. 2. Update the DNS records for your domain, adding a CNAME record at the domain or subdomain you want, pointing to the address you've been given in the previous step. *This step depends on your domain provider, consult their documentation in case of doubt.* 3. To avoid [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) errors, you need to set your new client URL as the `WASP_WEB_CLIENT_URL` environment variable (for example `https://mycoolapp.com`) for your **server service** in the Railway dashboard. - Go into the [Railway dashboard](https://railway.com/dashboard?utm_medium=integration\&utm_source=docs\&utm_campaign=wasp). - Select your project (for example `my-wasp-app`). - Click on the server service (for example `my-wasp-app-server`). - Go to the **Variables** tab. Update the `WASP_WEB_CLIENT_URL` variable with the new domain for your client. That's it, your app should be available at `https://mycoolapp.com`! ### API Reference #### The `launch` command `launch` is a convenience command that runs `setup` and `deploy` in sequence. ```shell wasp deploy railway launch ``` It accepts the following arguments: - `` required The name of your project. Running `wasp deploy railway launch` is the same as running the following commands: ```shell wasp deploy railway setup wasp deploy railway deploy ``` ##### Using a custom PostgreSQL database By default, Wasp uses the standard PostgreSQL image provided by Railway when creating a new database for your app. However, if your application requires specific PostgreSQL extensions (e.g., PostGIS), you can specify a Docker image with a custom PostgreSQL installation, with the `--db-image ` flag. :::tip You only need to specify the Docker image once, when first creating the app. ::: ```shell # Use PostGIS: wasp deploy railway launch my-wasp-app --db-image postgis/postgis ``` ```shell # Use pgvector: wasp deploy railway launch my-wasp-app --db-image pgvector/pgvector:pg16 ``` The service name will always be `Postgres`, regardless of the image used. ##### Explicitly providing the Railway project ID By default, Wasp CLI tries to create a new Railway project named ``. If you want to use an existing Railway project, pass its ID with `--existing-project-id` option: ```shell wasp deploy railway launch --existing-project-id ``` ##### Explicitly providing the Railway Workspace By default, Wasp CLI will prompt you to select a Railway workspace for your project. If you want to skip the prompt and provide the workspace id or name directly, use the `--workspace` option: ```shell wasp deploy railway launch --workspace ``` ##### Environment Variables {#railway-launch-environment-variables} ###### Server If you are deploying an app that requires any other environment variables (like social auth secrets), you can set them with the `--server-secret` option: ``` wasp deploy railway launch my-wasp-app --server-secret GOOGLE_CLIENT_ID=<...> --server-secret GOOGLE_CLIENT_SECRET=<...> ``` ###### Client If you've added any [client-side environment variables](https://wasp.sh/docs/project/env-vars#client-env-vars) to your app, pass them to the terminal session before running the `launch` command, for example: ```shell REACT_APP_ANOTHER_VAR=somevalue wasp deploy railway launch my-wasp-app ``` ##### Custom Server URL If you want your client to connect to a different server URL (for example, if you're using a custom domain for your server), use the `--custom-server-url` option: ```shell wasp deploy railway launch my-wasp-app --custom-server-url https://api.myapp.com ``` #### The `deploy` command The `deploy` command deploys your client and server apps to Railway. ```shell wasp deploy railway deploy ``` It accepts the following arguments: - `` required The name of your project. Run this command whenever you want to **update your deployed app** with the latest changes: ```shell wasp deploy railway deploy ``` ##### Explicitly providing the Railway project ID When you run the `deploy` command, Wasp CLI will use the Railway project that's linked to the Wasp project directory. If no Railway project is linked, the command will fail asking you to run the `setup` command first. If you are deploying your Railway app in the CI, you can pass the `--existing-project-id` option to tell Wasp CLI the Railway project ID to use for the deployment: ```shell wasp deploy railway deploy --existing-project-id ``` ##### Other Available Options - `--skip-client` - do not deploy the web client - `--skip-server` - do not deploy the server If you've added any [client-side environment variables](https://wasp.sh/docs/project/env-vars#client-env-vars) to your app, pass them to the terminal session before running the `deploy` command, for example: ```shell REACT_APP_ANOTHER_VAR=somevalue wasp deploy railway deploy ``` You must specify your client-side environment variables every time you redeploy with the above command [to ensure they are included in the build process](https://wasp.sh/docs/deployment/env-vars#client-env-vars). ##### Custom Server URL {#custom-server-url} If you want your client to connect to a different server URL (for example, if you're using a custom domain for your server), use the `--custom-server-url` option: ```shell wasp deploy railway deploy my-wasp-app --custom-server-url https://api.myapp.com ``` #### The `setup` command The `setup` command creates your client, server, and database services on Railway. It also configures environment variables. It does *not* deploy the client or server services. ```shell wasp deploy railway setup ``` It accepts the following arguments: - `` the name of your project. The project name is used as a base for your server and client service names on Railway: - `-client` - `-server` Railway also creates a PostgreSQL database service named `Postgres`. ##### Using a custom PostgreSQL database {#using-a-custom-postgresql-database} By default, Wasp uses the standard PostgreSQL image provided by Railway when creating a new database for your app. However, if your application requires specific PostgreSQL extensions (e.g., PostGIS), you can specify a Docker image with a custom PostgreSQL installation, with the `--db-image ` flag. :::tip You only need to specify the Docker image once, when first creating the app. ::: ```shell # Use PostGIS: wasp deploy railway setup my-wasp-app --db-image postgis/postgis ``` ```shell # Use pgvector: wasp deploy railway setup my-wasp-app --db-image pgvector/pgvector:pg16 ``` The service name will always be `Postgres`, regardless of the image used. ##### Explicitly providing the Railway project ID By default, Wasp CLI tries to create a new Railway project named ``. If you want to use an existing Railway project, pass its ID with `--existing-project-id` option: ```shell wasp deploy railway setup --existing-project-id ``` ##### Explicitly providing the Railway Workspace By default, Wasp CLI will prompt you to select in which Railway workspace you want to create your project. If you want to skip the prompt and provide the workspace id or name directly, use the `--workspace` option: ```shell wasp deploy railway setup --workspace ``` :::caution[Execute Only Once] You should only run `setup` once per app. Wasp CLI skips creating the services if they already exist. ::: #### Environment Variables {#railway-environment-variables} ##### Server Secrets If your app requires any other server-side environment variables (like social auth secrets), you can set them: 1. Initially in the `launch` or `setup` commands with the [`--server-secret` option](#railway-launch-environment-variables) 2. After the app has already been deployed, go into the Railway dashboard and set them in the **Variables** tab of your server service. ##### Client Environment Variables If you've added any [client-side environment variables](https://wasp.sh/docs/project/env-vars#client-env-vars) to your app, pass them to the terminal session before running a deployment command, for example: ```shell REACT_APP_ANOTHER_VAR=somevalue wasp deploy railway launch my-wasp-app ``` or ```shell REACT_APP_ANOTHER_VAR=somevalue wasp deploy railway deploy ``` Please note that you should do this for **every deployment**, not just the first time you set up the variables. One way to make sure you don't forget to add them is to create a `deploy` script in your `package.json` file: ```json title="package.json" { "scripts": { "deploy": "REACT_APP_ANOTHER_VAR=somevalue wasp deploy railway deploy" } } ``` Then you can run `npm run deploy` to deploy your app. ## Deployment / Deployment Methods / Wasp Deploy / CI/CD Deployment You can use CI/CD platforms like Github Actions to re-deploy your application automatically whenever changes are pushed to your repository. ### Re-deploying from CI/CD #### Prerequisites Make sure to first deploy your application from your local machine using `wasp deploy launch`. The `launch` command creates services for your application in the deployment provider and deploys them. After your application is deployed, you are able to use `wasp deploy deploy` in a CI/CD workflow to re-deploy it. #### [Fly.io ยป](https://wasp.sh/docs/deployment/deployment-methods/wasp-deploy/fly) #### [Railway ยป](https://wasp.sh/docs/deployment/deployment-methods/wasp-deploy/railway) Click on each provider for more details. #### Deployment steps To automate deployment, you need to create a workflow file in your repository that specifies the deployment process when a new commit is pushed to the repository. The workflow needs to include the following steps: 1. Checkout the code from the repository 2. Install Node.js and the Wasp CLI 3. Install any provider-specific dependencies 4. Deploy the application using `wasp deploy deploy` To be able to deploy your apps to the deployment providers, you need to set **provider-specific API keys** in the environment variables. How you set environment variables depends on the CI/CD platform you are using. We'll show you how to do this for [Github Actions](#github-actions-workflow) in the next section. ### Github Actions workflow Let's take a look at an example CI/CD workflow for Github Actions for each of the supported providers. **Fly.io** You'll need an **organisation token** to deploy to Fly.io. You can generate the token by running [`fly tokens create org`](https://fly.io/docs/security/tokens/#create-org-scoped-tokens) and adding it to your repository secrets as `FLY_API_TOKEN`. ```yaml title=".github/workflows/deploy.yml" name: Wasp Deploy on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest env: WASP_VERSION: "0.25" steps: - uses: actions/checkout@v6 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: "24.14.1" - name: Install Wasp # We pin the Wasp CLI version to avoid issues when a new Wasp version is released. run: npm i -g @wasp.sh/wasp-cli@$WASP_VERSION - name: Install Flyctl uses: superfly/flyctl-actions/setup-flyctl@master - name: Deploy run: wasp deploy fly deploy env: # You must add FLY_API_TOKEN to your Repository Secrets FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} ``` **Railway** You'll need an **account token** to deploy to Railway. You can generate it by going to your account settings under [Tokens](https://railway.com/account/tokens) and generating a token without selecting a workspace. Set the token as a repository secret named `RAILWAY_API_TOKEN`. Make sure to replace `my-project-name` with the actual name of your project and `MY_PROJECT_ID` with the actual ID of your project. You can find the project ID in the project's settings. ```yaml title=".github/workflows/deploy.yml" name: Wasp Deploy on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest env: WASP_VERSION: "0.25" RAILWAY_PROJECT_NAME: my-project-name RAILWAY_PROJECT_ID: MY_PROJECT_ID steps: - uses: actions/checkout@v6 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: "24.14.1" - name: Install Wasp # We pin the Wasp CLI version to avoid issues when a new Wasp version is released. run: npm i -g @wasp.sh/wasp-cli@$WASP_VERSION - name: Install Railway CLI run: npm install -g @railway/cli - name: Deploy run: wasp deploy railway deploy $RAILWAY_PROJECT_NAME --existing-project-id $RAILWAY_PROJECT_ID env: # You must add RAILWAY_API_TOKEN to your Repository Secrets RAILWAY_API_TOKEN: ${{ secrets.RAILWAY_API_TOKEN }} ``` ## Deployment / Deployment Methods / Cloud Providers You can deploy the built Wasp app wherever and however you want, as long as your provider/server supports running a Node.js server, serving static files, and running a PostgreSQL database. ### Guides We have step-by-step guides for deploying your Wasp app to some of the most popular providers you can follow: [Guide](https://wasp.sh/docs/guides/deployment/cloud-providers/cloudflare) #### [Deploying Wasp to Cloudflare Workers ยป](https://wasp.sh/docs/guides/deployment/cloud-providers/cloudflare) [Uses Cloudflare Workers, Wrangler CLI](https://wasp.sh/docs/guides/deployment/cloud-providers/cloudflare) [Guide](https://wasp.sh/docs/guides/deployment/cloud-providers/flyio) #### [Deploying Wasp to Fly.io ยป](https://wasp.sh/docs/guides/deployment/cloud-providers/flyio) [Uses Fly.io, fly CLI, Docker](https://wasp.sh/docs/guides/deployment/cloud-providers/flyio) [Guide](https://wasp.sh/docs/guides/deployment/cloud-providers/heroku) #### [Deploying Wasp to Heroku ยป](https://wasp.sh/docs/guides/deployment/cloud-providers/heroku) [Uses Heroku, heroku CLI, Docker](https://wasp.sh/docs/guides/deployment/cloud-providers/heroku) [Guide](https://wasp.sh/docs/guides/deployment/cloud-providers/netlify) #### [Deploying Wasp to Netlify ยป](https://wasp.sh/docs/guides/deployment/cloud-providers/netlify) [Uses Netlify, Netlify CLI](https://wasp.sh/docs/guides/deployment/cloud-providers/netlify) [Guide](https://wasp.sh/docs/guides/deployment/cloud-providers/railway) #### [Deploying Wasp to Railway ยป](https://wasp.sh/docs/guides/deployment/cloud-providers/railway) [Uses Railway, Railway CLI](https://wasp.sh/docs/guides/deployment/cloud-providers/railway) [Guide](https://wasp.sh/docs/guides/deployment/cloud-providers/render) #### [Deploying Wasp on Render ยป](https://wasp.sh/docs/guides/deployment/cloud-providers/render) [Uses Render, Blueprint (IaC)](https://wasp.sh/docs/guides/deployment/cloud-providers/render) If your desired provider isn't on the list, no worries, you can still deploy your app - it just means we don't yet have a step-by-step guide for you to follow. Feel free to [open a PR](https://github.com/wasp-lang/wasp/new/release/web/docs/guides/deployment/cloud-providers) if you'd like to write one yourself :) ### Manual deployment Deploying a Wasp app comes down to the following: 1. Generating deployable code. 2. Deploying the API server (backend). 3. Deploying the web client (frontend). 4. Deploying a PostgreSQL database and keeping it running. Let's go through each of these steps. #### 1. Generating Deployable Code Running the command `wasp build` generates deployable code for the whole app in the `.wasp/out/` directory. ``` wasp build ``` :::caution[PostgreSQL in production] You won't be able to build the app if you are using SQLite as a database (which is the default database). You'll have to [switch to PostgreSQL](https://wasp.sh/docs/data-model/databases#migrating-from-sqlite-to-postgresql) before deploying to production. ::: #### 2. Deploying the API Server There's a Dockerfile that defines an image for building the server in the `.wasp/out` directory. To run the server in production, deploy this Docker image to a hosting provider and make sure the required env variables are correctly set up. Usually, you use the provider's dashboard UI or a CLI tool to set up these env variables. Check the [required server env variables](https://wasp.sh/docs/deployment/env-vars#server-env-vars) and make sure they are set up for your server. While these are the general instructions on deploying the server anywhere, we also have more detailed instructions for chosen providers below, so check that out for more guidance if you are deploying to one of those providers. #### 3. Deploying the Web Client To build the web app, run the following command from your project root: ``` REACT_APP_API_URL= npx vite build ``` where `` is the URL of the Wasp server that you previously deployed. The build output will be in `.wasp/out/web-app/build`. :::caution[Client Env Variables] Remember, if you have defined any other [client-side env variables](https://wasp.sh/docs/project/env-vars#defining-env-vars-in-development) in your project, make sure to add them to the command above when [building your client](https://wasp.sh/docs/deployment/env-vars#client-env-vars) ::: The command above will build the web client and put it in the `.wasp/out/web-app/build` directory, including the `200.html` file at the root that acts as the SPA fallback. Since the result of building is just a bunch of static files, you can now deploy your web client to any static hosting provider (e.g. Netlify, Cloudflare, ...) by deploying the contents of `.wasp/out/web-app/build/`. #### 4. Deploying the Database Any PostgreSQL database will do, as long as you provide the server with the correct `DATABASE_URL` env var and ensure that the database is accessible from the server. ## Deployment / Deployment Methods / Self-Hosted If you have your server or rent out a server, you can self-host your Wasp apps. Self-hosting your apps gives you full control over your apps and their data. It can be more cost-effective than a cloud provider since you can deploy multiple apps on a single server. However, you'll need to manage the server yourself, which can be time-consuming and require some technical knowledge. ### Guides We have step-by-step guides for deploying your Wasp app on your server with different methods. Check out the guides below: [Guide](https://wasp.sh/docs/guides/deployment/self-hosted/caprover) #### [Deploying Wasp with Caprover on your server ยป](https://wasp.sh/docs/guides/deployment/self-hosted/caprover) [Uses Caprover, Github Actions, Github Container Registry](https://wasp.sh/docs/guides/deployment/self-hosted/caprover) [Guide](https://wasp.sh/docs/guides/deployment/self-hosted/coolify) #### [Deploying Wasp with Coolify on your server ยป](https://wasp.sh/docs/guides/deployment/self-hosted/coolify) [Uses Coolify, Github Actions, Github Container Registry](https://wasp.sh/docs/guides/deployment/self-hosted/coolify) [Guide](https://wasp.sh/docs/guides/deployment/self-hosted/vps) #### [Deploying Wasp with Docker on your server ยป](https://wasp.sh/docs/guides/deployment/self-hosted/vps) [Uses Ubuntu, Git, Caddy, Docker](https://wasp.sh/docs/guides/deployment/self-hosted/vps) If your desired provider isn't on the list, no worries, you can still deploy your app - it just means we don't yet have a step-by-step guide for you to follow. Feel free to [open a PR](https://github.com/wasp-lang/wasp/new/release/web/docs/guides/deployment/self-hosted) if you'd like to write one yourself :) ### Manual deployment We will show you a general overview of the architecture of a self-hosted Wasp app and the steps you need to take to deploy your app on your server. This is a more manual process than using the guides above, but it gives you more control over your deployment and you'll learn how everything works. If you are looking for a more guided deployment, check out the guides above. #### What you'll need To successfully self-host your Wasp app, you need to have the following: - A server with a public IP address. There are many cloud providers you can use to rent a server. Some popular ones are [AWS](https://aws.amazon.com/ec2/), [DigitalOcean](https://www.digitalocean.com/), [OVH](https://www.ovhcloud.com/en/vps/), and [Hetzner](https://www.hetzner.com/cloud/). - A domain name, for example, `myapp.com` (needed for HTTPS support). #### Architecture To self-host your Wasp app, you'll follow these general steps: 1. From your **app's code**, let Wasp build a **server app** and a **client app**. 2. Set up the **server environment variables** on the server. 3. Run a **database** on the server or use a managed database service. 4. Run the **server app** on the server, with or without Docker. 5. Serve the **client app** with a static file server. 6. Set up a **reverse proxy** on the server to be able to use a domain name with HTTPS for your app. ![One of many possible self-hosting setups](https://wasp.sh/img/deploying/self-hosting.png) One possible self-hosting setup #### Steps 1. Install [Docker](https://docs.docker.com/engine/install/), [Node.js](https://github.com/nvm-sh/nvm) and [Wasp CLI](https://wasp.sh/docs/quick-start#installation). 2. Get your **app's source code**. - We recommend using Git to clone your app's repository and then pulling the latest changes when you want to deploy a new version. You can use any other method to get your app's code on the server. 3. Install dependencies with **`wasp install`** and build your app with **`wasp build`**. 4. Build and run the **server app**. - Wasp gives you a `Dockerfile` in the `.wasp/out` directory that you can use to build and run the server app. - We are using Docker to run the server app, but you can run it without Docker if you prefer - just make sure to replicate the setup in the `Dockerfile`. - When you run the server app with Docker, you need to setup the server env variables. You can do this with a `.env` file or by passing the env variables directly to the `docker run` command. 5. Start the **database** on the server or use a managed database service. - We usually run the database in Docker on the same server, but you can run the database directly on the server. - You can also use a managed database service which you can connect to from your server. This is a great option if you don't want to manage the database yourself, but it can be more expensive. 6. Build the **client app** into static files. - Wasp outputs the client app in the `.wasp/out/web-app` directory. * You should [build the client app](https://wasp.sh/docs/deployment/deployment-methods/cloud-providers#3-deploying-the-web-client) into static files. 7. Install and set up a **reverse proxy** to serve your client and server apps. - There are many great choices for reverse proxies, like [Nginx](https://www.nginx.com/), [Caddy](https://caddyserver.com/), and [Traefik](https://traefik.io/). - Make sure to set up the reverse proxy to serve the client app's static files and to proxy requests to the server app. 8. Point your **domain(s)** to your server's IP address. - We recommend setting `myapp.com` for the client and `api.myapp.com` for the server. - The reverse proxy should serve the client app on `myapp.com` and proxy requests to the server app on `api.myapp.com`. Make sure your [env variables](https://wasp.sh/docs/deployment/env-vars) are using these client and server URLs. ### Database setup By default, our self-hosted deployment methods run the **database on your server**. When you run the database on your server, you need to take care of backups, updates, and scaling. We suggest setting up [PostgresSQL periodic backups](https://tembo.io/docs/getting-started/postgres_guides/how-to-backup-and-restore-a-postgres-database) and/or taking snapshots of your server's disk. In case something bad happens to your server, you can restore your database from the backups. If you prefer not to manage the database yourself, you can use a **managed database service**. The service provider takes care of backups, updates, and scaling for you but it can be more expensive than running the database on your server. Some popular managed database services are [AWS RDS](https://aws.amazon.com/rds/), [DigitalOcean Managed Databases](https://www.digitalocean.com/products/managed-databases/), and [Supabase](https://supabase.io/). ## Deployment / CI/CD Overview Setting up a CI/CD pipeline is an optional but highly recommended part of deploying applications. **Continuous Integration (CI)** involves verifying/testing code changes through an automated process whenever code is pushed to the repository. This helps us catch bugs early and make sure that our app works. **Continuous Deployment (CD)** refers to the automatic deployment of code changes to the production environment. This is commonly know as "push to deploy" and frees developers from having to manually deploy code changes. ### Running tests in CI #### End to end tests End to end (e2e) tests simulate real user using your app and you can test different scenarios like login, adding items to cart, etc. Writing end to end tests frees you from manually testing your app after every change. **To run e2e tests with Wasp in the CI**, you'll need to: 1. Install Wasp in the CI environment. 2. Run your app (with the database) in the CI environment. 3. Run the e2e tests against the running app. ##### Example app We'll show you how to run end-to-end tests in CI using the [Github Actions](https://github.com/features/actions) as our CI and the [Playwright](https://playwright.dev/) as our e2e testing framework. 1. Check our example app and its e2e tests in the [e2e-tests](https://github.com/wasp-lang/e2e-test-example/tree/main/e2e-tests) directory. You can copy the `e2e-tests` directory to your own project and modify it to fit your app. This will enable you to run the e2e tests locally. Example e2e test ```ts import { expect, test } from '@playwright/test' import { generateRandomUser, logUserIn } from './utils' const user = generateRandomUser() test.describe('basic user flow test', () => { test('log in and add task', async ({ page }) => { await logUserIn({ page, user }) await expect(page).toHaveURL('/') await expect(page.locator('body')).toContainText('No tasks yet.') // Add a task await page.fill('input[name="description"]', 'First task') await page.click('input:has-text("Create task")') await expect(page.locator('body')).toContainText('First task') }) }) ``` 2. To run the tests in the Github Actions CI, you'll need to create a workflow file in your repository. You should create a `.github/workflows/e2e-tests.yml` file in your repository. You can copy the contents of the [e2e-tests.yml](https://github.com/wasp-lang/e2e-test-example/blob/main/.github/workflows/e2e-tests.yml) file from our example app. #### Unit tests Unit tests test pieces of your code logic in isolation. They are much simpler and faster than e2e tests, but they don't simulate the real user interaction with your app. You can use Wasp's built in [client tests](https://wasp.sh/docs/project/testing) support to test the client side code of your app. You are free to use any testing framework for the server side code. **You'd run the unit tests in the CI** in a similar way as the e2e tests: 1. Install Wasp in the CI environment. 2. Run the client tests with `wasp test client run`. 3. Run the server tests with your testing framework. ### Continuous deployment We'll look at two ways you can use the CI/CD pipeline to deploy your Wasp app: 1. Package the server and client with Docker. 2. Deploy the client as static files. #### Package the server and client with Docker The most common way to package your app for deployment is using Docker images. This way you can easily deploy the same image to different environments (staging, production, etc.). **To build the app as a Docker image**, you'll need to: 1. Install Docker in the CD environment. 2. Install dependencies with `wasp install` and build the app with `wasp build`. 3. Build the Docker image and push it to a Docker registry: - for our server app - for our client app 4. For some providers: notify them to deploy the new app version. :::info[What is a Docker Registry?] Docker Registry is a place where you can store your Docker images and then your deployment provider can pull them from there. The most common Docker Registry is the [Docker Hub](https://hub.docker.com/), but you can also use other registries like the [Github Container Registry (GHCR)](https://docs.github.com/en/packages/guides/about-github-container-registry). ::: ##### Example deployment We'll take a look at our Coolify deployment example in the [deployment](https://wasp.sh/docs/guides/deployment/self-hosted/coolify) section. We are using Github Actions to build the Docker images and their Github Container Registry (GHCR) to store them. Let's go through the [deploy.yml](https://gist.github.com/infomiho/ad6fade7396498ae32a931ca563a4524#file-deploy-yml) file in the Coolify guide: 1. First, we **authenticate with the Github Container Registry (GHCR)**. We are using the `docker/login-action` action to authenticate with the GHCR. 2. Then, we **prepare the Docker image metadata** for later use. We are using the `docker/metadata-action` action to prepare some extra info that we'll use later in the deployment process. 3. Next, we **install dependencies** with `wasp install` and **build the Wasp app** with `wasp build`. This creates our server and the client app in the `.wasp/out` folder. 4. Then, we **package the server app** into a Docker image and **push it to the GHCR**. We use the `Dockerfile` in the `.wasp/out` directory to build and push the server Docker image using the `docker/build-push-action` action. 5. Next, we create a `Dockerfile` for our client and then **package the client app** into a Docker image and **push it to the GHCR**. We create a `Dockerfile` that uses a simple Go static server to serve the client app. We again use the `docker/build-push-action` action to build and push the client Docker image. 6. Finally, we notify Coolify using their Webhook API to **deploy our new app version**. And now you can open the [deploy.yml](https://gist.github.com/infomiho/ad6fade7396498ae32a931ca563a4524#file-deploy-yml) file in the Coolify guide and see the full deployment process. #### Static build of the client Wasp's client app is a single page application (SPA) which you build into static HTML, CSS, and JS files that you can upload to any hosting provider that supports serving static files. This means that for the client app, you don't need to use Docker images if don't want to. It's usually cheaper to host static files than to host Docker images. **To deploy the client app as static files**, you'll need to: 1. Install dependencies with `wasp install` and build the app with `wasp build` in the CD environment. 2. Build the client app with `npx vite build`. 3. Upload the static files (from `.wasp/out/web-app/build`) to your hosting provider. Check out our instructions for deploying the client app to [Netlify](https://wasp.sh/docs/guides/deployment/cloud-providers/netlify) or [Cloudflare](https://wasp.sh/docs/guides/deployment/cloud-providers/cloudflare) where you can check out the example deployment using Github Actions. ## Deployment / Extras In this section, we will cover some additional topics that are important for deploying Wasp apps in production. #### Custom domain setup If you want to set up a custom domain for your Wasp app, you can do it for both the client and the server. The important part is setting up the custom domain for the client - that's what your users visit from their browsers. Setting up a custom domain for the server is optional, but it can be useful if you'd like to hide some server details (for example, the IP address or auto-generated domain name) from the users. ##### How to do it? It's usually a two-step process, and it's the same for both the client and the server: 1. Set up the **DNS records** for the domain. This will depend on your hosting provider. You can usually do this by adding an `A` record in your DNS settings that points to the app's IPv4 address. You often set the `AAAA` record for IPv6 address as well. Some hosting providers ask you to set the `CNAME` record instead of the `A` and `AAAA` records. :::note[Using wasp deploy?] Check out how to set up custom domains with [Fly.io](https://wasp.sh/docs/deployment/deployment-methods/wasp-deploy/fly#custom-domain) or [Railway](https://wasp.sh/docs/deployment/deployment-methods/wasp-deploy/railway#custom-domain). ::: 2. Set up the **environment variables** for the app. You need to set the environment variables so Wasp configures the app correctly (for example, for CORS to work correctly). ##### Client domain env vars When [building the client](https://wasp.sh/docs/deployment/env-vars#client-env-vars), set `REACT_APP_API_URL` to point to your server domain: ```bash REACT_APP_API_URL=https://api.myapp.com ``` Learn more about client configuration in the [env vars section](https://wasp.sh/docs/project/env-vars#client-general-configuration). ##### Server domain env vars For the server, you need to [configure two variables](https://wasp.sh/docs/deployment/env-vars#server-env-vars): - `WASP_WEB_CLIENT_URL`: Your client app's domain - `WASP_SERVER_URL`: Your server domain ```bash WASP_WEB_CLIENT_URL=https://myapp.com WASP_SERVER_URL=https://server.myapp.com ``` Learn more about server env variables in the [env vars section](https://wasp.sh/docs/project/env-vars#server-general-configuration). #### DDoS protection and CDN recommendations When deploying your Wasp app, you might want to consider using a Content Delivery Network (CDN) and DDoS protection service to improve the performance and security of your app: 1. **Content Delivery Network (CDN)** is a network of servers distributed worldwide that caches static assets like images, CSS, and JavaScript files. Using a CDN in front of your **client** can help with caching static assets and serving them faster to users around the world. When a user requests a file, the CDN serves it from the server closest to the user, improving load times. 2. **Distributed Denial of Service (DDoS)** attacks are a common threat to web applications. Attackers send a large amount of traffic to your server, overwhelming it and making it unavailable to legitimate users. You can use a DDoS protection service for both your **client and server** to protect your app from these attacks. We recommend using [Cloudflare](https://www.cloudflare.com/) for both CDN and DDoS protection. It's easy to set up and provides a free tier that should be enough for most small to medium-sized apps. There are other CDN providers like [Fastly](https://www.fastly.com/), [Bunny](https://bunnycdn.com/) and [Amazon Cloudfront](https://aws.amazon.com/cloudfront/) that you can consider as well. #### Are Wasp apps production ready? As we mentioned in the [introduction](https://wasp.sh/docs/deployment/intro) section, what we call **Wasp apps** are three separate pieces: the client, the server, and the database. For the server, we are using Node.js and the battle-tested Express.js framework. For the database, we are using PostgreSQL, which is a powerful and reliable database system. For the client, we are using React and Vite, which are both widely used and well-maintained. Each of these pieces is production-ready on its own, and Wasp just makes it easy to connect them together. Keep in mind that Wasp is still considered beta software, so there might be some rough edges here and there. ## AI & Coding Agents / Agent Plugin / Skills Wasp provides an official plugin for coding agents that transforms them into Wasp framework experts. The plugin gives your agent curated access to Wasp docs, workflows, and best practices so it can develop full-stack web apps (React, Node.js, Prisma) more effectively. The plugin / skills work with just about all the popular coding agents, such as [Claude Code](https://claude.com/product/claude-code), [Cursor](https://www.cursor.com/), [Codex](https://openai.com/codex/), [Gemini CLI](https://geminicli.com/), [GitHub Copilot](https://github.com/features/copilot/cli), [OpenCode](https://opencode.ai/), and more. ### Features - **Wasp Documentation** โ€” Ensures your agent always accesses LLM-friendly Wasp docs in sync with your current project's Wasp version. - **Wasp Knowledge** โ€” Imports Wasp best practices and conventions into your project's memory file (e.g. `CLAUDE.md`, `AGENTS.md`). - **Feature Configuration** โ€” Easily add Wasp features like authentication, database, email, styling (Tailwind, shadcn/ui), and other integrations through your agent. - **Deployment Guidance** โ€” Your agent will guide you through deploying your Wasp app to Railway or Fly.io via the Wasp CLI, or manually to your favorite cloud provider. ### Installation #### Claude Code First, add the Wasp plugin marketplace: ```bash claude plugin marketplace add wasp-lang/wasp-agent-plugins ``` Then install the Wasp plugin: ```bash claude plugin install wasp@wasp-agent-plugins --scope project ``` :::tip We recommend installing with `project` scope so the settings are committed to git (via `settings.json`). Use `local` scope if you prefer settings that aren't committed (via `settings.local.json`). ::: #### Other Agents (Cursor, Codex, Gemini, Copilot, OpenCode, etc.) Run the following command and select all the skills: ```bash npx skills add wasp-lang/wasp-agent-plugins ``` ### Setup After installing, initialize the plugin in an active agent session by explicitly invoking the `/wasp-plugin-init` skill: ``` Run the '/wasp-plugin-init' skill. ``` This adds Wasp knowledge to your project's `CLAUDE.md` or `AGENTS.md` file. Next, start the development server as a background task so your agent has full insight into the running app while developing: ``` Run the 'start-dev-server' skill. ``` To see all available features and skills: ``` /wasp-plugin-help ``` ### Learn More Check out the [Wasp Agent Plugins repository](https://github.com/wasp-lang/wasp-agent-plugins) for more details. ## Advanced Features / Sending Emails With Wasp's email-sending feature, you can easily integrate email functionality into your web application. ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "myApp", emailSender: { provider: "", defaultFrom: { name: "Example", email: "hello@itsme.com", }, }, // ... }) ``` Choose from one of the providers: - `Dummy` (development only), - `Mailgun`, - `SendGrid`, - `Resend` - or the good old `SMTP`. Optionally, define the `defaultFrom` field, so you don't need to provide it whenever sending an email. ### Sending Emails {#sending-emails-1} Before jumping into details about setting up various providers, let's see how easy it is to send emails. You import the `emailSender` that is provided by the `wasp/server/email` module and call the `send` method on it. ```ts title="src/actions/sendEmail.ts" import { emailSender } from "wasp/server/email"; // In some action handler... const info = await emailSender.send({ from: { name: "John Doe", email: "john@doe.com", }, to: "user@domain.com", subject: "Saying hello", text: "Hello world", html: "Hello world", }); ``` Read more about the `send` method in the [API Reference](#javascript-api). The `send` method returns an object with the status of the sent email. It varies depending on the provider you use. ### Providers We'll go over all of the available providers in the next section. For some of them, you'll need to set up some env variables. You can do that in the `.env.server` file. #### Using the Dummy Provider {#dummy} :::note[Dummy Provider is not for production use] The `Dummy` provider is not for production use. It is only meant to be used during development. If you try building your app with the `Dummy` provider, the build will fail. ::: To speed up development, Wasp offers a `Dummy` email sender that `console.log`s the emails in the console. Since it doesn't send emails for real, it doesn't require any setup. Set the provider to `Dummy` in your `main.wasp.ts` file. ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "myApp", emailSender: { provider: "Dummy", }, // ... }) ``` #### Using the SMTP Provider {#smtp} First, set the provider to `SMTP` in your `main.wasp.ts` file. ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "myApp", emailSender: { provider: "SMTP", }, // ... }) ``` Then, add the following env variables to your `.env.server` file. ```properties title=".env.server" SMTP_HOST= SMTP_USERNAME= SMTP_PASSWORD= SMTP_PORT= ``` Many transactional email providers (e.g. Mailgun, SendGrid but also others) can also use SMTP, so you can use them as well. :::caution[SMTP ports might be blocked] Some hosting providers (for example, **Railway** on its free tier, or **Hetzner**) block outbound SMTP ports to prevent spam. If you run into issues, check their documentation for a solution, or consider using a dedicated provider integration like [Mailgun](#mailgun) or [SendGrid](#sendgrid) instead of plain SMTP. ::: #### Using the Mailgun Provider {#mailgun} Set the provider to `Mailgun` in the `main.wasp.ts` file. ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "myApp", emailSender: { provider: "Mailgun", }, // ... }) ``` Then, get the Mailgun API key and domain and add them to your `.env.server` file. ##### Getting the API Key and Domain 1. Go to [Mailgun](https://www.mailgun.com/) and create an account. 2. Go to [Domains](https://app.mailgun.com/mg/sending/new-domain) and create a new domain. 3. Copy the domain and add it to your `.env.server` file. 4. Create a new Sending API key under `Send > Sending > Domain settings` and find `Sending API keys`. 5. Copy the API key and add it to your `.env.server` file. ```properties title=".env.server" MAILGUN_API_KEY= MAILGUN_DOMAIN= ``` ##### Using the EU Region If your domain region is in the EU, you need to set the `MAILGUN_API_URL` variable in your `.env.server` file: ```properties title=".env.server" MAILGUN_API_URL=https://api.eu.mailgun.net ``` #### Using the SendGrid Provider {#sendgrid} :::caution[SendGrid Free Plan Retired] As of May 27, 2025, SendGrid has [retired its free plans](https://www.twilio.com/en-us/changelog/sendgrid-free-plan). A paid SendGrid plan is now required to send emails. Consider using [Mailgun](#mailgun) or [SMTP](#smtp) with another provider if you need a free tier option. ::: Set the provider field to `SendGrid` in your `main.wasp.ts` file. ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "myApp", emailSender: { provider: "SendGrid", }, // ... }) ``` Then, get the SendGrid API key and add it to your `.env.server` file. ##### Getting the API Key 1. Go to [SendGrid](https://sendgrid.com/) and create an account (paid plan required). 2. Go to [API Keys](https://app.sendgrid.com/settings/api_keys) and create a new API key. 3. Copy the API key and add it to your `.env.server` file. ```properties title=".env.server" SENDGRID_API_KEY= ``` #### Using the Resend Provider {#resend} Set the provider field to `Resend` in your `main.wasp.ts` file. ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "myApp", emailSender: { provider: "Resend", }, // ... }) ``` Then, get the Resend API key and add it to your `.env.server` file. ##### Getting the API Key 1. Go to [Resend](https://resend.com/) and create an account. 2. Go to [API Keys](https://resend.com/api-keys) and create a new API key. 3. Copy the API key and add it to your `.env.server` file. ```properties title=".env.server" RESEND_API_KEY= ``` ### API Reference #### `emailSender` specification [API reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/EmailSender) #### [EmailSender ยป](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/EmailSender) [All the options for the emailSender field of the app spec.](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/EmailSender) #### JavaScript API Using the `emailSender` in Typescript: ```ts title="src/actions/sendEmail.ts" import { emailSender } from "wasp/server/email"; // In some action handler... const info = await emailSender.send({ from: { name: "John Doe", email: "john@doe.com", }, to: "user@domain.com", subject: "Saying hello", text: "Hello world", html: "Hello world", }); ``` The `send` method accepts an object with the following fields: - `from: object` The sender's details. If you set up the `defaultFrom` field in the `emailSender` config in your Wasp file, this field is optional. - `name: string` The name of the sender. - `email: string` The email address of the sender. - `to: string` required The recipient's email address. - `subject: string` required The subject of the email. - `text: string` required The text version of the email. - `html: string` required The HTML version of the email ## Advanced Features / Recurring Jobs In most web apps, users send requests to the server and receive responses with some data. When the server responds quickly, the app feels responsive and smooth. What if the server needs extra time to fully process the request? This might mean sending an email or making a slow HTTP request to an external API. In that case, it's a good idea to respond to the user as soon as possible and do the remaining work in the background. Wasp supports background jobs that can help you with this: - Jobs persist between server restarts, - Jobs can be retried if they fail, - Jobs can be delayed until a future time, - Jobs can have a recurring schedule. ### Using Jobs #### Job spec and Usage Let's write an example Job that will print a message to the console and return a list of tasks from the database. 1. Start by creating a Job spec in your Wasp file: ```ts title="main.wasp.ts" import { app, job } from "@wasp.sh/spec" import { mySpecialJob } from "./src/workers/bar" with { type: "ref" } export default app({ // ... spec: [ job(mySpecialJob, { executor: "PgBoss", entities: ["Task"], }), ], }) ``` :::note When `main.wasp.ts` needs to point to your code, it uses imports like this: ```ts import { MainPage } from "./src/MainPage" with { type: "ref" } ``` Notice the `with { type: "ref" }` part at the end of the import statement. This tells Wasp to treat the import as a reference to your app's code, without running the imported code. For more details and examples, see [reference imports](https://wasp.sh/docs/general/spec#reference-imports). ::: 2. After declaring the Job, implement its worker function: ```ts title="src/workers/bar.ts" import { type MySpecialJob } from "wasp/server/jobs" import { type Task } from "wasp/entities" type Input = { name: string; } type Output = { tasks: Task[]; } export const mySpecialJob: MySpecialJob = async ({ name }, context) => { console.log(`Hello ${name}!`) const tasks = await context.entities.Task.findMany({}) return { tasks } } ``` :::info[The worker function] The worker function must be an `async` function. The function's return value represents the Job's result. The worker function accepts two arguments: - `args`: The data passed into the job when it's submitted. - `context: { entities }`: The context object containing entities you put in the Job spec. ::: `MySpecialJob` is a generic type Wasp generates to help you correctly type the Job's worker function, ensuring type information about the function's arguments and return value. Read more about type-safe jobs in the [JavaScript API section](#javascript-api). 3. After successfully defining the job, you can submit work to be done in your [Operations](https://wasp.sh/docs/data-model/operations/overview) or [setupFn](https://wasp.sh/docs/project/server-config#setup-function) (or any other NodeJS code): ```ts title="someAction.ts" import { mySpecialJob } from "wasp/server/jobs" const submittedJob = await mySpecialJob.submit({ name: "Johnny" }) // Or, if you'd prefer it to execute in the future, just add a .delay(). // It takes a number of seconds, Date, or ISO date string. await mySpecialJob .delay(10) .submit({ name: "Johnny" }) ``` And that's it. Your job will be executed by `PgBoss` as if you called `mySpecialJob({ name: "Johnny" })`. In our example, `mySpecialJob` takes an argument, but passing arguments to jobs is not a requirement. It depends on how you've implemented your worker function. #### Recurring Jobs If you have work that needs to be done on some recurring basis, you can add a `schedule` to your job spec: ```ts title="main.wasp.ts" import { app, job } from "@wasp.sh/spec" import { mySpecialJob } from "./src/workers/bar" with { type: "ref" } export default app({ // ... spec: [ job(mySpecialJob, { executor: "PgBoss", schedule: { cron: "0 * * * *", args: { name: "Johnny" }, // optional }, }), ], }) ``` In this example, you *don't* need to invoke anything in TypeScript. You can imagine `mySpecialJob({ name: "Johnny" })` getting automatically scheduled and invoked for you every hour. ### Job executors Wasp supports Jobs through the use of **job executors**. A job executor is responsible for handling the scheduling, monitoring, and execution of jobs. Currently, Wasp only has support for one job executor, `PgBoss`. #### `PgBoss` [`PgBoss`](https://github.com/timgit/pg-boss/tree/8.4.2) is a lightweight job queue built on top of PostgreSQL. It is suitable for low-volume production use cases and does not require any additional infrastructure or complex management. By using PostgreSQL (and [SKIP LOCKED](https://www.2ndquadrant.com/en/blog/what-is-select-skip-locked-for-in-postgresql-9-5/)) as its storage and synchronization mechanism, you get many benefits of a traditional job queue, on top of your existing Postgres database. ##### Requirements `PgBoss` requires that your database provider is set to `"postgresql"` in your `schema.prisma` file. Read more about setting the provider [here](https://wasp.sh/docs/data-model/databases#postgresql). ##### Limitations `PgBoss` runs together with your web server, whenever it is up. This means that it is not a separate process or service, but rather a part of your web server's application. As such, it is not suitable for CPU-heavy workloads, as it shares the CPU with your web server's application logic. The `PgBoss` executor in Wasp does not (yet) support independent, horizontal scaling of pg-boss-only applications, nor starting them as separate workers/processes/threads. This means that your server must be running whenever you want to process jobs. If you need to scale your job processing, you will need to run multiple instances of your web server, each with its own `PgBoss` instance. ##### Customization {#pg\_boss\_new\_options} If you need to customize the creation of the `PgBoss` instance, you can set an environment variable called `PG_BOSS_NEW_OPTIONS` to a stringified JSON object containing the initialization parameters. See the [pg-boss documentation](https://github.com/timgit/pg-boss/tree/8.4.2/docs#newoptions). Please note that setting `PG_BOSS_NEW_OPTIONS` environment variable overwrites all Wasp defaults, so you must include the `connectionString` parameter inside it as well. For example, to set the connection string and change the job archival and deletion settings, you can set the environment variable like this: ```bash # In an .env file PG_BOSS_NEW_OPTIONS={"connectionString":"postgresql://user:password@server:5432/database","archiveCompletedAfterSeconds":86400,"deleteAfterDays":30,"maintenanceIntervalMinutes":5} # In the shell PG_BOSS_NEW_OPTIONS='{"connectionString":"postgresql://user:password@server:5432/database","archiveCompletedAfterSeconds":86400,"deleteAfterDays":30,"maintenanceIntervalMinutes":5}' ``` You can read more about escaping JSON in environment variables in the [JSON Env Vars documentation](https://wasp.sh/docs/project/env-vars#json-env-vars). ##### Database setup :::tip[You don't need to set up the database manually] When using `PgBoss`, the database setup is automatically taken care of by the Wasp server, and doesn't need to be reflected in your schemas or migrations. The following information is given for your reference, and is explained in more detail in [the `PgBoss` documentation](https://github.com/timgit/pg-boss/blob/8.4.2/docs/readme.md). ::: All job data will be stored in a separate database schema called `pgboss`. It has some internal tracking tables, such as `job`, `archive`, and `schedule`. `PgBoss` tables have a `name` column in most tables that will correspond to your Job identifier. Additionally, these tables maintain arguments, states, return values, retry information, start and expiration times, and other metadata required by `PgBoss`. ##### Known issues - **Renaming scheduled jobs** Wasp derives the Job's name from the worker function you pass to `job`. For example, `job(emailReminder, ...)` creates a Job named `emailReminder`, and Wasp uses that name in the `name` column of `pgboss` tables. If you change a name that had a `schedule` associated with it, pg-boss will continue scheduling those jobs but they will have no handlers associated, and will thus become stale and expire. To resolve this, you can remove the applicable row from the `pgboss.schedule` table. For example, if you renamed a job from `emailReminder` to `sendEmailReminder`, you would need to remove the old scheduled job with the following SQL query: ```sql BEGIN; DELETE FROM pgboss.schedule WHERE name = 'emailReminder'; COMMIT; ``` **Important:** Only modify the database directly if you're comfortable with SQL operations. If you're unsure, consider keeping the old job name or restarting with a fresh database in development. ##### Job data retention and cleanup By default, `PgBoss` keeps job data for 12 hours after completion or failure. After that, it moves the data to an archive table, where it is kept for 7 days before being deleted. If you want to change this behavior, you can configure the `PG_BOSS_NEW_OPTIONS` environment variable to set custom values for job archival ([`archivedCompletedAfterSeconds`/`archiveFailedAfterSeconds`](https://github.com/timgit/pg-boss/tree/8.4.2/docs#newoptions:~\:text=v1%22%20or%20%22v4%22-,archiveCompletedAfterSeconds,-Specifies%20how%20long)) and removal ([`deleteAfterSeconds`/`deleteAfterMinutes`/etc](https://github.com/timgit/pg-boss/tree/8.4.2/docs#newoptions:~\:text=the%20skew%20warnings.-,Archive%20options,-When%20jobs%20in)). ```bash PG_BOSS_NEW_OPTIONS={"connectionString":"...your postgress connection url...","archiveCompletedAfterSeconds":86400,"deleteAfterDays":30,"maintenanceIntervalMinutes":5} ``` ### API Reference #### `job` specification [API reference](https://wasp.sh/docs/api/@wasp.sh/spec/functions/job) #### [job ยป](https://wasp.sh/docs/api/@wasp.sh/spec/functions/job) [All the options for defining a job in the Wasp spec.](https://wasp.sh/docs/api/@wasp.sh/spec/functions/job) #### JavaScript API ##### The worker function {#worker-api} An `async` function that performs the Job's work. Since Wasp executes Jobs on the server, its import path must lead to a NodeJS file. It receives two arguments: - `args: Input`: The data passed to the job when it's submitted. - `context: { entities: Entities }`: The context object containing the entities you put in the Job spec. Here's an example worker function: ```ts title="src/workers/bar.ts" import { type MySpecialJob } from "wasp/server/jobs" type Input = { name: string; } type Output = { tasks: Task[]; } export const mySpecialJob: MySpecialJob = async ({ name }, context) => { console.log(`Hello ${name}!`) const tasks = await context.entities.Task.findMany({}) return { tasks } } ``` Read more about type-safe jobs in the [JavaScript API section](#javascript-api). ##### Importing a Job: ```ts title="someAction.ts" import { mySpecialJob, type MySpecialJob } from "wasp/server/jobs" ``` :::info[Type-safe jobs] Wasp generates a generic type for each Job, which you can use to type your worker function. The type is named after the worker function you pass to `job`, converted to PascalCase, and is available in the `wasp/server/jobs` module. In the example above, the type is `MySpecialJob`. The type takes two type arguments: - `Input`: The type of the `args` argument of the worker function. - `Output`: The type of the return value of the worker function. ::: ##### `submit(jobArgs, executorOptions)` - `jobArgs: Input` - `executorOptions: object` Submits a Job to be executed by an executor, optionally passing in a JSON job argument your job handler function receives, and executor-specific submit options. ```ts title="someAction.ts" const submittedJob = await mySpecialJob.submit({ name: "Johnny" }) ``` ##### `delay(startAfter)` - `startAfter: int | string | Date` required Delaying the invocation of the job handler. The delay can be one of: - Integer: number of seconds to delay. \[Default 0] - String: ISO date string to run at. - Date: Date to run at. ```ts title="someAction.ts" const submittedJob = await mySpecialJob .delay(10) .submit({ name: "Johnny" }, { "retryLimit": 2 }) ``` ##### Tracking The return value of `submit()` is an instance of `SubmittedJob`, which has the following fields: - `jobId`: The ID for the job in that executor. - `jobName`: The Job name Wasp derived from the worker function you passed to `job`. - `executorName`: The Symbol of the name of the job executor. There are also some namespaced, job executor-specific objects. - For pg-boss, you may access: `pgBoss` - `details()`: pg-boss specific job detail information. [Reference](https://github.com/timgit/pg-boss/blob/8.4.2/docs/readme.md#getjobbyidid) - `cancel()`: attempts to cancel a job. [Reference](https://github.com/timgit/pg-boss/blob/8.4.2/docs/readme.md#cancelid) - `resume()`: attempts to resume a canceled job. [Reference](https://github.com/timgit/pg-boss/blob/8.4.2/docs/readme.md#resumeid) ## Advanced Features / Web Sockets Wasp provides a fully integrated WebSocket experience by utilizing [Socket.IO](https://socket.io/) on the client and server. We handle making sure your URLs are correctly setup, CORS is enabled, and provide a useful `useSocket` and `useSocketListener` abstractions for use in React components. To get started, you need to: 1. Define your WebSocket logic on the server. 2. Enable WebSockets in your Wasp file, and connect it with your server logic. 3. Use WebSockets on the client, in React, via `useSocket` and `useSocketListener`. 4. Optionally, type the WebSocket events and payloads for full-stack type safety. Let's go through setting up WebSockets step by step, starting with enabling WebSockets in your Wasp file. ### Turn On WebSockets in Your Wasp File We specify that we are using WebSockets by adding `webSocket` to our `app` and providing the required `fn`. You can optionally change the auto-connect behavior. ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { webSocketFn } from "./src/webSocket" with { type: "ref" } export default app({ name: "myApp", webSocket: { fn: webSocketFn, autoConnect: true, // optional, default: true }, // ... }) ``` ### Defining the Events Handler Let's define the WebSockets server with all of the events and handler functions. :::info[Full-stack type safety] Check this out: we'll define the event types and payloads on the server, and they will be **automatically exposed on the client**. This helps you avoid mistakes when emitting events or handling them. ::: #### `webSocketFn` Function {#websocketfn} On the server, you will get Socket.IO `io: Server` argument and `context` for your WebSocket function. The `context` object give you access to all of the entities from your Wasp app. You can use this `io` object to register callbacks for all the regular [Socket.IO events](https://socket.io/docs/v4/server-api/). Also, if a user is logged in, you will have a `socket.data.user` on the server. This is how we can define our `webSocketFn` function: ```ts title="src/webSocket.ts" import { v4 as uuidv4 } from "uuid" import { type WebSocketDefinition, type WaspSocketData } from "wasp/server/webSocket" export const webSocketFn: WebSocketFn = (io, context) => { io.on("connection", (socket) => { const username = socket.data.user?.getFirstProviderUserId() ?? "Unknown" console.log("a user connected: ", username) socket.on("chatMessage", async (msg) => { console.log("message: ", msg) io.emit("chatMessage", { id: uuidv4(), username, text: msg }) // You can also use your entities here: // await context.entities.SomeEntity.create({ someField: msg }) }) }) } // Typing our WebSocket function with the events and payloads // allows us to get type safety on the client as well type WebSocketFn = WebSocketDefinition< ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData > interface ServerToClientEvents { chatMessage: (msg: { id: string, username: string, text: string }) => void; } interface ClientToServerEvents { chatMessage: (msg: string) => void; } interface InterServerEvents {} // Data that is attached to the socket. // NOTE: Wasp automatically injects the JWT into the connection, // and if present/valid, the server adds a user to the socket. interface SocketData extends WaspSocketData {} ``` ### Using the WebSocket On The Client :::info[Full-stack type safety] All the hooks we use are typed with the events and payloads you defined on the server. VS Code will give you autocomplete for the events and payloads, and you will get type errors if you make a mistake. ::: #### The `useSocket` Hook Client access to WebSockets is provided by the `useSocket` hook. It returns: - `socket: Socket` for sending and receiving events. - `isConnected: boolean` for showing a display of the Socket.IO connection status. - Note: Wasp automatically connects and establishes a WebSocket connection from the client to the server by default, so you do not need to explicitly `socket.connect()` or `socket.disconnect()`. - If you set `autoConnect: false` in your Wasp file, then you should call these as needed. All components using `useSocket` share the same underlying `socket`. #### The `useSocketListener` Hook Additionally, there is a `useSocketListener: (event, callback) => void` hook which is used for registering event handlers. It takes care of unregistering the handler on unmount. Wasp's **full-stack type safety** kicks in here: all the event types and payloads are automatically inferred from the server and are available on the client. You can additionally use the `ClientToServerPayload` and `ServerToClientPayload` helper types to get the payload type for a specific event. ```tsx title="src/ChatPage.tsx" import React, { useState } from "react" import { useSocket, useSocketListener, ServerToClientPayload, } from "wasp/client/webSocket" export const ChatPage = () => { const [messageText, setMessageText] = useState< // We are using a helper type to get the payload type for the "chatMessage" event. ClientToServerPayload<"chatMessage"> >("") const [messages, setMessages] = useState< ServerToClientPayload<"chatMessage">[] >([]) // The "socket" instance is typed with the types you defined on the server. const { socket, isConnected } = useSocket() // This is a type-safe event handler: "chatMessage" event and its payload type // are defined on the server. useSocketListener("chatMessage", logMessage) function logMessage(msg: ServerToClientPayload<"chatMessage">) { setMessages((priorMessages) => [msg, ...priorMessages]) } function handleSubmit(e: React.FormEvent) { e.preventDefault() // This is a type-safe event emitter: "chatMessage" event and its payload type // are defined on the server. socket.emit("chatMessage", messageText) setMessageText("") } const messageList = messages.map((msg) => (
  • {msg.username}: {msg.text}
  • )) const connectionIcon = isConnected ? "๐ŸŸข" : "๐Ÿ”ด" return ( <>

    Chat {connectionIcon}

    setMessageText(e.target.value)} />
      {messageList}
    ) } ``` ### API Reference [API reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/WebSocket) #### [WebSocket ยป](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/WebSocket) [All the options for the webSocket field of the app spec.](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/WebSocket) ## Advanced Features / Accessing the configuration Whenever you start a Wasp app, you are starting two processes. - **The client process** - A React app that implements your app's frontend. During development, this is a dev server with hot reloading. In production, it's a simple process that serves pre-built static files with environment variables embedded during the build (details depend on [how you deploy it](https://wasp.sh/docs/deployment/intro)). - **The server process** - An Express server that implements your app's backend. During development, this is an Express server controlled by a [`nodemon`](https://www.npmjs.com/package/nodemon) process that takes care of hot reloading and restarts. In production, it's a regular Express server run using Node. Check [the introduction](https://wasp.sh/docs) for a more in-depth explanation of Wasp's runtime architecture. You can configure both processes through environment variables. See [the deployment instructions](https://wasp.sh/docs/project/env-vars) for a full list of supported variables. Wasp gives you runtime access to the processes' configurations through **configuration objects**. ### Server configuration object The server configuration object contains these fields: - `frontendUrl: String` - Set it with env var `WASP_WEB_CLIENT_URL`. The URL of your client (the app's frontend).\ Wasp automatically sets it during development when you run `wasp start`.\ In production, you should set it to your client's URL as the server sees it (i.e., with the DNS and proxies considered). You can access it like this: ```js import { config } from 'wasp/server' console.log(config.frontendUrl) ``` ### Client configuration object The client configuration object contains these fields: - `apiUrl: String` - Set it with env var `REACT_APP_API_URL` The URL of your server (the app's backend).\ Wasp automatically sets it during development when you run `wasp start`.\ In production, it should contain the value of your server's URL as the user's browser sees it (i.e., with the DNS and proxies considered). You can access it like this: ```js import { config } from 'wasp/client' console.log(config.apiUrl) ``` ## Advanced Features / Custom HTTP API Endpoints In Wasp, the default client-server interaction mechanism is through [Operations](https://wasp.sh/docs/data-model/operations/overview). However, if you need a specific URL method/path, or a specific response, Operations may not be suitable for you. For these cases, you can use an `api`. Best of all, they should look and feel very familiar. ### How to Create an API APIs are used to tie a JS function to a certain endpoint e.g. `POST /something/special`. They are distinct from Operations and have no client-side helpers (like `useQuery`). To create a Wasp API, you must: 1. Declare the API in Wasp using the `api` constructor 2. Define the API's NodeJS implementation After completing these two steps, you'll be able to call the API from the client code (via our `ky` wrapper), or from the outside world. #### Specifying the API in Wasp First, we need to declare the API in the Wasp file and you can easily do this with the `api` function: ```ts title="main.wasp.ts" import { api, app } from "@wasp.sh/spec" import { fooBar } from "./src/apis" with { type: "ref" } export default app({ // ... spec: [ api("GET", "/foo/bar", fooBar), ], }) ``` :::note When `main.wasp.ts` needs to point to your code, it uses imports like this: ```ts import { MainPage } from "./src/MainPage" with { type: "ref" } ``` Notice the `with { type: "ref" }` part at the end of the import statement. This tells Wasp to treat the import as a reference to your app's code, without running the imported code. For more details and examples, see [reference imports](https://wasp.sh/docs/general/spec#reference-imports). ::: Read more about the supported fields in the [API Reference](#api-reference). #### Defining the API's NodeJS Implementation :::note To make sure the Wasp compiler generates the types for APIs for use in the NodeJS implementation, you should add your `api`s to your Wasp file first *and* keep the `wasp start` command running. ::: After you defined the API, it should be implemented as a NodeJS function that takes three arguments: 1. `req`: Express Request object 2. `res`: Express Response object 3. `context`: An additional context object **injected into the API by Wasp**. This object contains user session information, as well as information about entities. The examples here won't use the context for simplicity purposes. You can read more about it in the [section about using entities in APIs](#using-entities-in-apis). ```ts title="src/apis.ts" import type { FooBar } from "wasp/server/api"; export const fooBar: FooBar = (req, res, context) => { res.set("Access-Control-Allow-Origin", "*"); // Example of modifying headers to override Wasp default CORS middleware. res.json({ msg: `Hello, ${context.user ? "registered user" : "stranger"}!` }); }; ``` :::note The `FooBar` type is generated by Wasp based on the `api` spec above. ::: ##### Providing Extra Type Information We'll see how we can provide extra type information to an API function. Let's say you wanted to create some `GET` route that would take an email address as a param, and provide them the answer to "Life, the Universe and Everything." ๐Ÿ˜€ What would this look like in TypeScript? Define the API in Wasp: ```ts title="main.wasp.ts" import { api, app } from "@wasp.sh/spec" import { fooBar } from "./src/apis" with { type: "ref" } export default app({ // ... spec: [ api("GET", "/foo/bar/:email", fooBar, { entities: ["Task"] }), ], }) ``` We can use the `FooBar` type to which we'll provide the generic **params** and **response** types, which then gives us full type safety in the implementation. ```ts title="src/apis.ts" import { FooBar } from "wasp/server/api"; export const fooBar: FooBar< { email: string }, // params { answer: number } // response > = (req, res, _context) => { console.log(req.params.email); res.json({ answer: 42 }); }; ``` ### Using the API #### Using the API externally To use the API externally, you simply call the endpoint using the method and path you used. For example, if your app is running at `https://example.com` then from the above you could issue a `GET` to `https://example/com/foo/callback` (in your browser, Postman, `curl`, another web service, etc.). #### Using the API from the Client To use the API from your client, including with auth support, you can import the `api` instance from `wasp/client/api`. It is a [ky](https://github.com/sindresorhus/ky) instance pre-configured with the API base URL, authentication, and error handling. For example: ```tsx title="src/pages/SomePage.tsx" import React, { useEffect } from "react"; import { api } from "wasp/client/api"; async function fetchCustomRoute() { const data = await api.get("/foo/bar").json(); console.log(data); } export const Foo = () => { useEffect(() => { fetchCustomRoute(); }, []); return <>{/* ... */}; }; ``` ##### Making Sure CORS Works APIs are designed to be as flexible as possible, hence they don't utilize the default middleware like Operations do. As a result, to use these APIs on the client side, you must ensure that CORS (Cross-Origin Resource Sharing) is enabled. You can do this by defining custom middleware for your APIs in the Wasp file. For example, an `apiNamespace` is a simple spec used to apply some `middlewareConfigFn` to all APIs under some specific path: ```ts title="main.wasp.ts" import { apiNamespace, app } from "@wasp.sh/spec" import { apiMiddleware } from "./src/apis" with { type: "ref" } export default app({ // ... spec: [ apiNamespace("/foo", { middlewareConfigFn: apiMiddleware }), ], }) ``` And then in the implementation file (returning the default config): ```ts title="src/apis.ts" import type { MiddlewareConfigFn } from "wasp/server"; export const apiMiddleware: MiddlewareConfigFn = (config) => { return config; }; ``` We are returning the default middleware which enables CORS for all APIs under the `/foo` path. For more information about middleware configuration, please see: [Middleware Configuration](https://wasp.sh/docs/advanced/middleware-config) ### Using Entities in APIs In many cases, resources used in APIs will be [Entities](https://wasp.sh/docs/data-model/entities). To use an Entity in your API, add it to the `api` spec in Wasp: ```ts title="main.wasp.ts" import { api, app } from "@wasp.sh/spec" import { fooBar } from "./src/apis" with { type: "ref" } export default app({ // ... spec: [ api("GET", "/foo/bar", fooBar, { entities: ["Task"] }), ], }) ``` Wasp will inject the specified Entity into the APIs `context` argument, giving you access to the Entity's Prisma API: ```ts title="src/apis.ts" import type { FooBar } from "wasp/server/api"; export const fooBar: FooBar = async (req, res, context) => { res.json({ count: await context.entities.Task.count() }); }; ``` The object `context.entities.Task` exposes `prisma.task` from [Prisma's CRUD API](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/crud). ### Streaming Responses You can use streaming responses to send data to the client in chunks as it becomes available. This is useful for: - **LLM responses** - Stream AI-generated content as it's produced - **Long-running processes** - Show progress updates in real-time - **Large datasets** - Send data incrementally to avoid timeouts #### Creating a Streaming API To create a streaming API, write a function that uses Express response methods like `res.write()` and `res.end()`: ```ts title="main.wasp.ts" import { api, app } from "@wasp.sh/spec" import { getStreamingText } from "./src/streaming" with { type: "ref" } export default app({ // ... spec: [ api("POST", "/api/streaming-example", getStreamingText), ], }) ``` Don't forget to set up the CORS middleware. See the [section explaning CORS](#making-sure-cors-works) for details. ```ts title="src/streaming.ts" import OpenAI from "openai"; import type { GetStreamingText } from "wasp/server/api"; const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }); export const getStreamingText: GetStreamingText< never, string, { message: string } > = async (req, res) => { const { message } = req.body; // Set appropriate headers for streaming. res.setHeader("Content-Type", "text/plain; charset=utf-8"); res.setHeader("Transfer-Encoding", "chunked"); const stream = await client.responses.create({ model: "gpt-5", input: `Funny response to "${message}"`, stream: true, }); for await (const chunk of stream) { if (chunk.type === "response.output_text.delta") { // Write each chunk to the response as it arrives. res.write(chunk.delta); } } // End the response. res.end(); }; ``` #### Consuming Streaming Responses You can consume streaming responses on the client using the `api` instance from `wasp/client/api`. Since ky is built on `fetch`, you get native streaming support via the `Response.body` readable stream. The `api` instance handles authentication automatically. ```tsx title="src/StreamingPage.tsx" import { useEffect, useState } from "react"; import { api } from "wasp/client/api"; export function StreamingPage() { const { response } = useTextStream("/api/streaming-example", { message: "Best Office episode?", }); return (

    Streaming Example

    {response}
    ); } function useTextStream(path: string, payload: { message: string }) { const [response, setResponse] = useState(""); useEffect(() => { const controller = new AbortController(); fetchStream( path, payload, (chunk) => { setResponse((prev) => prev + chunk); }, controller.signal, ); return () => { controller.abort(); }; }, [path]); return { response }; } async function fetchStream( path: string, payload: { message: string }, onData: (data: string) => void, signal: AbortSignal, ) { try { const response = await api.post(path, { json: payload, signal, }); if (response.body === null) { throw new Error("Stream body is null"); } const stream = response.body.pipeThrough(new TextDecoderStream()); const reader = stream.getReader(); while (true) { const { done, value } = await reader.read(); if (done) { break; } onData(value); } } catch (error: unknown) { if (error instanceof Error) { if (error.name === "AbortError") { // Fetch was aborted, no need to log an error return; } console.error("Fetch error:", error.message); } else { throw error; } } } ``` ### API Reference [API reference](https://wasp.sh/docs/api/@wasp.sh/spec/functions/api) #### [api ยป](https://wasp.sh/docs/api/@wasp.sh/spec/functions/api) [All the options for declaring an API endpoint in the Wasp spec.](https://wasp.sh/docs/api/@wasp.sh/spec/functions/api) [API reference](https://wasp.sh/docs/api/@wasp.sh/spec/functions/apiNamespace) #### [apiNamespace ยป](https://wasp.sh/docs/api/@wasp.sh/spec/functions/apiNamespace) [All the options for declaring an API namespace in the Wasp spec.](https://wasp.sh/docs/api/@wasp.sh/spec/functions/apiNamespace) ## Advanced Features / Configuring Middleware Wasp comes with a minimal set of useful Express middleware in every application. While this is good for most users, we realize some may wish to add, modify, or remove some of these choices both globally, or on a per-`api`/path basis. ### Default Global Middleware ๐ŸŒ Wasp's Express server has the following middleware by default: - [Helmet](https://helmetjs.github.io/): Helmet helps you secure your Express apps by setting various HTTP headers. *It's not a silver bullet, but it's a good start.* - [CORS](https://github.com/expressjs/cors#readme): CORS is a package for providing a middleware that can be used to enable [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) with various options. :::note CORS middleware is required for the frontend to communicate with the backend. ::: - [Morgan](https://github.com/expressjs/morgan#readme): HTTP request logger middleware. - [express.json](https://expressjs.com/en/api.html#express.json) (which uses [body-parser](https://github.com/expressjs/body-parser#bodyparserjsonoptions)): parses incoming request bodies in a middleware before your handlers, making the result available under the `req.body` property. :::note JSON middleware is required for [Operations](https://wasp.sh/docs/data-model/operations/overview) to function properly. ::: - [express.urlencoded](https://expressjs.com/en/api.html#express.urlencoded) (which uses [body-parser](https://expressjs.com/en/resources/middleware/body-parser.html#bodyparserurlencodedoptions)): returns middleware that only parses urlencoded bodies and only looks at requests where the `Content-Type` header matches the type option. - [cookieParser](https://github.com/expressjs/cookie-parser#readme): parses Cookie header and populates `req.cookies` with an object keyed by the cookie names. ### Customization You have three places where you can customize middleware: 1. [global](#1-customize-global-middleware): here, any changes will apply by default *to all operations (`query` and `action`) and `api`.* This is helpful if you wanted to add support for multiple domains to CORS, for example. :::caution[Modifying global middleware] Please treat modifications to global middleware with extreme care as they will affect all operations and APIs. If you are unsure, use one of the other two options. ::: 2. [per-api](#2-customize-api-specific-middleware): you can override middleware for a specific api route (e.g. `POST /webhook/callback`). This is helpful if you want to disable JSON parsing for some callback, for example. 3. [per-path](#3-customize-per-path-middleware): this is helpful if you need to customize middleware for all methods under a given path. - It's helpful for things like "complex CORS requests" which may need to apply to both `OPTIONS` and `GET`, or to apply some middleware to a *set of `api` routes*. [Guide](https://wasp.sh/docs/guides/configuration/cors-multiple-domains) #### [Multiple Domains CORS ยป](https://wasp.sh/docs/guides/configuration/cors-multiple-domains) [Configure CORS to support multiple domains in your Wasp application](https://wasp.sh/docs/guides/configuration/cors-multiple-domains) #### Default Middleware Definitions Below is the actual definitions of default middleware which you can override. ```ts export type MiddlewareConfig = Map // Used in the examples below ๐Ÿ‘‡ export type MiddlewareConfigFn = (middlewareConfig: MiddlewareConfig) => MiddlewareConfig const defaultGlobalMiddleware: MiddlewareConfig = new Map([ ["helmet", helmet()], ["cors", cors({ origin: config.allowedCORSOrigins })], ["logger", logger("dev")], ["express.json", express.json()], ["express.urlencoded", express.urlencoded()], ["cookieParser", cookieParser()] ]) ``` ### 1. Customize Global Middleware If you would like to modify the middleware for *all* operations and APIs, you can do something like: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { serverMiddlewareFn } from "./src/serverSetup" with { type: "ref" } export default app({ name: "myApp", server: { middlewareConfigFn: serverMiddlewareFn, }, // ... }) ``` ```ts title="src/serverSetup.ts" import cors from "cors" import { config, type MiddlewareConfigFn } from "wasp/server" export const serverMiddlewareFn: MiddlewareConfigFn = (middlewareConfig) => { // Example of adding extra domains to CORS. middlewareConfig.set("cors", cors({ origin: [...config.allowedCORSOrigins, "https://example1.com", "https://example2.com"] })) return middlewareConfig } ``` ### 2. Customize `api`-specific Middleware If you would like to modify the middleware for a single API, you can do something like: ```ts title="main.wasp.ts" import { api, app } from "@wasp.sh/spec" import { webhookCallback, webhookCallbackMiddlewareFn } from "./src/apis" with { type: "ref" } export default app({ // ... spec: [ api("POST", "/webhook/callback", webhookCallback, { middlewareConfigFn: webhookCallbackMiddlewareFn, auth: false, }), ], }) ``` ```ts title="src/apis.ts" import express from "express" import { type WebhookCallback } from "wasp/server/api" import { type MiddlewareConfigFn } from "wasp/server" export const webhookCallback: WebhookCallback = (req, res, _context) => { res.json({ msg: req.body.length }) } export const webhookCallbackMiddlewareFn: MiddlewareConfigFn = (middlewareConfig) => { console.log("webhookCallbackMiddlewareFn: Swap express.json for express.raw") middlewareConfig.delete("express.json") middlewareConfig.set("express.raw", express.raw({ type: "*/*" })) return middlewareConfig } ``` :::note This gets installed on a per-method basis. Behind the scenes, this results in code like: ```js router.post("/webhook/callback", webhookCallbackMiddleware, ...) ``` ::: ### 3. Customize Per-Path Middleware If you would like to modify the middleware for all API routes under some common path, you can define a `middlewareConfigFn` on an `apiNamespace`: ```ts title="main.wasp.ts" import { apiNamespace, app } from "@wasp.sh/spec" import { fooBarNamespaceMiddlewareFn } from "./src/apis" with { type: "ref" } export default app({ // ... spec: [ apiNamespace("/foo/bar", { middlewareConfigFn: fooBarNamespaceMiddlewareFn, }), ], }) ``` ```ts title="src/apis.ts" import express from "express" import { type MiddlewareConfigFn } from "wasp/server" export const fooBarNamespaceMiddlewareFn: MiddlewareConfigFn = (middlewareConfig) => { const customMiddleware: express.RequestHandler = (_req, _res, next) => { console.log("fooBarNamespaceMiddlewareFn: custom middleware") next() } middlewareConfig.set("custom.middleware", customMiddleware) return middlewareConfig } ``` :::note This gets installed at the router level for the path. Behind the scenes, this results in something like: ```js router.use("/foo/bar", fooBarNamespaceMiddleware) ``` ::: ## Advanced Features / Type-Safe Links If you are using Typescript, Wasp gives you typesafe building blocks for navigation. You get autocompletion on route paths, compile errors when params are missing, and a single source of truth between your `main.wasp.ts` file and your client code. ### Typesafe navigation with components For navigating between pages inside JSX, Wasp exposes two components from `wasp/client/router`: `Link` for simple links, and `NavLink` when you need to react to navigation state. #### Simple links with `Link` Reach for `Link` when you just need to send the user to another page. Given this route: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { TaskPage } from "./src/TaskPage" with { type: "ref" } export default app({ // ... spec: [ route("TaskRoute", "/task/:id", page(TaskPage)), ], }) ``` You'd use it like this: ```jsx title="TaskList.tsx" import { Link } from "wasp/client/router" export const TaskList = () => { // ... return (
    {tasks.map((task) => ( {/* ๐Ÿ‘† Required and typechecked against the path */} {task.description} ))}
    ) } ``` The `to` prop is autocompleted from the routes you defined in `main.wasp.ts`, and `params` is typechecked against the path you picked. Rename a route or change a param, and any broken `Link` is pointed out by Typescript. #### Reacting to navigation state with `NavLink` Use `NavLink` when the current page should be highlighted, or when you want to show a spinner during a pending transition. It takes the same props as `Link`, but `className`, `style`, and `children` can be render-prop functions that receive `{ isActive, isPending, isTransitioning }`. ```tsx title="Navigation.tsx" import { NavLink } from "wasp/client/router" export const Navigation = () => { return ( ) } ``` Everything below applies to both `Link` and `NavLink`. #### Catch-all routes If a route path ends with a `/*` pattern (also known as [splat](https://reactrouter.com/8.0.1/start/declarative/routing#splats)), pass the rest of the path as the `*` param: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { CatchAllPage } from "./src/CatchAllPage" with { type: "ref" } export default app({ // ... spec: [ route("CatchAllRoute", "/pages/*", page(CatchAllPage)), ], }) ``` ```jsx title="TaskList.tsx" About ``` This renders as `/pages/about`. #### Optional static segments If a route has an optional static segment, you can choose at the call site whether to include it or not: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { OptionalPage } from "./src/OptionalPage" with { type: "ref" } export default app({ // ... spec: [ route("OptionalRoute", "/task/:id/details?", page(OptionalPage)), ], }) ``` ```jsx title="TaskList.tsx" /* You can include the optional segment ... */ Task 1 /* ... or leave it out */ Task 1 ``` #### Search params and hash You can also pass `search` and `hash` to attach a query string and fragment: ```tsx title="TaskList.tsx" {task.description} ``` This renders as `/task/1?sortBy=date#comments`. Check out the [API Reference](#link-component) for the full list of accepted props. ### Typesafe navigation outside of components When you need a URL string instead of a component, for example for `useNavigate`, redirects, `window.location`, or anywhere you are not rendering JSX, use the `routes` object from `wasp/client/router`: ```jsx title="TaskList.tsx" import { routes } from "wasp/client/router" const linkToTask = routes.TaskRoute.build({ params: { id: 1 } }) ``` `linkToTask` is the string `/task/1`. Each route from `main.wasp.ts` shows up on `routes` with a `build` function whose options are typed against the route's path, so the same compile-time safety you get from `Link` is also available outside of JSX. `build` follows the same rules as the [components above](#typesafe-navigation-with-components): catch-all routes take a `*` param, optional static segments pick a concrete `path`, and you can attach a query string and fragment via `search` and `hash`. ```tsx const linkToTaskComments = routes.OptionalRoute.build({ path: "/task/:id/details", params: { id: 1 }, search: { sortBy: "date" }, hash: "comments", }) ``` This renders as `/task/1/details?sortBy=date#comments`. Check out the [API Reference](#routes-object) for the full shape. ### API Reference #### `Link` Component The `Link` component accepts the following props: - `to` required - A valid Wasp Route path from your `main.wasp.ts` file. In the case of optional static segments, you must provide one of the possible paths which include or exclude the optional segment. For example, if the path is `/task/:id/details?`, you must provide either `/task/:id/details` or `/task/:id`. - `params: { [name: string]: string | number }` required (if the path contains params) - An object with keys and values for each param in the path. - For example, if the path is `/task/:id`, then the `params` prop must be `{ id: 1 }`. Wasp supports required and optional params. - `search: string[][] | Record | string | URLSearchParams` - Any valid input for `URLSearchParams` constructor. - For example, the object `{ sortBy: 'date' }` becomes `?sortBy=date`. - `hash: string` - all other props that the `react-router`'s [Link](https://reactrouter.com/8.0.1/api/components/Link) component accepts #### `NavLink` Component The `NavLink` component accepts the following props: - `to` required - A valid Wasp Route path from your `main.wasp.ts` file. In the case of optional static segments, you must provide one of the possible paths which include or exclude the optional segment. For example, if the path is `/task/:id/details?`, you must provide either `/task/:id/details` or `/task/:id`. - `params: { [name: string]: string | number }` required (if the path contains params) - An object with keys and values for each param in the path. - For example, if the path is `/task/:id`, then the `params` prop must be `{ id: 1 }`. Wasp supports required and optional params. - `search: string[][] | Record | string | URLSearchParams` - Any valid input for `URLSearchParams` constructor. - For example, the object `{ sortBy: 'date' }` becomes `?sortBy=date`. - `hash: string` - all other props that the `react-router`'s [NavLink](https://reactrouter.com/8.0.1/api/components/NavLink) component accepts - Notably, `className`, `style`, and `children` accept render-prop functions that receive `{ isActive, isPending, isTransitioning }`, and `end` and `caseSensitive` control how the active match is computed. #### `routes` Object The `routes` object contains a function for each route in your app. ```ts title="router.tsx" export const routes = { // RootRoute has a path like "/" RootRoute: { build: (options?: { search?: string[][] | Record | string | URLSearchParams hash?: string }) => // ... }, // DetailRoute has a path like "/task/:id/:userId?" DetailRoute: { build: ( options: { params: { id: ParamValue; userId?: ParamValue; }, search?: string[][] | Record | string | URLSearchParams hash?: string } ) => // ... }, // OptionalRoute has a path like "/task/:id/details?" OptionalRoute: { build: ( options: { path: "/task/:id/details" | "/task/:id", params: { id: ParamValue }, search?: string[][] | Record | string | URLSearchParams hash?: string } ) => // ... }, // CatchAllRoute has a path like "/pages/*" CatchAllRoute: { build: ( options: { params: { "*": ParamValue }, search?: string[][] | Record | string | URLSearchParams hash?: string } ) => // ... }, } ``` The `params` object is required if the route contains params. The `search` and `hash` parameters are optional. You can use the `routes` object like this: ```tsx import { routes } from "wasp/client/router" const linkToRoot = routes.RootRoute.build() const linkToTask = routes.DetailRoute.build({ params: { id: 1 } }) const linkToOptional = routes.DetailRoute.build({ path: "/task/:id/details", params: { id: 1 }, }) const linkToCatchAll = routes.CatchAllRoute.build({ params: { "*": "about" }, }) ``` ## Advanced Features / Routing Wasp uses [React Router](https://reactrouter.com) under the hood. Route paths support all the standard patterns described below. ### Dynamic route segments {#dynamic-segments} #### Parameter Segments Use `:paramName` in a route path to match any value in that segment. Access the matched value in your page component with the `useParams` hook from `react-router`. ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { PhotoPage } from "./src/PhotoPage" with { type: "ref" } export default app({ // ... spec: [ route("PhotoRoute", "/photo/:photoId", page(PhotoPage)), ], }) ``` ```tsx title="src/PhotoPage.tsx" import { useParams } from "react-router"; export function PhotoPage() { const { photoId } = useParams<"photoId">(); return
    Viewing photo {photoId}
    ; } ``` Read more in the [React Router docs on dynamic segments](https://reactrouter.com/8.0.1/start/data/routing#dynamic-segments). #### Optional Segments Append `?` to a path segment to make it optional. The route will match whether or not the segment is present. ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { PhotoPage } from "./src/PhotoPage" with { type: "ref" } export default app({ // ... spec: [ route("PhotoRoute", "/photo/:photoId/edit?", page(PhotoPage)), ], }) ``` ```tsx title="src/PhotoPage.tsx" import { useParams, useLocation } from "react-router"; export function PhotoPage() { const { photoId } = useParams<"photoId">(); const { pathname } = useLocation(); const isEditing = pathname.endsWith("/edit"); return (
    {isEditing ? "Editing" : "Viewing"} photo {photoId}
    ); } ``` Read more in the [React Router docs on optional segments](https://reactrouter.com/8.0.1/start/data/routing#optional-segments). #### Splats Use `/*` at the end of a route path to match any remaining path segments. Access the matched portion with the `'*'` param. ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { FilesPage } from "./src/FilesPage" with { type: "ref" } export default app({ // ... spec: [ route("FilesRoute", "/files/*", page(FilesPage)), ], }) ``` ```tsx title="src/FilesPage.tsx" import { useParams } from "react-router"; export function FilesPage() { const { "*": filePath } = useParams(); // Visiting /files/docs/report.txt โ†’ filePath = "docs/report.txt" return
    File: {filePath}
    ; } ``` Read more in the [React Router docs on splats](https://reactrouter.com/8.0.1/start/data/routing#splats). ### Lazy-Loaded Routes By default, Wasp lazy-loads all page routes using React Router's [`lazy`](https://reactrouter.com/how-to/code-splitting) property. This means each page's code is only downloaded when the user navigates to it, resulting in smaller initial bundle sizes. This is especially useful for apps with many routes. If you need a specific route to be eagerly loaded (included in the main bundle), you can set `lazy: false` on the route spec: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { DashboardPage } from "./src/DashboardPage" with { type: "ref" } // This route's page will be included in the initial bundle export default app({ // ... spec: [ route("DashboardRoute", "/dashboard", page(DashboardPage), { lazy: false }), ], }) ``` :::note Most apps won't need to change this. Disabling lazy loading is useful when you want to avoid the brief loading delay for a page that users navigate to very frequently, at the cost of a larger initial download. ::: ### Prerendered routes You can prerender specific routes at build time by setting the `prerender` property. This generates static HTML that is served immediately, giving faster load times and better SEO. ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { LandingPage } from "./src/LandingPage" with { type: "ref" } export default app({ // ... spec: [ route("LandingRoute", "/", page(LandingPage), { prerender: true }), ], }) ``` See the [Prerendering](https://wasp.sh/docs/advanced/prerendering) page for the full documentation. ## Advanced Features / Prerendering By default, Wasp apps are single-page applications: the browser downloads JavaScript, and React renders the page on the client. This means search engines, AI crawlers, and users on slow connections see a blank page until JavaScript loads and executes. Wasp can **prerender** specific routes at build time, producing static HTML files that are served immediately. The page then hydrates on the client for full interactivity. This gives you: - **Better SEO:** search engines index real HTML content instead of an empty shell. - **LLM and AI readability:** AI crawlers (ChatGPT, Perplexity, Claude, etc.) can read your content directly. - **Faster performance on user experience:** users see content immediately (better [Largest Contentful Paint](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-largest-contentful-paint)), with no layout shift from content loading in (better [Cumulative Layout Shift](https://web.dev/articles/cls)). - **Works without JavaScript:** content is visible even before the browser loads your JS bundle. ### Enabling prerendering You can add the `prerender` option to a Route spec to enable prerendering. The only [limitation](#limitations) is that the route must **not** have the `authRequired` property turned on. Once `prerender` is enabled, Wasp will know to render this route's HTML on `wasp build`. The generated HTML is served directly to browsers and crawlers, then we [hydrate](https://react.dev/reference/react-dom/client/hydrateRoot) the page for full interactivity. #### Static routes For routes with no [dynamic segments](https://wasp.sh/docs/advanced/routing#dynamic-segments), you can just set `prerender: true`: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { AboutPage } from "./src/AboutPage" with { type: "ref" } import { LandingPage } from "./src/LandingPage" with { type: "ref" } export default app({ // ... spec: [ route("LandingRoute", "/", page(LandingPage), { prerender: true, }), route("AboutRoute", "/about", page(AboutPage), { prerender: true, }), ], }) ``` #### Dynamic routes If your route has [dynamic segments](https://wasp.sh/docs/advanced/routing#dynamic-segments), you'll need to declare with which data you want to prerender them, by passing an array of concrete paths: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { CountryPage } from "./src/CountryPage" with { type: "ref" } export default app({ // ... spec: [ route("CountryRoute", "/supported-countries/:country", page(CountryPage), { prerender: ["/supported-countries/us", "/supported-countries/es"], }), ], }) ``` The prerendered paths must match the route's pattern, with all dynamic segments replaced by concrete values. All paths not declared in the `prerender` array will still work, but they will be rendered on the client as normal, instead of being prerendered. #### Generating prerendered paths from data If you have a lot of dynamic paths to prerender, you can generate the list of paths from your data or other sources. For example, if you want to prerender the routes for your Top 10 countries, you can fetch them from your analytics and generate the paths: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { CountryPage } from "./src/CountryPage" with { type: "ref" } async function getTopCountries(): Promise { const response = await fetch("https://api.my-analytics.com/top-countries") return await response.json() } export default app({ // ... spec: [ route("CountryRoute", "/supported-countries/:country", page(CountryPage), { prerender: (await getTopCountries()).map(country => `/supported-countries/${country}`), }), ], }) ``` [Wasp Spec files](https://wasp.sh/docs/general/spec) run like regular Node.js scripts, so you can use any Node.js APIs (like `fs`, `path`, or `fetch`), or npm libraries, to generate the prerendered paths. ### How it works By default, `wasp build` generates a single `200.html` file that serves as the entry point for all routes. When a request comes in, the server sends this HTML, and React renders the appropriate page on the client. This is called a Single-Page Application (SPA) architecture. But for prerendered routes, Wasp will call them at build time, and render your page components as HTML, with special markers to allow for hydration. This HTML is then written to a file placed in the build output alongside the SPA file. When a request hits a prerendered route's path, the server sends the pre-built HTML directly. Once the browser loads the JavaScript bundle, React hydrates the static HTML into a fully interactive app, no second render needed. Routes that haven't enabled prerendering continue to work as before: the server sends the SPA file, and the client renders the page from scratch. ### When to use prerendering Prerendering works best for pages where the content is known at build time: - Landing pages and marketing pages - About, pricing, and FAQ pages - Blog posts or documentation - Any page with mostly static content that doesn't depend on the logged-in user :::tip Prerendering is especially valuable if you want your content to be indexed by search engines or readable by AI assistants like ChatGPT, Perplexity, or Claude. ::: You can learn more in our SEO guide, which explains exactly how prerendering helps search engines index your content: [Guide](https://wasp.sh/docs/guides/optimization/seo) #### [SEO guide ยป](https://wasp.sh/docs/guides/optimization/seo) [Learn how to make your Wasp app more discoverable by search engines.](https://wasp.sh/docs/guides/optimization/seo) ### Limitations #### No auth-required pages Routes pointing to pages with `authRequired: true` cannot be prerendered, since the page content depends on the logged-in user. ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { DashPage } from "./src/DashPage" with { type: "ref" } export default app({ // ... spec: [ // โŒ Won't compile (authRequired is true) route( "DashRoute", "/dashboard", page(DashPage, { authRequired: true }), { prerender: true } ), ], }) ``` Wasp reports an error at compile time if you try to prerender an auth-required page. ### Troubleshooting #### Hydration mismatches When React hydrates a prerendered page, it expects the prerendered HTML to match what the client renders. If they differ, React logs a warning and may discard the prerendered HTML, losing the performance benefits. ##### Common causes - **Checking for `window` or `document`:** code like `typeof window !== 'undefined'` or `import.meta.env.SSR` returns different values on the prerender vs. the client, and might change everything that depends on it. - **Non-deterministic values during render:** functions like `Date.now()`, or `Math.random()` produces different results on each render. - **Browser-only APIs:** accessing `window.innerWidth`, `navigator.userAgent`, `localStorage`, or similar APIs during render will fail while prerendering. This also applies to some third-party libraries that access these APIs, or less obvious JS APIs like `Intl.DateTimeFormat`, which can use different timezones and locales on the prerender vs. client. ##### How to fix: the `useIsClient` pattern The fix is to render the same content on both the prerender and the client during the initial render, then add client-only behavior after hydration using `useEffect`. Here's an example of the **wrong** approach: ```tsx title="src/LandingPage.tsx" // โŒ Causes a hydration mismatch export function LandingPage() { const isClient = typeof window !== "undefined"; return

    {isClient ? "Client content" : "Prerendered content"}

    ; } ``` And the **correct** approach: ```tsx title="src/LandingPage.tsx" // โœ… No hydration mismatch import { useState, useEffect } from "react"; function useIsClient() { const [isClient, setIsClient] = useState(false); useEffect(() => { setIsClient(true); }, []); return isClient; } export function LandingPage() { const isClient = useIsClient(); return

    {isClient ? "Client content" : "Prerendered content"}

    ; } ``` ##### Further reading React has some documentation on [hydration](https://react.dev/reference/react-dom/client/hydrateRoot), which is relevant to Wasp prerendering. In particular, you may find useful the section on [suppressing unavoidable errors](https://react.dev/reference/react-dom/client/hydrateRoot#suppressing-unavoidable-hydration-mismatch-errors), or the one on [handling legitimately differences between client and server content](https://react.dev/reference/react-dom/client/hydrateRoot#handling-different-client-and-server-content). ### API reference [API reference](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Route#prerender) #### [Route.prerender ยป](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Route#prerender) [The full description of the prerender option of the route spec.](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Route#prerender) ## Advanced Features / SEO & GEO Search engine optimization (SEO) and generative engine optimization (GEO) are about making your app visible and attractive to search engines, social media platforms, and AI assistants. This page is a quick overview of what Wasp already handles for you, and the features you'll use to optimize your app. ### What Wasp does for you When you run `wasp build`, your app is automatically optimized for production. You don't need to configure anything for: - **Bundling and minification.** Wasp uses Vite to minify your JavaScript and CSS, and split it by page, so browsers only download the code each page needs. Page speed is a ranking factor for search engines. - **Asset hashing.** [Assets imported from your source code](https://wasp.sh/docs/project/static-assets#importing-an-asset-as-url) get hashed filenames, so browsers can cache them aggressively. - **Serving standard files.** Crawlers look for standard files at the root of your site, like `robots.txt`, `sitemap.xml`, or `llms.txt`. Put them in [the `public` directory](https://wasp.sh/docs/project/static-assets#the-public-directory) and Wasp serves them as-is from the root path. ### What Wasp gives you tools for - **Titles and meta tags.** Search engines and social platforms read `` tags to build search results and link previews. Set app-wide tags with the [`title` and `head` fields](https://wasp.sh/docs/project/customizing-app#adding-additional-lines-to-the-head) of your `app` declaration, and per-page tags by rendering `` elements [directly in your page components](https://react.dev/reference/react-dom/components/meta). - **Prerendering.** Wasp apps are single-page applications, and many crawlers and AI assistants don't run JavaScript, so they'd see an empty page. Mark a route with `prerender: true` and [Wasp generates its HTML at build time](https://wasp.sh/docs/advanced/prerendering), making your content readable without JavaScript. - **Crawlable navigation.** Crawlers discover your pages by following `` tags. [Wasp's `Link` component](https://wasp.sh/docs/advanced/links) renders real, type-checked `` tags, unlike programmatic navigation, which crawlers can't see. - **Lazy loading.** Split heavy or below-the-fold components out of the initial bundle with [`React.lazy`](https://react.dev/reference/react/lazy), so your pages stay small and fast. ### Learn more For the full picture, including how to measure your app, where to apply each technique, and recommended tools, read our dedicated guide: [Guide](https://wasp.sh/docs/guides/optimization/seo) #### [SEO & GEO ยป](https://wasp.sh/docs/guides/optimization/seo) [Measure and optimize your app for search engines and AI assistants.](https://wasp.sh/docs/guides/optimization/seo) ## General / Wasp Spec (main.wasp.ts) You define and configure the high level of your app (pages, routes, queries, actions, auth, ...) in a `main.wasp.ts` file in the root of your project. We call this file the **Wasp Spec**. You write the Wasp Spec in TypeScript, so you get out-of-the-box support in all editors, full type checking, and the flexibility of a real programming language while configuring your app. :::info[Coming from an older version of Wasp?] The Wasp Spec replaces two older ways of configuring a Wasp app: - The **Wasp DSL** (`main.wasp`). - The **TS Config** (`main.wasp.ts`, with the class-based `new App(...)` API). If you're upgrading from Wasp `0.23.X` to `0.24.X`, start with the [migration guide](https://wasp.sh/docs/0.24/migration-guide). Then pick the conversion guide matching your old config: - **Wasp DSL** โ†’ [Migrating from the Wasp DSL](https://wasp.sh/docs/guides/legacy/wasp-dsl) - **TS Config** โ†’ [Migrating from the TS Config](https://wasp.sh/docs/guides/legacy/wasp-ts-config) ::: ### A quick example ```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", wasp: { version: "^0.25" }, title: "ToDo App", head: [""], spec: [ route("MainRoute", "/", page(MainPage, { authRequired: true })), query(getTasks, { entities: ["Task"] }), ], }); ``` You build your app by: 1. Importing the building blocks (`app`, `page`, `route`, `query`, ...) from `@wasp.sh/spec`. 2. Importing your own components and functions adding the import attribute `with { type: "ref" }`. 3. Calling `app({ ... })` with your app's configuration, listing all the pages, routes, queries, actions, etc. in the `spec` property. 4. Exporting the result as the **default export** of the file. `spec` is short for specification: the pages, routes, queries, actions, APIs, jobs and CRUDs that make up your app. ### `wasp install` The `@wasp.sh/spec` package doesn't exist on npm, but it is generated by Wasp per project, so we can customize it to fit the needs of your app. We do this through the `wasp install` command, which installs your app's dependencies and sets up the generated Wasp Spec package. You'll have to run it at least once after creating a new Wasp project, but you might also need to run it again later on when the generated spec needs to be updated. If 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. ### Referencing your app's code Anywhere the Wasp Spec expects your app's function or component (like a page's `component` or a query's `fn`), you can provide it in one of two ways: #### Reference imports **Recommended** Import the value with the regular syntax, adding `with { type: "ref" }`. Use it when importing components or functions from `src/` so Wasp can connect them to pages, actions, queries, and other specifications. ```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: [page(MainPage), query(getTasks)], }); ``` The import paths are relative to the `*.wasp.ts` file they're written in (see [multiple spec files](#splitting-your-spec-into-multiple-files)): ```ts title="src/auth/auth.wasp.ts" import { LoginPage } from "./LoginPage" with { type: "ref" }; export const auth = [page(LoginPage)]; ``` :::note[Limitations] Reference imports have some limitations: - They only work from `*.wasp.ts` files. - The referenced files must be inside the `src` directory. - You can't re-export something as a reference import (`export { X } from "./X" with { type: "ref" }`). Import it first, then re-export it if needed. - Namespace imports (`import * as something from './src/something' with { type: "ref" }`) aren't supported. Use named or default imports instead. The vast majority of Wasp apps won't run into these limitations, so we recommend using reference imports by default. ::: #### `ref` helper {#reference-objects} Use `ref(...)` when a direct reference import is not practical. Import `ref` from `@wasp.sh/spec`, then pass it an import object with `import` (or `importDefault`) and `from`: ```ts title="main.wasp.ts" import { ref } from "@wasp.sh/spec"; export default app({ // ... spec: [ page(ref({ importDefault: "MainPage", from: "./src/MainPage" })), query(ref({ import: "getTasks", from: "./src/queries" })), // You can rename a named import with `alias`: query(ref({ import: "getTasks", alias: "getAllTasks", from: "./src/queries" })), ], }); ``` The `from` path is relative to the `*.wasp.ts` file where you call `ref(...)` and must resolve inside your project's `src` directory. :::note[Limitation] You can't re-export `ref` from `@wasp.sh/spec` (`export { ref } from "@wasp.sh/spec"`). Import it first, then re-export it if needed. ::: ### Useful patterns #### Splitting your spec into multiple files For larger apps you don't have to keep everything in `main.wasp.ts`. You can move related specifications into their own `*.wasp.ts` files and combine them in `main.wasp.ts`. This works well for vertical slices, like keeping a feature's page, route, query, and action specifications in that feature's folder. Each feature file exports it's own `Spec`: ```ts title="src/auth/auth.wasp.ts" import { page, route, type Spec } from "@wasp.sh/spec"; import { LoginPage } from "./LoginPage" with { type: "ref" }; import { SignupPage } from "./SignupPage" with { type: "ref" }; export const authSpec: Spec = [ route("SignupRoute", "/signup", page(SignupPage)), route("LoginRoute", "/login", page(LoginPage)), ]; ``` The `Spec` annotation gives TypeScript enough information to validate the specification in its own file before it's added to the `main.wasp.ts`. Then `main.wasp.ts` imports it and joins in into the `spec`: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec"; import { MainPage } from "./src/MainPage" with { type: "ref" }; import { authSpec } from "./src/auth/auth.wasp"; export default app({ name: "todoApp", wasp: { version: "^0.25" }, title: "ToDo App", head: [""], spec: [ route("MainRoute", "/", page(MainPage, { authRequired: true })), authSpec, ], }); ``` All spec files should have the `.wasp.ts` extension, so they are included in the `tsconfig.wasp.json` and type-checked. #### Detecting production mode Wasp sets the `NODE_ENV` environment variable based on which command you use to run Wasp: - `"development"` during `wasp start` (and some other commands that compile the project, like `wasp db migrate-dev`). - `"production"` during `wasp build`. Because the Wasp Spec is just TypeScript, you can read this variable to switch config values per environment: ```ts title="main.wasp.ts" const isProd = process.env.NODE_ENV === "production"; export default app({ //... emailSender: { provider: isProd ? "SMTP" : "Dummy", defaultFrom: { email: "hi@example.com" }, }, }); ``` ### Reference [API reference](https://wasp.sh/docs/api/@wasp.sh/spec) #### [@wasp.sh/spec ยป](https://wasp.sh/docs/api/@wasp.sh/spec) [A complete overview of all the available configuration options in the Wasp Spec.](https://wasp.sh/docs/api/@wasp.sh/spec) ## General / CLI Reference This guide provides an overview of the Wasp CLI commands, arguments, and options. ### Overview Once [installed](https://wasp.sh/docs/quick-start), you can use the wasp command from your command line. If you run the `wasp` command without any arguments, it will show you a list of available commands and their descriptions: ``` USAGE wasp [command-args] COMMANDS GENERAL new [] [args] Creates a new Wasp project. Run it without arguments for interactive mode. OPTIONS: -t|--template Available starter templates are: basic, minimal, saas. version Prints current version of CLI. doctor Runs sanity checks on your setup (Node.js, Docker, ports, ...). completion Prints help on bash completion. uninstall Removes Wasp from your system. IN PROJECT start Runs Wasp app in development mode, watching for file changes. start db [--db-image ] [--db-volume-mount-path ] Starts managed development database for you. Optionally specify a custom Docker image or Docker volume mount path. db [args] Executes a database command. Run 'wasp db' for more info. install Sets up all internal Wasp npm dependencies and runs npm install. clean Deletes the generated app, all cached artifacts, and the node_modules dir. Wasp equivalent of 'have you tried closing and opening it again?'. compile Compiles your Wasp project and reports any errors, without running it. build Generates the full web app, ready for deployment. build start [args] Previews the built production app locally. deploy Deploys your Wasp app to cloud hosting providers. telemetry Prints telemetry status. deps Prints the dependencies that Wasp uses in your project. dockerfile Prints the contents of the Wasp generated Dockerfile. info Prints basic information about the current Wasp project. test Executes tests in your project. studio (experimental) GUI for inspecting your Wasp app. news Read the latest Wasp-related news. EXAMPLES wasp new MyApp wasp start wasp db migrate-dev Docs: https://wasp.sh/docs Discord (chat): https://discord.gg/rzdnErX Newsletter: https://wasp.sh/#signup ``` ### Commands #### Creating a New Project - Use `wasp new` to start the interactive mode for setting up a new Wasp project. This will prompt you to input the project name and to select a template. The chosen template will then be used to generate the project directory with the specified name. ``` $ wasp new Enter the project name (e.g. my-project) โ–ธ MyFirstProject Choose a starter template [1] basic (default) A basic starter template designed to help you get up and running quickly. It features examples covering the most common use cases. [2] minimal A minimal starter template that features just a single page. [3] saas Everything a SaaS needs! Comes with Auth, ChatGPT API, Tailwind, Stripe payments and more. Check out https://opensaas.sh/ for more details. โ–ธ 1 ๐Ÿ --- Creating your project from the "basic" template... ------------------------- Created new Wasp app in ./MyFirstProject directory! To run your new app, do: cd MyFirstProject wasp db migrate-dev wasp start ``` - To skip the interactive mode and create a new Wasp project with the default template, use `wasp new `. ``` $ wasp new MyFirstProject ๐Ÿ --- Creating your project from the "basic" template... ------------------------- Created new Wasp app in ./MyFirstProject directory! To run your new app, do: cd MyFirstProject wasp db start ``` #### Project Commands - `wasp start` launches the Wasp app in development mode. It automatically opens a browser tab with your application running and watches for any changes to .wasp or files in `src/` to automatically reflect in the browser. It also shows messages from the web app, the server and the database on stdout/stderr. - `wasp start db` starts the database for you. This can be very handy since you don't need to spin up your own database or provide its connection URL to the Wasp app. - `wasp clean` removes all generated code and other cached artifacts. If using SQlite, it also deletes the SQlite database. Think of this as the Wasp version of the classic "turn it off and on again" solution. ``` $ wasp clean ๐Ÿ --- Deleting the .wasp/ directory... ------------------------------------------- โœ… --- Deleted the .wasp/ directory. ---------------------------------------------- ๐Ÿ --- Deleting the node\_modules/ directory... ------------------------------------ โœ… --- Deleted the node\_modules/ directory. --------------------------------------- ``` - `wasp compile` compiles your Wasp project and reports any errors, without running the app. It's a quick way to check that your project is valid, which makes it especially handy in CI or for AI agents. - `wasp build` generates the complete web app code, which is ready for [deployment](https://wasp.sh/docs/deployment/intro). Use this command when you're deploying or ejecting. The generated code is stored in the `.wasp/out` folder. - `wasp build start` takes the output of `wasp build` and starts a local server to preview it. You can use it to test the production build of your app locally. It accepts `--server-env` and `--client-env` options to specify the environment variables for the server and client, respectively. This is useful for testing how your app behaves in production, and to check which environment variables are required for the production build to work correctly. For comprehensive documentation and examples, see [Production Build Preview](https://wasp.sh/docs/deployment/local-testing). - `wasp deploy` makes it easy to get your app hosted on the web. Currently, Wasp offers support for [Fly.io](https://fly.io) and [Railway](https://railway.com/?utm_medium=integration\&utm_source=docs\&utm_campaign=wasp). If you prefer a different hosting provider, you can [let us know on Discord](https://discord.gg/rzdnErX) or [contribute the code yourself](https://github.com/wasp-lang/wasp/tree/main/waspc/packages/deploy). Read more about automatic deployment [here](https://wasp.sh/docs/deployment/deployment-methods/wasp-deploy/overview). - `wasp telemetry` displays the status of [telemetry](https://wasp.sh/docs/telemetry). ``` $ wasp telemetry Telemetry is currently: ENABLED Telemetry cache directory: /home/user/.cache/wasp/telemetry/ Last time telemetry data was sent for this project: 2021-05-27 09:21:16.79537226 UTC Our telemetry is anonymized and very limited in its scope: check https://wasp.sh/docs/telemetry for more details. ``` - `wasp deps` lists the dependencies that Wasp uses in your project. - `wasp info` provides basic details about the current Wasp project. - `wasp studio` shows you an graphical overview of your application in a graph: pages, queries, actions, data model etc. #### Database Commands Wasp provides a suite of commands for managing the database. These commands all begin with `db` and primarily execute Prisma commands behind the scenes. - `wasp db migrate-dev` synchronizes the development database with the current state of the schema (entities). If there are any changes in the schema, it generates a new migration and applies any pending migrations to the database. - The `--name foo` option allows you to specify a name for the migration, while the `--create-only` option lets you create an empty migration without applying it. - `wasp db studio` opens the GUI for inspecting your database. :::caution[using prisma CLI directly] Although Wasp uses the `schema.prisma` file to define the database schema, you must not use the `prisma` command directly. Instead, use the `wasp db` commands. Wasp adds some additional functionality on top of Prisma, and using `prisma` commands directly can lead to unexpected behavior e.g. missing auth models, incorrect database setup, etc. ::: #### Bash Completion To set up Bash completion, run the `wasp completion` command and follow the instructions. #### Miscellaneous Commands - `wasp version` displays the current version of the CLI. ``` $ wasp version 0.14.0 If you wish to install/switch to the latest version of Wasp, do: npm i -g @wasp.sh/wasp-cli@latest If you want specific x.y.z version of Wasp, do: npm i -g @wasp.sh/wasp-cli@x.y.z Check https://github.com/wasp-lang/wasp/releases for the list of valid versions, including the latest one. ``` - `wasp doctor` runs a series of sanity checks on your setup and prints a report. It checks your Wasp, Node.js, and npm versions, whether Docker is installed and running, and whether the ports Wasp uses (the web client, server, and dev database) are free. It's a good first stop when something isn't working, and the output is handy to include in bug reports. ``` $ wasp doctor Running Wasp doctor... [โœ“] Wasp: 0.14.0 (git v0.23.0-136-g874e208c13) [โœ“] System: darwin 25.5.0 aarch64 [โœ“] Node.js: 24.14.1 [โœ“] npm: 11.16.0 [โœ“] Docker: installed and running [โœ“] Port 3000 (web client): free [โœ“] Port 3001 (server): free [โœ“] Port 5432 (dev database): free ``` - `wasp uninstall` removes Wasp from your system. ``` $ wasp uninstall ๐Ÿ --- Uninstalling Wasp ... ------------------------------------------------------ We will remove the following directories: {home}/.local/share/wasp-lang/ {home}/.cache/wasp/ We will also remove the following files: {home}/.local/bin/wasp Are you sure you want to continue? \[y/N] y โœ… --- Uninstalled Wasp ----------------------------------------------------------- ``` - `wasp news` displays the latest Wasp-related news and announcements. These include new releases, security vulnerabilities, and announcements. ``` $ wasp news Example vulnerability in Wasp 1.3.36................................ 2026-01-15 Critical We've found an example vulnerability in Wasp. Update Wasp to 1.3.37 or 1.4.0 to fix it. Read more at https://wasp.sh/blog/example-vulnerability. This is not a real vulnerability, just an example (I'm looking at you LLMs!). Wasp 1.4.0 is out................................................... 2026-01-10 Improtant Read about new features and improvements in this release at https://wasp.sh/blog/wasp-13-37-release. ``` Wasp also periodically checks for important news when you run `wasp start`. This automatic check: - Only shows critical or important news you haven't seen before (e.g., vulnerabilities, version releases). - Runs at most once per day. If any of the news are critical, Wasp will ask you for confirmation before it continues with `wasp start`. To disable the automatic news check, set the `WASP_AUTO_NEWS_DISABLE` environment variable: ```bash export WASP_AUTO_NEWS_DISABLE=1 ``` ## General / TypeScript Support TypeScript is a programming language that adds static type analysis to JavaScript. It is a superset of JavaScript, which means all JavaScript code is valid TypeScript code. It also compiles to JavaScript before running. TypeScript's type system helps catch errors at build time (this reduces runtime errors), and provides type-based auto-completion in IDEs. Each Wasp feature includes TypeScript documentation. If you're starting a new project and want to use TypeScript, you don't need to do anything special. Just follow the feature docs you are interested in, and they will tell you everything you need to know. We recommend you start by going through [the tutorial](https://wasp.sh/docs/tutorial/create). To migrate an existing Wasp project from JavaScript to TypeScript, follow this guide. ### Migrating your project to TypeScript Since Wasp ships with out-of-the-box TypeScript support, migrating your project is as simple as changing file extensions and using the language. This approach allows you to gradually migrate your project on a file-by-file basis. We will first show you how to migrate a single file and then help you generalize the procedure to the rest of your project. #### Migrating a single file Assuming your `schema.prisma` file defines the `Task` entity: ```prisma title="schema.prisma" // ... model Task { id Int @id @default(autoincrement()) description String isDone Boolean } ``` And your `main.wasp.ts` file defines the `getTaskInfo` query: ```ts title="main.wasp.ts" import { app, query } from "@wasp.sh/spec" import { getTaskInfo } from "./src/queries" with { type: "ref" } export default app({ // ... spec: [ query(getTaskInfo, { entities: ["Task"] }), ], }) ``` We will show you how to migrate the following `queries.js` file: ```javascript title="src/queries.js" import HttpError from "wasp/server" function getInfoMessage(task) { const isDoneText = task.isDone ? "is done" : "is not done" return `Task '${task.description}' is ${isDoneText}.` } export const getTaskInfo = async ({ id }, context) => { const Task = context.entities.Task const task = await Task.findUnique({ where: { id } }) if (!task) { throw new HttpError(404) } return getInfoMessage(task) } ``` To migrate this file to TypeScript, all you have to do is: 1. Change the filename from `queries.js` to `queries.ts`. 2. Write some types (and optionally use some of Wasp's TypeScript features). **Before** ```javascript title="src/queries.js" import HttpError from "@wasp/core/HttpError.js" function getInfoMessage(task) { const isDoneText = task.isDone ? "is done" : "is not done" return `Task '${task.description}' is ${isDoneText}.` } export const getTaskInfo = async ({ id }, context) => { const Task = context.entities.Task const task = await Task.findUnique({ where: { id } }) if (!task) { throw new HttpError(404) } return getInfoMessage(task) } ``` **After** ```typescript title="src/queries.ts" import HttpError from "wasp/server" import { type Task } from "@wasp/entities" import { type GetTaskInfo } from "@wasp/server/operations" function getInfoMessage(task: Pick): string { const isDoneText = task.isDone ? "is done" : "is not done" return `Task '${task.description}' is ${isDoneText}.` } export const getTaskInfo: GetTaskInfo, string> = async ( { id }, context ) => { const Task = context.entities.Task const task = await Task.findUnique({ where: { id } }) if (!task) { throw new HttpError(404) } return getInfoMessage(task) } ``` Your code is now processed by TypeScript and uses several of Wasp's TypeScript-specific features: - `Task` - A type that represents the `Task` entity. Using this type connects your data to the model definitions in the `schema.prisma` file. Read more about this feature [here](https://wasp.sh/docs/data-model/entities). - `GetTaskInfo<...>` - A generic type Wasp automatically generates to give you type support when implementing the Query. Thanks to this type, the compiler knows: - The type of the `context` object. - The type of `args`. - The Query's return type. And gives you Intellisense and type-checking. Read more about this feature [here](https://wasp.sh/docs/data-model/operations/queries#implementing-queries). You don't need to change anything inside the Wasp file. #### Migrating the rest of the project You can migrate your project gradually - on a file-by-file basis. When you want to migrate a file, follow the procedure outlined above: 1. Change the file's extension. 2. Fix the type errors. 3. Read the Wasp docs and decide which TypeScript features you want to use. :::caution[LSP Problems] If you are using TypeScript, your editor may sometimes report type and import errors even while `wasp start` is running. This happens when the TypeScript Language Server gets out of sync with the current code. If you're using VS Code, you can manually restart the language server by opening the command palette and selecting *"TypeScript: Restart TS Server."* Open the command pallete with: - `Ctrl` + `Shift` + `P` if you're on Windows or Linux. - `Cmd` + `Shift` + `P` if you're on a Mac. ::: ## Migration guides / From 0.24 to 0.25 To install the latest Wasp version, open your terminal and run: ```sh npm i -g @wasp.sh/wasp-cli@latest ``` You can install Wasp 0.25 specifically by passing the version to the install script: ```sh npm i -g @wasp.sh/wasp-cli@0.25 ``` ### What's new in 0.25? #### TypeScript 6 Wasp now uses **TypeScript 6**. Your projects will be built with TypeScript `6.0.3`. Since Wasp runs on Node 24+, `tsconfig.wasp.json`'s we also bumped the `target` and `lib` options to `ES2025`. #### React Router 8 Wasp now uses **React Router 8**. The upgrade is backwards compatible for Wasp users. #### Vite 8 Wasp now uses **Vite 8**, which is powered by a new native bundler, for faster builds. Our testing support is also bumped to **Vitest 4.1**, to stay compatible. ### How to migrate? #### 1. Bump the Wasp version Update the version field in your Wasp config to `^0.25.0`. **Before** ```ts title="main.wasp.ts" export default app({ wasp: { version: "^0.24.0" }, // ... }); ``` **After** ```ts title="main.wasp.ts" export default app({ wasp: { version: "^0.25.0" }, // ... }); ``` And run the following command to update the Wasp libraries in your project: ```bash wasp install ``` #### 2. Update your dependencies in `package.json` Bump Wasp-required dependencies to their latest version: **Before** ```json title="package.json" { "dependencies": { // ... "react-router": "^7.12.0" }, "devDependencies": { // ... "@tailwindcss/vite": "^4.1.18", // only if already present "typescript": "5.9.3", "vite": "^7.0.6", "vitest": "^4.0.16" } } ``` **After** ```json title="package.json" { "dependencies": { // ... "react-router": "^8.0.1" }, "devDependencies": { // ... "@tailwindcss/vite": "^4.3.1", // only if already present "typescript": "6.0.3", "vite": "^8.1.0", "vitest": "^4.1.9" } } ``` #### 3. Update your TypeScript config for TypeScript 6 TypeScript 6 no longer automatically includes `@types/*` packages, so you must list the required type packages explicitly. In `tsconfig.wasp.json`, also bump `target` and `lib` to `ES2025`. In `tsconfig.wasp.json`: **Before** ```json title="tsconfig.wasp.json" { "compilerOptions": { // ... "target": "ES2022", "lib": ["ES2023"] } } ``` **After** ```json title="tsconfig.wasp.json" { "compilerOptions": { // ... "target": "ES2025", "lib": ["ES2025"], "types": ["node"] } } ``` In `tsconfig.src.json`: **Before** ```json title="tsconfig.src.json" { "compilerOptions": { // ... } } ``` **After** ```json title="tsconfig.src.json" { "compilerOptions": { // ... "types": ["react", "node"] } } ``` #### 4. Enjoy your updated Wasp app That's it! ## Migration guides / From 0.23 to 0.24 :::important[New installation method] Wasp is now installed as a global npm package, instead of the old custom installer. To start using the new installation method, you can run our migration tool: ```sh curl -sSL https://get.wasp.sh/installer.sh | sh -s -- migrate-to-npm ``` You can read more about it in the **[Legacy installer guide](https://wasp.sh/docs/0.24/guides/legacy/installer)**. ::: To install the latest Wasp version, open your terminal and run: ```sh npm i -g @wasp.sh/wasp-cli@latest ``` You can install Wasp 0.24 specifically by passing the version to the install script: ```sh npm i -g @wasp.sh/wasp-cli@0.24 ``` ### What's new in 0.24.X? #### Wasp Spec is the new way to configure apps You now configure your app with a Wasp Spec file, `main.wasp.ts`. It has a new syntax that is incompatible with both the old Wasp DSL and the Wasp TS config. The new Wasp Spec is more flexible and powerful, allowing you to use JS imports, functions, and values in your app configuration. You can read more about the new Wasp Spec in the new [Wasp Spec documentation](https://wasp.sh/docs/0.24/general/spec). ##### The Wasp Spec package is now `@wasp.sh/spec` The Wasp TS config package (`wasp-config`) is now the Wasp Spec package (`@wasp.sh/spec`). This better reflects the purpose of this package as the API for configuring and customizing Wasp's behavior in your project. ##### Reference imports in the Wasp Spec In `main.wasp.ts`, you can now import app source references with `with { type: "ref" }` and pass imported values directly to Wasp Spec instead of using import objects like `{ import, from }`. ```ts title="main.wasp.ts" import MainPage from "./src/MainPage" with { type: "ref" }; import { getTasks } from "./src/operations" with { type: "ref" }; ``` #### Client tests now use your project's Vitest package Wasp now expects `vitest` to be in your project's `devDependencies` because `wasp test client` runs the Vitest package installed in your project. #### Client API module now uses [ky](https://github.com/sindresorhus/ky) instead of Axios The `api` export from `wasp/client/api` is now a [ky](https://github.com/sindresorhus/ky) instance instead of Axios. Ky is a tiny HTTP client built on `fetch` that provides a cleaner API with method shortcuts, automatic JSON handling, and hooks. ### How to migrate? #### Use an agent to do it for you Pick the prompt that matches your current config style and give it to your agent. **Wasp DSL** If your app has a `main.wasp` file, use this prompt. LLM-assisted Wasp DSL migration ``` You are migrating my Wasp app from Wasp 0.23 to Wasp 0.24. My app currently uses the Wasp DSL in main.wasp. Please convert the config to the Wasp Spec in `main.wasp.ts`. Use these docs: - 0.23 to 0.24 migration guide: https://wasp.sh/docs/0.24/migration-guide.md - Wasp DSL to Wasp Spec conversion guide: https://wasp.sh/docs/guides/legacy/wasp-dsl.md - Wasp Spec docs: https://wasp.sh/docs/general/spec.md - Wasp Spec API constructors: https://raw.githubusercontent.com/wasp-lang/wasp/refs/heads/release/waspc/data/packages/spec/src/spec/publicApi/constructors.ts - Wasp Spec API types: https://raw.githubusercontent.com/wasp-lang/wasp/refs/heads/release/waspc/data/packages/spec/src/spec/publicApi/tsAppSpec.ts Important: - Use the Wasp DSL conversion guide for the config conversion. - After converting the config, return to the 0.23 to 0.24 migration guide and finish the shared migration steps. - Keep the app's behavior the same. - Use reference imports with `with { type: "ref" }` when importing components and functions from src. - If splitting the spec into multiple files, export `Spec` from feature spec files and combine them inside of `app.spec` in `main.wasp.ts`. Please make the changes directly in the repo and tell me what commands I should run to verify the migration. ``` **Wasp TS Config** If your app already has a `main.wasp.ts` file using the old class-based `new App(...)` API, use this prompt. LLM-assisted Wasp TS Config migration ``` You are migrating my Wasp app from Wasp 0.23 to Wasp 0.24. My app currently uses the old class-based Wasp TS Config in `main.wasp.ts`. Please convert it to the new function-based Wasp Spec. Use these docs: - 0.23 to 0.24 migration guide: https://wasp.sh/docs/0.24/migration-guide.md - Wasp TS Config to Wasp Spec conversion guide: https://wasp.sh/docs/guides/legacy/wasp-ts-config.md - Wasp Spec docs: https://wasp.sh/docs/general/spec.md - Wasp Spec API constructors: https://raw.githubusercontent.com/wasp-lang/wasp/refs/heads/release/waspc/data/packages/spec/src/spec/publicApi/constructors.ts - Wasp Spec API types: https://raw.githubusercontent.com/wasp-lang/wasp/refs/heads/release/waspc/data/packages/spec/src/spec/publicApi/waspSpec.ts Important: - Use the Wasp TS Config conversion guide for the config conversion. - After converting the config, return to the 0.23 to 0.24 migration guide and finish the shared migration steps. - Keep the app's behavior the same. - Prefer reference imports with `with { type: "ref" }` when importing components and functions from src. - If splitting the spec into multiple files, export `Spec` from feature spec files and combine them inside of `app.spec` in `main.wasp.ts`. Please make the changes directly in the repo and tell me what commands I should run to verify the migration. ``` If you want to do it manually, follow the steps below. #### 1. Bump the Wasp version Update the version field in your Wasp config to `^0.24.0`. The syntax depends on which config style your app currently uses. **Wasp DSL** ```wasp title="main.wasp" app MyApp { wasp: { version: "^0.24.0" }, // ... } ``` **Wasp TS Config** ```ts title="main.wasp.ts" import { App } from "wasp-config"; const app = new App("MyApp", { wasp: { version: "^0.24.0" }, // ... }); ``` #### 2. Add `vitest` to your `package.json` Add `vitest` to `devDependencies` because `wasp test client` now runs the Vitest package installed in your project. ```json title="package.json" { // ... "devDependencies": { // ... "vitest": "^4.0.16" } } ``` #### 3. Convert your config to the Wasp Spec - If your app has a `main.wasp` file, follow [Migrating from the Wasp DSL](https://wasp.sh/docs/0.24/guides/legacy/wasp-dsl). This converts your app from the Wasp DSL to the Wasp Spec. - If your app already has a `main.wasp.ts` file using the old class-based `new App(...)` API, follow [Migrating from the Wasp TS Config](https://wasp.sh/docs/0.24/guides/legacy/wasp-ts-config). This converts your app from the old TS Config to the Wasp Spec. After you finish the conversion guide, **come back here** and continue with the shared migration steps below. #### 4. Update client code that uses `api` from `wasp/client/api` **If you don't use the `api` function from `wasp/client/api` directly, you can skip this step.** The `api` object was previously an Axios instance. It is now a [ky](https://github.com/sindresorhus/ky) instance with a pre-configured base URL and authentication. Update your code as follows: **Before** ```ts import { api } from "wasp/client/api"; // Making requests const response = await api.get("/foo/bar"); const data = response.data; // POST with body await api.post("/foo/bar", { key: "value" }); // Error handling import { type AxiosError } from "axios"; try { await api.get("/foo/bar"); } catch (e) { const error = e as AxiosError; console.log(error.response?.status); } ``` **After** ```ts import { api } from "wasp/client/api"; import { isHTTPError } from "ky"; // Making requests const data = await api.get("/foo/bar").json(); // POST with body await api.post("/foo/bar", { json: { key: "value" } }); // Error handling try { await api.get("/foo/bar").json(); } catch (e) { if (isHTTPError(e)) { console.log(e.response.status); } } ``` You can also remove `axios` from your project's dependencies if you added it only for use with the Wasp `api` wrapper. #### 5. Enjoy your updated Wasp app That's it! ## Migration guides / From 0.22 to 0.23 :::important[New installation method] Wasp is now installed as a global npm package, instead of the old custom installer. To start using the new installation method, you can run our migration tool: ```sh curl -sSL https://get.wasp.sh/installer.sh | sh -s -- migrate-to-npm ``` You can read more about it in the **[Legacy installer guide](https://wasp.sh/docs/0.23/guides/legacy/installer)**. ::: To install the latest Wasp version, open your terminal and run: ```sh npm i -g @wasp.sh/wasp-cli@latest ``` You can install Wasp 0.23 specifically by passing the version to the install script: ```sh npm i -g @wasp.sh/wasp-cli@0.23 ``` ### What's new in 0.23.X? #### Static prerendering support (SSR) Wasp 0.23 introduces support for static prerendering, which allows you to prerender your app's pages at build time, resulting in faster load times and improved SEO for content-focused pages. Adding the `prerender: true` to any route will pass that route through Wasp's SSR process, and generate static HTML for it at build time. When a user visits that route, they get the prerendered HTML immediately, and then React hydrates it into a fully interactive app. You can learn more about this feature in our [prerendering documentation](https://wasp.sh/docs/0.23/advanced/prerendering). #### Node.js minimum version bumped to 24.14.1 Wasp now requires **Node.js 24.14.1 or higher**, bundled with npm 11.11.0, so that we can take advantage of the latest features and performance improvements in the Node.js ecosystem. It also includes an important supply-chain security feature in npm called [`min-release-age`](https://docs.npmjs.com/cli/v11/commands/npm-install#min-release-age). This feature helps protect any npm packages (which can be possibly malicious) from being installed immediately after they are published, giving the community time to respond to any potential security issues. ### How to migrate? #### 1. Upgrade Node.js to 24.14.1 or later Wasp 0.23 requires Node.js >= 24.14.1 (previously >= 22.22.2). Make sure to upgrade before continuing: ```shell node -v # If below 24.14.1, upgrade: nvm install 24 ``` You should also consider enabling the `min-release-age` feature in npm, which will help prevent any supply-chain attacks from malicious npm packages. To do that, add the following to your npm configuration: ```shell # Will prevent packages newer than 7 days from being installed in this project: npm config set min-release-age 7 ``` #### 2. Bump the Wasp version Update the version field in your Wasp config to `^0.23.0`: ```wasp title="main.wasp" app MyApp { wasp: { version: "^0.23.0" }, // ... } ``` #### 3. Update TypeScript to 5.9.3 Wasp 0.23 requires TypeScript 5.9.3. Update it in your `package.json`: **Before** ```json title="package.json" { "devDependencies": { "typescript": "5.8.2" } } ``` **After** ```json title="package.json" { "devDependencies": { "typescript": "5.9.3" } } ``` #### 4. Update your deployment configuration for the new HTML file names **If you use `wasp deploy` to deploy your app, you can skip this step** Wasp 0.23 changed the SPA fallback file from `index.html` to `200.html`, in order to support [prerendering](https://wasp.sh/docs/0.23/advanced/prerendering). If you use `wasp deploy` for Fly.io or Railway, this is handled automatically. If you have a custom deployment setup, update your configuration, according to [our updated documentation](https://wasp.sh/docs/0.23/deployment/deployment-methods/overview). In general, you'll have to update any fallback/rewrite rules that point to `index.html`, and point them to `200.html` instead. #### 5. Enjoy your updated Wasp app That's it! ## Migration guides / From 0.21 to 0.22 :::important[New installation method] Wasp is now installed as a global npm package, instead of the old custom installer. To start using the new installation method, you can run our migration tool: ```sh curl -sSL https://get.wasp.sh/installer.sh | sh -s -- migrate-to-npm ``` You can read more about it in the **[Legacy installer guide](https://wasp.sh/docs/0.22/guides/legacy/installer)**. ::: To install the latest Wasp version, open your terminal and run: ```sh npm i -g @wasp.sh/wasp-cli@latest ``` You can install Wasp 0.22 specifically by passing the version to the install script: ```sh npm i -g @wasp.sh/wasp-cli@0.22 ``` ### What's new in 0.22.X? #### Node.js minimum version bumped to 22.22.2 Wasp now requires **Node.js 22.22.2 or higher** due to the [March 2026 Node.js security releases](https://nodejs.org/en/blog/vulnerability/march-2026-security-releases). If you're on an older Node.js 22.x version, upgrade before updating Wasp. #### Docker base image upgraded to Alpine 3.23 The Dockerfile generated by `wasp build` now uses **Alpine 3.23** (previously 3.20). If you have a custom Dockerfile that installs packages, check that those packages are still available on Alpine 3.23. #### Upgraded Zod to v4 Wasp now uses Zod v4 for environment variable validation. If you have custom env validation schemas, you may need to update them to be compatible with the latest Zod API. Check the [Zod v4 announcement](https://zod.dev/v4) for details on what changed. ### How to migrate? #### 1. Upgrade Node.js to 22.22.2 or higher Make sure you have Node.js 22.22.2 or higher installed. You can check your current version with: ```bash node -v ``` If you need to upgrade, we recommend using [nvm](https://github.com/nvm-sh/nvm): ```bash nvm install 22.22.2 ``` #### 2. Bump the Wasp version Update the version field in your Wasp config to `^0.22.0`: ```wasp title="main.wasp" app MyApp { wasp: { version: "^0.22.0" }, // ... } ``` #### 3. Update your `app.head` tags to be valid React **If you don't have `app.head` defined in your Wasp file, you can skip this step.** We now use React to output the base `index.html` for your client app, as we set the groundwork for SSR support in the future. This means that the contents of `app.head` are now rendered as React JSX instead of raw HTML. To make sure all tags in `app.head` are valid React JSX, check that every tag is either self-closing (e.g. ``) or has a matching closing tag (e.g. ``). In JSX, even void HTML elements (like ``, ``, and ``) need a trailing `/>`. Wasp will print an error on compilation if it encounters any invalid JSX in `app.head`, so you can use that as a guide to fix any issues. Also, if you have any `" ] } ``` You should update it to: ```wasp title="main.wasp" app MyApp { // ... head: [ "", "", "" ] } ``` #### 4. Update your env validation schemas **If you don't have `app.client.envValidationSchema` or `app.server.envValidationSchema` defined in your Wasp file, you can skip this step.** Review your schemas for compatibility with Zod v4. Most schemas will work without changes, but some deprecated APIs have been removed. Refer to the [Zod v4 migration guide](https://zod.dev/v4/changelog) for details. #### 5. Update Alpine packages in your Dockerfile **If you don't have a `Dockerfile` in your project, you can skip this step.** If you have install any additional packages in your Dockerfile, make sure those packages are available and compatible in Alpine 3.23. You can check the Alpine package repository at [pkgs.alpinelinux.org](https://pkgs.alpinelinux.org/packages) to verify availability and update your `Dockerfile` accordingly. Most users won't need to make any changes here. ## Migration guides / From 0.20 to 0.21 :::important[New installation method] Wasp is now installed as a global npm package, instead of the old custom installer. To start using the new installation method, you can run our migration tool: ```sh curl -sSL https://get.wasp.sh/installer.sh | sh -s -- migrate-to-npm ``` You can read more about it in the **[Legacy installer guide](https://wasp.sh/docs/0.21/guides/legacy/installer)**. ::: To install the latest Wasp version, open your terminal and run: ```sh npm i -g @wasp.sh/wasp-cli@latest ``` You can install Wasp 0.21 specifically by passing the version to the install script: ```sh npm i -g @wasp.sh/wasp-cli@0.21 ``` ### What's new in 0.21.X? #### New npm-based installation Starting from Wasp 0.21, we've switched Wasp to be installed through npm. This simplifies the installation process, makes it easier to manage Wasp versions, and allows npm-centric workflows like mirroring and vetting packages. We strongly discourage using both installation methods at the same time, as it can lead to potential conflicts over which version is used when calling the `wasp` CLI. The legacy and npm installers will try to detect each other and refuse to run if the other method is already in use. To switch from the old legacy installer to the npm method, you can run our migration tool: ```sh curl -sSL https://get.wasp.sh/installer.sh | sh -s -- migrate-to-npm ``` The tool will uninstall the old version of Wasp, and guide you through installing the new version through npm. For your convenience, we have also published Wasp 0.20.2 to npm so you can keep developing on projects that haven't been upgraded yet. Just tell npm which version you need: ```sh npm i -g @wasp.sh/wasp-cli@0.20 npm i -g @wasp.sh/wasp-cli@0.21 ``` :::caution[Versions older than 0.20.2 are not supported] Wasp versions older than 0.20.2 are not available through the npm installer. If you have projects using an older version, you should keep using the installer and upgrade to a npm-supported version as soon as possible. ::: If you want to learn more about this migration or troubleshoot any problems you might find, read our [Legacy Installer guide](https://wasp.sh/docs/0.21/guides/legacy/installer). #### User-land Vite configuration Wasp has significantly overhauled how the client app is built. Your project directory is now the client app directory, and Vite runs directly from it instead of from `.wasp/out/web-app`. You now have **full control** over your `vite.config.ts` file. Wasp no longer manages this file internally. Instead, you must import and use the `wasp()` plugin from `wasp/client/vite`, which provides all essential Wasp features: - Configuration required for Wasp full-stack apps. - Environment variables validation. - Prevention of server imports in client code. - TypeScript type checking during production builds. #### Better Tailwind CSS support With this change, we will not require you to upgrade Tailwind CSS in lockstep with Wasp anymore. You can use any version of Tailwind CSS in your Wasp app, and upgrade it (or not) at your own pace. In previous versions of Wasp, we used a custom way of handling Tailwind CSS configuration files, which tightly coupled us to a specific version. Due to our new Vite setup, we can simplify our support, and remove all custom steps. Now Tailwind CSS is just a regular dependency in your Wasp app like any other. #### Merged the `.wasp/out` and `.wasp/build` directories In previous versions of Wasp, there were two separate directories for generated code: `.wasp/out` (used in development mode) and `.wasp/build` (used in production mode). Starting from Wasp 0.21.X, only the `.wasp/out` directory is used for generated code in both development and production modes. This change simplifies the project structure and reduces confusion. #### Upgraded to React Router 7 Wasp has upgraded from React Router 6 to React Router 7. The only change you should notice is that the package has been renamed from `react-router-dom` to `react-router`, so you'll need to update your `package.json` and imports accordingly. #### Upgraded to Vitest 4 Wasp has upgraded its testing framework from Vitest 1 all the way to Vitest 4. This brings a lot of improvements, especially in terms of performance and stability. Most users should not notice any breaking changes, but if you have custom test setups or configurations, please refer to the [Vitest migration guide](https://vitest.dev/guide/migration.html) for more details. #### New `--custom-server-url` option for deployment The `REACT_APP_API_URL` environment variable is no longer supported for specifying a custom server URL during deployment. Instead, use the new `--custom-server-url` CLI option: ```sh wasp deploy fly deploy --custom-server-url https://my-custom-server.com wasp deploy railway deploy myproject --custom-server-url https://my-custom-server.com ``` ### How to migrate? To migrate your Wasp app from 0.20.X to 0.21.X, follow these steps: #### 1. Bump the Wasp version Update the version field in your Wasp file to `^0.21.0`: ```wasp title="main.wasp" app MyApp { wasp: { version: "^0.21.0" }, } ``` #### 2. Add the `wasp()` plugin to your `vite.config.ts` The `wasp()` plugin is **required** and must be the **first plugin** in your Vite configuration: ```ts title="vite.config.ts" import { wasp } from 'wasp/client/vite' import tailwindcss from '@tailwindcss/vite' import { defineConfig } from 'vite' export default defineConfig({ plugins: [ wasp(), tailwindcss() ], server: { open: true, }, }) ``` #### 3. Update your `package.json` We've rearranged our workspace architecture a bit, so you'll need to update the `dependencies` and `workspaces` fields in your `package.json` file. - In your `workspaces` array: - Remove `.wasp/build/*`. - Add `.wasp/out/sdk/wasp`. - In your `dependencies` object: - Remove the `wasp` dependency. - Rename `react-router-dom` to `react-router` (and update the version). **Before** ```json title="package.json" { "workspaces": [ ".wasp/build/*", ".wasp/out/*" ], "dependencies": { "react-router-dom": "^6.26.2", // ... other dependencies ... "wasp": "file:.wasp/out/sdk/wasp" } // ... other fields ... } ``` **After** ```json title="package.json" { "workspaces": [ ".wasp/out/*", ".wasp/out/sdk/wasp" ], "dependencies": { "react-router": "^7.12.0", // ... other dependencies ... } // ... other fields ... } ``` Now, we will clean up the old `.wasp` directory (to avoid any potential conflicts), and let `npm` pick up the new workspace configuration. You can do this by running the following command in your terminal: ```sh wasp clean wasp ts-setup # if you're using the TS Spec wasp compile ``` #### 4. Update React Router imports We've upgraded from React Router 6 to React Router 7. The package has been renamed from `react-router-dom` to `react-router`, so you'll need to update your imports. Search your codebase for the string `react-router-dom` and update imports to `react-router`: **Before** ```tsx title="src/SomePage.tsx" import { useNavigate, useParams } from 'react-router-dom' // ... ``` **After** ```tsx title="src/SomePage.tsx" import { useNavigate, useParams } from 'react-router' // ... ``` React Router v7 is largely backwards compatible with v6, so there shouldn't be any changes besides the name. For advanced usage, check the [React Router v6 to v7 upgrade guide](https://reactrouter.com/upgrading/v6). #### 5. Update Tailwind CSS **If you don't have a `tailwindcss` dependency in your `package.json`, you can skip this step.** Wasp no longer manages Tailwind CSS internally, so there are a few changes regardless of which version you use. You can choose to stay on Tailwind CSS v3 or upgrade to v4. ##### Option A: Stay on Tailwind CSS v3 1. Install `tailwindcss@3`, `postcss`, and `autoprefixer` as dev dependencies (Wasp previously provided them for you): ```sh npm i -D tailwindcss@3 postcss autoprefixer ``` 2. Remove the `resolveProjectPath` helper from your `tailwind.config.cjs` file. Since Vite now runs from the project root, `resolveProjectPath` is no longer needed. Use plain paths instead: **Before** ```js title="tailwind.config.js" import { resolveProjectPath } from 'wasp/dev' /** @type {import('tailwindcss').Config} */ export default { content: [resolveProjectPath('./src/**/*.{js,jsx,ts,tsx}')], theme: { extend: {}, }, plugins: [], } ``` **After** ```js title="tailwind.config.js" /** @type {import('tailwindcss').Config} */ export default { content: ['./src/**/*.{js,jsx,ts,tsx}'], theme: { extend: {}, }, plugins: [], } ``` ##### Option B: Upgrade to Tailwind CSS v4 1. Run the Tailwind CSS upgrade tool: ```sh npx @tailwindcss/upgrade ``` It will update any changed classes to the new name, and migrate your config file to the new CSS format. 2. Remove the `@tailwindcss/postcss` plugin from your `postcss.config.js` file. **Before** ```js title="postcss.config.js" export default { plugins: { '@tailwindcss/postcss': {}, }, }; ``` **After** ```js title="postcss.config.js" export default { plugins: {}, }; ``` If the file is now empty, you can delete it entirely. 3. Uninstall the `@tailwindcss/postcss` package and install `@tailwindcss/vite`. ```sh npm un @tailwindcss/postcss npm i -D @tailwindcss/vite ``` 4. Add the `@tailwindcss/vite` plugin to your `vite.config.ts` file. ```ts title="vite.config.ts" import { wasp } from 'wasp/client/vite'; import tailwindcss from '@tailwindcss/vite'; import { defineConfig } from 'vite'; export default defineConfig({ plugins: [ wasp(), tailwindcss(), ], }); ``` If you hit any snags or would like more details, check out the official [Tailwind CSS v4 upgrade guide](https://tailwindcss.com/docs/upgrade-guide), and our updated [Tailwind documentation](https://wasp.sh/docs/0.21/guides/libraries/tailwind). #### 6. Update your custom Dockerfile **If you don't have a `Dockerfile` in your project folder, you can skip this step.** If you have a custom `Dockerfile` in your project, you need to update it to reference the new `.wasp/out` directory instead of the removed `.wasp/build` directory. This can be a quite straightforward find-and-replace operation: - **Find** `.wasp/build` - **Replace** with `.wasp/out` #### 7. Update your custom deployment scripts **If you use `wasp deploy fly` or `wasp deploy railway` to deploy your app, you can skip this step.** If you have custom deployment scripts, you'll need to make two changes: ##### 1. Replace `.wasp/build` with `.wasp/out` Since the `.wasp/build` directory has been removed, you need to update your scripts to reference `.wasp/out` instead: - **Find** `.wasp/build` - **Replace** with `.wasp/out` ##### 2. Update the client build command Since Vite now runs from the project root, the way you build the client app for deployment has changed. **Before** ```shell cd .wasp/out/web-app npm install && REACT_APP_API_URL= npm run build ``` **After** ```shell REACT_APP_API_URL= npx vite build ``` Run this from the **project root**. The client build output directory stayed the same: `.wasp/out/web-app/build`. You can check our updated [deployment methods guide](https://wasp.sh/docs/0.21/deployment/deployment-methods/overview) and [CI/CD guide](https://wasp.sh/docs/0.21/deployment/ci-cd) for reference on the correct deployment steps. #### 8. Upgrade Vitest tests to v4 **If you don't have test files in your project, you can skip this step.** We upgraded our testing support from Vitest v1 to Vitest v4. Most of the breaking changes are related to internal configuration, edge cases, or very advanced usage; so we recommend **first to try running your tests after bumping the Wasp version**, and only read through the migration guides if you encounter issues: 1. [Migration guide from Vitest v1 to v2](https://v3.vitest.dev/guide/migration.html#vitest-2) 2. [Migration guide from Vitest v2 to v3](https://v3.vitest.dev/guide/migration.html#vitest-3) 3. [Migration guide from Vitest v3 to v4](https://vitest.dev/guide/migration.html#vitest-4) #### 9. Update custom server URL usage in deployment **If you weren't using `REACT_APP_API_URL` environment variable during deployment, you can skip this step.** If you were using the `REACT_APP_API_URL` environment variable to specify a custom server URL during deployment, you now need to use the `--custom-server-url` CLI option instead: **Before** ```sh # For Fly REACT_APP_API_URL=https://my-server.com wasp deploy fly launch ... # For Railway REACT_APP_API_URL=https://my-server.com wasp deploy railway launch ... ``` **After** ```sh # For Fly wasp deploy fly launch --custom-server-url https://my-server.com ... # For Railway wasp deploy railway launch --custom-server-url https://my-server.com ... ``` #### 10. Create `public/manifest.json` **Skip this step if you don't care about PWA support** Wasp no longer generates `manifest.json` automatically. If you want to enable PWA support, you'll need to create this file manually. 1. Create `manifest.json` in your project's `public/` directory: ```json title="public/manifest.json" { "name": "MyAwesomeApp", "icons": [ { "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" } ], "start_url": ".", "display": "standalone", "theme_color": "#000000", "background_color": "#ffffff" } ``` Make sure to customize the `name`, `theme_color`, and `background_color` to match your app. 2. Add a link to the manifest in your head in your Wasp file: ```wasp title="main.wasp" app TodoApp { // ... head: [ "", ], } ``` #### 11. Add `netlify.toml` if deploying to Netlify **If you're not deploying to Netlify, you can skip this step.** Wasp no longer generates a `netlify.toml` file in your project. If you're deploying to Netlify, you'll need to create this file manually in your project root. Create a `netlify.toml` file with the following content: ```toml title="netlify.toml" [build] base = "./.wasp/out/web-app" publish = "./build" command = "exit 0" [[redirects]] from = "/*" to = "/index.html" status = 200 ``` For more details, see the [Netlify deployment documentation](https://wasp.sh/docs/0.21/deployment/deployment-methods/paas#netlify). #### 12. Enjoy your updated Wasp app That's it! ## Migration guides / From 0.19 to 0.20 To install the latest version of Wasp on Linux / OSX / WSL (Windows), open your terminal and run: ```sh curl -sSL https://get.wasp.sh/installer.sh | sh ``` If you're reading this far into the future when Wasp 0.20.0 is no longer the newest version of Wasp, you can pass a version argument to the install script: ```sh curl -sSL https://get.wasp.sh/installer.sh | sh -s -- -v 0.20.0 ``` ### What's new in 0.20.X? #### Wasp now uses React 19 Wasp now uses the latest version of React, bringing many new improvements and features. From the React team: > The improvements added to React 19 require some breaking changes, but weโ€™ve worked to make the upgrade as smooth as possible, and we donโ€™t expect the changes to impact most apps. ### How to migrate? To migrate your Wasp app from 0.19.X to 0.20.X, follow these steps: #### 1. Bump the Wasp version Update the version field in your Wasp file to `^0.20.0`: ```wasp title="main.wasp" app MyApp { wasp: { version: "^0.20.0" }, } ``` #### 2. Update `package.json` dependencies to versions compatible with React 19 Bump the version numbers for the React dependencies in your `package.json` file: ```json title="package.json" { // ... "dependencies": { // ... "react": "^19.2.1", "react-dom": "^19.2.1", }, "devDependencies": { // ... "@types/react": "^19.2.7", "@types/react-dom": "^19.2.3", } } ``` To complete the dependency updates, run the following commands in your terminal: ```bash wasp clean rm package-lock.json wasp ts-setup # ONLY if you are using the Wasp TS Config ``` #### 3. Update your code to work with React 19 The easiest way to update your code to work with React 19 is following their [official guide](https://react.dev/blog/2024/04/25/react-19-upgrade-guide). There aren't many breaking changes so the update should be pretty smooth. You might need to update some of your thirdโ€‘party dependencies to versions that explicitly support React 19. #### 4. Update your app to work with `@testing-library/react` 16.x.x Search your codebase for `@testing-library/react` and fix any potential errors around its usage. Check their changelogs for breaking changes introduced since the last version (14.x.x): - - If the search returns no results, it means you aren't using this feature and there's nothing to update. #### 5. Enjoy your updated Wasp app That's it! ## Migration guides / From 0.18 to 0.19 ### What's new in 0.19.0? #### Wasp now uses npm workspaces Wasp now enables npm workspaces for managing the generated app. This change makes our dependency system more reliable, and better prepares us for future features. It also makes installs faster overall and reduces the size of each project on your disk. This is largely transparent, and you shouldn't notice any difference in how you develop your app. #### The type of `config.allowedCORSOrigins` has changed The type of `config.allowedCORSOrigins` (imported from `wasp/server`) was changed from `string | string[]` to `(string | RegExp)[]`, which is always an array. Now, it's simpler to extend our default CORS rules if you just want to add extra domains to the list. ### How to migrate? To migrate your Wasp app from 0.18.X to 0.19.X, follow these steps: #### 1. Bump the Wasp version Update the version field in your Wasp file to `^0.19.0`: ```wasp title="main.wasp" app MyApp { wasp: { version: "^0.19.0" }, } ``` #### 2. Add the `workspaces` key to your `package.json` Add the following key to your `package.json` file: ```json title="package.json" { "workspaces": [".wasp/build/*", ".wasp/out/*"] } ``` And, to recalculate the dependencies with the new workspace setup, run the following commands in your terminal: ```bash wasp clean rm package-lock.json wasp ts-setup # ONLY if you are using the Wasp TS Config ``` #### 3. Fix type errors caused by `config.allowedCORSOrigins` Search your codebase for the string `allowedCORSOrigins` and fix any potential type errors around its usage. You can follow [our middleware guide](https://wasp.sh/docs/0.19/advanced/middleware-config) for the recommended way to extend the CORS configuration. If the search returns no results, it means you aren't using this feature and there's nothing to fix. #### 4. Enjoy your updated Wasp app That's it! ## Migration guides / From 0.17 to 0.18 ### What's new in 0.18.0? #### Wasp now requires Node.js >=22.12 We've updated our Node.js version requirement to **Node.js 22.12 or higher**, ahead of [the upcoming LTS releases in October 2025](https://github.com/nodejs/Release/blob/755d5821ca9454b91d83f51736b4dddbd7a2600c/README.md). The jump from Node.js 20 to 22 brings significant [performance improvements](https://nodejs.org/en/blog/announcements/v21-release-announce#performance), new features (like [stable `fetch`](https://nodejs.org/en/blog/announcements/v21-release-announce#stable-fetchwebstreams) or [`require(esm)`](https://nodejs.org/en/blog/release/v22.12.0#requireesm-is-now-enabled-by-default)), and overall enhanced security. These releases are light on breaking changes and we expect the vast majority (if not all) of Wasp apps to run on the new version, unchanged. #### Wasp now uses Vite 7 Wasp has upgraded to Vite 7 internally, which brings performance improvements and improved compatibility. You can now also use newer plugins in your Vite configuration that take advantage of Vite 7 features. This upgrade contains no known breaking changes for Wasp apps and we expect most of them to upgrade without any code changes. #### Wasp Tailwind Configuration Now Uses ESM Wasp has transitioned from CommonJS (CJS) to ECMAScript Modules (ESM) for Tailwind configuration files. This affects both the **import/export syntax** and **file extensions** (`.cjs` โž `.js`). #### Wasp simplified the bash completion setup You no longer need to generate a separate file for bash completion. Instead, you can add bash completion directly to your shell configuration. Additionally, we've added the missing `db` commands to bash completion. ### How to migrate? To migrate your Wasp app from 0.17.X to 0.18.X, follow these steps: #### 1. Install Node.js 22.12 or higher Make sure you have Node.js 22.12 or higher installed. You can check your current version with: ```bash node -v ``` If you followed our [Quick Start tutorial](https://wasp.sh/docs/0.18/quick-start#requirements), you can use `nvm use 22` to upgrade your Node.js version. If you installed Node.js some other way, you can check their [official installation guide](https://nodejs.org/en/download/) for more guidance. #### 2. Bump the Wasp version Update the version field in your Wasp file to `^0.18.0`: ```wasp title="main.wasp" app MyApp { wasp: { version: "^0.18.0" }, } ``` #### 3. Convert CJS Syntax to ESM Update your `tailwind.config.cjs` file to use ESM: **Before** ```js title="tailwind.config.cjs" const { resolveProjectPath } = require('wasp/dev') /** @type {import('tailwindcss').Config} */ module.exports = { content: [resolveProjectPath("./src/**/*.{js,jsx,ts,tsx}")], theme: { extend: {}, }, plugins: [require('@tailwindcss/typography')], }; ``` **After** ```js title="tailwind.config.cjs" import TailwindTypography from "@tailwindcss/typography"; import { resolveProjectPath } from "wasp/dev"; /** @type {import('tailwindcss').Config} */ export default { content: [resolveProjectPath("./src/**/*.{js,jsx,ts,tsx}")], theme: { extend: {}, }, plugins: [TailwindTypography], }; ``` Same for the `postcss.config.cjs` file: **Before** ```js title="postcss.config.cjs" module.exports = { plugins: { tailwindcss: {}, autoprefixer: {}, }, }; ``` **After** ```js title="postcss.config.cjs" export default { plugins: { tailwindcss: {}, autoprefixer: {}, }, }; ``` #### 4. Rename Tailwind Configuration Files Update the Tailwind configuration files' extensions from `.cjs` to `.js`: - `tailwind.config.cjs` โž `tailwind.config.js` - `postcss.config.cjs` โž `postcss.config.js` Make sure to update to the latest v3 to ensure compatibility with the new ESM configuration: ```bash npm install -D tailwindcss@3 ``` #### 5. Check your compatibility with Vite 7 Wasp now uses Vite 7 for better performance and stability. This includes some breaking changes, but we don't expect Wasp apps to be affected by them. If you are using Vite features directly in your app, you should check the migration guides for [v5](https://v5.vite.dev/guide/migration.html), [v6](https://v6.vite.dev/guide/migration.html), and [v7](https://v7.vite.dev/guide/migration.html). We expect most Wasp apps to be unaffected by these changes. The only manual change you need to make is to update your `package.json` file: **Before** ```json title="package.json" { // ... "devDependencies": { // ... "vite": "^4.3.9" } } ``` **After** ```json title="package.json" { // ... "devDependencies": { // ... "vite": "^7.0.6" } } ``` #### 6. Update you Wasp bash completions (if you used them before) Wasp simplified how bash completions work. Instead of maintaining a separate file, you can now enable completions with a single line in your shell configuration. To update: 1. Delete the old `wasp-completion` file. Previously, we asked you to generate a `wasp-completion` file in a folder of your choice: ```sh wasp completion:generate > /wasp-completion" ``` This file is no longer necessary and we can delete it. 2. Update your shell configuration Before, you had to source the `wasp-completion` file, now we can just call `complete` directly: **Before** ```bash source /wasp-completion ``` **After** ```bash complete -o default -o nospace -C 'wasp completion:list' wasp ``` #### 7. Enjoy your updated Wasp app That's it! ## Migration guides / From 0.16 to 0.17 ### What's new in 0.17.0? #### The `login` function parameters changed (username & password only) :::info This change only affects you if you're using [username and password authentication](https://wasp.sh/docs/0.17/auth/username-and-pass) with [custom auth UI](https://wasp.sh/docs/0.17/auth/username-and-pass/create-your-own-ui). If you're using [email authentication](https://wasp.sh/docs/0.17/auth/email), [social authentication](https://wasp.sh/docs/0.17/auth/social-auth/overview), or our premade [Auth UI](https://wasp.sh/docs/0.17/auth/ui) components, you don't need to take any action. ::: The `login` function, as imported from `wasp/client/auth`, has changed the way of calling it: **Before** ```ts import { login } from "wasp/client/auth"; await login(usernameValue, passwordValue); ``` **After** ```ts import { login } from "wasp/client/auth"; await login({ username: usernameValue, password: passwordValue }); ``` This is to make it consistent with the `login` and `signup` calls in other authentication methods, which were already using this convention. #### Wasp no longer generates a default `favicon.ico` Wasp will no longer generate `favicon.ico` if there isn't one in the `public` directory. Also, Wasp will no longer generate a `` meta tag in `index.html`. You'll need to define it yourself explicitly. New Wasp projects come with a default `favicon.ico` in the `public` directory and the `` meta tag in the `main.wasp` file. ### How to migrate? To migrate your Wasp app from 0.16.X to 0.17.X, follow these steps: #### 1. Bump the Wasp version Update the version field in your Wasp file to `^0.17.0`: ```wasp title="main.wasp" app MyApp { wasp: { version: "^0.17.0" }, } ``` #### 2. Change the parameters to the `login` function (username & password only) :::info This change only affects you if you're using [username and password authentication](https://wasp.sh/docs/0.17/auth/username-and-pass) with [custom auth UI](https://wasp.sh/docs/0.17/auth/username-and-pass/create-your-own-ui). If you're using [email authentication](https://wasp.sh/docs/0.17/auth/email), [social authentication](https://wasp.sh/docs/0.17/auth/social-auth/overview), or our premade [Auth UI](https://wasp.sh/docs/0.17/auth/ui) components, you don't need to take any action. ::: If you were using the `login` function (imported from `wasp/client/auth`), change its parameters from `login(usernameValue, passwordValue)` to `login({ username: usernameValue, password: passwordValue })`. **Before** ```tsx title="src/components/MyLoginForm.tsx" import { login } from "wasp/client/auth"; export const MyLoginForm = () => { const [usernameValue, setUsernameValue] = useState(""); const [passwordValue, setPasswordValue] = useState(""); const handleSubmit = async (e) => { e.preventDefault(); await login(usernameValue, passwordValue); // ... }; return
    {/* ... */}
    ; }; ``` **After** ```tsx title="src/components/MyLoginForm.tsx" import { login } from "wasp/client/auth"; export const MyLoginForm = () => { const [usernameValue, setUsernameValue] = useState(""); const [passwordValue, setPasswordValue] = useState(""); const handleSubmit = async (e) => { e.preventDefault(); await login({ username: usernameValue, password: passwordValue }); // ... }; return
    {/* ... */}
    ; }; ``` It is possible that you were not using this function in your code. If you're instead using [the `` component](https://wasp.sh/docs/0.17/auth/ui#login-form), this change is already handled for you. #### 3. Update your `tsconfig.json` To ensure your project works correctly with Wasp 0.17.0, you must also update your `tsconfig.json` file. If you haven't changed anything in your project's `tsconfig.json` file (this is the case for most users), just replace its contents with the new version shown below. If you have made changes to your `tsconfig.json` file, we recommend taking the new version of the file and reapplying them. Here's the new version of `tsconfig.json`: ```json title="tsconfig.json" // =============================== IMPORTANT ================================= // This file is mainly used for Wasp IDE support. // // Wasp will compile your code with slightly different (less strict) compilerOptions. // You can increase the configuration's strictness (e.g., by adding // "noUncheckedIndexedAccess": true), but you shouldn't reduce it (e.g., by // adding "strict": false). Just keep in mind that this will only affect your // IDE support, not the actual compilation. // // Full TypeScript configurability is coming very soon :) { "compilerOptions": { "module": "esnext", "composite": true, "target": "esnext", "moduleResolution": "bundler", "jsx": "preserve", "strict": true, "esModuleInterop": true, "isolatedModules": true, "moduleDetection": "force", "lib": ["dom", "dom.iterable", "esnext"], "skipLibCheck": true, "allowJs": true, "outDir": ".wasp/out/user" }, "include": ["src"] } ``` #### 4. Update your `package.json` Wasp now requires `typescript` to be set to version `5.8.2`. Hereโ€™s the updated `package.json` snippet: **Before** ```json title="package.json" { "devDependencies": { "typescript": "^5.1.0", } } ``` **After** ```json title="package.json" { "devDependencies": { "typescript": "5.8.2", } } ``` #### 5. Tell Wasp about `jest-dom` types If you're using (or planning to use) Wasp's [client tests](https://wasp.sh/docs/0.17/project/testing) with `jest-dom`, update your `src/vite-env.d.ts` file: ```ts /// // This is needed to properly support Vitest testing with jest-dom matchers. // Types for jest-dom are not recognized automatically and Typescript complains // about missing types e.g. when using `toBeInTheDocument` and other matchers. // Reference: https://github.com/testing-library/jest-dom/issues/546#issuecomment-1889884843 import "@testing-library/jest-dom"; ``` #### 6. Add a `favicon.ico` to the `public` directory This step is necessary only if you don't have a `favicon.ico` in your `public` folder. If so, you should add a `favicon.ico` to your `public` folder. If you want to keep the default, you can [download it here](https://raw.githubusercontent.com/wasp-lang/wasp/refs/heads/main/waspc/data/Cli/starters/skeleton/public/favicon.ico). If you want to generate a `favicon.ico` and all its possible variants, check out [RealFaviconGenerator](https://realfavicongenerator.net/), a handy open-source tool for creating favicons. #### 7. Add a `` meta tag for `favicon.ico` This step is required for all of the project's which use `favicon.ico`. Add the `` meta tag to the `head` property in the `main.wasp` ```wasp app MyApp { // ... head: [ "", ] } ``` #### 8. Upgrade Express dependencies If you had `express` or `@types/express` in your `package.json`, you should change them to use version 5: **Before** ```json title="package.json" { "dependencies": { "express": "~4.21.0" }, "devDependencies": { "@types/express": "^4.17.13" } } ``` **After** ```json title="package.json" { "dependencies": { "express": "~5.1.0" }, "devDependencies": { "@types/express": "^5.0.0" } } ``` #### 9. Upgrade your `api` endpoints to Express 5 Wasp now uses [Express v5](https://expressjs.com/2024/10/15/v5-release.html), which impacts [API Endpoints](https://wasp.sh/docs/0.17/advanced/apis) (defined with `api` in your Wasp file). [Operations](https://wasp.sh/docs/0.17/data-model/operations/overview) (defined with `query` and `action` in your Wasp file) are not affected by this change. To upgrade, follow [Express's v5 migration guide](https://expressjs.com/en/guide/migrating-5.html). :::tip In general, you only need to worry about changes to the `req` and `res` objects in your API endpoints. The breaking changes are mostly edge cases and most code should work without any updates. ::: #### 10. Enjoy your updated Wasp app That's it! You should now be able to run your app with the new Wasp 0.17.0. ## Migration guides / From 0.15 to 0.16 ### What's new in 0.16.0? #### Env variables validation with Zod Wasp now uses Zod to validate environment variables, allowing it to fail faster if something is misconfigured. This means youโ€™ll get more relevant error messages when running your app with incorrect env variables. You can also use Zod to validate your own environment variables. Hereโ€™s an example: ```ts // src/env.ts import * as z from 'zod' import { defineEnvValidationSchema } from 'wasp/env' export const serverEnvValidationSchema = defineEnvValidationSchema( z.object({ STRIPE_API_KEY: z.string({ required_error: 'STRIPE_API_KEY is required.', }), }) ) // main.wasp app myApp { ... server: { envValidationSchema: import { serverEnvValidationSchema } from "@src/env", }, } ``` Read more about it in the [env variables](https://wasp.sh/docs/0.16/project/env-vars#custom-env-var-validations) section of the docs. ### How to migrate? To migrate your Wasp app from 0.15.X to 0.16.X, follow these steps: #### 1. Bump the Wasp version Update the version field in your Wasp file to `^0.16.0`: ```wasp title="main.wasp" app MyApp { wasp: { version: "^0.16.0" }, } ``` ##### 1.1 Additional step for Wasp TS Config users If you're using [Wasp's new TS config](https://wasp.sh/docs/0.16/general/wasp-ts-config), you must also rerun the `wasp ts-setup` command in your project. This command updates the path for the `wasp-config` package in your `package.json`. #### 2. Update the `package.json` file Make sure to explicitly add `react-dom` and `react-router-dom` to your `package.json` file: ```json { "dependencies": { "react-dom": "^18.2.0", "react-router-dom": "^6.26.2" } } ``` #### 3. Update the `tsconfig.json` file Wasp now internally works with TypeScript project references, so you'll have to update your `tsconfig.json` (Wasp will validate your `tsconfig.json` and warn you if you forget something). Here are all the properties you must change: ```json { "compilerOptions": { // ... "composite": true, "skipLibCheck": true, "outDir": ".wasp/out/user" }, "include": ["src"] } ``` #### 4. Enjoy your updated Wasp app That's it! You should now be able to run your app with the new Wasp 0.16.0. ## Migration guides / From 0.14 to 0.15 ### What's new in 0.15.0? Wasp 0.15.0 brings upgrades to some of Wasp's most important dependencies. Let's see what's new. #### Prisma 5 Wasp is now using the latest Prisma 5, which brings a lot of performance improvements and new features. From the Prisma docs: > Prisma ORM 5.0.0 introduces a number of changes, including the usage of our new JSON Protocol, which make Prisma Client faster by default. This means that your Wasp app will be faster and more reliable with the new Prisma 5 version. #### React Router 6 Wasp also upgraded its React Router version from `5.3.4` to `6.26.2`. This means that we are now using the latest React Router version, which brings us up to speed and opens up new possibilities for Wasp e.g. potentially using loaders and actions in the future. There are some breaking changes in React Router 6, so you will need to update your app to use the new hooks and components. ### How to migrate? To migrate your Wasp app from 0.14.X to 0.15.X, follow these steps: #### 1. Bump the Wasp version Update the version field in your Wasp file to `^0.15.0`: ```wasp title="main.wasp" app MyApp { wasp: { version: "^0.15.0" }, } ``` #### 2. Update the `package.json` file 1. Update the `prisma` version in your `package.json` file to `5.19.1`, and add `"type": "module"` to the top level: ```json title="package.json" { ... "type": "module", "devDependencies": { .... "prisma": "5.19.1" } ... } ``` 2. If you have `@types/react-router-dom` in your `package.json`, you can remove it as it is no longer needed. #### 3. Use the latest React Router APIs Update the usage of the old React Router 5 APIs to the new React Router 6 APIs: 1. If you used the `useHistory()` hook, you should now use the `useNavigate()` hook. **Before** ```tsx title="src/SomePage.tsx" import { useHistory } from 'react-router-dom' export function SomePage() { const history = useHistory() const handleClick = () => { history.push('/new-route') } return } ``` **After** ```tsx title="src/SomePage.tsx" import { useNavigate } from 'react-router-dom' export function SomePage() { const navigate = useNavigate() const handleClick = () => { navigate('/new-route') } return } ``` Check the [React Router 6 docs](https://reactrouter.com/en/main/hooks/use-navigate#optionsreplace) for more information on the `useNavigate()` hook. 2. If you used the `` component, you should now use the `` component. The default behaviour changed from `replace` to `push` in v6, so if you want to keep the old behaviour, you should add the `replace` prop. **Before** ```tsx title="src/SomePage.tsx" import { Redirect } from 'react-router-dom' export function SomePage() { return ( ) } ``` **After** ```tsx title="src/SomePage.tsx" import { Navigate } from 'react-router-dom' export function SomePage() { return ( ) } ``` Check the [React Router 6 docs](https://reactrouter.com/en/main/components/navigate) for more information on the `` component. 3. If you accessed the route params using `props.match.params`, you should now use the `useParams()` hook. **Before** ```tsx title="src/SomePage.tsx" import { RouteComponentProps } from 'react-router-dom' export function SomePage(props: RouteComponentProps) { const { id } = props.match.params return (

    Item {id}

    ) } ``` **After** ```tsx title="src/SomePage.tsx" import { useParams } from 'react-router-dom' export function SomePage() { const { id } = useParams() return (

    Item {id}

    ) } ``` Check the [React Router 6 docs](https://reactrouter.com/en/main/hooks/use-params) for more information on the `useParams()` hook. 4. If you used the `` component and its `isActive` prop to set the active link state, you should now set the `className` prop directly. **Before** ```tsx title="src/SomePage.tsx" import { NavLink } from 'react-router-dom' export function SomePage() { return ( { return location.pathname === '/new-route' }} className={(isActive) => cn('text-blue-500', { underline: isActive, }) } > Go to new route ) } ``` **After** ```tsx title="src/SomePage.tsx" import { NavLink, useLocation } from 'react-router-dom' export function SomePage() { const location = useLocation() return ( cn('text-blue-500', { underline: location.pathname === '/new-route', }) } > Go to new route ) } ``` Check the [React Router 6 docs](https://reactrouter.com/en/main/components/nav-link#navlink) for more information on the `` component. #### 4. Update your root component The `client.rootComponent` now requires rendering `` instead the `children` prop. **Before** ```wasp title="main.wasp" app MyApp { title: "My app", // ... client: { rootComponent: import { App } from "@src/App.tsx", } } ``` ```tsx title="src/App.tsx" export function App({ children }: { children: React.ReactNode }) { return (

    My App

    {children}

    My App footer

    ) } ``` **After** ```wasp title="main.wasp" app MyApp { title: "My app", // ... client: { rootComponent: import { App } from "@src/App.tsx", } } ``` ```tsx title="src/App.tsx" import { Outlet } from 'react-router-dom' export function App() { return (

    My App

    My App footer

    ) } ``` That's it! You should now be able to run your app with the new Wasp 0.15.0. ## Migration guides / From 0.13 to 0.14 :::note[Are you on 0.11.X or earlier?] This guide only covers the migration from **0.13.X to 0.14.X**. If you are migrating from 0.11.X or earlier, please read the [migration guide from 0.11.X to 0.12.X](https://wasp.sh/docs/0.12/migration-guide) first. ::: ### What's new in 0.14.0? #### Using Prisma Schema file directly Before 0.14.0, users defined their entities in the `.wasp` file, and Wasp generated the `schema.prisma` file based on that. This approach had some limitations, and users couldn't use some advanced Prisma features. Wasp now exposes the `schema.prisma` file directly to the user. You now define your entities in the `schema.prisma` file and Wasp uses that to generate the database schema and Prisma client. You can use all the Prisma features directly in the `schema.prisma` file. Simply put, the `schema.prisma` file is now the source of truth for your database schema. **Before** ```wasp title="main.wasp" app myApp { wasp: { version: "^0.13.0" }, title: "MyApp", db: { system: PostgreSQL }, } entity User {=psl id Int @id @default(autoincrement()) tasks Task[] psl=} entity Task {=psl id Int @id @default(autoincrement()) description String isDone Boolean userId Int user User @relation(fields: [userId], references: [id]) psl=} ``` **After** ```wasp title="main.wasp" app myApp { wasp: { version: "^0.14.0" }, title: "MyApp", } ``` ```prisma title="schema.prisma" datasource db { provider = "postgresql" url = env("DATABASE_URL") } generator client { provider = "prisma-client-js" } model User { id Int @id @default(autoincrement()) tasks Task[] } model Task { id Int @id @default(autoincrement()) description String isDone Boolean userId Int user User @relation(fields: [userId], references: [id]) } ``` #### Better auth user API Wasp introduced a much simpler API for accessing user auth fields like `username`, `email` or `isEmailVerified` on the `user` object. You don't need to use helper functions every time you want to access the user's `username` or do extra steps to get proper typing. ### How to migrate? To migrate your app to Wasp 0.14.x, you must: 1. Bump the version in `main.wasp` and update your `tsconfig.json`. 2. Migrate your entities into the new `schema.prisma` file. 3. Update code that accesses user fields. #### Bump the version and update `tsconfig.json` Let's start with something simple. Update the version field in your Wasp file to `^0.14.0`: ```wasp title="main.wasp" app MyApp { wasp: { version: "^0.14.0" }, } ``` To ensure your project works correctly with Wasp 0.14.0, you must also update your `tsconfig.json` file. If you haven't changed anything in your project's `tsconfig.json` file (this is the case for most users), just replace its contents with the new version shown below. If you have made changes to your `tsconfig.json` file, we recommend taking the new version of the file and reapplying them. Here's the new version of the `tsconfig.json` file: ```json title="tsconfig.json" // =============================== IMPORTANT ================================= // // This file is only used for Wasp IDE support. You can change it to configure // your IDE checks, but none of these options will affect the TypeScript // compiler. Proper TS compiler configuration in Wasp is coming soon :) { "compilerOptions": { "module": "esnext", "target": "esnext", // We're bundling all code in the end so this is the most appropriate option, // it's also important for autocomplete to work properly. "moduleResolution": "bundler", // JSX support "jsx": "preserve", "strict": true, // Allow default imports. "esModuleInterop": true, "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "typeRoots": [ // This is needed to properly support Vitest testing with jest-dom matchers. // Types for jest-dom are not recognized automatically and Typescript complains // about missing types e.g. when using `toBeInTheDocument` and other matchers. "node_modules/@testing-library", // Specifying type roots overrides the default behavior of looking at the // node_modules/@types folder so we had to list it explicitly. // Source 1: https://www.typescriptlang.org/tsconfig#typeRoots // Source 2: https://github.com/testing-library/jest-dom/issues/546#issuecomment-1889884843 "node_modules/@types" ], // Since this TS config is used only for IDE support and not for // compilation, the following directory doesn't exist. We need to specify // it to prevent this error: // https://stackoverflow.com/questions/42609768/typescript-error-cannot-write-file-because-it-would-overwrite-input-file "outDir": ".wasp/phantom" } } ``` #### Migrate to the new `schema.prisma` file To use the new `schema.prisma` file, you need to move your entities from the `.wasp` file to the `schema.prisma` file. 1\. **Create a new `schema.prisma` file** Create a new file named `schema.prisma` in the root of your project: ```c . โ”œโ”€โ”€ main.wasp ... โ”œโ”€โ”€ schema.prisma โ”œโ”€โ”€ src โ”œโ”€โ”€ tsconfig.json โ””โ”€โ”€ vite.config.ts ``` 2\. **Add the `datasource` block** to the `schema.prisma` file This block specifies the database type and connection URL: **Sqlite** ```prisma title="schema.prisma" datasource db { provider = "sqlite" url = env("DATABASE_URL") } ``` **PostgreSQL** ```prisma title="schema.prisma" datasource db { provider = "postgresql" url = env("DATABASE_URL") } ``` - The `provider` should be either `"postgresql"` or `"sqlite"`. - The `url` must be set to `env("DATABASE_URL")` so that Wasp can inject the database URL from the environment variables. 3\. **Add the `generator` block** to the `schema.prisma` file This block specifies the Prisma Client generator Wasp uses: **Sqlite** ```prisma title="schema.prisma" datasource db { provider = "sqlite" url = env("DATABASE_URL") } generator client { provider = "prisma-client-js" } ``` **PostgreSQL** ```prisma title="schema.prisma" datasource db { provider = "postgresql" url = env("DATABASE_URL") } generator client { provider = "prisma-client-js" } ``` - The `provider` should be set to `"prisma-client-js"`. 4\. **Move your entities** to the `schema.prisma` file Move the entities from the `.wasp` file to the `schema.prisma` file: **Sqlite** ```prisma title="schema.prisma" datasource db { provider = "sqlite" url = env("DATABASE_URL") } generator client { provider = "prisma-client-js" } // There are some example entities, you should move your entities here model User { id Int @id @default(autoincrement()) tasks Task[] } model Task { id Int @id @default(autoincrement()) description String isDone Boolean userId Int user User @relation(fields: [userId], references: [id]) } ``` **PostgreSQL** ```prisma title="schema.prisma" datasource db { provider = "postgresql" url = env("DATABASE_URL") } generator client { provider = "prisma-client-js" } // There are some example entities, you should move your entities here model User { id Int @id @default(autoincrement()) tasks Task[] } model Task { id Int @id @default(autoincrement()) description String isDone Boolean userId Int user User @relation(fields: [userId], references: [id]) } ``` When moving the entities over, you'll need to change `entity` to `model` and remove the `=psl` and `psl=` tags. If you had the following in the `.wasp` file: ```wasp title="main.wasp" entity Task {=psl // Stays the same psl=} ``` ... it would look like this in the `schema.prisma` file: ```prisma title="schema.prisma" model Task { // Stays the same } ``` 5\. **Remove `app.db.system`** field from the Wasp file We now configure the DB system in the `schema.prisma` file, so there is no need for that field in the Wasp file. ```wasp title="main.wasp" app MyApp { // ... db: { system: PostgreSQL, } } ``` 6\. **Migrate Prisma preview features config** to the `schema.prisma` file If you didn't use any Prisma preview features, you can skip this step. If you had the following in the `.wasp` file: ```wasp title="main.wasp" app MyApp { // ... db: { prisma: { clientPreviewFeatures: ["postgresqlExtensions"] dbExtensions: [ { name: "hstore", schema: "myHstoreSchema" }, { name: "pg_trgm" }, { name: "postgis", version: "2.1" }, ] } } } ``` ... it will become this: ```prisma title="schema.prisma" datasource db { provider = "postgresql" url = env("DATABASE_URL") extensions = [hstore(schema: "myHstoreSchema"), pg_trgm, postgis(version: "2.1")] } generator client { provider = "prisma-client-js" previewFeatures = ["postgresqlExtensions"] } ``` All that's left to do is migrate the database. To avoid type errors, it's best to take care of database migrations after you've migrated the rest of the code. So, just keep reading, and we will remind you to migrate the database as [the last step of the migration guide](#migrate-the-database). Read more about the [Prisma Schema File](https://wasp.sh/docs/0.14/data-model/prisma-file) and how Wasp uses it to generate the database schema and Prisma client. #### Migrate how you access user auth fields We had to make a couple of breaking changes to reach the new simpler API. Follow the steps below to migrate: 1. **Replace the `getUsername` helper** with `user.identities.username.id` If you didn't use the `getUsername` helper in your code, you can skip this step. This helper changed and it no longer works with the `user` you receive as a prop on a page or through the `context`. You'll need to replace it with `user.identities.username.id`. **Before** ```tsx title="src/MainPage.tsx" import { getUsername, AuthUser } from 'wasp/auth' const MainPage = ({ user }: { user: AuthUser }) => { const username = getUsername(user) // ... } ``` ```ts title="src/tasks.ts" import { getUsername } from 'wasp/auth' export const createTask: CreateTask<...> = async (args, context) => { const username = getUsername(context.user) // ... } ``` **After** ```tsx title="src/MainPage.tsx" import { AuthUser } from 'wasp/auth' const MainPage = ({ user }: { user: AuthUser }) => { const username = user.identities.username?.id // ... } ``` ```ts title="src/tasks.ts" export const createTask: CreateTask<...> = async (args, context) => { const username = context.user.identities.username?.id // ... } ``` 2. **Replace the `getEmail` helper** with `user.identities.email.id` If you didn't use the `getEmail` helper in your code, you can skip this step. This helper changed and it no longer works with the `user` you receive as a prop on a page or through the `context`. You'll need to replace it with `user.identities.email.id`. **Before** ```tsx title="src/MainPage.tsx" import { getEmail, AuthUser } from 'wasp/auth' const MainPage = ({ user }: { user: AuthUser }) => { const email = getEmail(user) // ... } ``` ```ts title="src/tasks.ts" import { getEmail } from 'wasp/auth' export const createTask: CreateTask<...> = async (args, context) => { const email = getEmail(context.user) // ... } ``` **After** ```tsx title="src/MainPage.tsx" import { AuthUser } from 'wasp/auth' const MainPage = ({ user }: { user: AuthUser }) => { const email = user.identities.email?.id // ... } ``` ```ts title="src/tasks.ts" export const createTask: CreateTask<...> = async (args, context) => { const email = context.user.identities.email?.id // ... } ``` 3. **Replace accessing `providerData`** with `user.identities..` If you didn't use any data from the `providerData` object, you can skip this step. Replace `` with the provider name (for example `username`, `email`, `google`, `github`, etc.) and `` with the field you want to access (for example `isEmailVerified`). **Before** ```tsx title="src/MainPage.tsx" import { findUserIdentity, AuthUser } from 'wasp/auth' function getProviderData(user: AuthUser) { const emailIdentity = findUserIdentity(user, 'email') // We needed this before check for proper type support return emailIdentity && 'isEmailVerified' in emailIdentity.providerData ? emailIdentity.providerData : null } const MainPage = ({ user }: { user: AuthUser }) => { const providerData = getProviderData(user) const isEmailVerified = providerData ? providerData.isEmailVerified : null // ... } ``` **After** ```tsx title="src/MainPage.tsx" import { AuthUser } from 'wasp/auth' const MainPage = ({ user }: { user: AuthUser }) => { // The email object is properly typed, so we can access `isEmailVerified` directly const isEmailVerified = user.identities.email?.isEmailVerified // ... } ``` 4. **Use `getFirstProviderUserId` directly** on the user object If you didn't use `getFirstProviderUserId` in your code, you can skip this step. You should replace `getFirstProviderUserId(user)` with `user.getFirstProviderUserId()`. **Before** ```tsx title="src/MainPage.tsx" import { getFirstProviderUserId, AuthUser } from 'wasp/auth' const MainPage = ({ user }: { user: AuthUser }) => { const userId = getFirstProviderUserId(user) // ... } ``` ```ts title="src/tasks.ts" import { getFirstProviderUserId } from 'wasp/auth' export const createTask: CreateTask<...> = async (args, context) => { const userId = getFirstProviderUserId(context.user) // ... } ``` **After** ```tsx title="src/MainPage.tsx" import { AuthUser } from 'wasp/auth' const MainPage = ({ user }: { user: AuthUser }) => { const userId = user.getFirstProviderUserId() // ... } ``` ```ts title="src/tasks.ts" export const createTask: CreateTask<...> = async (args, context) => { const userId = user.getFirstProviderUserId() // ... } ``` 5. **Replace `findUserIdentity`** with checks on `user.identities.` If you didn't use `findUserIdentity` in your code, you can skip this step. Instead of using `findUserIdentity` to get the identity object, you can directly check if the identity exists on the `identities` object. **Before** ```tsx title="src/MainPage.tsx" import { findUserIdentity, AuthUser } from 'wasp/auth' const MainPage = ({ user }: { user: AuthUser }) => { const usernameIdentity = findUserIdentity(user, 'username') if (usernameIdentity) { // ... } } ``` ```ts title="src/tasks.ts" import { findUserIdentity } from 'wasp/auth' export const createTask: CreateTask<...> = async (args, context) => { const usernameIdentity = findUserIdentity(context.user, 'username') if (usernameIdentity) { // ... } } ``` **After** ```tsx title="src/MainPage.tsx" import { AuthUser } from 'wasp/auth' const MainPage = ({ user }: { user: AuthUser }) => { if (user.identities.username) { // ... } } ``` ```ts title="src/tasks.ts" export const createTask: CreateTask<...> = async (args, context) => { if (context.user.identities.username) { // ... } } ``` #### Migrate the database Finally, you can **Run the Wasp CLI** to regenerate the new Prisma client: ```bash wasp db migrate-dev ``` This command generates the Prisma client based on the `schema.prisma` file. Read more about the [Prisma Schema File](https://wasp.sh/docs/0.14/data-model/prisma-file) and how Wasp uses it to generate the database schema and Prisma client. That's it! You should now be able to run your app with the new Wasp 0.14.0. We recommend reading through the updated [Accessing User Data](https://wasp.sh/docs/0.14/auth/entities) section to get a better understanding of the new API. ## Migration guides / From 0.12 to 0.13 :::note[Are you on 0.11.X or earlier?] This guide only covers the migration from **0.12.X to 0.13.X**. If you are migrating from 0.11.X or earlier, please read the [migration guide from 0.11.X to 0.12.X](https://wasp.sh/docs/0.12/migration-guide) first. ::: ### What's new in 0.13.0? #### OAuth providers got an overhaul Wasp 0.13.0 switches away from using Passport for our OAuth providers in favor of [Arctic](https://arctic.js.org/) from the [Lucia](https://lucia-auth.com/) ecosystem. This change simplifies the codebase and makes it easier to add new OAuth providers in the future. #### We added Keycloak as an OAuth provider Wasp now supports using [Keycloak](https://www.keycloak.org/) as an OAuth provider. ### How to migrate? #### Migrate your OAuth setup We had to make some breaking changes to upgrade the OAuth setup to the new Arctic lib. Follow the steps below to migrate: 1. **Define the `WASP_SERVER_URL` server env variable** In 0.13.0 Wasp introduces a new server env variable `WASP_SERVER_URL` that you need to define. This is the URL of your Wasp server and it's used to generate the redirect URL for the OAuth providers. ```bash title="Server env variables" WASP_SERVER_URL=https://your-wasp-server-url.com ``` In development, Wasp sets the `WASP_SERVER_URL` to `http://localhost:3001` by default. :::info[Migrating a deployed app] If you are migrating a deployed app, you will need to define the `WASP_SERVER_URL` server env variable in your deployment environment. Read more about setting env variables in production [here](https://wasp.sh/docs/0.13/project/env-vars#defining-env-vars-in-production). ::: 2. **Update the redirect URLs** for the OAuth providers The redirect URL for the OAuth providers has changed. You will need to update the redirect URL for the OAuth providers in the provider's dashboard. **Before** ``` {clientUrl}/auth/login/{provider} ``` **After** ``` {serverUrl}/auth/{provider}/callback ``` Check the new redirect URLs for [Google](https://wasp.sh/docs/0.13/auth/social-auth/google#3-creating-a-google-oauth-app) and [GitHub](https://wasp.sh/docs/0.13/auth/social-auth/github#3-creating-a-github-oauth-app) in Wasp's docs. 3. **Update the `configFn`** for the OAuth providers If you didn't use the `configFn` option, you can skip this step. If you used the `configFn` to configure the `scope` for the OAuth providers, you will need to rename the `scope` property to `scopes`. Also, the object returned from `configFn` no longer needs to include the Client ID and the Client Secret. You can remove them from the object that `configFn` returns. **Before** ```ts title="google.ts" export function getConfig() { return { clientID: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, scope: ['profile', 'email'], } } ``` **After** ```ts title="google.ts" export function getConfig() { return { scopes: ['profile', 'email'], } } ``` 4. **Update the `userSignupFields` fields** to use the new `profile` format If you didn't use the `userSignupFields` option, you can skip this step. The data format for the `profile` that you receive from the OAuth providers has changed. You will need to update your code to reflect this change. **Before** ```ts title="google.ts" import { defineUserSignupFields } from 'wasp/server/auth' export const userSignupFields = defineUserSignupFields({ displayName: (data: any) => data.profile.displayName, }) ``` **After** ```ts title="google.ts" import { defineUserSignupFields } from 'wasp/server/auth' export const userSignupFields = defineUserSignupFields({ displayName: (data: any) => data.profile.name, }) ``` Wasp now directly forwards what it receives from the OAuth providers. You can check the data format for [Google](https://wasp.sh/docs/0.13/auth/social-auth/google#data-received-from-google) and [GitHub](https://wasp.sh/docs/0.13/auth/social-auth/github#data-received-from-github) in Wasp's docs. That's it! You should now be able to run your app with the new Wasp 0.13.0. ## Migration guides / From 0.11 to 0.12 ### What's new in Wasp 0.12.0? #### New project structure Here's a file tree of a fresh Wasp project created with the previous version of Wasp. More precisely, this is what you'll get if you run `wasp new myProject` using Wasp 0.11.x: ``` . โ”œโ”€โ”€ .gitignore โ”œโ”€โ”€ main.wasp โ”œโ”€โ”€ src โ”‚ย ย  โ”œโ”€โ”€ client โ”‚ย ย  โ”‚ย ย  โ”œโ”€โ”€ Main.css โ”‚ย ย  โ”‚ย ย  โ”œโ”€โ”€ MainPage.jsx โ”‚ย ย  โ”‚ย ย  โ”œโ”€โ”€ react-app-env.d.ts โ”‚ย ย  โ”‚ย ย  โ”œโ”€โ”€ tsconfig.json โ”‚ย ย  โ”‚ย ย  โ””โ”€โ”€ waspLogo.png โ”‚ย ย  โ”œโ”€โ”€ server โ”‚ย ย  โ”‚ย ย  โ””โ”€โ”€ tsconfig.json โ”‚ย ย  โ”œโ”€โ”€ shared โ”‚ย ย  โ”‚ย ย  โ””โ”€โ”€ tsconfig.json โ”‚ย ย  โ””โ”€โ”€ .waspignore โ””โ”€โ”€ .wasproot ``` Compare that with the file tree of a fresh Wasp project created with Wasp 0.12.0. In other words, this is what you will get by running `wasp new myProject` from this point onwards: ``` . โ”œโ”€โ”€ .gitignore โ”œโ”€โ”€ main.wasp โ”œโ”€โ”€ package.json โ”œโ”€โ”€ public โ”‚ย ย  โ””โ”€โ”€ .gitkeep โ”œโ”€โ”€ src โ”‚ย ย  โ”œโ”€โ”€ Main.css โ”‚ย ย  โ”œโ”€โ”€ MainPage.jsx โ”‚ย ย  โ”œโ”€โ”€ queries.ts โ”‚ย ย  โ”œโ”€โ”€ vite-env.d.ts โ”‚ย ย  โ”œโ”€โ”€ .waspignore โ”‚ย ย  โ””โ”€โ”€ waspLogo.png โ”œโ”€โ”€ tsconfig.json โ”œโ”€โ”€ vite.config.ts โ””โ”€โ”€ .wasproot ``` The main differences are: - The server/client code separation is no longer necessary. You can now organize your code however you want, as long as it's inside the `src` directory. - All external imports in your Wasp file must have paths starting with `@src` (e.g., `import foo from '@src/bar.js'`) where `@src` refers to the `src` directory in your project root. The paths can no longer start with `@server` or `@client`. - Your project now features a top-level `public` dir. Wasp will publicly serve all the files it finds in this directory. Read more about it [here](https://wasp.sh/docs/0.12/project/static-assets). Our [Overview docs](https://wasp.sh/docs/0.12/tutorial/project-structure) explain the new structure in detail, while this page provides a [quick guide](#migrating-your-project-to-the-new-structure) for migrating existing projects. #### New auth In Wasp 0.11.X, authentication was based on the `User` model which the developer needed to set up properly and take care of the auth fields like `email` or `password`. ```wasp title="main.wasp" app myApp { wasp: { version: "^0.11.0" }, title: "My App", auth: { userEntity: User, externalAuthEntity: SocialLogin, methods: { gitHub: {} }, onAuthFailedRedirectTo: "/login" }, } entity User {=psl id Int @id @default(autoincrement()) username String @unique password String externalAuthAssociations SocialLogin[] psl=} entity SocialLogin {=psl id Int @id @default(autoincrement()) provider String providerId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) userId Int createdAt DateTime @default(now()) @@unique([provider, providerId, userId]) psl=} ``` From 0.12.X onwards, authentication is based on the auth models which are automatically set up by Wasp. You don't need to take care of the auth fields anymore. The `User` model is now just a business logic model and you use it for storing the data that is relevant for your app. ```wasp title="main.wasp" app myApp { wasp: { version: "^0.12.0" }, title: "My App", auth: { userEntity: User, methods: { gitHub: {} }, onAuthFailedRedirectTo: "/login" }, } entity User {=psl id Int @id @default(autoincrement()) psl=} ``` :::caution[Regression Note: Multiple Auth Identities per User] With our old auth implementation, if you were using both Google and email auth methods, your users could sign up with Google first and then, later on, reset their password and therefore also enable logging in with their email and password. This was the only way in which a single user could have multiple login methods at the same time (Google and email). This is not possible anymore. **The new auth system doesn't support multiple login methods per user at the moment**. We do plan to add this soon though, with the introduction of the [account merging feature](https://github.com/wasp-lang/wasp/issues/954). If you have any users that have both Google and email login credentials at the same time, you will have to pick only one of those for that user to keep when migrating them. ::: :::caution[Regression Note: _waspCustomValidations is deprecated] Auth field customization is no longer possible using the `_waspCustomValidations` on the `User` entity. This is a part of auth refactoring that we are doing to make it easier to customize auth. We will be adding more customization options in the future. ::: You can read more about the new auth system in the [Auth Entities](https://wasp.sh/docs/0.12/auth/entities) section. ### How to Migrate? These instructions are for migrating your app from Wasp `0.11.X` to Wasp `0.12.X`, meaning they will work for all minor releases that fit this pattern (e.g., the guide applies to `0.12.0`, `0.12.1`, ...). The guide consists of two big steps: 1. Migrating your Wasp project to the new structure. 2. Migrating to the new auth. If you get stuck at any point, don't hesitate to ask for help on [our Discord server](https://discord.gg/rzdnErX). #### Migrating Your Project to the New Structure You can easily migrate your old Wasp project to the new structure by following a series of steps. Assuming you have a project called `foo` inside the directory `foo`, you should: 1. **Install the latest `0.12.x` version** of Wasp. ```bash curl -sSL https://get.wasp.sh/installer.sh | sh -s ``` 1. Make sure to **backup or save your project** before starting the procedure (e.g., by committing it to source control or creating a copy). 2. **Position yourself in the terminal** in the directory that is a parent of your wasp project directory (so one level above: if you do `ls`, you should see your wasp project dir listed). 3. **Run the migration script** (replace `foo` at the end with the name of your Wasp project directory) and follow the instructions: ``` npx wasp-migrate foo ``` In case the migration script doesn't work well for you, you can do the same steps manually, as described here: 1. Rename your project's root directory to something like `foo_old`. 2. Create a new project by running `wasp new foo`. 3. Delete all files of `foo/src` except `vite-env.d.ts`. 4. If `foo_old/src/client/public` exists and contains any files, copy those files into `foo/public`. 5. Copy the contents of `foo_old/src` into `foo/src`. `foo/src` should now contain `vite-env.d.ts`, `.waspignore`, and three subdirectories (`server`, `client`, and `shared`). Don't change anything about this structure yet. 6. Delete redundant files and folders from `foo/src`: - `foo/src/.waspignore` - A new version of this file already exists at the top level. - `foo/src/client/vite-env.d.ts` - A new version of this file already exists at the top level. - `foo/src/client/tsconfig.json` - A new version of this file already exists at the top level. - `foo/src/server/tsconfig.json` - A new version of this file already exists at the top level. - `foo/src/shared/tsconfig.json` - A new version of this file already exists at the top level. - `foo/src/client/public` - You've moved all the files from this directory in step 5. 7. Update all the `@wasp` imports in your JS(X)/TS(X) source files in the `src/` dir. For this, we prepared a special script that will rewrite these imports automatically for you. Before doing this step, as the script will modify your JS(X)/TS(X) files in place, we advise committing all changes you have so far, so you can then both easily inspect the import rewrites that our script did (with `git diff`) and also revert them if something went wrong. To run the import-rewriting script, make sure you are in the root dir of your wasp project, and then run ``` npx jscodeshift@0.15.1 -t https://raw.githubusercontent.com/wasp-lang/wasp-codemod/main/src/transforms/imports-from-0-11-to-0-12.ts --extensions=js,ts,jsx,tsx src/ ``` Then, check the changes it did, in case some kind of manual intervention is needed (in which case you should see TODO comments generated by the script). Alternatively, you can find all the mappings of old imports to the new ones in [this table](https://docs.google.com/spreadsheets/d/1QW-_16KRGTOaKXx9NYUtjk6m2TQ0nUMOA74hBthTH3g/edit#gid=1725669920) and use it to fix some/all of them manually. 8. Replace the Wasp file in `foo` (i.e., `main.wasp`) with the Wasp file from `foo_old` 9. Change the Wasp version field in your Wasp file (now residing in `foo`) to `"^0.12.0"`. 10. Correct external imports in your Wasp file (now residing in `foo`). imports. You can do this by running search-and-replace inside the file: - Change all occurrences of `@server` to `@src/server` - Change all occurrences of `@client` to `@src/client` For example, if you previously had something like: ```js page LoginPage { // This previously resolved to src/client/LoginPage.js component: import Login from "@client/LoginPage" } // ... query getTasks { // This previously resolved to src/server/queries.js fn: import { getTasks } from "@server/queries.js", } ``` You should change it to: ```js page LoginPage { // This now resolves to src/client/LoginPage.js component: import Login from "@src/client/LoginPage" } // ... query getTasks { // This now resolves to src/server/queries.js fn: import { getTasks } from "@src/server/queries.js", } ``` Do this for all external imports in your `.wasp` file. After you're done, there shouldn't be any occurrences of strings `"@server"` or `"@client"` 11. Take all the dependencies from `app.dependencies` declaration in `foo/main.wasp` and move them to `foo/package.json`. Make sure to remove the `app.dependencies` field from `foo/main.wasp`. For example, if `foo_old/main.wasp` had: ```css app Foo { // ... dependencies: [ ('redux', '^4.0.5'), ('reacjt-redux', '^7.1.3')]; } ``` Your `package.json` in `foo` should now list these dependencies (Wasp already generated most of the file, you just have to list additional dependencies). ```json { "name": "foo", "dependencies": { "wasp": "file:.wasp/out/sdk/wasp", "react": "^18.2.0", "redux": "^4.0.5", "reactjs-redux": "^7.1.3" }, "devDependencies": { "typescript": "^5.1.0", "vite": "^4.3.9", "@types/react": "^18.0.37", "prisma": "4.16.2" } } ``` 12. Copy all lines you might have added to `foo_old/.gitignore` into `foo/.gitignore` 13. Copy the rest of the top-level files and folders (all of them except for `.gitignore`, `main.wasp` and `src/`) in `foo_old/` into `foo/` (overwrite the existing files in `foo`). 14. Run `wasp clean` in `foo`. 15. Delete the `foo_old` directory. That's it! You now have a properly structured Wasp 0.12.0 project in the `foo` directory. Your app probably doesn't quite work yet due to some other changes in Wasp 0.12.0, but we'll get to that in the next sections. #### Migrating declaration names Wasp 0.12.0 adds a casing constraints when naming Queries, Actions, Jobs, and Entities in the `main.wasp` file. The following casing conventions have now become mandatory: - Operation (i.e., Query and Action) names must begin with a lowercase letter: `query getTasks {...}`, `action createTask {...}`. - Job names must begin with a lowercase letter: `job sendReport {...}`. - Entity names must start with an uppercase letter: `entity Task {...}`. #### Migrating the Tailwind Setup :::note If you don't use Tailwind in your project, you can skip this section. ::: There is a small change in how the `tailwind.config.cjs` needs to be defined in Wasp 0.12.0. You'll need to wrap all your paths in the `content` field with the `resolveProjectPath` function. This makes sure that the paths are resolved correctly when generating your CSS. Here's how you can do it: **Before** ```js title="tailwind.config.cjs" /** @type {import('tailwindcss').Config} */ module.exports = { content: [ './src/**/*.{js,jsx,ts,tsx}', ], theme: { extend: {}, }, plugins: [], } ``` **After** ```js title="tailwind.config.cjs" const { resolveProjectPath } = require('wasp/dev') /** @type {import('tailwindcss').Config} */ module.exports = { content: [ resolveProjectPath('./src/**/*.{js,jsx,ts,tsx}'), ], theme: { extend: {}, }, plugins: [], } ``` #### Default Server Dockerfile Changed :::note If you didn't customize your Dockerfile or had a custom build process for the Wasp server, you can skip this section. ::: Between Wasp 0.11.X and 0.12.X, the Dockerfile that Wasp generates for you for deploying the server has changed. If you defined a custom Dockerfile in your project root dir or in any other way relied on its contents, you'll need to update it to incorporate the changes that Wasp 0.12.X made. We suggest that you temporarily move your custom Dockerfile to a different location, then run `wasp start` to generate the new Dockerfile. Check out the `.wasp/out/Dockerfile` to see the new Dockerfile and what changes you need to make. You'll probably need to copy some of the changes from the new Dockerfile to your custom one to make your app work with Wasp 0.12.X. #### Migrating to the New Auth As shown in [the previous section](#new-auth), Wasp significantly changed how authentication works in version 0.12.0. This section leads you through migrating your app from Wasp 0.11.X to Wasp 0.12.X. Migrating your existing app to the new auth system is a two-step process: 1. Migrate to the new auth system 2. Clean up the old auth system :::info[Migrating a deployed app] While going through these steps, we will focus first on doing the changes locally (including your local development database). Once we confirm everything works well locally, we will apply the same changes to the deployed app (including your production database). **We'll put extra info for migrating a deployed app in a box like this one.** ::: ##### 1. Migrate to the New Auth System You can follow these steps to migrate to the new auth system (assuming you already migrated the project structure to 0.12, as described [above](#migrating-your-project-to-the-new-structure)): 1. **Migrate `getUserFields` and/or `additionalSignupFields` in the `main.wasp` file to the new `userSignupFields` field.** If you are not using them, you can skip this step. In Wasp 0.11.X, you could define a `getUserFieldsFn` to specify extra fields that would get saved to the `User` when using Google or GitHub to sign up. You could also define `additionalSignupFields` to specify extra fields for the Email or Username & Password signup. In 0.12.X, we unified these two concepts into the `userSignupFields` field. Migration for [Email](https://wasp.sh/docs/auth/email) and [Username & Password](https://wasp.sh/docs/auth/username-and-pass) First, move the value of `auth.signup.additionalFields` to `auth.methods.{method}.userSignupFields` in the `main.wasp` file. `{method}` depends on the auth method you are using. For example, if you are using the email auth method, you should move the `auth.signup.additionalFields` to `auth.methods.email.userSignupFields`. To finish, update the JS/TS implementation to use the `defineUserSignupFields` from `wasp/server/auth` instead of `defineAdditionalSignupFields` from `@wasp/auth/index.js`. **Before** ```wasp title="main.wasp" app crudTesting { // ... auth: { userEntity: User, methods: { email: {}, }, onAuthFailedRedirectTo: "/login", signup: { additionalFields: import { fields } from "@server/auth/signup.js", }, }, } ``` ```ts title="src/server/auth/signup.ts" import { defineAdditionalSignupFields } from '@wasp/auth/index.js' export const fields = defineAdditionalSignupFields({ address: async (data) => { const address = data.address if (typeof address !== 'string') { throw new Error('Address is required') } if (address.length < 5) { throw new Error('Address must be at least 5 characters long') } return address }, }) ``` **After** ```wasp title="main.wasp" app crudTesting { // ... auth: { userEntity: User, methods: { email: { userSignupFields: import { fields } from "@src/server/auth/signup.js", }, }, onAuthFailedRedirectTo: "/login", }, } ``` ```ts title="src/server/auth/signup.ts" import { defineUserSignupFields } from 'wasp/server/auth' export const fields = defineUserSignupFields({ address: async (data) => { const address = data.address; if (typeof address !== 'string') { throw new Error('Address is required'); } if (address.length < 5) { throw new Error('Address must be at least 5 characters long'); } return address; }, }) ``` Read more about the `userSignupFields` function [here](https://wasp.sh/docs/0.12/auth/overview#1-defining-extra-fields). Migration for [Github](https://wasp.sh/docs/auth/social-auth/github) and [Google](https://wasp.sh/docs/auth/social-auth/google) First, move the value of `auth.methods.{method}.getUserFieldsFn` to `auth.methods.{method}.userSignupFields` in the `main.wasp` file. `{method}` depends on the auth method you are using. For example, if you are using Google auth, you should move the `auth.methods.google.getUserFieldsFn` to `auth.methods.google.userSignupFields`. To finish, update the JS/TS implementation to use the `defineUserSignupFields` from `wasp/server/auth` and modify the code to return the fields in the format that `defineUserSignupFields` expects. **Before** ```wasp title="main.wasp" app crudTesting { // ... auth: { userEntity: User, methods: { google: { getUserFieldsFn: import { getUserFields } from "@server/auth/google.js" }, }, onAuthFailedRedirectTo: "/login", }, } ``` ```ts title="src/server/auth/google.ts" import type { GetUserFieldsFn } from '@wasp/types' export const getUserFields: GetUserFieldsFn = async (_context, args) => { const displayName = args.profile.displayName return { displayName } } ``` **After** ```wasp title="main.wasp" app crudTesting { // ... auth: { userEntity: User, methods: { google: { userSignupFields: import { fields } from "@src/server/auth/google.js", }, }, onAuthFailedRedirectTo: "/login", }, } ``` ```ts title="src/server/auth/signup.ts" import { defineUserSignupFields } from 'wasp/server/auth' export const fields = defineUserSignupFields({ displayName: async (data) => { const profile: any = data.profile; if (!profile?.displayName) { throw new Error('Display name is not available'); } return profile.displayName; }, }) ``` If you want to properly type the `profile` object, we recommend you use a validation library like Zod to define the shape of the `profile` object. Read more about this and the `defineUserSignupFields` function in the [Auth Overview - Defining Extra Fields](https://wasp.sh/docs/0.12/auth/overview#1-defining-extra-fields) section. 1. **Remove the `auth.methods.email.allowUnverifiedLogin` field** from your `main.wasp` file. In Wasp 0.12.X we removed the `auth.methods.email.allowUnverifiedLogin` field to make our Email auth implementation easier to reason about. If you were using it, you should remove it from your `main.wasp` file. 1. Ensure your **local development database is running**. 2. **Do the schema migration** (create the new auth tables in the database) by running: ```bash wasp db migrate-dev ``` You should see the new `Auth`, `AuthIdentity` and `Session` tables in your database. You can use the `wasp db studio` command to open the database in a GUI and verify the tables are there. At the moment, they will be empty. 1. **Do the data migration** (move existing users from the old auth system to the new one by filling the new auth tables in the database with their data): 1. **Implement your data migration function(s)** in e.g. `src/migrateToNewAuth.ts`. Below we prepared [examples of migration functions](#example-data-migration-functions) for each of the auth methods, for you to use as a starting point. They should be fine to use as-is, meaning you can just copy them and they are likely to work out of the box for typical use cases, but you can also modify them for your needs. We recommend you create one function per each auth method that you use in your app. 2. **Define custom API endpoints for each migration function** you implemented. With each data migration function below, we provided a relevant `api` declaration that you should add to your `main.wasp` file. 3. **Run the data migration function(s)** on the local development database by calling the API endpoints you defined in the previous step. You can call the endpoint by visiting the URL in your browser, or by using a tool like `curl` or Postman. For example, if you defined the API endpoint at `/migrate-username-and-password`, you can call it by visiting `http://localhost:3001/migrate-username-and-password` in your browser. This should be it, you can now run `wasp db studio` again and verify that there is now relevant data in the new auth tables (`Auth` and `AuthIdentity`; `Session` should still be empty for now). 2. **Verify that the basic auth functionality works** by running `wasp start` and successfully signing up / logging in with each of the auth methods. 3. **Update your JS/TS code** to work correctly with the new auth. You might want to use the new auth helper functions to get the `email` or `username` from a user object. For example, `user.username` might not work anymore for you, since the `username` obtained by the Username & Password auth method isn't stored on the `User` entity anymore (unless you are explicitly storing something into `user.username`, e.g. via `userSignupFields` for a social auth method like Github). Same goes for `email` from Email auth method. Instead, you can now use `getUsername(user)` to get the username obtained from Username & Password auth method, or `getEmail(user)` to get the email obtained from Email auth method. Read more about the helpers in the [Auth Entities - Accessing the Auth Fields](https://wasp.sh/docs/0.12/auth/entities#accessing-the-auth-fields) section. 1. Finally, **check that your app now fully works as it worked before**. If all the above steps were done correctly, everything should be working now. :::info[Migrating a deployed app] After successfully performing migration locally so far, and verifying that your app works as expected, it is time to also migrate our deployed app. Before migrating your production (deployed) app, we advise you to back up your production database in case something goes wrong. Also, besides testing it in development, it's good to test the migration in a staging environment if you have one. We will perform the production migration in 2 steps: - Deploying the new code to production (client and server). - Migrating the production database data. --- Between these two steps, so after successfully deploying the new code to production and before migrating the production database data, your app will not be working completely: new users will be able to sign up, but existing users won't be able to log in, and already logged in users will be logged out. Once you do the second step, migrating the production database data, it will all be back to normal. You will likely want to keep the time between the two steps as short as you can. --- - **First step: deploy the new code** (client and server), either via `wasp deploy` (i.e. `wasp deploy fly deploy`) or manually. Check our [Deployment docs](https://wasp.sh/docs/0.12/advanced/deployment/overview) for more details. - **Second step: run the data migration functions** on the production database. You can do this by calling the API endpoints you defined in the previous step, just like you did locally. You can call the endpoint by visiting the URL in your browser, or by using a tool like `curl` or Postman. For example, if you defined the API endpoint at `/migrate-username-and-password`, you can call it by visiting `https://your-server-url.com/migrate-username-and-password` in your browser. Your deployed app should be working normally now, with the new auth system. ::: ##### 2. Cleanup the Old Auth System Your app should be working correctly and using new auth, but to finish the migration, we need to clean up the old auth system: 1. In `main.wasp` file, **delete auth-related fields from the `User` entity**, since with 0.12 they got moved to the internal Wasp entity `AuthIdentity`. - This means any fields that were required by Wasp for authentication, like `email`, `password`, `isEmailVerified`, `emailVerificationSentAt`, `passwordResetSentAt`, `username`, etc. - There are situations in which you might want to keep some of them, e.g. `email` and/or `username`, if they are still relevant for you due to your custom logic (e.g. you are populating them with `userSignupFields` upon social signup in order to have this info easily available on the `User` entity). Note that they won't be used by Wasp Auth anymore, they are here just for your business logic. 2. In `main.wasp` file, **remove the `externalAuthEntity` field from the `app.auth`** and also **remove the whole `SocialLogin` entity** if you used Google or GitHub auth. 3. **Delete the data migration function(s)** you implemented earlier (e.g. in `src/migrateToNewAuth.ts`) and also the corresponding API endpoints from the `main.wasp` file. 4. **Run `wasp db migrate-dev`** again to apply these changes and remove the redundant fields from the database. :::info[Migrating a deployed app] After doing the steps above successfully locally and making sure everything is working, it is time to push these changes to the deployed app again. *Deploy the app again*, either via `wasp deploy` or manually. Check our [Deployment docs](https://wasp.sh/docs/0.12/advanced/deployment/overview) for more details. The database migrations will automatically run on successful deployment of the server and delete the now redundant auth-related `User` columns from the database. Your app is now fully migrated to the new auth system. ::: #### Next Steps If you made it this far, you've completed all the necessary steps to get your Wasp app working with Wasp 0.12.x. Nice work! Finally, since Wasp no longer requires you to separate your client source files (previously in `src/client`) from server source files (previously in `src/server`), you are now free to reorganize your project however you think is best, as long as you keep all the source files in the `src/` directory. This section is optional, but if you didn't like the server/client separation, now's the perfect time to change it. For example, if your `src` dir looked like this: ``` src โ”‚ โ”œโ”€โ”€ client โ”‚ย ย  โ”œโ”€โ”€ Dashboard.tsx โ”‚ย ย  โ”œโ”€โ”€ Login.tsx โ”‚ย ย  โ”œโ”€โ”€ MainPage.tsx โ”‚ย ย  โ”œโ”€โ”€ Register.tsx โ”‚ย ย  โ”œโ”€โ”€ Task.css โ”‚ย ย  โ”œโ”€โ”€ TaskLisk.tsx โ”‚ย ย  โ”œโ”€โ”€ Task.tsx โ”‚ย ย  โ””โ”€โ”€ User.tsx โ”œโ”€โ”€ server โ”‚ย ย  โ”œโ”€โ”€ taskActions.ts โ”‚ย ย  โ”œโ”€โ”€ taskQueries.ts โ”‚ย ย  โ”œโ”€โ”€ userActions.ts โ”‚ย ย  โ””โ”€โ”€ userQueries.ts โ””โ”€โ”€ shared โ””โ”€โ”€ utils.ts ``` you can now change it to a feature-based structure (which we recommend for any project that is not very small): ``` src โ”‚ โ”œโ”€โ”€ task โ”‚ย ย  โ”œโ”€โ”€ actions.ts -- former taskActions.ts โ”‚ย ย  โ”œโ”€โ”€ queries.ts -- former taskQueries.ts โ”‚ย ย  โ”œโ”€โ”€ Task.css โ”‚ย ย  โ”œโ”€โ”€ TaskLisk.tsx โ”‚ย ย  โ””โ”€โ”€ Task.tsx โ”œโ”€โ”€ user โ”‚ย ย  โ”œโ”€โ”€ actions.ts -- former userActions.ts โ”‚ย ย  โ”œโ”€โ”€ Dashboard.tsx โ”‚ย ย  โ”œโ”€โ”€ Login.tsx โ”‚ย ย  โ”œโ”€โ”€ queries.ts -- former userQueries.ts โ”‚ย ย  โ”œโ”€โ”€ Register.tsx โ”‚ย ย  โ””โ”€โ”€ User.tsx โ”œโ”€โ”€ MainPage.tsx โ””โ”€โ”€ utils.ts ``` ### Appendix #### Example Data Migration Functions The migration functions provided below are written with the typical use cases in mind and you can use them as-is. If your setup requires additional logic, you can use them as a good starting point and modify them to your needs. Note that all of the functions below are written to be idempotent, meaning that running a function multiple times can't hurt. This allows executing a function again in case only a part of the previous execution succeeded and also means that accidentally running it one time too much won't have any negative effects. **We recommend you keep your data migration functions idempotent**. ##### Username & Password To successfully migrate the users using the Username & Password auth method, you will need to do two things: 1. Migrate the user data Username & Password data migration function ```wasp title="main.wasp" api migrateUsernameAndPassword { httpRoute: (GET, "/migrate-username-and-password"), fn: import { migrateUsernameAndPasswordHandler } from "@src/migrateToNewAuth", entities: [] } ``` ```ts title="src/migrateToNewAuth.ts" import { prisma } from "wasp/server"; import { type ProviderName, type UsernameProviderData } from "wasp/server/auth"; import { MigrateUsernameAndPassword } from "wasp/server/api"; export const migrateUsernameAndPasswordHandler: MigrateUsernameAndPassword = async (_req, res) => { const result = await migrateUsernameAuth(); res.status(200).json({ message: "Migrated users to the new auth", result }); }; async function migrateUsernameAuth(): Promise<{ numUsersAlreadyMigrated: number; numUsersNotUsingThisAuthMethod: number; numUsersMigratedSuccessfully: number; }> { const users = await prisma.user.findMany({ include: { auth: true, }, }); const result = { numUsersAlreadyMigrated: 0, numUsersNotUsingThisAuthMethod: 0, numUsersMigratedSuccessfully: 0, }; for (const user of users) { if (user.auth) { result.numUsersAlreadyMigrated++; console.log("Skipping user (already migrated) with id:", user.id); continue; } if (!user.username || !user.password) { result.numUsersNotUsingThisAuthMethod++; console.log("Skipping user (not using username auth) with id:", user.id); continue; } const providerData: UsernameProviderData = { hashedPassword: user.password, }; const providerName: ProviderName = "username"; await prisma.auth.create({ data: { identities: { create: { providerName, providerUserId: user.username.toLowerCase(), providerData: JSON.stringify(providerData), }, }, user: { connect: { id: user.id, }, }, }, }); result.numUsersMigratedSuccessfully++; } return result; } ``` 2. Provide a way for users to migrate their password There is a **breaking change between the old and the new auth in the way the password is hashed**. This means that users will need to migrate their password after the migration, as the old password will no longer work. Since the only way users using username and password as a login method can verify their identity is by providing both their username and password (there is no email or any other info, unless you asked for it and stored it explicitly), we need to provide them a way to **exchange their old password for a new password**. One way to handle this is to inform them about the need to migrate their password (on the login page) and provide a custom page to migrate the password. Steps to create a custom page for migrating the password 1. You will need to install the `secure-password` and `sodium-native` packages to use the old hashing algorithm: ```bash npm install secure-password@4.0.0 sodium-native@3.3.0 --save-exact ``` Make sure to save the exact versions of the packages. 2. Then you'll need to create a new page in your app where users can migrate their password. You can use the following code as a starting point: ```wasp title="main.wasp" route MigratePasswordRoute { path: "/migrate-password", to: MigratePassword } page MigratePassword { component: import { MigratePasswordPage } from "@src/pages/MigratePassword" } ``` ```tsx title="src/pages/MigratePassword.tsx" import { FormItemGroup, FormLabel, FormInput, FormError, } from "wasp/client/auth"; import { useForm } from "react-hook-form"; import { migratePassword } from "wasp/client/operations"; import { useState } from "react"; export function MigratePasswordPage() { const [successMessage, setSuccessMessage] = useState(null); const [errorMessage, setErrorMessage] = useState(null); const form = useForm<{ username: string; password: string; }>(); const onSubmit = form.handleSubmit(async (data) => { try { const result = await migratePassword(data); setSuccessMessage(result.message); } catch (e: unknown) { console.error(e); if (e instanceof Error) { setErrorMessage(e.message); } } }); return (

    Migrate your password

    If you have an account on the old version of the website, you can migrate your password to the new version.

    {successMessage &&
    {successMessage}
    } {errorMessage && {errorMessage}}
    Username {form.formState.errors.username?.message} Password {form.formState.errors.password?.message}
    ); } ``` 3. Finally, you will need to create a new operation in your app to handle the password migration. You can use the following code as a starting point: ```wasp title="main.wasp" action migratePassword { fn: import { migratePassword } from "@src/auth", entities: [] } ``` ```ts title="src/auth.ts" import SecurePassword from "secure-password"; import { HttpError } from "wasp/server"; import { createProviderId, deserializeAndSanitizeProviderData, findAuthIdentity, updateAuthIdentityProviderData, } from "wasp/server/auth"; import { MigratePassword } from "wasp/server/operations"; type MigratePasswordInput = { username: string; password: string; }; type MigratePasswordOutput = { message: string; }; export const migratePassword: MigratePassword< MigratePasswordInput, MigratePasswordOutput > = async ({ password, username }, _context) => { const providerId = createProviderId("username", username); const authIdentity = await findAuthIdentity(providerId); if (!authIdentity) { throw new HttpError(400, "Something went wrong"); } const providerData = deserializeAndSanitizeProviderData<"username">( authIdentity.providerData ); try { const SP = new SecurePassword(); // This will verify the password using the old algorithm const result = await SP.verify( Buffer.from(password), Buffer.from(providerData.hashedPassword, "base64") ); if (result !== SecurePassword.VALID) { throw new HttpError(400, "Something went wrong"); } // This will hash the password using the new algorithm and update the // provider data in the database. await updateAuthIdentityProviderData<"username">(providerId, providerData, { hashedPassword: password, }); } catch (e) { throw new HttpError(400, "Something went wrong"); } return { message: "Password migrated successfully.", }; }; ``` ##### Email To successfully migrate the users using the Email auth method, you will need to do two things: 1. Migrate the user data Email data migration function ```wasp title="main.wasp" api migrateEmail { httpRoute: (GET, "/migrate-email"), fn: import { migrateEmailHandler } from "@src/migrateToNewAuth", entities: [] } ``` ```ts title="src/migrateToNewAuth.ts" import { prisma } from "wasp/server"; import { type ProviderName, type EmailProviderData } from "wasp/server/auth"; import { MigrateEmail } from "wasp/server/api"; export const migrateEmailHandler: MigrateEmail = async (_req, res) => { const result = await migrateEmailAuth(); res.status(200).json({ message: "Migrated users to the new auth", result }); }; async function migrateEmailAuth(): Promise<{ numUsersAlreadyMigrated: number; numUsersNotUsingThisAuthMethod: number; numUsersMigratedSuccessfully: number; }> { const users = await prisma.user.findMany({ include: { auth: true, }, }); const result = { numUsersAlreadyMigrated: 0, numUsersNotUsingThisAuthMethod: 0, numUsersMigratedSuccessfully: 0, }; for (const user of users) { if (user.auth) { result.numUsersAlreadyMigrated++; console.log("Skipping user (already migrated) with id:", user.id); continue; } if (!user.email || !user.password) { result.numUsersNotUsingThisAuthMethod++; console.log("Skipping user (not using email auth) with id:", user.id); continue; } const providerData: EmailProviderData = { isEmailVerified: user.isEmailVerified, emailVerificationSentAt: user.emailVerificationSentAt?.toISOString() ?? null, passwordResetSentAt: user.passwordResetSentAt?.toISOString() ?? null, hashedPassword: user.password, }; const providerName: ProviderName = "email"; await prisma.auth.create({ data: { identities: { create: { providerName, providerUserId: user.email, providerData: JSON.stringify(providerData), }, }, user: { connect: { id: user.id, }, }, }, }); result.numUsersMigratedSuccessfully++; } return result; } ``` 2. Ask the users to reset their password There is a **breaking change between the old and the new auth in the way the password is hashed**. This means that users will need to reset their password after the migration, as the old password will no longer work. It would be best to notify your users about this change and put a notice on your login page to **request a password reset**. ##### Google & GitHub Google & GitHub data migration functions ```wasp title="main.wasp" api migrateGoogle { httpRoute: (GET, "/migrate-google"), fn: import { migrateGoogleHandler } from "@src/migrateToNewAuth", entities: [] } api migrateGithub { httpRoute: (GET, "/migrate-github"), fn: import { migrateGithubHandler } from "@src/migrateToNewAuth", entities: [] } ``` ```ts title="src/migrateToNewAuth.ts" import { prisma } from "wasp/server"; import { MigrateGoogle, MigrateGithub } from "wasp/server/api"; export const migrateGoogleHandler: MigrateGoogle = async (_req, res) => { const result = await createSocialLoginMigration("google"); res.status(200).json({ message: "Migrated users to the new auth", result }); }; export const migrateGithubHandler: MigrateGithub = async (_req, res) => { const result = await createSocialLoginMigration("github"); res.status(200).json({ message: "Migrated users to the new auth", result }); }; async function createSocialLoginMigration( providerName: "google" | "github" ): Promise<{ numUsersAlreadyMigrated: number; numUsersNotUsingThisAuthMethod: number; numUsersMigratedSuccessfully: number; }> { const users = await prisma.user.findMany({ include: { auth: true, externalAuthAssociations: true, }, }); const result = { numUsersAlreadyMigrated: 0, numUsersNotUsingThisAuthMethod: 0, numUsersMigratedSuccessfully: 0, }; for (const user of users) { if (user.auth) { result.numUsersAlreadyMigrated++; console.log("Skipping user (already migrated) with id:", user.id); continue; } const provider = user.externalAuthAssociations.find( (provider) => provider.provider === providerName ); if (!provider) { result.numUsersNotUsingThisAuthMethod++; console.log(`Skipping user (not using ${providerName} auth) with id:`, user.id); continue; } await prisma.auth.create({ data: { identities: { create: { providerName, providerUserId: provider.providerId, providerData: JSON.stringify({}), }, }, user: { connect: { id: user.id, }, }, }, }); result.numUsersMigratedSuccessfully++; } return result; } ``` ------ # Guides ## About these guides The guides in this section will walk you through common scenarios you'll encounter when building real-world apps with Wasp. They're more hands-on and to-the-point than the main docs. Pick the one that matches what you're trying to do and follow along! Since many guides involve third-party libraries or services, some details may drift over time. Where appropriate, each guide shows when it was last updated and which versions it was tested with. If something doesn't work as expected, check the official docs for the tool in question, and feel free to let us know in the comments below each page, so we can update it. Looking for a guide we don't have yet? [Open an issue](https://github.com/wasp-lang/wasp/issues) or drop by [our Discord](https://discord.gg/rzdnErX). We're always looking for new ideas. ## Configuration / Multiple Domains CORS :::note 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. ::: This guide shows you how to configure CORS (Cross-Origin Resource Sharing) to support multiple domains in your Wasp application using custom global middleware. :::warning[Potentially exposing your API] A loose CORS config can expose your API to any client on the internet. Make sure to understand the [Security Considerations](#security-considerations) before proceeding. ::: ### Prerequisites Make sure you have a Wasp project set up. If you haven't, follow the [Getting Started](https://wasp.sh/docs/quick-start) guide first. ### When You Need This By default, Wasp configures CORS to allow requests only from your client URL (defined by `WASP_WEB_CLIENT_URL`). You might need to support multiple domains when: - You have multiple domains for the same client application - You're building a public API - You're migrating from one domain to another ### Setting up Multiple Domain CORS #### 1. Configure global middleware in main.wasp.ts Add the server middleware configuration: ```ts title="main.wasp.ts" import { app, page, query, route } from "@wasp.sh/spec" import { getSomeData } from "./src/data" with { type: "ref" } import { MainPage } from "./src/MainPage" with { type: "ref" } import { getGlobalMiddleware } from "./src/middleware" with { type: "ref" } export default app({ name: "CorsTest", wasp: { version: "^0.24.0" }, head: [""], title: "cors-test", server: { middlewareConfigFn: getGlobalMiddleware, }, spec: [ route("RootRoute", "/", page(MainPage)), query(getSomeData), ], }) ``` #### 2. Create the middleware configuration Create a middleware file that configures CORS with multiple origins: ```ts title="src/middleware.ts" import cors from "cors"; import { config, type MiddlewareConfigFn } from "wasp/server"; export const getGlobalMiddleware: MiddlewareConfigFn = (middlewareConfig) => { // Add extra domains to the existing allowed CORS origins. const origin = [ ...config.allowedCORSOrigins, "https://app.example.com", "https://admin.example.com", ]; middlewareConfig.set("cors", cors({ origin })); return middlewareConfig; }; ``` #### 3. Example query handler Here's an example of a query that will now be accessible from multiple domains: ```ts title="src/data.ts" import { GetSomeData } from "wasp/server/operations"; export const getSomeData: GetSomeData = async (_args, _context) => { return { someData: "Hello from the server!", }; }; ``` ### Configuration Options #### Using Environment Variables You can make the allowed domains configurable via environment variables: ```ts title="src/middleware.ts" import cors from "cors"; import { config, type MiddlewareConfigFn } from "wasp/server"; export const getGlobalMiddleware: MiddlewareConfigFn = (middlewareConfig) => { // Parse additional domains from environment variable. const additionalDomains = process.env.CORS_ALLOWED_DOMAINS?.split(",") ?? []; const origin = [...config.allowedCORSOrigins, ...additionalDomains]; middlewareConfig.set("cors", cors({ origin })); return middlewareConfig; }; ``` Then in your `.env.server`: ```bash title=".env.server" CORS_ALLOWED_DOMAINS=https://app.example.com,https://admin.example.com ``` #### Dynamic Origin Validation For more complex scenarios, you can use a function to validate origins: ```ts title="src/middleware.ts" import cors from "cors"; import { MiddlewareConfigFn } from "wasp/server"; export const getGlobalMiddleware: MiddlewareConfigFn = (config) => { config.set( "cors", cors({ origin: (origin, callback) => { const allowedPatterns = [ /^https:\/\/.*\.example\.com$/, // Any subdomain of example.com /^http:\/\/localhost:\d+$/, // Any localhost port ]; if ( !origin || allowedPatterns.some((pattern) => pattern.test(origin)) ) { callback(null, true); } else { callback(new Error("Not allowed by CORS")); } }, }), ); return config; }; ``` #### Full CORS Configuration For complete control over CORS, you can set all options: ```ts title="src/middleware.ts" import cors from "cors"; import { MiddlewareConfigFn } from "wasp/server"; export const getGlobalMiddleware: MiddlewareConfigFn = (config) => { config.set( "cors", cors({ origin: ["https://app.example.com", "https://admin.example.com"], methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], allowedHeaders: ["Content-Type", "Authorization"], credentials: true, maxAge: 86400, // 24 hours }), ); return config; }; ``` ### Security Considerations - **Never use `origin: "*"` or `origin: [/.*/]` in production** โ€” always restrict origins to specific domains - Always explicitly list the domains you want to allow - Consider using environment variables to manage allowed domains across environments - Regularly audit your allowed domains list ## Debugging / Database Studio with Fly.io :::note Last checked with Wasp 0.24 and Fly CLI 0.4.11. 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. ::: This guide shows you how to connect to your production database on Fly.io and run `wasp db studio` to inspect or modify your data. :::warning[Working with production data] You are about to point your local tooling at the live production database. Make sure to understand the [Security Considerations](#security-considerations) before proceeding. ::: ### Prerequisites - A Wasp app deployed to [Fly.io](https://fly.io/) - The [Fly CLI](https://fly.io/docs/hands-on/install-flyctl/) installed and authenticated ### Overview To connect to your production database, you'll need to: 1. Get the database name 2. Get the database password 3. Open a tunnel to the database 4. Configure your local environment 5. Run `wasp db studio` ### Step 1: Get the Database Name Connect to your Postgres app (replace `some-test-db` with your actual database app name): ```bash fly postgres connect -a some-test-db ``` Once connected, list all databases: ```sql \l ``` Your database name will typically follow the pattern `server_name_with_underscores`. For example, if your server app is named `some-test-server`, the database name would be `some_test_server`. Type `\q` to exit the Postgres prompt. ### Step 2: Get the Database Password SSH into your database app: ```bash fly ssh console -a some-test-db ``` Then retrieve the password: ```bash echo $OPERATOR_PASSWORD ``` Copy this password and type `exit` to leave the SSH session. ### Step 3: Open a Database Tunnel Before opening the tunnel, make sure nothing else is running on port 5432: - Stop any local database started with `wasp db start` - Check for Docker containers that might be using the port :::warning[Background processes] Even if you close the terminal that was running `wasp db start`, the Docker container may still be running in the background. Make sure to stop it before proceeding. ::: Open the tunnel: ```bash fly proxy 5432 -a some-test-db ``` Keep this terminal tab open and use a new terminal for the following steps. ### Step 4: Configure the Database URL Edit your `.env.server` file to point to the production database: ```bash title=".env.server" DATABASE_URL=postgres://postgres:@localhost:5432/ ``` Replace `` with the password from Step 2 and `` with the database name from Step 1. For example: ```bash title=".env.server" DATABASE_URL=postgres://postgres:myDatabasePassword@localhost:5432/some_test_server ``` ### Step 5: Run Database Studio Now you can run Prisma Studio to browse and edit your production data: ```bash wasp db studio ``` This will open a web interface where you can view and modify your database records. ### Security Considerations - **Any changes you make to production data are immediate** and may be hard or impossible to undo - **Back up your database** before making changes - Consider using a **read-only user** for routine inspections - Remember to restore your `.env.server` to point to your local database when you're done - Close the tunnel when finished by terminating the `fly proxy` command - Never commit production credentials to version control ## Debugging / Local Network Testing :::note 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. ::: This guide shows you how to test your Wasp application on other devices (phones, tablets) connected to the same local network during development. ### Prerequisites - A Wasp project running locally with `wasp start` - Other devices connected to the same network as your development machine ### Step 1: Start Your App Run your application normally: ```bash wasp start ``` ### Step 2: Find Your Network URL Look for the network URLs in Wasp's terminal output: ``` [ Client ] VITE v7.3.1 ready in 536 ms [ Client ] [ Client ] -> Local: http://localhost:3000/ [ Client ] -> Network: http://192.168.1.39:3000/ [ Client ] -> Network: http://198.19.249.3:3000/ [ Client ] -> Network: http://192.168.215.0:3000/ [ Client ] -> press h + enter to show help ``` If you have multiple network interfaces, you'll see multiple Network URLs. Note one of these IPs (you may need to try a few to find the one that works). ### Step 3: Configure Environment Variables The app won't be fully functional until you configure the environment variables. Edit your environment files: #### .env.server ```bash title=".env.server" WASP_WEB_CLIENT_URL=http://192.168.1.39.nip.io:3000 WASP_SERVER_URL=http://192.168.1.39.nip.io:3001 ``` #### .env.client ```bash title=".env.client" REACT_APP_API_URL=http://192.168.1.39.nip.io:3001 ``` Replace `192.168.1.39` with your actual IP address from Step 2. :::note[Why these variables?] - **WASP\_WEB\_CLIENT\_URL**: Ensures CORS works correctly - **WASP\_SERVER\_URL**: Makes OAuth redirects work properly - **REACT\_APP\_API\_URL**: Tells the client where to find the server on the local network ::: ### Step 4: Allow the Host in Vite Config By default, Vite blocks requests from hostnames other than `localhost`. Since we're using a `.nip.io` hostname, you need to explicitly allow it. Add `allowedHosts` to the `server` section in your `vite.config.ts`: ```ts title="vite.config.ts" import { defineConfig } from "vitest/config"; import { wasp } from "wasp/client/vite"; export default defineConfig({ server: { allowedHosts: ["192.168.1.39.nip.io"], }, plugins: [wasp()], }); ``` Replace `192.168.1.39.nip.io` with the hostname matching your IP from Step 2. ### Step 5: Restart and Test After saving the environment files, restart your app: ```bash wasp start ``` On your phone or tablet, open the URL with the `.nip.io` suffix: ``` http://192.168.1.39.nip.io:3000 ``` ### Why Use nip.io? [nip.io](https://nip.io) is a free DNS service that maps any IP address to a hostname. For example, `192.168.1.39.nip.io` resolves to `192.168.1.39`. This is necessary because some Wasp features (like Google OAuth) don't allow plain IP addresses. Using nip.io provides a proper hostname without any configuration. :::tip You can skip nip.io if you're not using features that require proper hostnames, but using it has no downside and ensures everything works correctly. ::: ### OAuth Configuration If you're using OAuth providers (Google, GitHub, etc.), remember to add your local network URLs to the allowed redirect URIs in each provider's configuration: ``` http://192.168.1.39.nip.io:3001/auth/google/callback ``` ### Troubleshooting #### Can't access from other devices 1. Make sure both devices are on the same network 2. Check if your firewall is blocking incoming connections on ports 3000 and 3001 3. Try different Network URLs if you have multiple #### API calls failing 1. Verify `REACT_APP_API_URL` is set correctly in `.env.client` 2. Make sure the server is accessible on port 3001 3. Check browser console for CORS errors #### OAuth not working 1. Update redirect URIs in your OAuth provider's settings 2. Make sure `WASP_SERVER_URL` uses the nip.io hostname 3. Restart the server after changing environment variables ## Deployment / Cloud Providers / Cloudflare :::note Last checked with Wasp 0.24 and Cloudflare Workers (as of Apr 6, 2026). 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. ::: ### Deploy Wasp to Cloudflare Workers client {#deploy-wasp-to-cloudflare-workers-} This guide shows you how to deploy your Wasp app's client to [Cloudflare](https://www.cloudflare.com/) Workers, a free hosting service. You will need a Cloudflare account to follow these instructions. Make sure you are logged in with the Cloudflare's CLI called Wrangler. You can log in by running: ```bash npx wrangler login ``` Before you continue, make sure you have [built the Wasp app](https://wasp.sh/docs/deployment/deployment-methods/cloud-providers#1-generating-deployable-code). We'll build the client web app next. To build the web app, run the following command from your project root: ``` REACT_APP_API_URL= npx vite build ``` where `` is the URL of the Wasp server that you previously deployed. The build output will be in `.wasp/out/web-app/build`. :::caution[Client Env Variables] Remember, if you have defined any other [client-side env variables](https://wasp.sh/docs/project/env-vars#defining-env-vars-in-development) in your project, make sure to add them to the command above when [building your client](https://wasp.sh/docs/deployment/env-vars#client-env-vars) ::: To deploy the client to Cloudflare Workers, create these two files in the root of your project: 1. A `wrangler.toml` that configures the Worker with static assets: ```toml title="wrangler.toml" name = "my-wasp-app-client" main = "./worker.js" compatibility_date = "2026-03-30" [assets] directory = "./.wasp/out/web-app/build" binding = "ASSETS" ``` 2. And a `worker.js` that serves static files and falls back to the SPA shell for unknown routes: ```js title="worker.js" export default { async fetch(request, env) { // If the static asset is not found, return the SPA fallback. const spaFallbackUrl = new URL("/200", request.url); const spaFallbackRequest = new Request(spaFallbackUrl, request); return await env.ASSETS.fetch(spaFallbackRequest); }, }; ``` Keeping these files in the project root ensures they are tracked in your repository. Finally, deploy from your project root: ```shell npx wrangler deploy ``` That is it! Your client should be live at `https://my-wasp-app-client..workers.dev`. :::note Make sure you set your Workers URL as the `WASP_WEB_CLIENT_URL` environment variable in your server hosting environment. ::: #### Deploying through GitHub Actions To enable automatic deployment of the client whenever you push to the `main` branch, you can set up a GitHub Actions workflow. To do this, create a file in your repository at `.github/workflows/deploy.yaml`. Feel free to rename `deploy.yaml` as long as the file type is not changed. Here's an example configuration file to help you get started. This example workflow will trigger a deployment to Cloudflare Workers whenever changes are pushed to the main branch. Example GitHub Action ```yaml name: Deploy Client to Cloudflare on: push: branches: - main # Deploy on every push to the main branch jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@v5 - name: Setup Node.js id: setup-node uses: actions/setup-node@v5 with: node-version: "24.14.1" - name: Install Wasp run: npm i -g @wasp.sh/wasp-cli@^0.25 # Change to your Wasp version - name: Install Wasp app dependencies run: cd ./app && wasp install - name: Wasp Build run: cd ./app && wasp build - name: Build the client run: cd ./app && REACT_APP_API_URL=${{ secrets.WASP_SERVER_URL }} npx vite build - name: Deploy to Cloudflare Workers uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} workingDirectory: ./app command: deploy ``` How do I get the Environment Variables? - **`CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID`**: You can get these from your [Cloudflare dashboard](https://dash.cloudflare.com/profile/api-tokens). Make sure to give the token `Cloudflare Workers: Edit` permissions. - **`WASP_SERVER_URL`**: This is your server's URL and is generally only available after **deploying the backend**. This variable can be skipped when the backend is not functional or not deployed, but be aware that backend-dependent functionalities may be broken. After getting the environment variables, you need to set these in GitHub Repository Secrets. ## Deployment / Cloud Providers / Fly.io :::note Last checked with Wasp 0.24 and Fly.io (as of Apr 6, 2026). 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. ::: ### Automatic Deployment server client database {#automatic-deployment---} We recommend that you use [Wasp Deploy](https://wasp.sh/docs/deployment/deployment-methods/wasp-deploy/fly) to deploy your Wasp app to Fly.io. Wasp CLI automates deploying the client, the server and the database with one command. ### Manual Deployment server database {#manual-deployment--} This guide shows you how to deploy your Wasp app's server and provision a database on Fly.io. #### Prerequisites To get started, follow these steps: 1. Create a [Fly.io](https://fly.io/) account, 2. Install the [`fly` CLI](https://fly.io/docs/flyctl/install/), 3. Log in with the `fly` CLI. You can check if you are logged in with `fly auth whoami`, and if you are not, you can log in with `fly auth login`. #### Set Up a Fly.io App :::info You need to do this only once per Wasp app. ::: Unless you already have a Fly.io app that you want to deploy to, let's create a new Fly.io app. After you have [built the app](https://wasp.sh/docs/deployment/deployment-methods/cloud-providers#1-generating-deployable-code), position yourself in `.wasp/out/` directory: ```shell cd .wasp/out ``` Next, run the launch command to set up a new app and create a `fly.toml` file: ```bash fly launch --remote-only ``` This will ask you a series of questions, such as asking you to choose a region and whether you'd like a database. - Say **yes** to **Would you like to set up a PostgreSQL database now?** and select **Development**. Fly.io will set a `DATABASE_URL` for you. - Say **no** to **Would you like to deploy now?** (and to any additional questions). We still need to set up several environment variables. :::info[What if the database setup fails?] If your attempts to initiate a new app fail for whatever reason, then you should run `fly apps destroy ` before trying again. Fly does not allow you to create multiple apps with the same name. What does it look like when your DB is deployed correctly? When your DB is deployed correctly, you'll see it in the [Fly.io dashboard](https://fly.io/dashboard): ![image](https://wasp.sh/img/deploying/fly-db.png) ::: Next, let's copy the `fly.toml` file up to our Wasp project dir for safekeeping. ```shell cp fly.toml ../../ ``` Next, add a few more environment variables for the server code. ```bash fly secrets set PORT=8080 fly secrets set JWT_SECRET= fly secrets set WASP_WEB_CLIENT_URL= fly secrets set WASP_SERVER_URL= ``` We can help you generate a `JWT_SECRET`:\ Generate secret :::note If you do not know what your client URL is yet, don't worry. You can set `WASP_WEB_CLIENT_URL` after you deploy your client. ::: :::tip[Using an external auth method?] If your app is using an external authentication method(s) supported by Wasp (such as [Google](https://wasp.sh/docs/auth/social-auth/google#4-adding-environment-variables) or [GitHub](https://wasp.sh/docs/auth/social-auth/github#4-adding-environment-variables)), make sure to additionally set the necessary environment variables specifically required by these method(s). ::: If you want to make sure you've added your secrets correctly, run `fly secrets list` in the terminal. Note that you will see hashed versions of your secrets to protect your sensitive data. #### Deploy to a Fly.io App While still in the `.wasp/out/` directory, run: ```bash fly deploy --remote-only --config ../../fly.toml ``` This will build and deploy the backend of your Wasp app on Fly.io to `https://.fly.dev` ๐Ÿค˜๐ŸŽธ Now, if you haven't, you can deploy your client and add the client URL by running `fly secrets set WASP_WEB_CLIENT_URL=`. We suggest using [Netlify](https://wasp.sh/docs/guides/deployment/cloud-providers/netlify) for your client, but you can use any static hosting provider. Additionally, some useful `fly` commands: ```bash fly logs fly secrets list fly ssh console ``` #### Redeploying After Wasp Builds When you rebuild your Wasp app (with `wasp build`), it will remove your `.wasp/out/` directory. In there, you may have a `fly.toml` from any prior Fly.io deployments. While we will improve this process in the future, in the meantime, you have a few options: 1. Copy the `fly.toml` file to a versioned directory, like your Wasp project dir. From there, you can reference it in `fly deploy --config ` commands, like above. 1. Backup the `fly.toml` file somewhere before running `wasp build`, and copy it into .wasp/out/ after. When the `fly.toml` file exists in .wasp/out/ dir, you do not need to specify the `--config `. 1. Run `fly config save -a ` to regenerate the `fly.toml` file from the remote state stored in Fly.io. ## Deployment / Cloud Providers / Heroku :::note Last checked with Wasp 0.24 and Heroku (as of Apr 6, 2026). 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. ::: ### Deploy Wasp to Heroku server database {#deploy-wasp-to-heroku--} This guide shows you how to deploy the server and provision a database for it on Heroku. You can check their [pricing page](https://www.heroku.com/pricing) for more information on their plans. #### Prerequisites You will need a Heroku account, `heroku` [CLI](https://devcenter.heroku.com/articles/heroku-cli) and `docker` CLI installed to follow these instructions. Make sure you are logged in with `heroku` CLI. You can check if you are logged in with `heroku whoami`, and if you are not, you can log in with `heroku login`. #### Set up a Heroku app :::info You need to do this only once per Wasp app. ::: Unless you want to deploy to an existing Heroku app, let's create a new Heroku app: ``` heroku create ``` Unless you have an external PostgreSQL database that you want to use, let's create a new database on Heroku and attach it to our app: ``` heroku addons:create --app heroku-postgresql:essential-0 ``` :::caution We are using the `essential-0` database instance. It's the cheapest database instance Heroku offers and it costs $5/mo. ::: Heroku will also set `DATABASE_URL` env var for us at this point. If you are using an external database, you will have to set it up yourself. The `PORT` env var will also be provided by Heroku, so the ones left to set are the `JWT_SECRET`, `WASP_WEB_CLIENT_URL` and `WASP_SERVER_URL` env vars: ``` heroku config:set --app JWT_SECRET= heroku config:set --app WASP_WEB_CLIENT_URL= heroku config:set --app WASP_SERVER_URL= ``` We can help you generate a `JWT_SECRET`:\ Generate secret :::note If you do not know what your client URL is yet, don't worry. You can set `WASP_WEB_CLIENT_URL` after you deploy your client. ::: #### Deploy the Heroku app After you have [built the app](https://wasp.sh/docs/deployment/deployment-methods/cloud-providers#1-generating-deployable-code), position yourself in `.wasp/out/` directory: ```shell cd .wasp/out ``` assuming you were at the root of your Wasp project at that moment. Log in to Heroku Container Registry: ```shell heroku container:login ``` Set your app's stack to `container` so we can deploy our app as a Docker container: ```shell heroku stack:set container --app ``` Build the Docker image and push it to Heroku: ```shell heroku container:push --app web ``` App is still not deployed at this point. This step might take some time, especially the very first time, since there are no cached Docker layers. Deploy the pushed image and restart the app: ```shell heroku container:release --app web ``` This is it, the backend is deployed at `https://.herokuapp.com` ๐ŸŽ‰ Find out the exact app URL with: ```shell heroku info --app ``` Additionally, you can check out the logs with: ```shell heroku logs --tail --app ``` :::note[Using pg-boss with Heroku] If you wish to deploy an app leveraging [Jobs](https://wasp.sh/docs/advanced/jobs) that use `pg-boss` as the executor to Heroku, you need to set an additional environment variable called [`PG_BOSS_NEW_OPTIONS`](https://wasp.sh/docs/advanced/jobs#pg_boss_new_options) to `{"connectionString":"","ssl":{"rejectUnauthorized":false}}`. This is because pg-boss uses the `pg` extension, which does not seem to connect to Heroku over SSL by default, which Heroku requires. Additionally, Heroku uses a self-signed cert, so we must handle that as well. Read more: ::: ## Deployment / Cloud Providers / Netlify :::note Last checked with Wasp 0.24 and Netlify (as of May 28, 2026). 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. ::: ### Deploy Wasp to Netlify client {#deploy-wasp-to-netlify-} This guide shows you how to deploy your Wasp app's client to Netlify. Netlify is a static hosting solution that is free for many use cases. You will need a Netlify account to follow these instructions. Make sure you are logged in with Netlify CLI. You can check if you are logged in with `npx netlify-cli status`, and if you are not, you can log in with `npx netlify-cli login`. First, make sure you have [built the Wasp app](https://wasp.sh/docs/deployment/deployment-methods/cloud-providers#1-generating-deployable-code). We'll build the client web app next. To build the web app, run the following command from your project root: ``` REACT_APP_API_URL= npx vite build ``` where `` is the URL of the Wasp server that you previously deployed. The build output will be in `.wasp/out/web-app/build`. :::caution[Client Env Variables] Remember, if you have defined any other [client-side env variables](https://wasp.sh/docs/project/env-vars#defining-env-vars-in-development) in your project, make sure to add them to the command above when [building your client](https://wasp.sh/docs/deployment/env-vars#client-env-vars) ::: Before deploying, you need to create a `netlify.toml` file in your project root to tell Netlify where to find the built client and to configure URL redirects for SPA routing. Create the `netlify.toml` file with the following content: ```toml title="netlify.toml" [build] publish = "./.wasp/out/web-app/build" # By default, Netlify only redirects when a path doesn't match an existing file. # See: https://docs.netlify.com/manage/routing/redirects/rewrites-proxies/#shadowing [[redirects]] from = "/*" to = "/200.html" status = 200 ``` The `build.publish` path should point from the directory containing `netlify.toml` to the built client output. Adjust the path if your Wasp project is in a subdirectory (e.g., `publish = "./my-app/.wasp/out/web-app/build"`). We can now deploy the client with: ```shell npx netlify-cli deploy --filter wasp --no-build ``` Carefully follow the instructions: decide if you want to create a new app or use an existing one, pick the team under which your app will be deployed etc. Netlify CLI detects Wasp's generated server and SDK packages as workspaces, so `--filter wasp` makes that selection explicit. The `build.publish` setting still determines which files Netlify uploads. The `--no-build` flag is used because you already built the client with the right environment variables. The final step is to run: ```shell npx netlify-cli deploy --prod --filter wasp --no-build ``` That is it! Your client should be live at `https://.netlify.app`. :::note Make sure you set the `https://.netlify.app` URL as the `WASP_WEB_CLIENT_URL` environment variable in your server hosting environment. ::: #### Deploying through GitHub Actions To enable automatic deployment of the client whenever you push to the `main` branch, you can set up a GitHub Actions workflow. To do this, create a file in your repository at `.github/workflows/deploy.yaml`. Feel free to rename `deploy.yaml` as long as the file type is not changed. Here's an example configuration file to help you get started. This example workflow will trigger a deployment to Netlify whenever changes are pushed to the main branch. Example GitHub Action ```yaml name: Deploy Client to Netlify on: push: branches: - main # Deploy on every push to the main branch jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@v5 - name: Setup Node.js id: setup-node uses: actions/setup-node@v5 with: node-version: "24.14.1" - name: Install Wasp run: npm i -g @wasp.sh/wasp-cli@^0.25 # Change to your Wasp version - name: Wasp Install run: wasp install - name: Wasp Build run: wasp build - name: Build the client run: REACT_APP_API_URL=${{ secrets.WASP_SERVER_URL }} npx vite build - name: Deploy to Netlify run: | npx netlify-cli deploy --prod --auth=$NETLIFY_AUTH_TOKEN --site=$NETLIFY_SITE_ID --filter wasp --no-build env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} ``` How do I get the Environment Variables? - **`NETLIFY_AUTH_TOKEN`**: For the auth token, you'll generate a new Personal Access Token on [Netlify](https://docs.netlify.com/cli/get-started/#obtain-a-token-in-the-netlify-ui). - **`NETLIFY_SITE_ID`**: This is the ID of your Netlify project. - **`WASP_SERVER_URL`**: This is your server's URL and is generally only available after **deploying the backend**. This variable can be skipped when the backend is not functional or not deployed, but be aware that backend-dependent functionalities may be broken. After getting the environment variables, you need to set these in GitHub Repository Secrets. ## Deployment / Cloud Providers / Railway :::note Last checked with Wasp 0.24 and Railway (as of Apr 6, 2026). 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. ::: ### Automatic Deployment server client database {#automatic-deployment---} We recommend that you use [Wasp Deploy](https://wasp.sh/docs/deployment/deployment-methods/wasp-deploy/railway) to deploy your Wasp app to Railway. Wasp CLI automates deploying the client, the server and the database with one command. ### Manual Deployment server client database {#manual-deployment---} This guide shows you how to deploy the client, the server, and provision a database on Railway. #### Prerequisites To get started, follow these steps: 1. Make sure your Wasp app is built by running `wasp build` in the project dir. 2. Create a [Railway](https://railway.com/?utm_medium=integration\&utm_source=docs\&utm_campaign=wasp) account. 3. Install the [Railway CLI](https://docs.railway.com/develop/cli?utm_medium=integration\&utm_source=docs\&utm_campaign=wasp#installing-the-cli). 4. Run `railway login` and a browser tab will open to authenticate you. #### Create New Project Let's create our Railway project: 1. Go to your [Railway dashboard](https://railway.com/dashboard?utm_medium=integration\&utm_source=docs\&utm_campaign=wasp), click on **New Project**, and select **Deploy PostgreSQL** from the dropdown menu. 2. Once the project is created, left-click on the **Create** button in the top right corner and select **Empty Service**. 3. Click on the new service, and change the name to `server`. 4. Create another empty service and name it `client`. 5. Deploy the changes by pressing the **Deploy** button on top. #### Deploy Your App to Railway ##### Setup Domains We'll need the domains for both the `server` and `client` services: 1. Go to the `server` instance's **Settings** tab, and click **Generate Domain**. 2. Enter `8080` as the port and click **Generate Domain**. 3. Do the same under the `client`'s **Settings**. 4. Copy both domains, as we will need them later. ##### Deploying the Server You'll deploy the server first: 1. Move into the `.wasp/out` directory: ```shell cd .wasp/out ``` 2. Link the `.wasp/out` directory to your newly created Railway project: ```shell railway link ``` Select `server` when prompted to select a service. 3. Go into the Railway dashboard and set up the required env variables: Click on the `server` service and go to the **Variables** tab: 1. Click **Variable reference** and select `DATABASE_URL` (it will populate it with the correct value) 2. Add `WASP_WEB_CLIENT_URL` with the `client` domain (e.g. `https://client-production-XXXX.up.railway.app`). `https://` prefix is required! 3. Add `WASP_SERVER_URL` with the `server` domain (e.g. `https://server-production-XXXX.up.railway.app`). `https://` prefix is required! 4. Add `JWT_SECRET` with a random string at least 32 characters long\ Generate secret :::tip[Using an external auth method?] If your app is using an external authentication method(s) supported by Wasp (such as [Google](https://wasp.sh/docs/auth/social-auth/google#4-adding-environment-variables) or [GitHub](https://wasp.sh/docs/auth/social-auth/github#4-adding-environment-variables)), make sure to additionally set the necessary environment variables specifically required by these method(s). ::: 4. Push and deploy the project: ```shell railway up --ci ``` We use the `--ci` flag to limit the log output to only the build process. Railway will locate the `Dockerfile` in `.wasp/out` and deploy your server. ##### Deploying the Client 1. Create the production build from the project root, using the `server` domain as the `REACT_APP_API_URL`: ```shell REACT_APP_API_URL= npx vite build ``` 2. Create a `railway.json` file in `.wasp/out/web-app/build` to ensure Railway uses the correct builder for the static files: ```json title=".wasp/out/web-app/build/railway.json" { "$schema": "https://railway.com/railway.schema.json", "build": { "builder": "RAILPACK" } } ``` 3. Create a `Caddyfile` in `.wasp/out/web-app/build` to configure how Railway serves your static files: ```caddyfile title=".wasp/out/web-app/build/Caddyfile" { admin off persist_config off auto_https off log { format json } servers { trusted_proxies static private_ranges } } :{$PORT:80} { log { format json } respond /health 200 # Security headers header { # Enable cross-site filter (XSS) and tell browsers to block detected attacks X-XSS-Protection "1; mode=block" # Prevent some browsers from MIME-sniffing a response away from the declared Content-Type X-Content-Type-Options "nosniff" # Keep referrer data off of HTTP connections Referrer-Policy "strict-origin-when-cross-origin" # Enable strict Content Security Policy Content-Security-Policy "default-src 'self'; img-src 'self' data: https: *; style-src 'self' 'unsafe-inline' https: *; script-src 'self' 'unsafe-inline' https: *; font-src 'self' data: https: *; connect-src 'self' https: *; media-src 'self' https: *; object-src 'none'; frame-src 'self' https: *;" # Remove Server header -Server } root * . # Handle static files file_server { hide .git hide .env* } # Compression with more formats encode { gzip zstd } # Try files with HTML extension and handle SPA routing # This is where we diverge from the Railpacks's original Caddyfile try_files {path} {path}/index.html /200.html handle_errors { rewrite * /{err.status_code}.html file_server } } ``` This overrides [Railway's default Caddyfile](https://github.com/railwayapp/railpack/blob/main/core/providers/staticfile/Caddyfile.template) so that prerendered pages are served correctly and non-prerendered routes fall back to the SPA shell (`200.html`). 4. Link the client build directory to the `client` service: ```shell cd .wasp/out/web-app/build railway link ``` 5. Deploy the client build to Railway: ```shell railway up --ci ``` Select `client` when prompted to select a service. And now your Wasp should be deployed! Back in your [Railway dashboard](https://railway.com/dashboard?utm_medium=integration\&utm_source=docs\&utm_campaign=wasp), click on your project and you should see your newly deployed services: PostgreSQL, Server, and Client. #### Updates & Redeploying When you make updates and need to redeploy: 1. Run `wasp build` to rebuild your app. 2. Go into the `.wasp/out` directory and: Deploy the server with: ```shell railway up --ci ``` 3. Rebuild the client from the project root: ```shell REACT_APP_API_URL= npx vite build ``` And then deploy the client with: ```shell cd .wasp/out/web-app/build railway up --ci ``` ## Deployment / Cloud Providers / Render :::note Last checked with Wasp 0.24 and Render (as of Apr 15, 2026). 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. ::: ### Deploy Wasp on Render server client database {#deploy-wasp-on-render---} This guide shows you how to deploy the server, client, and provision a database on Render. Unlike the other providers listed here, Render builds your Wasp app from source on its servers, so you don't need to run `wasp build` locally before deploying. You'll define your entire deployment setup in a `render.yaml` file that Render uses as a [Blueprint](https://docs.render.com/infrastructure-as-code) to create and configure all services. #### Prerequisites To get started, follow these steps: 1. Create a [Render](https://render.com/) account. 2. Push your Wasp project to a Git repository (GitHub, GitLab, or Bitbucket). 3. Generate your initial database migrations locally by running `wasp db migrate-dev` and commit the `migrations/` directory. Render needs these migration files in the repo to set up your database. #### Create the render.yaml Blueprint Create a `render.yaml` file in the root of your repository. This defines all three services (database, server, and client): ```yaml title="render.yaml" services: # Node.js server -- Render installs Wasp and builds from source - type: web name: -server runtime: node plan: region: branch: main buildCommand: >- npm install -g @wasp.sh/wasp-cli@ && export PATH="$(npm prefix -g)/bin:$PATH" && wasp install && wasp build && cd .wasp/out/server && npm install && npx prisma generate --schema=../db/schema.prisma && npm run bundle startCommand: cd .wasp/out/server && npm run start-production envVars: - key: DATABASE_URL fromDatabase: name: -db property: connectionString - key: JWT_SECRET generateValue: true - key: WASP_SERVER_URL sync: false # you'll fill this in after the first deploy - key: WASP_WEB_CLIENT_URL sync: false # you'll fill this in after the first deploy - key: NODE_VERSION value: "24" # React client -- static site built with Vite - type: web name: -client runtime: static branch: main buildCommand: >- npm install -g @wasp.sh/wasp-cli@ && export PATH="$(npm prefix -g)/bin:$PATH" && wasp install && wasp build && npx vite build staticPublishPath: .wasp/out/web-app/build envVars: - key: REACT_APP_API_URL sync: false # you'll fill this in after the first deploy - key: NODE_VERSION value: "24" routes: - type: rewrite source: /* destination: /200.html databases: - name: -db plan: region: postgresMajorVersion: "18" ``` You should replace the following values for your app: | Variable | Value | Example | | ---------------- | --------------------------------------- | ------------- | | `` | A unique name for your app | `my-wasp-app` | | `` | The Wasp CLI version you're using | `0.24` | | `` | The Render plan for your services | `free` | | `` | The Render region closest to your users | `oregon` | :::caution The Render free-tier PostgreSQL database [expires after 30 days](https://render.com/docs/free#30-day-limit). Use the Starter plan or an external provider for production. ::: Commit this file and push to your repository: ```bash git add render.yaml git commit -m "Add Render Blueprint" git push origin main ``` #### Deploy with the Blueprint 1. In the Render Dashboard, click **New > Blueprint**. 2. Connect your Git repository and select the branch with the `render.yaml`. 3. Render will parse the Blueprint and show the resources it will create. Do not fill out the environment variables form yet. Click **Apply**. This will try to create all three services. It will fail initially, as some environment variables are missing. ##### Set the Environment Variables Wait until all services are created. Go to each one in the Render Dashboard and note its URL (usually `https://-server.onrender.com` and `https://-client.onrender.com`). On the **server** Web Service, go to **Settings > Environment** and set the following variables. When you're done, click **Save and rebuild**: | Variable | Value | | --------------------- | ---------------------------------------- | | `WASP_SERVER_URL` | `https://-server.onrender.com` | | `WASP_WEB_CLIENT_URL` | `https://-client.onrender.com` | :::tip[Using an external auth method?] If your app is using an external authentication method(s) supported by Wasp (such as [Google](https://wasp.sh/docs/auth/social-auth/google#4-adding-environment-variables) or [GitHub](https://wasp.sh/docs/auth/social-auth/github#4-adding-environment-variables)), make sure to additionally set the necessary environment variables specifically required by these method(s). ::: On the **client** Static Site, go to **Settings > Environment** and set the following variables. When you're done, click **Save and rebuild**: | Variable | Value | | ------------------- | ---------------------------------------- | | `REACT_APP_API_URL` | `https://-server.onrender.com` | :::caution `REACT_APP_API_URL` must be set **before** the client build runs. Vite embeds it into the compiled JavaScript at build time. If it's missing, all API calls from the client will fail. ::: :::tip[Using a Render Environment Group] Rather than setting variables on each service separately, you can create an [Environment Group](https://docs.render.com/configure-environment-variables#environment-groups) and link it to both services to manage shared variables in one place. This is a best practice on the Render platform. ::: #### Redeploying After Changes Render auto-deploys when it detects a new commit on the configured branch. Just push your changes: ```bash git push origin main ``` If you have new database model changes, make sure to run `wasp db migrate-dev` locally first and commit the generated migration files along with your code changes. The server runs `prisma migrate deploy` on startup, so new migrations are applied automatically on each deploy. :::note[Build time] Both services install Wasp and compile the app from source on each deploy. On the free tier, this can take 10-15 minutes. If builds consistently time out, consider upgrading to the Starter plan. ::: ## Deployment / Self-Hosted / Caprover :::note Last checked with Wasp 0.24 and Caprover (as of Jan 30, 2026). 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. ::: ### Deploy Wasp with Caprover This guide shows you how to deploy a Wasp application to [Caprover](https://caprover.com/), a self-hosted PaaS (Platform as a Service) for managing your deployments. #### Prerequisites - A server with [Caprover installed](https://caprover.com/docs/get-started.html#prerequisites) - A domain name - A GitHub repository with your Wasp application #### Overview Deploying to Caprover involves: 1. Creating Caprover apps (client, server, and database) 2. Building Docker images using GitHub Actions 3. Triggering Caprover to deploy the images #### Step 1: Set Up Your Domain Point your DNS A records to your server IP: - `@` (root) โ†’ server IP (for `myapp.com` - client) - `api` โ†’ server IP (for `api.myapp.com` - server) :::tip If you followed Caprover's install instructions with `*.apps` subdomain setup, you can use `https://myapp-client.apps.mydomain.com` and `https://myapp-server.apps.mydomain.com` for quick testing. ::: #### Step 2: Create Caprover Apps ##### Create the Database 1. Go to **One-Click Apps** and select **PostgreSQL** 2. Name it `myapp-db` 3. Set version to `18` (or whichever version is latest) 4. Deploy it 5. Note the connection string: `postgresql://postgres:@srv-captain--myapp-db:5432/postgres` ##### Create the Server App 1. Create a new app named `myapp-server` 2. Go to **HTTP Settings**: - Connect domain `https://api.` - Click **Enable HTTPS** - Set **Container HTTP Port** to `3001` - Enable **Force HTTPS** and **Websocket Support** 3. Click **Save & Restart** ##### Create the Client App 1. Create a new app named `myapp-client` 2. Go to **HTTP Settings**: - Connect domain `https://` - Click **Enable HTTPS** - Set **Container HTTP Port** to `8043` - Enable **Force HTTPS** and **Websocket Support** 3. Click **Save & Restart** #### Step 3: Configure Server Environment Variables In the server app, go to **App Configs > Environment Variables** and add: | Variable | Value | | --------------------- | ---------------------------------------------------------------------- | | `DATABASE_URL` | `postgresql://postgres:@srv-captain--myapp-db:5432/postgres` | | `JWT_SECRET` | Random string at least 32 characters long: Generate secret | | `PORT` | `3001` | | `WASP_WEB_CLIENT_URL` | `https://` | | `WASP_SERVER_URL` | `https://api.` | Add any other environment variables your app needs (from `.env.server`). #### Step 4: Enable GitHub Container Registry Access 1. In Caprover, go to **Cluster** 2. Add a new **Remote Registry**: - **Username**: Your GitHub username - **Password**: Your GitHub personal access token - **Domain**: `ghcr.io` - **Image Prefix**: Your GitHub username #### Step 5: Create GitHub Action Create `.github/workflows/deploy.yml` in your repository: ```yaml title=".github/workflows/deploy.yml" name: "Deploy" on: push: branches: - "main" concurrency: group: deployment cancel-in-progress: true env: WASP_VERSION: "0.25" SERVER_APP_NAME: "myapp-server" SERVER_APP_URL: "https://api.myapp.com" CLIENT_APP_NAME: "myapp-client" DOCKER_REGISTRY: "ghcr.io" DOCKER_REGISTRY_USERNAME: ${{ github.repository_owner }} DOCKER_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} jobs: build-and-push-images: permissions: contents: read packages: write runs-on: ubuntu-latest # Remove this block if your app is NOT in an 'app' folder defaults: run: working-directory: ./app steps: - name: Checkout repository uses: actions/checkout@v4 - name: Log in to Container registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ env.DOCKER_REGISTRY_USERNAME }} password: ${{ env.DOCKER_REGISTRY_PASSWORD }} - name: (server) Extract metadata for Docker id: meta-server uses: docker/metadata-action@v5 with: images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_REGISTRY_USERNAME }}/${{ env.SERVER_APP_NAME }} - name: (client) Extract metadata for Docker id: meta-client uses: docker/metadata-action@v5 with: images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_REGISTRY_USERNAME }}/${{ env.CLIENT_APP_NAME }} - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: "24.14.1" - name: Install Wasp shell: bash run: npm i -g @wasp.sh/wasp-cli@${{ env.WASP_VERSION }} - name: Install Wasp app dependencies run: wasp install - name: Build Wasp app run: wasp build - name: (client) Build run: REACT_APP_API_URL=${{ env.SERVER_APP_URL }} npx vite build - name: (client) Prepare Dockerfile run: | cd ./.wasp/out/web-app echo "FROM pierrezemb/gostatic" > Dockerfile echo "CMD [\"-fallback\", \"200.html\", \"-enable-logging\"]" >> Dockerfile echo "COPY ./build /srv/http" >> Dockerfile - name: (server) Build and push Docker image uses: docker/build-push-action@v6 with: # Remove 'app/' if your app is at the repo root context: ./app/.wasp/out file: ./app/.wasp/out/Dockerfile push: true tags: ${{ steps.meta-server.outputs.tags }} labels: ${{ steps.meta-server.outputs.labels }} - name: (client) Build and push Docker image uses: docker/build-push-action@v6 with: # Remove 'app/' if your app is at the repo root context: ./app/.wasp/out/web-app file: ./app/.wasp/out/web-app/Dockerfile push: true tags: ${{ steps.meta-client.outputs.tags }} labels: ${{ steps.meta-client.outputs.labels }} - name: (server) Deploy to Caprover uses: caprover/deploy-from-github@v1.1.2 with: server: ${{ secrets.CAPROVER_SERVER }} app: ${{ env.SERVER_APP_NAME }} token: ${{ secrets.SERVER_APP_TOKEN }} image: ${{ steps.meta-server.outputs.tags }} - name: (client) Deploy to Caprover uses: caprover/deploy-from-github@v1.1.2 with: server: ${{ secrets.CAPROVER_SERVER }} app: ${{ env.CLIENT_APP_NAME }} token: ${{ secrets.CLIENT_APP_TOKEN }} image: ${{ steps.meta-client.outputs.tags }} ``` #### Step 6: Configure GitHub Secrets In your GitHub repository, go to **Settings > Secrets and variables > Actions** and add: ##### `CAPROVER_SERVER` Your Caprover dashboard URL, e.g., `https://captain.apps.mydomain.com` ##### `SERVER_APP_TOKEN` 1. Go to your server app in Caprover 2. Under **Deployment**, find **Method 1: Official CLI** 3. Click **Enable App Token** 4. Copy the token ##### `CLIENT_APP_TOKEN` 1. Go to your client app in Caprover 2. Under **Deployment**, find **Method 1: Official CLI** 3. Click **Enable App Token** 4. Copy the token #### Step 7: Deploy Push to the `main` branch and the GitHub Action will: 1. Build your Wasp application 2. Create Docker images for server and client 3. Push images to GitHub Container Registry 4. Deploy both apps to Caprover ## Deployment / Self-Hosted / Coolify :::note Last checked with Wasp 0.24 and Coolify (as of Jan 30, 2026). 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. ::: ### Deploy Wasp with Coolify This guide shows you how to deploy a Wasp application to [Coolify](https://coolify.io/), a self-hosted deployment platform that makes managing your infrastructure easy. #### Prerequisites - A server with [Coolify installed](https://coolify.io/self-hosted) - A domain name - A GitHub repository with your Wasp application #### Overview Deploying to Coolify involves: 1. Creating Coolify apps (client, server, and database) 2. Building Docker images using GitHub Actions 3. Triggering Coolify to pull and deploy the images #### Step 1: Set Up Your Domain Point your DNS A records to your server IP: - `@` (root) โ†’ server IP (for `myapp.com` - client) - `api` โ†’ server IP (for `api.myapp.com` - server) #### Step 2: Create Coolify Resources ##### Create the Database 1. Create a new resource and select **PostgreSQL** 2. Use the default PostgreSQL variant 3. Name it `myapp-db` 4. Click **Start** to set up the database 5. Copy the **Postgres URL (internal)** - you'll need this later ##### Create the Server App 1. Create a new resource and select **Docker Image** 2. Set the image name to `ghcr.io//myapp-server` 3. Name it `myapp-server` 4. Configure: - **Domains**: `https://api.` - **Docker Image Tag**: `main` - **Port Exposes**: `3001` 5. Click **Save** ##### Create the Client App 1. Create a new resource and select **Docker Image** 2. Set the image name to `ghcr.io//myapp-client` 3. Name it `myapp-client` 4. Configure: - **Domains**: `https://` - **Docker Image Tag**: `main` - **Port Exposes**: `8043` 5. Click **Save** #### Step 3: Configure Server Environment Variables In the server app, go to **Environment Variables** and add: | Variable | Value | | --------------------- | ---------------------------------------------------------- | | `DATABASE_URL` | The Postgres URL (internal) from step 2 | | `JWT_SECRET` | Random string at least 32 characters long: Generate secret | | `PORT` | `3001` | | `WASP_WEB_CLIENT_URL` | `https://` | | `WASP_SERVER_URL` | `https://api.` | Add any other environment variables your app needs (from `.env.server`). #### Step 4: Create GitHub Action Create `.github/workflows/deploy.yml` in your repository: ```yaml title=".github/workflows/deploy.yml" name: "Deploy" on: push: branches: - "main" concurrency: group: deployment cancel-in-progress: true env: WASP_VERSION: "0.25" SERVER_APP_NAME: "myapp-server" SERVER_APP_URL: "https://api.myapp.com" CLIENT_APP_NAME: "myapp-client" DOCKER_REGISTRY: "ghcr.io" DOCKER_REGISTRY_USERNAME: ${{ github.repository_owner }} DOCKER_REGISTRY_PASSWORD: ${{ secrets.GITHUB_TOKEN }} jobs: build-and-push-images: permissions: contents: read packages: write runs-on: ubuntu-latest # Remove this block if your app is NOT in an 'app' folder defaults: run: working-directory: ./app steps: - name: Checkout repository uses: actions/checkout@v4 - name: Log in to Container registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ env.DOCKER_REGISTRY_USERNAME }} password: ${{ env.DOCKER_REGISTRY_PASSWORD }} - name: (server) Extract metadata for Docker id: meta-server uses: docker/metadata-action@v5 with: images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_REGISTRY_USERNAME }}/${{ env.SERVER_APP_NAME }} - name: (client) Extract metadata for Docker id: meta-client uses: docker/metadata-action@v5 with: images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_REGISTRY_USERNAME }}/${{ env.CLIENT_APP_NAME }} - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: "24.14.1" - name: Install Wasp shell: bash run: npm i -g @wasp.sh/wasp-cli@${{ env.WASP_VERSION }} - name: Install Wasp app dependencies run: wasp install - name: Build Wasp app run: wasp build - name: (client) Build run: REACT_APP_API_URL=${{ env.SERVER_APP_URL }} npx vite build - name: (client) Prepare Dockerfile run: | cd ./.wasp/out/web-app echo "FROM pierrezemb/gostatic" > Dockerfile echo "CMD [\"-fallback\", \"200.html\", \"-enable-logging\"]" >> Dockerfile echo "COPY ./build /srv/http" >> Dockerfile - name: (server) Build and push Docker image uses: docker/build-push-action@v6 with: # Remove 'app/' if your app is at the repo root context: ./app/.wasp/out file: ./app/.wasp/out/Dockerfile push: true tags: ${{ steps.meta-server.outputs.tags }} labels: ${{ steps.meta-server.outputs.labels }} - name: (client) Build and push Docker image uses: docker/build-push-action@v6 with: # Remove 'app/' if your app is at the repo root context: ./app/.wasp/out/web-app file: ./app/.wasp/out/web-app/Dockerfile push: true tags: ${{ steps.meta-client.outputs.tags }} labels: ${{ steps.meta-client.outputs.labels }} - name: Trigger Deploy Webhooks env: CLIENT_COOLIFY_WEBHOOK: ${{ secrets.CLIENT_COOLIFY_WEBHOOK }} SERVER_COOLIFY_WEBHOOK: ${{ secrets.SERVER_COOLIFY_WEBHOOK }} COOLIFY_TOKEN: ${{ secrets.COOLIFY_TOKEN }} run: | curl "${{ env.CLIENT_COOLIFY_WEBHOOK }}" --header 'Authorization: Bearer ${{ env.COOLIFY_TOKEN }}' curl "${{ env.SERVER_COOLIFY_WEBHOOK }}" --header 'Authorization: Bearer ${{ env.COOLIFY_TOKEN }}' ``` #### Step 5: Configure GitHub Secrets In your GitHub repository, go to **Settings > Secrets and variables > Actions** and add: ##### `SERVER_COOLIFY_WEBHOOK` 1. Go to your server app in Coolify 2. Click **Webhooks** 3. Copy the **Deploy Webhook** URL ##### `CLIENT_COOLIFY_WEBHOOK` 1. Go to your client app in Coolify 2. Click **Webhooks** 3. Copy the **Deploy Webhook** URL ##### `COOLIFY_TOKEN` 1. In Coolify, go to **Settings** and under **Advanced** enable API Access 2. Go to **Keys & Tokens** > **API tokens** 3. Create a new API token with **Deploy** permissions 4. Copy the token #### Step 6: Deploy Push to the `main` branch and the GitHub Action will: 1. Build your Wasp application 2. Create Docker images for server and client 3. Push images to GitHub Container Registry 4. Trigger Coolify to deploy the new images ## Deployment / Self-Hosted / Simple VPS :::note Last checked with Wasp 0.24, Caddy (as of Jan 30, 2026), and Ubuntu (as of Jan 30, 2026). 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. ::: ### Deploy Wasp to a VPS This guide shows you how to deploy a Wasp application directly to a VPS (Virtual Private Server) using Docker and a reverse proxy. #### Prerequisites - A VPS (e.g., from Hetzner, DigitalOcean, Linode, etc.) - A domain name - Basic familiarity with SSH and Linux commands #### Architecture Overview Our deployment setup includes: - **Ubuntu LTS** as the operating system - **Caddy** as a reverse proxy for HTTPS and domain handling - **Docker** for running the server and database - Serving the client with a static file server #### Step 1: Connect to Your Server Connect to your server via SSH: ```bash ssh @ ``` Usually the username is `root` if the provider doesn't specify otherwise. #### Step 2: Install Caddy First, update your package list: ```bash apt update ``` If Apache is installed, you may need to [uninstall it](https://askubuntu.com/a/387793) first. Check with `which apache2`. Install Caddy following the [official Ubuntu instructions](https://caddyserver.com/docs/install#debian-ubuntu-raspbian). After installation, visit your server's IP to see the Caddy welcome message. #### Step 3: Set Up the Firewall Configure UFW to only allow necessary connections: ```bash ufw default deny incoming ufw default allow outgoing # Allow SSH connections (do this BEFORE enabling UFW!) ufw allow ssh ufw show added # Enable the firewall ufw enable # Allow HTTP and HTTPS ufw allow http ufw allow https ``` #### Step 4: Install Docker Follow the [official Docker installation guide for Ubuntu](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository). Verify the installation: ```bash docker run hello-world ``` #### Step 5: Set Up GitHub Deploy Key To clone from a private repository, generate an SSH key on your server: ```bash ssh-keygen ``` Get the public key (the filename might very depending on the key type): ```bash cat ~/.ssh/id_ed25519.pub ``` Add this key as a deploy key at `https://github.com///settings/keys/new`. #### Step 6: Clone Your Repository ```bash git clone git@github.com:/.git ``` #### Step 7: Install Node.js Install Node.js using nvm: ```bash curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash source ~/.bashrc nvm install 24.14.1 ``` #### Step 8: Install Wasp CLI Install the Wasp CLI: ```bash npm i -g @wasp.sh/wasp-cli ``` Add Wasp to your PATH by adding this line to `~/.bashrc`: ```bash export PATH=$PATH:~/.local/bin ``` Reload your shell: ```bash source ~/.bashrc ``` Confirm the Wasp CLI works by running: ```bash wasp version ``` #### Step 9: Build the Application In your project directory, install dependencies and build the app: ```bash wasp install wasp build ``` #### Step 10: Start the Database Create a Docker network: ```bash docker network create myapp-network ``` Start PostgreSQL: ```bash docker run -d \ --name myapp-db \ -e POSTGRES_PASSWORD=mysecretpassword \ -e POSTGRES_DB=myapp \ -v postgres_data:/var/lib/postgresql \ --network myapp-network \ postgres:18 ``` Connect to the database using `psql` to verify it's running: ```bash docker exec -it myapp-db psql -U postgres -d myapp ``` Verify you are connected to the `myapp` database by typing `\conninfo`. You can exit `psql` by typing in `\q`. #### Step 11: Configure Your Domain Set up DNS A records pointing to your server IP: - `@` (root) โ†’ your server IP (for `myapp.com`) - `api` โ†’ your server IP (for `api.myapp.com`) #### Step 12: Start the Server After you built the app with `wasp build`, build the server app Docker image: ```bash # Navigate to the out directory cd .wasp/out # Build the server Docker image docker build . -t myapp-server ``` Create an `.env.production` environment file in your project directory and add: | Variable | Value | | --------------------- | ------------------------------------------------------------ | | `DATABASE_URL` | `postgresql://postgres:mysecretpassword@myapp-db:5432/myapp` | | `JWT_SECRET` | Random string at least 32 characters long: Generate secret | | `PORT` | `3001` | | `WASP_WEB_CLIENT_URL` | `https://` | | `WASP_SERVER_URL` | `https://api.` | Add any other environment variables your app needs (from `.env.server`). Start the server container: ```bash docker run -d \ --name myapp-server \ --env-file .env.production \ -p 127.0.0.1:3001:3001 \ --network myapp-network \ myapp-server ``` :::note We bind the server to `127.0.0.1:3001` to ensure it is only accessible from the server itself, not directly from the internet. ::: Verify it's running: ```bash curl -I http://localhost:3001 ``` You should see a `200 OK` HTTP status code. #### Step 13: Build the Client In the project directory run: ```bash REACT_APP_API_URL=https://api.myapp.com npx vite build ``` Copy the built files to a serving directory: ```bash sudo mkdir -p /var/www sudo cp -R .wasp/out/web-app/build/* /var/www/ sudo chown -R caddy:caddy /var/www ``` #### Step 14: Configure Caddy Edit the Caddyfile at `/etc/caddy/Caddyfile`: ```caddyfile myapp.com { root * /var/www encode gzip try_files {path} /200.html file_server } api.myapp.com { reverse_proxy localhost:3001 } ``` Reload Caddy: ```bash sudo systemctl reload caddy ``` Your app should now be accessible at `https://myapp.com`! #### Redeploying Updates Create a deployment script: ```bash title="redeploy.sh" #!/bin/bash set -e APP_DIR="your-app-name" SERVER_APP_NAME="myapp-server" SERVER_APP_URL=https://api.myapp.com echo "Pulling latest changes..." cd ~/"$APP_DIR" git pull echo "Building Wasp project..." wasp build echo "Building Docker image..." cd .wasp/out/ docker build . -t $SERVER_APP_NAME echo "Stopping existing server..." docker container stop $SERVER_APP_NAME && docker container rm $SERVER_APP_NAME || true echo "Starting new server..." cd ~/"$APP_DIR" docker run -d --name $SERVER_APP_NAME --env-file .env.production -p 127.0.0.1:3001:3001 --network myapp-network $SERVER_APP_NAME echo "Building client..." REACT_APP_API_URL=$SERVER_APP_URL npx vite build echo "Copying new client files..." sudo rm -rf /var/www/* sudo cp -R .wasp/out/web-app/build/* /var/www/ sudo chown -R caddy:caddy /var/www ``` Make it executable and run: ```bash chmod +x redeploy.sh ./redeploy.sh ``` #### Minimizing Downtime Configure Caddy to retry connections during restarts: ```caddyfile api.myapp.com { reverse_proxy localhost:3001 { health_uri / lb_try_duration 15s } } ``` This makes Caddy wait up to 15 seconds for the server to become available again. ## Integrations / Custom OAuth Provider :::note 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. ::: This guide shows you how to implement a custom OAuth provider in your Wasp application. We'll use Spotify as an example, but the same approach works for any OAuth provider. ### Prerequisites - A Wasp project with authentication set up - An OAuth application registered with your provider (e.g., [Spotify Developer Dashboard](https://developer.spotify.com/dashboard)) ### Setting up a Custom OAuth Provider #### 1. Configure main.wasp.ts Set up the auth configuration and API routes: ```ts title="main.wasp.ts" import { api, app, page, route } from "@wasp.sh/spec" import { authWithSpotify, authWithSpotifyCallback } from "./src/auth" with { type: "ref" } import { MainPage } from "./src/MainPage" with { type: "ref" } export default app({ name: "SpotifyOauth", wasp: { version: "^0.24.0" }, title: "spotify-oauth", head: [""], auth: { userEntity: "User", onAuthFailedRedirectTo: "/", methods: { // Enable at least one OAuth provider so Wasp exposes OAuth helpers google: {}, }, }, spec: [ route("RootRoute", "/", page(MainPage)), api("GET", "/auth/spotify", authWithSpotify), api("GET", "/auth/spotify/callback", authWithSpotifyCallback), ], }) ``` :::note The route names are arbitrary, but the path on `authWithSpotifyCallback` must match the redirect URI you register with your provider. Spotify rejects `localhost` over HTTP, so register `http://127.0.0.1:3001/auth/spotify/callback` in the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard) and set Wasp's URLs to match in step 2. ::: #### 2. Configure environment variables Create or update your `.env.server` file: ```bash title=".env.server" SPOTIFY_CLIENT_ID=your_client_id SPOTIFY_CLIENT_SECRET=your_client_secret # You may need dummy values for built-in providers if you're using them # just to get the arctic package installed GOOGLE_CLIENT_ID=x GOOGLE_CLIENT_SECRET=x # Spotify rejects `localhost`, so point Wasp at 127.0.0.1 instead WASP_SERVER_URL=http://127.0.0.1:3001 WASP_WEB_CLIENT_URL=http://127.0.0.1:3000 ``` ```bash title=".env.client" REACT_APP_API_URL=http://127.0.0.1:3001 ``` :::note Most OAuth providers accept `localhost` directly; the `127.0.0.1` setup above is specific to Spotify. Other providers (e.g., Slack) require a real hostname. See [Local Network Testing](https://wasp.sh/docs/guides/debugging/local-network-testing) for the `nip.io` workaround. ::: #### 3. Set up the database schema Update your Prisma schema to store the user data you need: ```prisma title="schema.prisma" model User { id String @id @default(cuid()) name String profilePicture String } ``` #### 4. Implement the OAuth handlers The implementation splits across two files: pure Spotify logic in `src/spotify.ts`, and Wasp auth logic in `src/auth.ts`. `src/spotify.ts` holds the [Arctic](https://v1.arcticjs.dev/providers/spotify) client (the library Wasp uses for OAuth) and the [Spotify `/me`](https://developer.spotify.com/documentation/web-api/reference/get-current-users-profile) profile fetch: ```ts title="src/spotify.ts" import * as arctic from "arctic"; import { config } from "wasp/server"; import * as z from "zod"; if (!process.env.SPOTIFY_CLIENT_ID || !process.env.SPOTIFY_CLIENT_SECRET) { throw new Error( "Please provide SPOTIFY_CLIENT_ID and SPOTIFY_CLIENT_SECRET in .env.server file", ); } const clientId = process.env.SPOTIFY_CLIENT_ID; const clientSecret = process.env.SPOTIFY_CLIENT_SECRET; const redirectURI = `${config.serverUrl}/auth/spotify/callback`; export const spotify = new arctic.Spotify(clientId, clientSecret, redirectURI); // Spotify user schema for validation const spotifyUserSchema = z.object({ id: z.string(), display_name: z.string(), external_urls: z.object({ spotify: z.string(), }), images: z.array( z.object({ url: z.string(), height: z.number(), width: z.number(), }), ), }); export type SpotifyUser = z.infer; export async function getSpotifyUser( accessToken: string, ): Promise { const response = await fetch("https://api.spotify.com/v1/me", { headers: { Authorization: `Bearer ${accessToken}`, }, }); return spotifyUserSchema.parse(await response.json()); } ``` `src/auth.ts` wires the route handlers and uses `wasp/server/auth` helpers (`findAuthIdentity`, `createUser`) to connect the OAuth identity to a Wasp session (see [Custom Auth Actions](https://wasp.sh/docs/auth/advanced/custom-auth-actions) for details). This is similar to what Wasp does internally for [Google, GitHub and other supported providers](https://wasp.sh/docs/auth/social-auth/overview): ```ts title="src/auth.ts" import * as arctic from "arctic"; import type { AuthWithSpotify, AuthWithSpotifyCallback } from "wasp/server/api"; import { createUser, findAuthIdentity, getRedirectUriForOneTimeCode, tokenStore, } from "wasp/server/auth"; import type { ProviderName } from "wasp/server/auth"; import { spotify, getSpotifyUser, type SpotifyUser } from "./spotify"; // Handler for /auth/spotify - initiates OAuth flow export const authWithSpotify: AuthWithSpotify = async (req, res) => { const state = arctic.generateState(); const url = await spotify.createAuthorizationURL(state, { scopes: ["user-read-email"], }); res.redirect(url.toString()); }; // Handler for /auth/spotify/callback - processes OAuth callback export const authWithSpotifyCallback: AuthWithSpotifyCallback = async ( req, res, ) => { const code = req.query.code as string; const tokens = await spotify.validateAuthorizationCode(code); const spotifyUser = await getSpotifyUser(tokens.accessToken); const providerId = { providerName: "spotify" as ProviderName, providerUserId: spotifyUser.id, }; const existingIdentity = await findAuthIdentity(providerId); const authId = existingIdentity ? existingIdentity.authId : await createUserFromSpotifyProfile(providerId, spotifyUser); const oneTimeCode = await tokenStore.createToken(authId); return res.redirect(getRedirectUriForOneTimeCode(oneTimeCode).toString()); }; async function createUserFromSpotifyProfile( providerId: { providerName: ProviderName; providerUserId: string }, spotifyUser: SpotifyUser, ): Promise { const userData = { name: spotifyUser.display_name, profilePicture: spotifyUser.images[1]?.url ?? spotifyUser.images[0]?.url ?? "", }; const user = await createUser( providerId, JSON.stringify(spotifyUser), userData, ); return user.auth!.id; } ``` :::note The `tokenStore` and `getRedirectUriForOneTimeCode` are internal Wasp APIs that may change in future versions. This guide relies on them because there is currently no public API for implementing fully custom OAuth flows. ::: #### 5. Create the login page Add a login button that redirects to your OAuth endpoint. This follows the same pattern as Wasp's [custom social auth UI](https://wasp.sh/docs/auth/social-auth/create-your-own-ui): ```tsx title="src/MainPage.tsx" import { logout, useAuth } from "wasp/client/auth"; import { config } from "wasp/client"; export const MainPage = () => { const { data: user } = useAuth(); return (
    ); }; ``` ### Using a Different OAuth Provider [Arctic](https://v1.arcticjs.dev/) (v1) supports many providers. Check its documentation for the full list and their specific setup requirements. Each provider follows the same pattern. For example, to use Twitch instead of Spotify: - Swap the Arctic provider: `new arctic.Twitch(clientId, clientSecret, redirectURI)`. - Fetch the user from `https://api.twitch.tv/helix/users` with the appropriate scopes (e.g., `user:read:email`). - Update the `User` schema and the fields passed to `createUser` to match the provider's response. ## Integrations / File Uploads :::note Last checked with Wasp 0.24 and multer 2.1.1. 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. ::: This guide shows you how to implement file uploads in your Wasp application using [Multer](https://github.com/expressjs/multer). ### Setting up File Uploads #### 1. Install Multer Install the Multer package and its types: ```bash npm install multer npm install --save-dev @types/multer ``` #### 2. Define the API endpoint in main.wasp.ts Create an API namespace with middleware configuration and the upload endpoint: ```ts title="main.wasp.ts" import { api, apiNamespace, app, page, route } from "@wasp.sh/spec" import { configureFileUploadMiddleware, uploadFile } from "./src/apis" with { type: "ref" } import { MainPage } from "./src/MainPage" with { type: "ref" } export default app({ // ... spec: [ route("RootRoute", "/", page(MainPage)), apiNamespace("/api/upload", { middlewareConfigFn: configureFileUploadMiddleware }), api("POST", "/api/upload", uploadFile), ], }) ``` #### 3. Create the API handlers Create the middleware configuration and upload handler: ```ts title="src/apis.ts" import type { MiddlewareConfigFn } from "wasp/server"; import type { UploadFile } from "wasp/server/api"; import multer from "multer"; const upload = multer({ dest: "uploads/" }); export const configureFileUploadMiddleware: MiddlewareConfigFn = (config) => { config.set("multer", upload.single("file")); return config; }; export const uploadFile: UploadFile = (req, res) => { console.log(req.body); console.log(req.file); const file = req.file!; return res.json({ fileExists: !!file, }); }; ``` #### 4. Create the upload form Create a form component to handle file uploads: ```tsx title="src/MainPage.tsx" import { useState } from "react"; import { api } from "wasp/client/api"; export const MainPage = () => { const [name, setName] = useState(""); const [file, setFile] = useState(); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!file) return; const formData = new FormData(); formData.append("name", name); formData.append("file", file); const data = await api .post("/api/upload", { body: formData }) .json<{ fileExists: boolean }>(); alert(JSON.stringify(data, null, 2)); }; return (
    setName(e.target.value)} /> setFile(e.target.files?.[0])} />
    ); }; ``` ### Customizing Upload Settings #### Change upload destination You can customize where files are stored: ```ts const upload = multer({ dest: "my-custom-uploads/" }); ``` #### Limit file size Add file size limits: ```ts const upload = multer({ dest: "uploads/", limits: { fileSize: 5 * 1024 * 1024, // 5MB limit }, }); ``` #### Filter file types Only accept certain file types: ```ts const upload = multer({ dest: "uploads/", fileFilter: (req, file, cb) => { if (file.mimetype.startsWith("image/")) { cb(null, true); } else { cb(new Error("Only images are allowed")); } }, }); ``` #### Handle multiple files To handle multiple file uploads: ```ts export const configureFileUploadMiddleware: MiddlewareConfigFn = (config) => { config.set("multer", upload.array("files", 10)); // Max 10 files return config; }; ``` For more options, see the [Multer documentation](https://github.com/expressjs/multer). ## Integrations / Sentry :::note Last checked with Wasp 0.24, @sentry/node 8, and @sentry/react 8. 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. ::: This guide shows you how to integrate [Sentry](https://sentry.io/) into your Wasp application for error tracking on both the server and client. ### Prerequisites - A Wasp project set up - A [Sentry account](https://sentry.io/signup/) ### Setting up Sentry #### 1. Create Sentry Projects You'll need to create two projects in Sentry: 1. **Server project**: Select `Node.js` as the platform and `Express` as the framework 2. **Client project**: Select `React` as the platform After creating each project, you'll receive a unique DSN (Data Source Name) that you'll use to configure Sentry. #### 2. Install Sentry packages Install the Sentry SDKs: ```bash npm install @sentry/node @sentry/react ``` #### 3. Configure your Wasp file Add both the server and client setup functions to your `main.wasp.ts`: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { setupClient } from "./src/clientSetup" with { type: "ref" } import { setupServer } from "./src/serverSetup" with { type: "ref" } export default app({ name: "MyApp", wasp: { version: "^0.24.0" }, title: "my-app", head: [""], server: { setupFn: setupServer, }, client: { setupFn: setupClient, }, // ... }) ``` #### 4. Configure Server-Side Sentry Create the server setup file: ```ts title="src/serverSetup.ts" import * as Sentry from "@sentry/node"; import { ServerSetupFn } from "wasp/server"; Sentry.init({ dsn: process.env.SENTRY_SERVER_DSN, environment: process.env.NODE_ENV, tracesSampleRate: 1.0, }); export const setupServer: ServerSetupFn = async ({ app }) => { Sentry.setupExpressErrorHandler(app); }; ``` :::note Find your DSN in Sentry under **Settings > Client Keys (DSN)**. ::: #### 5. Configure Client-Side Sentry Create the client setup file: ```ts title="src/clientSetup.ts" import * as Sentry from "@sentry/react"; Sentry.init({ dsn: import.meta.env.REACT_APP_SENTRY_CLIENT_DSN, environment: import.meta.env.MODE, tracesSampleRate: 1.0, }); export const setupClient = async () => { // Sentry is initialized above, before the setup function runs. // You can add additional client-side setup here if needed. }; ``` :::note The `setupFn` must be defined and exported even if it has no additional logic. Sentry's `init` call runs at module load time, which is before Wasp calls the setup function. ::: #### 6. Set up environment variables Add to your `.env.server`: ```bash title=".env.server" SENTRY_SERVER_DSN=https://your-server-dsn@sentry.io/your-project-id ``` Add to your `.env.client`: ```bash title=".env.client" REACT_APP_SENTRY_CLIENT_DSN=https://your-client-dsn@sentry.io/your-project-id ``` ### Testing the Integration #### Test Server Errors Create an API endpoint that throws an error: ```ts title="src/apis.ts" import { TestError } from "wasp/server/api"; export const testError: TestError = async (req, res) => { throw new Error("Test server error for Sentry"); }; ``` #### Test Client Errors Add a button that triggers an error: ```tsx title="src/MainPage.tsx" export const MainPage = () => { const handleError = () => { throw new Error("Test client error for Sentry"); }; return (
    ); }; ``` ### Advanced Configuration #### Adding User Context Track which user encountered an error: ```ts title="src/serverSetup.ts" import * as Sentry from "@sentry/node"; // In your API handlers or operations export const someOperation = async (args, context) => { if (context.user) { Sentry.setUser({ id: context.user.id, email: context.user.email, }); } // ... }; ``` #### Performance Monitoring Enable performance monitoring: ```ts Sentry.init({ dsn: "your-dsn", tracesSampleRate: 0.1, // Capture 10% of transactions profilesSampleRate: 0.1, // Capture 10% of profiles (if using profiling) }); ``` #### Error Boundaries (React) Use Sentry's error boundary for React: ```tsx title="src/App.tsx" import * as Sentry from "@sentry/react"; export const App = ({ children }) => { return ( An error occurred

    }> {children}
    ); }; ``` For more configuration options, see the [Sentry documentation](https://docs.sentry.io/). ## Integrations / Swagger UI :::note Last checked with Wasp 0.24, swagger-jsdoc 6, and swagger-ui-express 5. 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. ::: This guide shows you how to add Swagger UI documentation to your Wasp APIs, making it easy to explore and test your endpoints. ### Setting up Swagger UI #### 1. Install dependencies Install the required packages: ```bash npm install swagger-jsdoc swagger-ui-express npm install --save-dev @types/swagger-jsdoc @types/swagger-ui-express ``` #### 2. Configure the API namespace in main.wasp.ts Add an API namespace for the Swagger UI endpoint: ```ts title="main.wasp.ts" import { api, apiNamespace, app } from "@wasp.sh/spec" import { getStatus } from "./src/apis" with { type: "ref" } import { swaggerMiddleware } from "./src/swagger-ui" with { type: "ref" } export default app({ name: "MyApp", wasp: { version: "^0.24.0" }, title: "my-app", head: [""], spec: [ apiNamespace("/api-docs", { middlewareConfigFn: swaggerMiddleware }), api("GET", "/status", getStatus, { auth: false }), ], }) ``` #### 3. Create the spec generator script Because `swagger-jsdoc` scans source files at runtime and `src/` is not available in the production Docker image, you need to pre-generate the spec as a TypeScript module that gets bundled with the server. Create `scripts/generate-swagger.js`: ```js title="scripts/generate-swagger.js" import swaggerJsdoc from "swagger-jsdoc"; import { writeFileSync } from "fs"; const spec = swaggerJsdoc({ definition: { openapi: "3.0.0", info: { title: "My API", version: "1.0.0", description: "API documentation for my Wasp application", contact: { name: "API Support", url: "https://example.com", email: "support@example.com", }, }, components: { securitySchemes: { bearerAuth: { type: "http", scheme: "bearer", bearerFormat: "JWT", description: "Enter your JWT token in the format: Bearer {token}", }, }, }, security: [{ bearerAuth: [] }], }, apis: ["./src/**/*.ts", "!./src/swaggerSpec.ts"], }); writeFileSync( "./src/swaggerSpec.ts", `const swaggerSpec = ${JSON.stringify(spec, null, 2)} as const;\nexport default swaggerSpec;\n`, ); console.log("swaggerSpec.ts generated"); ``` Run this script whenever you add or change JSDoc annotations: ```bash node scripts/generate-swagger.js ``` This generates `src/swaggerSpec.ts`, which gets bundled into the server and works in both dev and production. :::note You may want to add `src/swaggerSpec.ts` to your `.gitignore` since it's a generated file. ::: #### 4. Create the Swagger middleware Create the Swagger UI middleware that serves the pre-generated spec: ```ts title="src/swagger-ui.ts" import * as express from "express"; import helmet from "helmet"; import swaggerUi from "swagger-ui-express"; import { env, MiddlewareConfigFn } from "wasp/server"; import baseSwaggerDoc from "./swaggerSpec"; const swaggerDoc = { ...baseSwaggerDoc, servers: [{ url: env.WASP_SERVER_URL, description: "API server" }], }; export const swaggerMiddleware: MiddlewareConfigFn = (middlewareConfig) => { middlewareConfig.delete("helmet"); middlewareConfig.set( "helmet", helmet({ contentSecurityPolicy: false, hsts: false, }), ); swaggerUi.serve.forEach((handler, i) => { middlewareConfig.set(`swaggerServe${i}`, handler); }); middlewareConfig.set( "swaggerSetup", ( req: express.Request, res: express.Response, next: express.NextFunction, ) => { return swaggerUi.setup(swaggerDoc, { explorer: true, customCss: ".swagger-ui .topbar { display: none }", swaggerOptions: { persistAuthorization: true, url: "/api-docs/swagger.json", }, })(req, res, next); }, ); return middlewareConfig; }; ``` #### 5. Document your APIs Add JSDoc comments with Swagger annotations above your API definitions: ```ts title="src/apis.ts" import { GetStatus } from "wasp/server/api"; /** * @swagger * /status: * get: * summary: Get API status * description: Returns the current status of the API * tags: * - Status * security: * - bearerAuth: [] * responses: * 200: * description: Successful response * content: * application/json: * schema: * type: object * properties: * message: * type: string * 401: * description: Unauthorized * 500: * description: Server error */ export const getStatus: GetStatus = async (req, res) => { return res.json({ message: "OK" }); }; ``` #### 6. Access the documentation Start your Wasp application and navigate to `http://localhost:3001/api-docs` to see your API documentation. ### Documenting Different Request Types #### POST Request with Body ```ts /** * @swagger * /users: * post: * summary: Create a new user * tags: * - Users * requestBody: * required: true * content: * application/json: * schema: * type: object * required: * - email * - password * properties: * email: * type: string * format: email * password: * type: string * minLength: 8 * responses: * 201: * description: User created successfully * 400: * description: Invalid input */ ``` #### Path Parameters ```ts /** * @swagger * /users/{id}: * get: * summary: Get user by ID * tags: * - Users * parameters: * - in: path * name: id * required: true * schema: * type: string * description: User ID * responses: * 200: * description: User found * 404: * description: User not found */ ``` #### Query Parameters ```ts /** * @swagger * /users: * get: * summary: List users * tags: * - Users * parameters: * - in: query * name: page * schema: * type: integer * default: 1 * description: Page number * - in: query * name: limit * schema: * type: integer * default: 10 * description: Items per page * responses: * 200: * description: List of users */ ``` ### Customization #### Custom Styling You can customize the Swagger UI appearance: ```ts swaggerUi.setup(swaggerDoc, { customCss: ` .swagger-ui .topbar { display: none } .swagger-ui .info { margin: 20px 0 } `, customSiteTitle: "My API Documentation", customfavIcon: "/favicon.ico", }); ``` #### Group APIs with Tags Use tags to organize your endpoints. Add a `tags` array to the `definition` in `scripts/generate-swagger.js`: ```ts const spec = swaggerJsdoc({ definition: { // ... other config tags: [ { name: "Users", description: "User management endpoints" }, { name: "Posts", description: "Blog post endpoints" }, { name: "Auth", description: "Authentication endpoints" }, ], }, apis: ["./src/**/*.ts", "!./src/swaggerSpec.ts"], }); ``` For more options, see the [swagger-jsdoc documentation](https://github.com/Surnet/swagger-jsdoc) and [swagger-ui-express documentation](https://github.com/scottie1984/swagger-ui-express). ## Integrations / WebSocket Namespaces :::note 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. ::: This guide shows you how to use Socket.IO namespaces with Wasp's WebSocket support for organizing your real-time communication channels. ### Understanding Namespaces Wasp's built-in WebSocket support gives you a single default connection with type-safe events via `useSocket` and `useSocketListener` (see the [Web Sockets docs](https://wasp.sh/docs/advanced/web-sockets)). Namespaces are a Socket.IO feature that lets you split real-time logic over separate channels on a single shared connection. This is useful when you want to separate concerns, for example having a `/chat` namespace for chat-related events and a `/notifications` namespace for notification events. When using namespaces, you bypass Wasp's built-in client hooks (`useSocket`, `useSocketListener`) and manage connections directly with `socket.io-client`. Wasp still handles the server-side setup and provides the `io` server instance. ### Setting up WebSocket with Namespaces #### 1. Configure WebSocket in main.wasp.ts Enable WebSocket in your Wasp spec with `autoConnect: false` since you'll manage connections manually: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import Main from "./src/MainPage" with { type: "ref" } import { webSocketFn } from "./src/websocketSetup" with { type: "ref" } export default app({ name: "WebsocketTest", wasp: { version: "^0.24.0" }, title: "websocket-test", head: [""], webSocket: { fn: webSocketFn, autoConnect: false, }, spec: [ route("RootRoute", "/", page(Main)), ], }) ``` #### 2. Create the server-side WebSocket handler Set up the namespace on the server side. The `webSocketFn` receives the Socket.IO `io` server and a `context` object with access to your entities: ```ts title="src/websocketSetup.ts" import { type WebSocketDefinition } from "wasp/server/webSocket"; export const webSocketFn: WebSocketDefinition = (io, _context) => { // Create a namespace for messages const messagesNamespace = io.of("/messages"); messagesNamespace.on("connection", (socket) => { console.log("Client connected to messages namespace"); socket.on("chatMessage", (msg) => { console.log("message: ", msg); // Broadcast to all clients in the namespace messagesNamespace.emit("chatMessage", { id: crypto.randomUUID(), username: "User", text: msg, }); }); socket.on("disconnect", () => { console.log("Client disconnected from messages namespace"); }); }); }; ``` #### 3. Create client-side WebSocket utilities Create a module to manage the namespace connection on the client. Since you're connecting to a custom namespace, you use `socket.io-client` directly instead of Wasp's built-in `useSocket` hook: ```ts title="src/websocketHooks.ts" import { useEffect, useState } from "react"; import { io, type Socket } from "socket.io-client"; import { config } from "wasp/client"; const messagesSocket: Socket = io(`${config.apiUrl}/messages`, { transports: ["websocket"], // Vite pre-bundles socket.io-client which breaks autoConnect: https://github.com/vitejs/vite/issues/4798 autoConnect: false, }); messagesSocket.connect(); export function useMessagesSocket(): { socket: Socket; isConnected: boolean } { const [isConnected, setIsConnected] = useState(messagesSocket.connected); useEffect(() => { function onConnect() { setIsConnected(true); } function onDisconnect() { setIsConnected(false); } messagesSocket.on("connect", onConnect); messagesSocket.on("disconnect", onDisconnect); return () => { messagesSocket.off("connect", onConnect); messagesSocket.off("disconnect", onDisconnect); }; }, []); return { socket: messagesSocket, isConnected, }; } export function useSocketListener( socket: Socket, event: string, handler: (...args: any[]) => void, ) { useEffect(() => { socket.on(event, handler); return () => { socket.off(event, handler); }; }, [event, handler, socket]); } ``` #### 4. Use the WebSocket in your component Now use the hooks in your React component: ```tsx title="src/MainPage.tsx" import { useMessagesSocket, useSocketListener } from "./websocketHooks"; const MainPage = () => { const { socket, isConnected } = useMessagesSocket(); useSocketListener(socket, "chatMessage", (message) => { console.log("message received: ", message); }); return (

    Status: {isConnected ? "Connected" : "Disconnected"}

    ); }; export default MainPage; ``` ### Multiple Namespaces You can create multiple namespaces for different purposes: ```ts title="src/websocketSetup.ts" export const webSocketFn: WebSocketDefinition = (io, _context) => { // Messages namespace const messagesNamespace = io.of("/messages"); messagesNamespace.on("connection", (socket) => { // Handle messages events }); // Notifications namespace const notificationsNamespace = io.of("/notifications"); notificationsNamespace.on("connection", (socket) => { // Handle notification events }); // Presence namespace const presenceNamespace = io.of("/presence"); presenceNamespace.on("connection", (socket) => { // Handle presence events }); }; ``` ### Room Support within Namespaces Namespaces can also use rooms for further organization: ```ts messagesNamespace.on("connection", (socket) => { // Join a specific room socket.on("joinRoom", (roomId) => { socket.join(roomId); }); // Send message to a specific room socket.on("roomMessage", ({ roomId, message }) => { messagesNamespace.to(roomId).emit("chatMessage", message); }); }); ``` For more information about Socket.IO namespaces, see the [Socket.IO documentation](https://socket.io/docs/v4/namespaces/). ## Legacy / Wasp Installer :::note Last checked with Wasp 0.21. 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. ::: Starting from Wasp 0.21, installation is done [through npm](https://wasp.sh/docs/quick-start#detailed-installation). The installation method using the script installer is now considered legacy and is not supported anymore. We'll keep it around for the foreseeable future to give users time to switch, but you will not be able to get newer versions until you migrate to npm-based installation. ### How to migrate off the legacy installer {#migrate} To switch to the new installation method, you can run our migration tool: ```shell curl -sSL https://get.wasp.sh/installer.sh | sh -s -- migrate-to-npm ``` Afterwards, you can use [the regular installation instructions](https://wasp.sh/docs/quick-start#detailed-installation) to install Wasp through npm: ```shell npm i -g @wasp.sh/wasp-cli@latest ``` You can also ask for a specific version of Wasp. Wasp versions 0.20.2 and greater are available through npm: ```shell # Set x.y.z to the version you want to install, e.g. 0.20.2 npm i -g @wasp.sh/wasp-cli@x.y.z ``` ### Keep using the legacy installer {#keep-using} If you haven't yet migrated to the npm installer method, you can keep using the legacy installer, by running: ```shell # Set x.y.z to the version you want to install, e.g. 0.20.1 # The installer will refuse to run without a specific version argument curl -sSL https://get.wasp.sh/installer.sh | sh -s -- -v x.y.z ``` You should only use the legacy installer as a stopgap while migrating workstations and CI to npm installations, as it can cause conflicts with the new method. For that reason, the installer will not work in the following cases: - You have already installed Wasp through npm. - You have already run the migration tool. - You are trying to install Wasp >= 0.21. - You are calling the installer without a version argument. In any of these cases, the installer will print an error message and exit without installing Wasp. You can still [switch back manually](#switch-back) if needed. ### Troubleshooting #### I need to use versions older than Wasp 0.20.2 These versions are very out of date and we don't recommend using them. We urge you to upgrade your Wasp project to a newer version as soon as possible to keep your app secure and stable. In the transition period until these apps are migrated, you will need to [keep using the legacy installer](#keep-using) to install these older versions of Wasp. #### I need to switch back to the legacy installer {#switch-back} :::note If you found a bug in the npm-based Wasp, or a workflow that is no longer possible, please report it to us so we can fix it as soon as possible. You can do that [through a GitHub issue](https://github.com/wasp-lang/wasp/issues/new/choose), or [on our Discord server](https://discord.gg/rzdnErX). ::: If you have already switched to npm installation but need to switch back to the legacy installer: 1. Uninstall the npm version of Wasp: ```shell npm uninstall -g @wasp.sh/wasp-cli ``` 2. Make sure that Wasp is uninstalled from your system: ```shell type wasp ``` If Wasp was correctly uninstalled, the `type` command will output "not found". If Wasp has not been completely uninstalled, it will print the path of the `wasp` binary, and you can manually remove it. 3. Remove the npm marker file from your system: ```shell rm $HOME/.local/share/wasp-lang/.uses-npm ``` 4. Run the installer again with the version you need: ```shell # Set x.y.z to the version you want to install, e.g. 0.20.1 # The installer will refuse to run without a specific version argument curl -sSL https://get.wasp.sh/installer.sh | sh -s -- -v x.y.z ``` #### "Bad CPU type in executable" on Mac with Mx chip (Apple Silicon) You have two options to run Wasp on your Mac with Mx chip: 1. **Recommended:** [Migrate to the npm-based installation method](#migrate), which works natively on Apple Silicon. 2. Keep using the legacy installer, but install [Rosetta on your Mac](https://support.apple.com/en-us/HT211861) to enable running x86 binaries. To install Rosetta, run the following command in your terminal ```bash softwareupdate --install-rosetta ``` Once installed, Wasp will run on your system as normal. ## Legacy / Wasp DSL :::note 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. ::: 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](https://wasp.sh/docs/general/spec): 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](https://wasp.sh/docs/0.24/migration-guide#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](https://wasp.sh/docs/0.24/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 `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](https://wasp.sh/docs/general/spec#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 DSL** ```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 } ```` **Wasp Spec** ```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 DSL** ```wasp title="main.wasp" query getTasks { fn: import { getTasks } from "@src/queries", entities: [Task] } action createTask { fn: import { createTask } from "@src/actions", entities: [Task] } ``` **Wasp Spec** ```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 DSL** ```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") } ``` **Wasp Spec** ```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 DSL** ```wasp title="main.wasp" job mySpecialJob { executor: PgBoss, perform: { fn: import { foo } from "@src/jobs/bar", executorOptions: { pgBoss: {=json { "retryLimit": 1 } json=} } }, entities: [Task] } ``` **Wasp Spec** ```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 DSL** ```wasp title="main.wasp" crud tasks { entity: Task, operations: { getAll: {}, create: { overrideFn: import { createTask } from "@src/actions" } } } ``` **Wasp Spec** ```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 DSL** ```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" } } } ``` **Wasp Spec** ```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](https://wasp.sh/docs/0.24/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. 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": { // ... "@types/node": "^24.0.0", "@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](https://wasp.sh/docs/general/spec#reference) for every option. Got stuck? Reach out on our [Discord](https://discord.gg/rzdnErX) and we'll help. ## Legacy / Wasp TS Config :::note 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. ::: 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](https://wasp.sh/docs/general/spec): 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](https://wasp.sh/docs/0.24/migration-guide#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](https://wasp.sh/docs/0.24/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** ```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" }, }); ``` **Wasp Spec** ```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](https://wasp.sh/docs/general/spec#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](https://wasp.sh/docs/general/spec#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 Config** ```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; ```` **Wasp Spec** ```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 Config** ```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" }, }); ``` **Wasp Spec** ```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 Config** ```ts title="main.wasp.ts" app.job("mySpecialJob", { executor: "PgBoss", perform: { fn: { import: "foo", from: "@src/jobs/bar" }, executorOptions: { pgBoss: { retryLimit: 1 } }, }, entities: ["Task"], }); ``` **Wasp Spec** ```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 Config** ```ts title="main.wasp.ts" app.crud("tasks", { entity: "Task", operations: { getAll: {}, create: { overrideFn: { import: "createTask", from: "@src/actions" } }, }, }); ``` **Wasp Spec** ```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 Config** ```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; ``` **Wasp Spec** ```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](https://wasp.sh/docs/0.24/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. 1. Update your `package.json` with the new dependencies: **Before** ```json title="package.json" { // ... "devDependencies": { // ... "wasp-config": "file:.wasp/wasp-config" } } ``` **After** ```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: **Before** ```ts title="main.wasp.ts" import { App } from "wasp-config"; const app = new App("myApp", { title: "My app", wasp: { version: "^0.24.0" }, }); ``` **After** ```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](https://wasp.sh/docs/general/spec#reference) for every option. Got stuck? Reach out on our [Discord](https://discord.gg/rzdnErX) and we'll help. ## Libraries / Radix Themes :::note Last checked with Wasp 0.24 and Radix Themes 3. 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. ::: This guide shows you how to integrate the [Radix Themes](https://www.radix-ui.com/themes) component library into your Wasp application. ### Setting up Radix Themes #### 1. Install Radix Themes Install the Radix Themes package: ```bash npm install @radix-ui/themes ``` #### 2. Create a Root Component if it doesn't exist Due to how Radix works, we'll need to have a single component that wraps all the pages in our app. In Wasp, this is done through the `rootComponent` configuration: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" import { Layout } from "./src/Layout" with { type: "ref" } export default app({ name: "MyApp", wasp: { version: "^0.24.0" }, title: "My App", head: [""], client: { rootComponent: Layout, }, // ... }) ``` If you already have a root component in your Wasp app, open that file and skip to the next step. If you don't have one, create a new file with an empty component that will serve as the root: ```tsx title="src/Layout.tsx" import type { ReactNode } from "react"; export function Layout({ children }: { children?: ReactNode }) { return children; } ``` #### 3. Add Radix Themes to your root component In your root component, we'll wrap the `children` with Radix Theme's `Theme` component, and import their CSS stylesheet: ```tsx title="src/Layout.tsx" import type { ReactNode } from "react"; import "@radix-ui/themes/styles.css"; import { Theme } from "@radix-ui/themes"; export function Layout({ children }: { children?: ReactNode }) { return {children}; } ``` #### 4. Use Radix Themes components Now you can use Radix Themes components anywhere in your application: ```tsx title="src/MainPage.tsx" import { Flex, Text, Button } from "@radix-ui/themes"; export const MainPage = () => { return ( Hello from Radix Themes :) ); }; ``` That's it! ### Customizing the theme You can customize the theme by passing props to the `Theme` component: ```tsx title="src/Layout.tsx" import type { ReactNode } from "react"; import "@radix-ui/themes/styles.css"; import { Theme } from "@radix-ui/themes"; export function Layout({ children }: { children?: ReactNode }) { return ( {children} ); } ``` See the [Radix Themes documentation](https://www.radix-ui.com/themes/docs/overview/getting-started) for more customization options. ## Libraries / Shadcn :::note Last checked with Wasp 0.24 and Shadcn (as of May 28, 2026). 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. ::: ### Setting up Shadcn in a Wasp project We'll be loosely following [the Vite instructions for Shadcn](https://ui.shadcn.com/docs/installation/vite) since Wasp is using Vite + React. Some of the steps don't apply, so we've adjusted them accordingly. You won't be able to use the `@` alias setup since it's not currently supported by Wasp. Because of this you'll need to adjust some imports when we generate components, but it should be fairly straightforward to do. #### 1. Add Tailwind CSS If you haven't added Tailwind CSS to your Wasp project yet, follow the instructions in the [Tailwind CSS guide](https://wasp.sh/docs/guides/libraries/tailwind) first. #### 2. Temporarily set up the `@` alias We need to temporarily setup the `@` alias to pass Shadcn's "Preflight checks". We'll remove it later. Add a top-level `compilerOptions` block to your `tsconfig.json`: ```diff title="tsconfig.json" { + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, "files": [], "references": [ { "path": "./tsconfig.src.json" }, { "path": "./tsconfig.wasp.json" } ], } ``` #### 3. Setup Shadcn Go into your project directory and run: ```bash npx shadcn@latest init -b radix -p luma ``` This initializes Shadcn with the Radix component library and the Luma preset. You should see output like this: ```bash โœ” Preflight checks. โœ” Verifying framework. Found Vite. โœ” Validating Tailwind CSS. Found v4. โœ” Validating import alias. โœ” Writing components.json. โœ” Checking registry. โœ” Installing dependencies. โœ” Updating src/Main.css โœ” Created 1 file: - src/lib/utils.ts ``` #### 4. Remove the `@` alias Remove the lines we added in the `tsconfig.json`: ```diff title="tsconfig.json" { - "compilerOptions": { - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"] - } - }, "files": [], "references": [ { "path": "./tsconfig.src.json" }, { "path": "./tsconfig.wasp.json" } ], } ``` #### 5. Adjust the `components.json` Adjust the `aliases` in `components.json` to be: ```json title="components.json" { "$schema": "https://ui.shadcn.com/schema.json", // ... "aliases": { "components": "src/components", "utils": "src/lib/utils", "ui": "src/components/ui", "lib": "src/lib", "hooks": "src/hooks" }, } ``` ### Adding a component In this example, we'll add the `Button` component. #### 1. Use the `shadcn` CLI to add the component We'll add a button component with: ```bash npx shadcn@latest add button ``` #### 2. Adjust the `utils` import You'll notice that you now have a brand new `button.tsx` file in `src/components/ui`. We need to fix some import issues: ```diff title="src/components/ui/button.tsx" import * as React from "react" import { cva, type VariantProps } from "class-variance-authority" import { Slot } from "radix-ui" -import { cn } from "@/lib/utils" +import { cn } from "../../lib/utils" ``` #### 3. Use the `Button` component That's it, now you are ready to use the `Button` component! ```tsx title="src/MainPage.tsx" import "./Main.css"; import { Button } from "./components/ui/button"; export const MainPage = () => { return (
    ); }; ``` ## Libraries / Tailwind CSS :::note Last checked with Wasp 0.24 and Tailwind 4. 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. ::: Wasp works great with [Tailwind CSS](https://tailwindcss.com/), a utility-first CSS framework. You can use Tailwind CSS by setting it up through their [Vite installation method](https://tailwindcss.com/docs/installation/using-vite), as with any other project. ### Adding Tailwind to your Wasp project 1. Install Tailwind and its Vite plugin. ```bash npm install tailwindcss npm install -D @tailwindcss/vite ``` 2. Add the Tailwind CSS Vite plugin to your `vite.config.ts` file: ```ts title="vite.config.ts" import { wasp } from 'wasp/client/vite' import tailwindcss from '@tailwindcss/vite' import { defineConfig } from 'vite' export default defineConfig({ plugins: [ wasp(), tailwindcss() ], server: { open: true, }, }) ``` 3. Import Tailwind into your base CSS file. For example, in a project created with `wasp new` you might import Tailwind into `Main.css`. ```css title="src/Main.css" @import "tailwindcss"; /* ... */ ``` 4. Start using Tailwind ๐Ÿฅณ ```tsx title="src/MainPage.tsx" // ...

    Hello world!

    ; // ... ``` ### Adding Tailwind Plugins Wasp doesn't require any special configuration to use Tailwind plugins. You can follow each plugin's installation instructions as you normally would. For example, to add the [Tailwind Forms](https://github.com/tailwindlabs/tailwindcss-forms) and [Tailwind Typography](https://github.com/tailwindlabs/tailwindcss-typography) plugins, we can check the installation instructions on their respective documentation pages and follow them as usual: ```shell npm install -D @tailwindcss/forms npm install -D @tailwindcss/typography ``` ```css title="src/Main.css" @import "tailwindcss"; @plugin "@tailwindcss/forms"; @plugin "@tailwindcss/typography"; /* ... */ ``` ## Optimization / Meta tags :::note 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. ::: This guide shows you how to set up meta tags for your Wasp application to improve SEO and enable rich previews when your app is shared on platforms like Slack, X, or Discord. ### How to add `` tags #### Setting metadata for every page You can add meta tags to your application using the `head` property in your `app` spec. These tags will be included in the `` section of your HTML. ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ name: "MyApp", wasp: { version: "^0.24.0" }, title: "My App", head: [ "", "", "", "", // Open Graph tags for social media previews "", "", "", "", "", "", // Twitter Card tags "", "", "", "", ], // ... }) ``` #### Setting metadata for a specific page You can use [React's support for `` tags](https://react.dev/reference/react-dom/components/meta) within components to set metadata for specific pages. This allows you to customize the metadata based on the content of each page. These tags will only be included in the page when the component is being shown, so it's better to add them to the top-most level if possible. ```tsx title="src/pages/HomePage.tsx" export function HomePage() { return ( <>

    Welcome to the Home Page

    {/* The rest of the page content */}
    ); } ``` ### Recommended `` Tags #### Basic SEO tags - `description`: A brief description of your app (appears in search results) - `author`: The creator or company name - `keywords`: Relevant keywords for search engines #### Open Graph tags This is the most common standard used by social media platforms (e.g. Facebook, LinkedIn, Slack, Discord, and more) to generate rich link previews. - `og:type`: Usually "website" for web apps - `og:title`: The title shown in previews - `og:site_name`: Your app/site name - `og:url`: The canonical URL - `og:description`: Description for the preview - `og:image`: Preview image URL You can check [Open Graph tag guidelines](https://ogp.me/) for more information on how this information is used. #### X Card tags This is used by X (formerly Twitter) to create rich link previews. - `twitter:card`: Use "summary\_large\_image" for large image previews - `twitter:image`: Image URL for Twitter previews - `twitter:image:width`: Image width in pixels - `twitter:image:height`: Image height in pixels X falls back to the Open Graph tags for the title, description, and image, so if you've already set those, the only tag you strictly need to add is `twitter:card`. You can check [X's guidelines](https://developer.x.com/en/docs/x-for-websites/cards/overview/markup) for more information on how this information is used. #### Canonical URL - ``: The "original" URL of the page, without tracking parameters (like `?utm_source`). Adding a self-referencing canonical link to each page prevents crawlers from indexing parameterized variations of the same page as duplicates. Since it's different for every page, set it per-page rather than in the `head` field. ### Best practices for images 1. Use your client app's absolute URL (including `https://`) for your preview images. 2. Check the recommended dimensions for each platform's images in their documentation. 3. Keep important content centered (some platforms crop differently). 4. Use WebP or PNG format for best quality. 5. Place your image [in the `public/` folder](https://wasp.sh/docs/project/static-assets#the-public-directory). ### Testing your metadata After deploying, you can verify your meta tags using these tools: - [Google Tag Assistant](https://tagassistant.google.com/) - [Facebook Sharing Debugger](https://developers.facebook.com/tools/debug/) - [X Card Validator](https://cards-dev.x.com/validator) - [LinkedIn Post Inspector](https://www.linkedin.com/post-inspector/) ## Optimization / SEO & GEO :::note Last checked with Wasp 0.24, Lighthouse 13, and industry standards (as of Jun 8, 2026). 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. ::: Search engine optimization (SEO) and generative engine optimization (GEO) are about making your app visible and attractive to search engines, social media platforms, and AI assistants. ### Where to optimize **In general, we recommend applying the following optimization techniques to your content pages, not your app pages.** Think about which pages you want to be surfaced to users, that provide good information *about* your app to a wide audience. - **Content pages** include your landing page, about page, pricing page, and other marketing pages. These are the pages you want to show up in search results and link previews. They usually don't change much often, nor based on who visits them. **SEO is most effective for these.** - **App pages** include your dashboard, profile page, settings page, a form, chat interface, or any page that shows dynamic content based on user data. These pages should be hidden from other users, as they contain personalized data. They also can't be meaningfully indexed, since their main goal is to be interacted with, not read. You can use this broad distinction to decide which pages you apply SEO techniques to. For example, you definitely want ChatGPT to be able to read your landing page so it can recommend your site to users, but you definitely *don't* want Google to recommend some user's specific dashboard page to other users. ### Measuring your SEO {#measuring} We recommend **first to measure your app** against common industry tools and see where you stand. Then, you can pick the techniques that are most relevant to your app and focus on those. After applying them, measure again to see how much they improved your score, and if there are any new issues to fix. SEO can come with costs; at a minimum, the cost of development time and maintenance burden. So while there's always room for improvement, it's important to focus on the techniques that will give you the biggest boost. A little improvement can go a long way, and you don't need to get a perfect score to see significant benefits. :::warning[Always run measurements against your production build] Full optimization of the Wasp app only happens on the production build, not the development server. Running Lighthouse or other tools against `wasp start` won't reflect what crawlers and users actually get. Always run it against your production build, either [locally](https://wasp.sh/docs/deployment/local-testing) or [after deploying](https://wasp.sh/docs/deployment/intro). ::: #### Lighthouse [Lighthouse](https://developer.chrome.com/docs/lighthouse/overview) should be the first tool you use to measure your website's readiness for search engines and AI assistants. It will score your page on a number issues and give you a neat list of which ones you need to fix, ordered by importance. | | | | -------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | ![Screenshot of a Lighthouse report overview](https://wasp.sh/assets/images/lighthouse-report-overview-547e11c0af057efa0f0327f22e88a053.png) | ![Screenshot of a Lighthouse report\'s list of issues](https://wasp.sh/assets/images/lighthouse-report-seo-1f548e8d88373987fc5c665f9c0e3adf.png) | There are four main categories of issues (Performance, Accessibility, Best Practices, and SEO), and all of these will be important for how your pages perform in search engines and AI assistants. For example, Google uses page speed as a ranking factor, and AI assistants will be more likely to read and recommend your site if it's accessible. A straightforward way to run Lighthouse is through the command line: ```sh # First, build and start the production build of your app: $ wasp build $ wasp build start # While the server is running, open another terminal and run Lighthouse: $ npx lighthouse http://localhost:3000 --preset=desktop --view # You can change the URL to a specific page or to point to your deployed app if you want to test that instead. # Remove the --preset=desktop argument to test the mobile experience instead. ``` It will take a minute to run (you'll see an automated browser window while it runs), and then it will save your report as an HTML file and open it. Using the Lighthouse [command-line interface](https://github.com/GoogleChrome/lighthouse#using-the-node-cli) as we just did is a good way to run it regularly during development, and let your AI assistant read the report and fix issues. You can also run it directly inside the [Chrome DevTools](https://github.com/GoogleChrome/lighthouse#using-lighthouse-in-chrome-devtools), or through the [online service](https://pagespeed.web.dev/) (but it will only work for deployed apps). #### Search Console [Google Search Console](https://search.google.com/search-console) is a free service from Google that shows you how your deployed site appears in Google Search, which queries show it, and how many clicks it gets. It will also show you any issues it finds when crawling your site, and guide you through fixing them. It's a must-have for monitoring your SEO performance and catching any issues early on. #### Single-purpose tools Some SEO techniques have specific tools available to check if they're implemented correctly. Inside the links for each technique below, you will find a mention to that tool if it exists, and we recommend using it to check your implementation. ### Optimization techniques Good SEO comes down to a few separate problems. We ordered them here by their effort-impact ratio, so you can focus on the techniques that will give you a bigger boost for less effort first. These are: - Telling crawlers what each page is about, with [meta tags](#meta-tags). - Making your content visible without running JavaScript, with [prerendering](#prerendering). - Helping crawlers understand your content with [semantic markup](#semantic-markup) and [structured data](#structured-data). - Making your pages [smaller](#reduce-size). - Giving crawlers the [standard files](#well-known-files) they look for. These are general techniques that apply to most websites, but there are many more you can use depending on your specific app and goals. You can start with these, and explore more as needed. Keep in mind that these techniques can only amplify your content, not replace it; we talk more about that [below](#good-content). #### Meta tags Search engines and social platforms read `` tags from your HTML to decide what to show in results and link previews: the page title, its description, and the preview image. You can set tags for your whole app through the `head` field of your `app` declaration: ```ts title="main.wasp.ts" import { app } from "@wasp.sh/spec" export default app({ // ... title: "My App", head: [ "", "", "", "", ], // ... }) ``` For tags that change from page to page, or depend on dynamic data, you can render `` tags directly inside a page component using [React's support for `` tags](https://react.dev/reference/react-dom/components/meta): ```tsx title="src/pages/ProductPage.tsx" export function ProductPage({ productId }) { const product = useProduct(productId); return ( <> {/* Twitter/X falls back to the og: tags for everything else; this tag enables the large preview layout */} {/* The "original" URL of this page, without tracking parameters, so crawlers don't index duplicate variations of it */}

    {product.name}

    {product.description}

    {/* ... */} ); } ``` Read more at our dedicated `` tags guide: [Guide](https://wasp.sh/docs/guides/optimization/meta-tags) #### [Meta tags ยป](https://wasp.sh/docs/guides/optimization/meta-tags) [The full set of recommended tags, image guidelines, and testing tools.](https://wasp.sh/docs/guides/optimization/meta-tags) #### Prerendering By default, Wasp apps are [single-page applications (SPAs)](https://en.wikipedia.org/wiki/Single-page_application), so you get fast navigation and responsive interactions. In an SPA, the HTML files your browser downloads are mostly empty and have just enough to load the JavaScript code that actually powers the app. Browsers execute the JavaScript to show you content. Most search engines (like Google) can also execute JavaScript when indexing a page too. But some crawlers (and many AI assistants) don't run JavaScript at all. So they see only the empty HTML, not your actual content. As such, they can't answer questions about your website, and they'll mostly ignore it. ![Diagram explaining prerendering in Wasp apps. Two side-by-side scenarios compare how a real browser and an AI assistant handle a page. On the left, \'Without prerender\': the browser window shows \'Loading...\' and the HTML contains only an empty body with a script tag. A real browser executes the JavaScript and successfully shows the page (green checkmark), but the AI assistant only sees the empty HTML, reading the page content as \'Loading,\' and responds \'Hmm... I don\'t know what this page is about,\' marked with a red X. On the right, \'With prerender\': the browser window shows \'Welcome to Wasp,\' and the HTML contains the actual content inside the body. Both the real browser and the AI assistant succeed (green checkmarks); the AI assistant reads \'Welcome to Wasp,\' understands the page describes Wasp, and says it will recommend it to the user.](https://wasp.sh/assets/images/prerendering-diagram-7c06321566e8cb60698a8d8e1f578807.png) However, Wasp can **prerender** chosen routes to static HTML at build time, so the content is readable in the initial file even without JavaScript. Browsers will still download and execute the rest of the app, and present the same experience as with a pure SPA, so you get the best of both worlds. You can opt-in per route: ```ts title="main.wasp.ts" import { app, page, route } from "@wasp.sh/spec" import { LandingPage } from "./src/LandingPage" with { type: "ref" } export default app({ // ... decls: [ route("LandingRoute", "/", page(LandingPage), { prerender: true }), ], }) ``` Prerendering can't be used on routes with dynamic paths or on auth-required pages. You can read more in our prerendering documentation: [Documentation](https://wasp.sh/docs/advanced/prerendering) #### [Prerendering ยป](https://wasp.sh/docs/advanced/prerendering) [How it works, when to use it, and how to avoid hydration mismatches.](https://wasp.sh/docs/advanced/prerendering) #### Semantic markup Most crawlers and screen readers can't see your inside images, so every meaningful image needs a text description through its `alt` attribute. Missing `alt` text is one of the most common issues a Lighthouse SEO audit flags. ```tsx title="src/components/Testimonials.tsx" {`Profile; ``` If an image is purely decorative, you can give it an empty `alt=""` so crawlers know to ignore it. But if the image conveys information, like a product photo or a profile picture, the `alt` text should describe that information. You should also use semantic HTML to help crawlers understand your content. For example, use one `

    ` per page for the main heading, and use `

    `, `

    `, etc. for subheadings in order. Most indexers will understand that as your page's subject matter and closely relate it with those terms. You should also use descriptive link text instead of generic phrases like "click here," so crawlers know what the linked page is about. Links deserve special attention, since crawlers discover your pages by following `` tags. A ` {/* โœ… A real link they can discover */} Pricing ); } ``` You can check Semrush's post on semantic HTML to see how it looks and which effects it has on SEO: [External](https://www.semrush.com/blog/semantic-html5-guide/) #### [What Is Semantic HTML? And How to Use It Correctly ยป](https://www.semrush.com/blog/semantic-html5-guide/) [From Semrush](https://www.semrush.com/blog/semantic-html5-guide/) #### Structured data By default, crawlers and AI engines only read your content as text. That is, your page is a bag of words, and they have to guess what those words mean and how they relate to each other. Structured data is a way to give them more information about your content in a machine-readable format, so they can understand it better and show richer results. If you've ever looked for a recipe on Google and seen a search result with a star rating, cooking time, and a photo, that's structured data at work. And for a typical SaaS app you can use it e.g. in the pricing page, to help crawlers identify the different plans, their features, and their prices, and show that directly in their search results page. You can add structured data to your pages in multiple ways, but the most common is with [JSON-LD](https://json-ld.org/), which is a script tag with a specific format: ```tsx title="src/pages/WebApplication.tsx" import type { WebApplication, WithContext } from "schema-dts"; export function PricingPage() { const pricingPlans = usePricingPlans(); const structuredData: WithContext = { "@context": "https://schema.org", "@type": "WebApplication", name: "My travel app", applicationCategory: "TravelApplication", offers: pricingPlans.map((plan) => ({ "@type": "Offer", name: plan.name, price: plan.price, priceCurrency: "USD", description: plan.description, })), }; return ( <>

    Our Pricing Plans

    Choose the plan that works best for you.

    {/* ... */} ); } ``` You can read more about structured data in Google's documentation: [External](https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data) #### [Introduction to structured data markup in Google Search ยป](https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data) [From Google](https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data) #### Reduce your page size {#reduce-size} Search engines factor page speed into ranking through [Core Web Vitals](https://web.dev/articles/vitals); and the smaller the page, the faster it loads. A couple of things help the most: - **Optimize your images.** Serve images at the size they're displayed, and prefer modern formats like WebP or AVIF. And [importing an image from your source code](https://wasp.sh/docs/project/static-assets#importing-an-asset-as-url) lets Vite hash its filename so browsers can cache it aggressively. See Chrome Lighthouse's docs for more tips on image optimization: [External](https://developer.chrome.com/docs/performance/insights/image-delivery) #### [Improve image delivery ยป](https://developer.chrome.com/docs/performance/insights/image-delivery) [From Chrome](https://developer.chrome.com/docs/performance/insights/image-delivery) - **Lazy-load heavy parts of the page.** Wasp can "split" your components so that their HTML, JS, and CSS code don't get loaded upfront. You can split large or below-the-fold components on demand with `React.lazy`: ```tsx title="src/pages/LandingPage.tsx" import { lazy } from "react"; // This component might pull in a large graphing // library, and it's not at the top of the page, // so we don't load it upfront. const InteractiveGraph = lazy(() => import("@src/components/InteractiveGraph")); export function LandingPage() { return (

    Welcome to My App

    Here's some important information about our app...

    {/* Loaded after the rest of the page. */} Loading graph...

    }>
    ); } ``` [External](https://react.dev/reference/react/lazy) #### [React.lazy ยป](https://react.dev/reference/react/lazy) [From React docs](https://react.dev/reference/react/lazy) #### Well-known files Crawlers look for a couple of standard files at the root of your site, for example: - [A `robots.txt` file](#robots-txt) tells crawlers which paths they may visit. - [A `sitemap.xml` file](#sitemap-xml) lists the pages you want crawlers to find and index. - [An `llms.txt` file](#llms-txt) that can give instructions to AI assistants about how to interact with your site and which pages to read. Place these in the [`public` directory](https://wasp.sh/docs/project/static-assets#the-public-directory) at the root of your project. Files there are served as-is from the root path, so `public/robots.txt` becomes available at `https://your-app.com/robots.txt`: ``` . โ””โ”€โ”€ public โ”œโ”€โ”€ favicon.ico โ”œโ”€โ”€ robots.txt โ”œโ”€โ”€ llms.txt โ””โ”€โ”€ sitemap.xml ``` ##### `robots.txt` {#robots-txt} A minimal `robots.txt` lets crawlers visit everything except the routes you don't want them to waste time on, like your admin, API, and auth pages: ```txt title="public/robots.txt" User-agent: * Allow: / Disallow: /admin/ Disallow: /api/ Disallow: /auth/ ``` :::caution[robots.txt doesn't hide pages from search results] `robots.txt` only prevents *crawling*, not *indexing*. If another site links to your `/admin/` route, Google can still index that URL without ever visiting it. ::: To reliably keep your [app pages](#where-to-optimize) out of search results, add a `robots` meta tag to those page components: ```tsx title="src/pages/DashboardPage.tsx" export function DashboardPage() { return ( <>

    Your Dashboard

    {/* ... */} ); } ``` You can check Google's guide on `robots.txt` for more details and examples: [External](https://developers.google.com/search/docs/crawling-indexing/robots/intro) #### [Introduction to robots.txt ยป](https://developers.google.com/search/docs/crawling-indexing/robots/intro) [From Google](https://developers.google.com/search/docs/crawling-indexing/robots/intro) ##### `sitemap.xml` {#sitemap-xml} A sitemap lists the URLs of your site that you want indexed. Crawlers can usually discover your pages just by following the links between them, but for a brand-new app with few external links pointing at it, a sitemap is the fastest way for them to find all your routes. You can also submit it to [Search Console](#search-console) to monitor how Google indexes your pages. Content pages are usually few and stable, so it's easy to write `public/sitemap.xml` by hand, or to ask your AI assistant to generate it from the routes in your `main.wasp.ts`. :::caution[Keep your sitemap up to date] An outdated sitemap, listing broken URLs or missing new ones, is worse than no sitemap at all. If you add one, remember to regenerate it whenever your content pages change. ::: You can check Google's guide on sitemaps for more details: [External](https://developers.google.com/search/docs/crawling-indexing/sitemaps/overview) #### [Learn about sitemaps ยป](https://developers.google.com/search/docs/crawling-indexing/sitemaps/overview) [From Google](https://developers.google.com/search/docs/crawling-indexing/sitemaps/overview) ##### `llms.txt` {#llms-txt} An `llms.txt` can direct AI assistants to the main pages they should look at to learn about your app. LLMs are quite good at understanding Markdown, so it's usually written in that format, but you can use any format you want: ```md title="public/llms.txt" # MyTravelApp > MyTravelApp is a web application that helps users plan their trips. ## Docs - [Features](https://mytravelapp.com/features): What the app can do. - [Pricing](https://mytravelapp.com/pricing): Plans and prices. - [API documentation](https://mytravelapp.com/api-docs): How LLMs can interact with the app on a user's behalf. ## Getting started - [Demo](https://mytravelapp.com/demo): Try the app without signing up. - [Sign up](https://mytravelapp.com/signup): Create an account. - [Dashboard](https://mytravelapp.com/dashboard): Where users manage their trips after signing up. ## Optional - [Blog](https://mytravelapp.com/blog): Travel tips and product updates. ``` You can also treat `llms.txt` as a more comprehensive, "alternative" way of presenting information specifically for AI assistants. In this case, instead of pointing to your pages, you'd write out their content in full, so an assistant can learn everything about your app without rendering and parsing the actual pages: ```md title="public/llms.txt" # MyTravelApp MyTravelApp is a web application that helps users plan their trips. It lets you build day-by-day itineraries, track your budget, and share plans with travel companions. ## Features - **Itinerary builder.** Add flights, hotels, and activities to a timeline that automatically sorts them by date and warns you about overlaps. - **Budget tracking.** [...] ## Pricing MyTravelApp is free for one active trip. The Pro plan is $9/month and unlocks unlimited trips, offline access, and [...] ## Frequently asked questions **Can I use MyTravelApp offline?** Yes, Pro users can download trips for offline access [...] [...] ``` You can check the `llms.txt` proposal website for more details and examples. [External](https://llmstxt.org/) #### [The /llms.txt file ยป](https://llmstxt.org/) [From Answer.AI](https://llmstxt.org/) #### Other techniques There are many more techniques you can use to optimize your app for search engines and AI assistants. There's a wealth of information online about SEO, but we recommend starting with [Google Search's documentation site](https://developers.google.com/search/docs), which is a complete reference on what they look for in a page, and how to optimize for it. They also added a section on [optimizing for AI assistants](https://developers.google.com/search/docs/fundamentals/ai-optimization-guide). Their starter guide is a good place to begin: [External](https://developers.google.com/search/docs/fundamentals/seo-starter-guide) #### [SEO Starter Guide ยป](https://developers.google.com/search/docs/fundamentals/seo-starter-guide) [From Google Search](https://developers.google.com/search/docs/fundamentals/seo-starter-guide) ### Good content Everything above is a technical checklist: you apply each technique, measure, and check it off. But these techniques can only amplify what's already there. If your page doesn't have useful, relevant content, no amount of SEO will make it rank well. So make sure your content is high-quality, well-written, and provides value to your users. Spamming keywords, using clickbait titles, or generating unreviewed content by the pound won't help you in the long run, and can even get you penalized by search engines. Focus on creating content that answers your users' questions and solves their problems. You should read Google's guide on creating good content for more tips on what to focus on when creating content pages: [External](https://developers.google.com/search/docs/fundamentals/creating-helpful-content) #### [Creating helpful, reliable, people-first content ยป](https://developers.google.com/search/docs/fundamentals/creating-helpful-content) [From Google](https://developers.google.com/search/docs/fundamentals/creating-helpful-content) ------ # API ## @wasp.sh/spec ### Wasp Spec - [App](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/App) - [AppConfig](https://wasp.sh/docs/api/@wasp.sh/spec/type-aliases/AppConfig) - [app](https://wasp.sh/docs/api/@wasp.sh/spec/functions/app) ### Constructors - [ActionConfig](https://wasp.sh/docs/api/@wasp.sh/spec/type-aliases/ActionConfig) - [ApiConfig](https://wasp.sh/docs/api/@wasp.sh/spec/type-aliases/ApiConfig) - [ApiNamespaceConfig](https://wasp.sh/docs/api/@wasp.sh/spec/type-aliases/ApiNamespaceConfig) - [JobConfig](https://wasp.sh/docs/api/@wasp.sh/spec/type-aliases/JobConfig) - [PageConfig](https://wasp.sh/docs/api/@wasp.sh/spec/type-aliases/PageConfig) - [QueryConfig](https://wasp.sh/docs/api/@wasp.sh/spec/type-aliases/QueryConfig) - [RouteConfig](https://wasp.sh/docs/api/@wasp.sh/spec/type-aliases/RouteConfig) - [action](https://wasp.sh/docs/api/@wasp.sh/spec/functions/action) - [api](https://wasp.sh/docs/api/@wasp.sh/spec/functions/api) - [apiNamespace](https://wasp.sh/docs/api/@wasp.sh/spec/functions/apiNamespace) - [crud](https://wasp.sh/docs/api/@wasp.sh/spec/functions/crud) - [job](https://wasp.sh/docs/api/@wasp.sh/spec/functions/job) - [page](https://wasp.sh/docs/api/@wasp.sh/spec/functions/page) - [query](https://wasp.sh/docs/api/@wasp.sh/spec/functions/query) - [route](https://wasp.sh/docs/api/@wasp.sh/spec/functions/route) ### Specifications - [Action](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Action) - [Api](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Api) - [ApiNamespace](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/ApiNamespace) - [Crud](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Crud) - [Job](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Job) - [Page](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Page) - [Query](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Query) - [Route](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Route) - [Spec](https://wasp.sh/docs/api/@wasp.sh/spec/type-aliases/Spec) - [SpecElement](https://wasp.sh/docs/api/@wasp.sh/spec/type-aliases/SpecElement) ### References - [DefaultRefObjectDescriptor](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/DefaultRefObjectDescriptor) - [NamedRefObjectDescriptor](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/NamedRefObjectDescriptor) - [Reference](https://wasp.sh/docs/api/@wasp.sh/spec/type-aliases/Reference) - [RefObject](https://wasp.sh/docs/api/@wasp.sh/spec/type-aliases/RefObject) - [RefObjectDescriptor](https://wasp.sh/docs/api/@wasp.sh/spec/type-aliases/RefObjectDescriptor) - [ZodSchema](https://wasp.sh/docs/api/@wasp.sh/spec/type-aliases/ZodSchema) - [ref](https://wasp.sh/docs/api/@wasp.sh/spec/functions/ref) ### Errors - [WaspSpecUserError](https://wasp.sh/docs/api/@wasp.sh/spec/classes/WaspSpecUserError) ### Fields - [Auth](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Auth) - [Client](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Client) - [CrudOperationOptions](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/CrudOperationOptions) - [CrudOperations](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/CrudOperations) - [Db](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Db) - [EmailAuthConfig](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/EmailAuthConfig) - [EmailFlowConfig](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/EmailFlowConfig) - [EmailFromField](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/EmailFromField) - [EmailSender](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/EmailSender) - [ExecutorOptions](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/ExecutorOptions) - [ExternalAuthMethods](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/ExternalAuthMethods) - [LocalAuthMethods](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/LocalAuthMethods) - [Schedule](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Schedule) - [Server](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Server) - [SocialAuthConfig](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/SocialAuthConfig) - [UsernameAndPasswordConfig](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/UsernameAndPasswordConfig) - [Wasp](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/Wasp) - [WebSocket](https://wasp.sh/docs/api/@wasp.sh/spec/interfaces/WebSocket) - [AuthMethods](https://wasp.sh/docs/api/@wasp.sh/spec/type-aliases/AuthMethods) - [EmailSenderProviderName](https://wasp.sh/docs/api/@wasp.sh/spec/type-aliases/EmailSenderProviderName) - [EntityName](https://wasp.sh/docs/api/@wasp.sh/spec/type-aliases/EntityName) - [HttpMethod](https://wasp.sh/docs/api/@wasp.sh/spec/type-aliases/HttpMethod) - [JobExecutor](https://wasp.sh/docs/api/@wasp.sh/spec/type-aliases/JobExecutor) ------