From monday-code
Migrates existing React/Vite frontends, Express/Node backends, or fullstack apps to monday-code platform structure. Adds .mondaycoderc, manifest.json, MondayContext, SDK integration, and auth while preserving code.
npx claudepluginhub mondaycom/agentic-mondayThis skill uses the workspace's default tool permissions.
Migrate an existing app to the monday-code platform. This skill analyzes the current project, identifies what's missing, and adds the required monday-code structure while preserving existing code.
Scaffolds production-ready full-stack monorepo with Next.js frontend, NestJS backend, MongoDB, Clerk auth, CRUD ops, Vitest tests, and GitHub Actions CI/CD. Runs via bun dev.
Guides monorepo design including structure patterns, package organization, polyrepo tradeoffs, dependency management, and scalable workspace configs.
Creates React and Vite code apps for Power Apps. Scaffolds projects, checks prerequisites like Node.js and pac CLI, authenticates, builds, and deploys to Power Platform.
Share bugs, ideas, or general feedback.
Migrate an existing app to the monday-code platform. This skill analyzes the current project, identifies what's missing, and adds the required monday-code structure while preserving existing code.
User: "I have an existing React + Express app I want to run on monday-code. How do I migrate it?"
Actions:
.mondaycoderc, manifest.json, MondayContext, auth middleware, secrets utilsnpm run build in both frontend and backend to verifyResult: App is monday-code compatible with CDN-served frontend and serverless backend, ready to deploy with /monday-code-deploy.
Ask the user:
Scan the project to understand its current structure:
# Check for existing files
- package.json (root, frontend/, backend/, src/, etc.)
- tsconfig.json
- vite.config.ts / next.config.js / webpack.config.js
- src/ directory structure
- .env files
- Any existing monday SDK usage
Detect the current setup:
Report findings to the user before making changes.
Based on the analysis, create a migration plan. Present this to the user for approval before proceeding.
The plan should list:
IMPORTANT: Always preserve existing application logic. Migration adds monday-code compatibility around existing code — it does NOT rewrite business logic. Migration changes should be kept to a minimum necessary to achieve compatibility. Do only specific monday code changes, do NOT refactor the existing codebase (no JS to TS, no Fastify to Express, etc.) unless absolutely required for compatibility. Then you MUST first accept the consent of the user before making any such changes.
Create or update these files in the project root (if they don't already exist):
.mondaycoderc:
{
"runtime": "nodejs22.x"
}
manifest.json — Use mcp__monday-apps__monday_apps_get_app_feature_schema to fetch the correct schema for the user's feature type and create the manifest file (manifest.json) with the appropriate content:
{
"name": "{{APP_NAME}}",
"description": "A monday.com app",
"features": [
{
"type": "{{FEATURE_TYPE}}",
"name": "{{FEATURE_NAME}}",
"settings": {}
}
],
"oauthScopes": []
}
If the project is NOT already split into frontend/ and backend/ directories:
frontend/ and backend/ directoriesfrontend/monday-code CDN serves static files from a dist/ directory. Any build tool that outputs static HTML/JS/CSS to dist/ will work (Vite, Webpack, esbuild, Parcel, Rollup, etc.). Keep the existing build tool — do NOT switch build tools unless the user requests it.
Required for monday-sdk-js: The global variable must be defined. How to do this depends on the build tool:
define: { global: "globalThis" } to vite.config.tsnew webpack.ProvidePlugin({ global: "globalThis" }) to plugins, or add node: { global: true } to config--define:global=globalThis flag or define: { global: "globalThis" } in build config<script>var global = globalThis;</script> to index.html before the app script<script>var global = globalThis;</script> to index.htmlAdd monday-sdk-js:
npm install monday-sdk-js
Create src/contexts/MondayContext.tsx (if it doesn't exist):
import React, { createContext, useContext, useEffect, useState } from "react";
import mondaySdk from "monday-sdk-js";
const monday = mondaySdk();
interface MondayContextType {
monday: ReturnType<typeof mondaySdk>;
userId: string;
accountId: string;
userName: string;
theme: string;
isLoading: boolean;
}
const MondayContext = createContext<MondayContextType | null>(null);
// Detect local development (not inside monday.com iframe)
// Adapt the dev-mode check to your build tool:
// Vite: import.meta.env.DEV
// Webpack/CRA: process.env.NODE_ENV === "development"
// Other: use your build tool's equivalent
const isLocalDev =
typeof window !== "undefined" &&
!window.location.ancestorOrigins?.length &&
process.env.NODE_ENV === "development";
export function MondayProvider({ children }: { children: React.ReactNode }) {
const [userId, setUserId] = useState("");
const [accountId, setAccountId] = useState("");
const [userName, setUserName] = useState("");
const [theme, setTheme] = useState("light");
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
if (isLocalDev) {
setUserId("test-user-1");
setAccountId("test-account-1");
setUserName("Local Developer");
setTheme("light");
setIsLoading(false);
return;
}
monday.get("context").then((res: any) => {
const ctx = res.data;
setUserId(String(ctx.user?.id || ""));
setAccountId(String(ctx.account?.id || ""));
setUserName(ctx.user?.name || "");
setTheme(ctx.theme || "light");
setIsLoading(false);
});
monday.listen("context", (res: any) => {
if (res.data?.theme) setTheme(res.data.theme);
});
}, []);
return (
<MondayContext.Provider
value={{ monday, userId, accountId, userName, theme, isLoading }}
>
{children}
</MondayContext.Provider>
);
}
export function useMondayContext() {
const ctx = useContext(MondayContext);
if (!ctx) throw new Error("useMondayContext must be used within MondayProvider");
return ctx;
}
Wrap the existing app root with <MondayProvider>:
main.tsx, index.tsx, App.tsx)<MondayProvider>Create src/services/api.ts (if the app has a backend):
import mondaySdk from "monday-sdk-js";
const monday = mondaySdk();
// Adapt the dev-mode check to your build tool:
// Vite: import.meta.env.DEV
// Webpack/CRA: process.env.NODE_ENV === "development"
const isLocalDev =
typeof window !== "undefined" &&
!window.location.ancestorOrigins?.length &&
process.env.NODE_ENV === "development";
async function getSessionToken(): Promise<string> {
if (isLocalDev) {
// Use your build tool's env var convention (e.g., VITE_DEV_TOKEN, REACT_APP_DEV_TOKEN)
return "dev-token";
}
const token = await monday.get("sessionToken");
return token.data;
}
async function getBackendUrl(): Promise<string> {
if (isLocalDev) {
return "http://localhost:8080";
}
const response = await monday.get("context");
// @ts-ignore
return response.data.appVersion.mondayCodeHostingUrl;
}
export async function apiFetch(path: string, options: RequestInit = {}) {
const token = await getSessionToken();
const backendUrl = await getBackendUrl();
const res = await fetch(`${backendUrl}${path}`, {
...options,
headers: {
"Content-Type": "application/json",
Authorization: token,
...options.headers,
},
});
if (!res.ok) throw new Error(`API error: ${res.status}`);
return res.json();
}
Then guide the user on replacing their existing API calls (e.g., fetch("/api/...") or axios calls) with apiFetch(...).
Create index.html in the frontend root (required by monday-code for CDN deployments):
// This file is required by monday-code for CDN deployments
// It serves the built static files
Ensure a deploy script exists. Keep the existing dev and build scripts as-is (they already work with the project's build tool). Add only the deploy script:
{
"scripts": {
"deploy": "mapps code:push -c -d dist -a ${MONDAY_APP_ID:?} --force && rm -f dist.zip"
}
}
IMPORTANT: The build script must output static files to a dist/ directory. If the existing build outputs to a different directory (e.g., build/, out/, public/), either:
dist/, ORdeploy script's -d flag to point to the actual output directoryIf the user wants monday.com native look and feel:
npm install @vibe/core
Suggest replacing existing UI component libraries (MUI, Chakra, etc.) with Vibe equivalents where it makes sense, but do NOT auto-replace — this is a separate task the user should opt into.
If the backend is not in a backend/ directory, ask the user whether to restructure.
Create preload.cjs (for dotenv in local dev):
require('dotenv').config();
Create index.js (monday-code serverless entry point):
import path from "path";
import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const { default: app } = await import(path.join(__dirname, "dist", "app.js"));
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
IMPORTANT: The existing server start code (e.g., app.listen(...)) must be separated:
app.ts should export the Express app WITHOUT calling .listen()server.ts should import the app and call .listen() (for local dev)index.js should import the compiled app and call .listen() (for monday-code)If the existing code has app.listen() in the same file as route definitions, split it:
app.ts (export default app)server.ts that imports app and starts listeningindex.js to import from dist/app.jsThis is required for any backend that needs to authenticate requests from the frontend. If the existing app has auth middleware, it must be updated to support the JWT format used by monday.com session tokens
Create src/middleware/auth.ts for JWT verification of monday session tokens:
import jwt from "jsonwebtoken";
import type { Request, Response, NextFunction } from "express";
export interface AuthContext {
userId: string;
accountId: string;
}
declare global {
namespace Express {
interface Request {
auth?: AuthContext;
}
}
}
export function authMiddleware(req: Request, res: Response, next: NextFunction) {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith("Bearer ")) {
return res.status(401).json({ error: "Missing authorization token" });
}
const token = authHeader.split(" ")[1];
const secret = getSecret("MONDAY_CLIENT_SECRET");
if (!secret) {
console.error("MONDAY_CLIENT_SECRET not configured");
return res.status(500).json({ error: "Server misconfigured" });
}
try {
const decoded = jwt.verify(token, secret) as any;
if (decoded.dat) {
req.auth = {
userId: String(decoded.dat.user_id),
accountId: String(decoded.dat.account_id),
};
} else {
req.auth = {
userId: String(decoded.userId),
accountId: String(decoded.accountId),
};
}
next();
} catch (err) {
return res.status(401).json({ error: "Invalid token" });
}
}
If the app needs to verify incoming requests from monday.com automations or webhooks, additional middleware is needed to verify the HMAC signature using the MONDAY_CLIENT_SECRET. This is separate from the session token verification and should be applied only to the specific routes that handle automation/webhook requests.
import jwt from "jsonwebtoken";
import type { Request, Response, NextFunction } from "express";
/** Define the session property on the request object */
declare global {
namespace Express {
interface Request {
session: {
accountId: string;
userId: string;
backToUrl: string | undefined;
shortLivedToken: string | undefined;
};
}
}
}
export default async function authenticationMiddleware(
req: Request,
res: Response,
next: NextFunction
) {
try {
const authorization = req.headers.authorization ?? req.query?.token;
if (typeof authorization !== "string") {
res
.status(401)
.json({ error: "not authenticated, no credentials in request" });
return;
}
if (typeof process.env.MONDAY_SIGNING_SECRET !== "string") {
res.status(500).json({ error: "Missing MONDAY_SIGNING_SECRET (should be in .env file)" });
return;
}
const { accountId, userId, backToUrl, shortLivedToken } = jwt.verify(
authorization,
getSecret("MONDAY_SIGNING_SECRET")
) as any;
req.session = { accountId, userId, backToUrl, shortLivedToken };
// shortLivedToken is only included for automations, not for regular session tokens. If it's present, you can use it to make authenticated api graphql requests back to monday.com on behalf of the user who triggered the automation.
next();
} catch (err) {
res
.status(401)
.json({ error: "authentication error, could not verify credentials" });
}
}
If the app already has auth middleware:
decoded.dat and decoded.userId patterns)If using MongoDB/Mongoose:
MNDY_MONGODB_CONNECTION_STRING env varaccountId filtering to all queries for multi-tenant isolationIf using PostgreSQL/MySQL/SQLite:
mapps code:env or secrets. FYI this option is discouraged due to increased latency and security considerations of connecting to an external DB from a mondday.com environmentMulti-tenant isolation — ALL database queries must filter by accountId:
interface BaseDocument {
accountId: string;
ownerId: string;
createdAt: string;
updatedAt: string;
}
// CORRECT
const items = await collection.find({ accountId: req.auth!.accountId }).toArray();
// WRONG — exposes all tenants' data
const items = await collection.find({}).toArray();
Scan existing queries and flag any that don't filter by tenant.
Add @mondaycom/apps-sdk:
npm install @mondaycom/apps-sdk
src/utils/secrets.ts:
Fetching secrets and environment variables should use the monday-code SDK utilities to ensure compatibility with monday-code's secret management and environment variable injection.
import { SecretsManager } from "@mondaycom/apps-sdk";
const secretsManager = new SecretsManager();
export async function getSecret(key: string): Promise<string | undefined> {
try {
const { value } = await secretsManager.getSecret(key);
return value;
} catch {
return process.env[key];
}
}
Then replace all process.env.KEY usages in the backend code with getSecret("KEY") to ensure it works both in local dev and when deployed to monday-code.
src/utils/env-vars.ts:
import { EnvironmentVariablesManager } from "@mondaycom/apps-sdk";
const envManager = new EnvironmentVariablesManager();
export function getEnvVar(key: string): string {
const value = envManager.getEnvVar(key);
if (!value) {
throw new Error(`Environment variable not set: ${key}`);
}
return value;
}
src/utils/logger.ts:
Logging should also use the monday-code Logger for better integration with monday-code's logging system.
import { Logger } from "@mondaycom/apps-sdk";
export function createLogger(tag: string) {
return new Logger(tag);
}
Then replace any console.log or other logging calls with the created logger instance (e.g., logger.info(...), logger.error(...)).
Ensure these scripts and dependencies exist:
{
"type": "module",
"scripts": {
"build": "rm -rf dist && tsc",
"dev": "tsx watch --require ./preload.cjs src/server.ts",
"start": "node --require ./preload.cjs index.js",
"deploy": "mapps code:push -a ${MONDAY_APP_ID:?} --force && rm -f code.tar.gz"
}
}
Add missing dependencies:
npm install @mondaycom/apps-sdk jsonwebtoken dotenv
npm install -D @types/jsonwebtoken tsx
Create .env.example in relevant directories:
Backend:
MNDY_MONGODB_CONNECTION_STRING=mongodb://localhost:27017/{{APP_NAME}}
MONDAY_CLIENT_SECRET=your-client-secret
PORT=8080
Frontend (use the env var prefix convention for your build tool — VITE_ for Vite, REACT_APP_ for CRA, etc.):
DEV_TOKEN=your-dev-jwt-token
Copy to .env if no .env exists. If .env already exists, merge new variables into it without overwriting existing values.
After completing the migration, present a checklist to the user:
Root:
.mondaycoderc exists with valid runtimemanifest.json exists with correct feature typeFrontend (if applicable):
global = globalThis definedist/ (or deploy script adjusted)monday-sdk-js installedMondayContext provider wraps the appindex.js CDN entry point existsnpm run build)api.ts service uses monday session tokens (if fullstack)Backend (if applicable):
"type": "module" in package.jsonapp.ts exports app WITHOUT calling .listen()server.ts handles local dev startupindex.js serverless entry point existspreload.cjs exists for dotenv@mondaycom/apps-sdk installedaccountIdnpm run build)Next steps:
https://<slug>.monday.com/developers/appsMONDAY_CLIENT_SECRET in .env/monday-code-deploy:monday-deploy to deployAfter migration, run builds to verify everything compiles:
# Frontend
cd frontend && npm run build
# Backend
cd backend && npm run build
If the project is using TypeScript, fix any TypeScript or build errors before considering migration complete.
.find(, .findOne(, .aggregate(, .updateOne(, etc.)accountId: req.auth!.accountId in the filteraccountId and ownerId fields to all document insert operationsaccountId for performanceBuild fails after migration: global is not defined
— The build tool is missing the global = globalThis define. See Step 4.2 for build-tool-specific fixes.
app.listen is not a function or server won't start on monday-code
— The app.listen() call must be separated from route definitions. app.ts must export the app without calling .listen(). See Step 5.2.
MONDAY_CLIENT_SECRET is undefined at runtime
— Use getSecret("MONDAY_CLIENT_SECRET") from apps-sdk instead of process.env. Make sure the secret is set via mapps code:env:set.
MongoDB connection fails after deploy
— Use MNDY_MONGODB_CONNECTION_STRING env var (auto-injected after first deploy). Do not hardcode the connection string.
TypeScript compilation errors after restructuring
— Check that tsconfig.json includes the new files and that all imports use the updated file paths. Run tsc --noEmit to see all errors before deploying.
Session token returns 401 in production
— The auth middleware must handle both decoded.dat.user_id (monday JWT format) and decoded.userId (legacy). See Step 5.3.