Fix: Wasp Not Working — Build Failing, Auth Not Working, or Operations Returning Empty
Quick Answer
How to fix Wasp full-stack framework issues — main.wasp configuration, queries and actions, authentication setup, Prisma integration, job scheduling, and deployment.
The Problem
wasp start fails with a configuration error:
wasp start
# Error: Invalid Wasp configuration — or —
# Error: Prisma schema validation failedOr queries return empty results:
const { data: tasks } = useQuery(getTasks);
// data is undefined or empty arrayOr authentication doesn’t redirect after login:
Login succeeds but user stays on the login pageWhy This Happens
Wasp is a full-stack framework that uses a declarative DSL (.wasp file) alongside React and Node.js code:
main.waspis the source of truth — it declares routes, operations (queries/actions), auth, jobs, and entities. The framework generates boilerplate code from this file. Syntax errors in the.waspfile stop everything.- Entities are Prisma models — Wasp uses Prisma under the hood. Entity definitions in the
.waspfile map to Prisma models. Database operations use the Prisma client. - Operations have server-side implementations — queries and actions declared in
.wasppoint to TypeScript functions insrc/server/. If the import path is wrong or the function doesn’t exist, operations silently fail. - Auth is declarative — authentication providers (email/password, Google, GitHub) are configured in
.wasp. The actual auth flow is generated — you don’t write login logic manually.
Fix 1: Basic Wasp Configuration
curl -sSL https://get.wasp-lang.dev/installer.sh | sh
wasp new my-app
cd my-app
wasp start// main.wasp — declarative configuration
app MyApp {
wasp: { version: "^0.14.0" },
title: "My App",
head: [
"<meta name='description' content='My Wasp App' />"
],
auth: {
userEntity: User,
methods: {
usernameAndPassword: {},
google: {},
github: {},
},
onAuthFailedRedirectTo: "/login",
onAuthSucceededRedirectTo: "/dashboard",
},
db: {
system: PostgreSQL,
// seeds: [import { seedDevData } from "@src/server/seeds"]
},
}
// Entities (Prisma models)
entity User {=psl
id Int @id @default(autoincrement())
tasks Task[]
createdAt DateTime @default(now())
psl=}
entity Task {=psl
id Int @id @default(autoincrement())
description String
isDone Boolean @default(false)
user User @relation(fields: [userId], references: [id])
userId Int
createdAt DateTime @default(now())
psl=}
// Routes
route RootRoute { path: "/", to: MainPage }
route LoginRoute { path: "/login", to: LoginPage }
route SignupRoute { path: "/signup", to: SignupPage }
route DashboardRoute { path: "/dashboard", to: DashboardPage }
// Pages
page MainPage {
component: import { MainPage } from "@src/client/MainPage"
}
page LoginPage {
component: import { LoginPage } from "@src/client/LoginPage"
}
page SignupPage {
component: import { SignupPage } from "@src/client/SignupPage"
}
page DashboardPage {
authRequired: true,
component: import { DashboardPage } from "@src/client/DashboardPage"
}
// Queries (read operations)
query getTasks {
fn: import { getTasks } from "@src/server/queries",
entities: [Task]
}
query getTask {
fn: import { getTask } from "@src/server/queries",
entities: [Task]
}
// Actions (write operations)
action createTask {
fn: import { createTask } from "@src/server/actions",
entities: [Task]
}
action updateTask {
fn: import { updateTask } from "@src/server/actions",
entities: [Task]
}
action deleteTask {
fn: import { deleteTask } from "@src/server/actions",
entities: [Task]
}Fix 2: Server Operations
// src/server/queries.ts
import { type GetTasks, type GetTask } from 'wasp/server/operations';
export const getTasks: GetTasks<void, Task[]> = async (args, context) => {
if (!context.user) throw new HttpError(401, 'Not authenticated');
return context.entities.Task.findMany({
where: { userId: context.user.id },
orderBy: { createdAt: 'desc' },
});
};
export const getTask: GetTask<{ id: number }, Task> = async ({ id }, context) => {
if (!context.user) throw new HttpError(401);
const task = await context.entities.Task.findUnique({ where: { id } });
if (!task || task.userId !== context.user.id) {
throw new HttpError(404, 'Task not found');
}
return task;
};// src/server/actions.ts
import { type CreateTask, type UpdateTask, type DeleteTask } from 'wasp/server/operations';
import { HttpError } from 'wasp/server';
export const createTask: CreateTask<{ description: string }, Task> = async ({ description }, context) => {
if (!context.user) throw new HttpError(401);
return context.entities.Task.create({
data: {
description,
userId: context.user.id,
},
});
};
export const updateTask: UpdateTask<{ id: number; isDone: boolean }, Task> = async ({ id, isDone }, context) => {
if (!context.user) throw new HttpError(401);
const task = await context.entities.Task.findUnique({ where: { id } });
if (!task || task.userId !== context.user.id) {
throw new HttpError(404);
}
return context.entities.Task.update({
where: { id },
data: { isDone },
});
};
export const deleteTask: DeleteTask<{ id: number }, void> = async ({ id }, context) => {
if (!context.user) throw new HttpError(401);
await context.entities.Task.delete({ where: { id } });
};Fix 3: Client Pages
// src/client/DashboardPage.tsx
import { useQuery, useAction } from 'wasp/client/operations';
import { getTasks, createTask, updateTask, deleteTask } from 'wasp/client/operations';
import { useState } from 'react';
export function DashboardPage() {
const { data: tasks, isLoading, error } = useQuery(getTasks);
const createTaskFn = useAction(createTask);
const updateTaskFn = useAction(updateTask);
const deleteTaskFn = useAction(deleteTask);
const [newTask, setNewTask] = useState('');
async function handleCreate(e: React.FormEvent) {
e.preventDefault();
if (!newTask.trim()) return;
await createTaskFn({ description: newTask });
setNewTask('');
// Wasp automatically invalidates the getTasks query
}
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h1>Dashboard</h1>
<form onSubmit={handleCreate}>
<input
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
placeholder="New task..."
/>
<button type="submit">Add</button>
</form>
<ul>
{tasks?.map(task => (
<li key={task.id}>
<input
type="checkbox"
checked={task.isDone}
onChange={() => updateTaskFn({ id: task.id, isDone: !task.isDone })}
/>
<span style={{ textDecoration: task.isDone ? 'line-through' : 'none' }}>
{task.description}
</span>
<button onClick={() => deleteTaskFn({ id: task.id })}>Delete</button>
</li>
))}
</ul>
</div>
);
}Fix 4: Authentication Pages
// src/client/LoginPage.tsx
import { LoginForm } from 'wasp/client/auth';
import { Link } from 'wasp/client/router';
export function LoginPage() {
return (
<div style={{ maxWidth: 400, margin: '0 auto', padding: 20 }}>
<h1>Login</h1>
<LoginForm />
<p>
Don't have an account? <Link to="/signup">Sign up</Link>
</p>
</div>
);
}
// src/client/SignupPage.tsx
import { SignupForm } from 'wasp/client/auth';
import { Link } from 'wasp/client/router';
export function SignupPage() {
return (
<div style={{ maxWidth: 400, margin: '0 auto', padding: 20 }}>
<h1>Sign Up</h1>
<SignupForm />
<p>
Already have an account? <Link to="/login">Log in</Link>
</p>
</div>
);
}
// Access user in any component
import { useAuth } from 'wasp/client/auth';
function UserMenu() {
const { data: user } = useAuth();
if (!user) return <Link to="/login">Log in</Link>;
return (
<div>
<span>Welcome, {user.identities.username?.id || user.id}</span>
<button onClick={() => window.location.href = '/auth/logout'}>Logout</button>
</div>
);
}Fix 5: Background Jobs
// main.wasp — declare a job
job dailyReport {
executor: PgBoss,
perform: {
fn: import { generateDailyReport } from "@src/server/jobs"
},
schedule: {
cron: "0 9 * * *" // Every day at 9 AM
},
entities: [Task, User]
}
job sendEmail {
executor: PgBoss,
perform: {
fn: import { sendEmail } from "@src/server/jobs"
},
entities: [User]
}// src/server/jobs.ts
import { type DailyReport, type SendEmail } from 'wasp/server/jobs';
export const generateDailyReport: DailyReport<void, void> = async (args, context) => {
const users = await context.entities.User.findMany({
include: { tasks: true },
});
for (const user of users) {
const completedToday = user.tasks.filter(t =>
t.isDone && isToday(t.createdAt)
).length;
await sendReportEmail(user, completedToday);
}
};
export const sendEmail: SendEmail<{ to: string; subject: string; html: string }, void> = async ({ to, subject, html }) => {
await emailProvider.send({ to, subject, html });
};
// Trigger a job manually from an action
import { sendEmail } from 'wasp/server/jobs';
export const inviteUser = async ({ email }, context) => {
await sendEmail.submit({
to: email,
subject: 'You are invited!',
html: '<p>Welcome!</p>',
});
};Fix 6: Deployment
# Deploy to Fly.io
wasp deploy fly setup
wasp deploy fly cmd secrets set DATABASE_URL=postgres://...
wasp deploy fly deploy
# Or build for self-hosting
wasp build
# Output in .wasp/build/
# Contains Dockerfile and docker-compose for both client and serverStill Not Working?
Wasp configuration error — check main.wasp syntax. Common issues: missing commas in entity definitions, wrong import paths (must use @src/ prefix), or referencing entities not declared in the operation’s entities list.
Queries return empty — the operation needs entities: [Task] in the .wasp declaration to access context.entities.Task. Without it, the Prisma client for that entity isn’t available. Also check auth — context.user is null for unauthenticated requests.
Auth redirect loop — onAuthSucceededRedirectTo in the auth config must point to a route that exists and doesn’t require auth itself. If the redirect target also requires auth, you get a loop.
Database errors after changing entities — run wasp db migrate-dev after modifying entity definitions. Wasp uses Prisma migrations. Without migrating, the database schema doesn’t match the code.
For related full-stack issues, see Fix: TanStack Start Not Working and Fix: Next.js App Router Not Working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: TanStack Start Not Working — Server Functions Failing, Routes Not Loading, or SSR Errors
How to fix TanStack Start issues — project setup, file-based routing, server functions with createServerFn, data loading, middleware, SSR hydration, and deployment configuration.
Fix: Waku Not Working — Server Components Not Rendering, Client Components Not Interactive, or Build Errors
How to fix Waku React framework issues — RSC setup, server and client component patterns, data fetching, routing, layout system, and deployment configuration.
Fix: Next.js Server Action Not Working — Action Not Called or Returns Error
How to fix Next.js Server Actions — use server directive, form binding, revalidation, error handling, middleware conflicts, and client component limitations.
Fix: Analog Not Working — Routes Not Loading, API Endpoints Failing, or Vite Build Errors
How to fix Analog (Angular meta-framework) issues — file-based routing, API routes with Nitro, content collections, server-side rendering, markdown pages, and deployment.