Prerequisites: A React app using Next.js 14+, Remix v2, React Router v7, or TanStack Start.
Step 1: Get Your Credentials
Create an App
Click New App and give it a name
Copy Credentials
Copy your App ID and create an API Key
Step 2: Install Packages
npm install modifywithai @ai-sdk/react ai
| Package | Purpose |
|---|
modifywithai | Core SDK with hooks, components, and framework handlers |
@ai-sdk/react | React hooks for AI chat (from Vercel AI SDK) |
ai | Vercel AI SDK core utilities |
Step 3: Add Environment Variables
MWAI_API_KEY=your_api_key_here
Never expose your API key to the browser. The token endpoint keeps it server-side.
Step 4: Create the Token Endpoint
The token endpoint generates short-lived tokens for the assistant. Your API key stays on the server—the browser only receives ephemeral tokens.
Next.js
Remix
TanStack Start
Create app/api/mwai/token/route.ts:import { createAssistantTokenHandler } from "modifywithai/nextjs"
export const POST = createAssistantTokenHandler({
appId: "your_app_id",
// apiKey defaults to process.env.MWAI_API_KEY
})
Create app/routes/api.mwai.token.ts:import { createAssistantTokenAction } from "modifywithai/remix"
export const action = createAssistantTokenAction({
appId: "your_app_id",
// apiKey defaults to process.env.MWAI_API_KEY
})
Create app/routes/api/mwai/token.ts:import { createAPIFileRoute } from "@tanstack/react-start/api"
import { createAssistantTokenHandler } from "modifywithai/tanstack-start"
const handler = createAssistantTokenHandler({
appId: "your_app_id",
// apiKey defaults to process.env.MWAI_API_KEY
})
export const APIRoute = createAPIFileRoute("/api/mwai/token")({
POST: handler,
})
| Option | Type | Default | Description |
|---|
appId | string | Required | Your app ID from the dashboard |
apiKey | string | process.env.MWAI_API_KEY | Your API key |
apiUrl | string | https://api.modifywithai.com | API endpoint |
getEndUserId | (request) => Promise<string | null> | Auto-generate | Function to get user ID |
Step 5: Create the Assistant Component
Create a component that uses the useAssistant hook. This is where you define what actions the agent can perform.
"use client" // Next.js only
import { useAssistant } from "modifywithai"
export function Assistant() {
const assistant = useAssistant({
availableActions: [
{
name: "createItem",
description: "Create a new item in the list",
options: {
title: {
type: "string",
description: "The item title",
required: true,
},
},
},
{
name: "deleteItem",
description: "Delete an item from the list",
options: {
id: {
type: "string",
description: "The item ID to delete",
required: true,
},
},
approvalRequired: true, // User must confirm
},
],
getContext: () => ({
// Pass current app state so the agent knows what's on screen
currentPage: "home",
items: [], // Your actual items here
}),
onAction: (action) => {
// Handle executed actions
switch (action.name) {
case "createItem":
console.log("Create item:", action.options.title)
break
case "deleteItem":
console.log("Delete item:", action.options.id)
break
}
},
})
if (!assistant.isReady) {
return <div>Loading assistant...</div>
}
return (
<div>
{/* Display messages */}
<div>
{assistant.messages.map((message) => (
<div key={message.id}>
<strong>{message.role}:</strong>
{message.parts.map((part, i) =>
part.type === "text" ? <span key={i}>{part.text}</span> : null
)}
</div>
))}
</div>
{/* Input form */}
<form onSubmit={assistant.handleSubmit}>
<input
value={assistant.input}
onChange={(e) => assistant.setInput(e.target.value)}
placeholder="Try: Create an item called Test"
/>
<button type="submit" disabled={assistant.status === "streaming"}>
Send
</button>
</form>
</div>
)
}
Step 6: Add the Component to Your App
Import and render the assistant component on your page:
Next.js
Remix
TanStack Start
import { Assistant } from "@/components/assistant"
export default function Home() {
return (
<main>
<h1>My App</h1>
<Assistant />
</main>
)
}
import { Assistant } from "~/components/assistant"
export default function Index() {
return (
<main>
<h1>My App</h1>
<Assistant />
</main>
)
}
import { createFileRoute } from "@tanstack/react-router"
import { Assistant } from "~/components/assistant"
export const Route = createFileRoute("/")({
component: Home,
})
function Home() {
return (
<main>
<h1>My App</h1>
<Assistant />
</main>
)
}
Optional: Add User Authentication
If your app has user authentication, pass the user ID to track conversations per user:
Next.js
Remix
TanStack Start
app/api/mwai/token/route.ts
import { createAssistantTokenHandler } from "modifywithai/nextjs"
import { auth } from "@/lib/auth" // Your auth library
export const POST = createAssistantTokenHandler({
appId: "your_app_id",
getEndUserId: async (request) => {
const session = await auth()
return session?.user?.id ?? null
},
})
app/routes/api.mwai.token.ts
import { createAssistantTokenAction } from "modifywithai/remix"
import { getSession } from "@/lib/auth.server"
export const action = createAssistantTokenAction({
appId: "your_app_id",
getEndUserId: async (request) => {
const session = await getSession(request)
return session?.userId ?? null
},
})
app/routes/api/mwai/token.ts
import { createAPIFileRoute } from "@tanstack/react-start/api"
import { createAssistantTokenHandler } from "modifywithai/tanstack-start"
import { getSession } from "@/lib/auth"
const handler = createAssistantTokenHandler({
appId: "your_app_id",
getEndUserId: async (request) => {
const session = await getSession(request)
return session?.userId ?? null
},
})
export const APIRoute = createAPIFileRoute("/api/mwai/token")({
POST: handler,
})
If no getEndUserId is provided, an anonymous ID is auto-generated for each session.
Optional: Using Server Data
Pass server-fetched data to the assistant component:
Next.js
Remix
TanStack Start
import { getProjects } from "@/lib/data"
import { Assistant } from "@/components/assistant"
export default async function DashboardPage() {
const projects = await getProjects()
return <Assistant initialProjects={projects} />
}
"use client"
import { useState } from "react"
import { useAssistant } from "modifywithai"
export function Assistant({ initialProjects }) {
const [projects, setProjects] = useState(initialProjects)
const assistant = useAssistant({
availableActions: [...],
getContext: () => ({
projects: projects.map(p => ({ id: p.id, name: p.name })),
}),
onAction: handleAction,
})
// ...
}
import { json } from "@remix-run/node"
import { useLoaderData, useFetcher } from "@remix-run/react"
import { useAssistant } from "modifywithai"
export async function loader() {
const projects = await getProjects()
return json({ projects })
}
export default function Dashboard() {
const { projects } = useLoaderData<typeof loader>()
const fetcher = useFetcher()
const assistant = useAssistant({
availableActions: [...],
getContext: () => ({ projects }),
onAction: (action) => {
// Call Remix actions
fetcher.submit(
{ name: action.options.name },
{ method: "post", action: "/projects" }
)
},
})
// ...
}
import { useAssistant } from "modifywithai"
import { createProject } from "@/server/projects"
export function Dashboard() {
const assistant = useAssistant({
availableActions: [...],
onAction: async (action) => {
// Call server functions directly
if (action.name === "createProject") {
await createProject({ name: action.options.name as string })
}
},
})
// ...
}
Next Steps