Skip to main content
The Projects API allows you to create, read, update, and delete projects. Each project represents a web application generated by ZapDev’s AI.

Data Model

Projects contain the following fields:
_id
Id<'projects'>
required
Unique identifier for the project
name
string
required
Display name of the project
userId
string
required
Clerk user ID of the project owner
framework
Framework
required
Framework used for the project: NEXTJS, ANGULAR, REACT, VUE, or SVELTE
modelPreference
string
Preferred AI model for code generation (optional)
createdAt
number
Timestamp when the project was created (milliseconds)
updatedAt
number
Timestamp when the project was last updated (milliseconds)

Queries

list

Get all projects for the authenticated user with preview attachments.
import { api } from '@/convex/_generated/api';
import { useQuery } from 'convex/react';

const projects = useQuery(api.projects.list);
Arguments: None Returns: Array of projects with preview attachments
previewAttachment
object | null
The most recent image attachment from the latest message in the project
Authentication: Returns empty array if user is not authenticated

get

Get a single project by ID.
import { api } from '@/convex/_generated/api';
import { useQuery } from 'convex/react';

const project = useQuery(api.projects.get, {
  projectId: "jh7...",
});
projectId
Id<'projects'>
required
The project ID to retrieve
Returns: Project object Authentication: Required. User must own the project. Throws: "Project not found" or "Unauthorized"

listShowcase

Get public showcase projects (up to 12 projects with at least one fragment).
import { api } from '@/convex/_generated/api';
import { useQuery } from 'convex/react';

const showcaseProjects = useQuery(api.projects.listShowcase);
Arguments: None Returns: Array of up to 12 projects sorted by creation date (descending)
messageCount
number
Total number of messages in the project
hasFragment
boolean
Whether the project has at least one fragment
Authentication: Public (no authentication required)

getForUser

Get a project for a specific user (for background jobs/Inngest).
import { api } from '@/convex/_generated/api';

const project = await ctx.runQuery(api.projects.getForUser, {
  userId: "user_...",
  projectId: "jh7...",
});
userId
string
required
The Clerk user ID
projectId
Id<'projects'>
required
The project ID to retrieve
Returns: Project object Throws: "Project not found" or "Unauthorized"

getForSystem

System-level query to get any project by ID (for Inngest background jobs only). Bypasses authentication.
import { api } from '@/convex/_generated/api';

const project = await ctx.runQuery(api.projects.getForSystem, {
  projectId: "jh7...",
});
projectId
Id<'projects'>
required
The project ID to retrieve
Returns: Project object Throws: "Project not found" Note: This is a system-level function that bypasses authentication. Use only from trusted Inngest workflows.

Mutations

create

Create a new project.
import { api } from '@/convex/_generated/api';
import { useMutation } from 'convex/react';

const createProject = useMutation(api.projects.create);

const projectId = await createProject({
  name: "My App",
  framework: "NEXTJS",
});
name
string
required
Display name for the project
framework
Framework
required
Framework to use: NEXTJS, ANGULAR, REACT, VUE, or SVELTE
Returns: Id<"projects"> - The newly created project ID Authentication: Required

update

Update a project’s name, framework, or model preference.
import { api } from '@/convex/_generated/api';
import { useMutation } from 'convex/react';

const updateProject = useMutation(api.projects.update);

const projectId = await updateProject({
  projectId: "jh7...",
  name: "Updated App Name",
  framework: "REACT",
  modelPreference: "gpt-4",
});
projectId
Id<'projects'>
required
The project ID to update
name
string
New name for the project (optional)
framework
Framework
New framework for the project (optional)
modelPreference
string
Preferred AI model (optional, set to undefined to clear)
Returns: Id<"projects"> - The updated project ID Authentication: Required. User must own the project. Throws: "Project not found" or "Unauthorized"

deleteProject

Delete a project and all associated data (messages, fragments, attachments).
import { api } from '@/convex/_generated/api';
import { useMutation } from 'convex/react';

const deleteProject = useMutation(api.projects.deleteProject);

const result = await deleteProject({
  projectId: "jh7...",
});
projectId
Id<'projects'>
required
The project ID to delete
Returns: { success: true } Authentication: Required. User must own the project. Throws: "Project not found" or "Unauthorized" Note: This operation cascades and deletes all messages, fragments, attachments, and fragment drafts associated with the project.

getOrCreateFragmentDraft

Get or create a fragment draft for a project.
import { api } from '@/convex/_generated/api';
import { useMutation } from 'convex/react';

const getOrCreateDraft = useMutation(api.projects.getOrCreateFragmentDraft);

const draft = await getOrCreateDraft({
  projectId: "jh7...",
  framework: "NEXTJS",
});
projectId
Id<'projects'>
required
The project ID
framework
Framework
required
Framework for the fragment draft
Returns: Fragment draft object with files, framework, createdAt, updatedAt Authentication: Required. User must own the project. Throws: "Unauthorized"

createForUser

Create a project with an explicit user ID (for use from actions).
import { api } from '@/convex/_generated/api';

const projectId = await ctx.runMutation(api.projects.createForUser, {
  userId: "user_...",
  name: "My App",
  framework: "NEXTJS",
});
userId
string
required
The Clerk user ID
name
string
required
Display name for the project
framework
Framework
required
Framework to use
Returns: Id<"projects"> - The newly created project ID

updateForUser

Update a project for a specific user (for use from background jobs/Inngest).
import { api } from '@/convex/_generated/api';

const projectId = await ctx.runMutation(api.projects.updateForUser, {
  userId: "user_...",
  projectId: "jh7...",
  name: "Updated Name",
});
userId
string
required
The Clerk user ID
projectId
Id<'projects'>
required
The project ID to update
name
string
New name (optional)
framework
Framework
New framework (optional)
modelPreference
string
Preferred AI model (optional)
Returns: Id<"projects"> - The updated project ID Throws: "Project not found" or "Unauthorized"

Actions

createWithMessage

Create a new project with an initial message. This is the primary entry point for new projects.
import { api } from '@/convex/_generated/api';
import { useAction } from 'convex/react';

const createWithMessage = useAction(api.projects.createWithMessage);

const result = await createWithMessage({
  value: "Create a todo app with Next.js",
});
value
string
required
The initial message content (user’s request)
Returns: Project object with additional fields:
id
Id<'projects'>
The newly created project ID
messageId
Id<'messages'>
The ID of the initial message
value
string
The message content
Authentication: Required Throws: "Unauthorized" or "You have run out of credits" Note: This action automatically:
  1. Checks and consumes 1 credit
  2. Generates a random project name
  3. Creates the project with NEXTJS framework
  4. Creates the initial USER message

createWithMessageAndAttachments

Create a new project with an initial message and optional attachments (images, Figma files, etc.).
import { api } from '@/convex/_generated/api';
import { useAction } from 'convex/react';

const createWithAttachments = useAction(
  api.projects.createWithMessageAndAttachments
);

const result = await createWithAttachments({
  value: "Create an app based on this design",
  attachments: [
    {
      url: "https://...",
      size: 123456,
      width: 1920,
      height: 1080,
    },
  ],
});
value
string
required
The initial message content
attachments
array
Optional array of attachment objects
attachments[].url
string
required
URL of the attachment
attachments[].size
number
required
File size in bytes
attachments[].width
number
Image width in pixels (optional)
attachments[].height
number
Image height in pixels (optional)
Returns: Project object with id, messageId, and value Authentication: Required Throws: "Unauthorized" or "You have run out of credits" Note: This action automatically checks and consumes 1 credit before creating the project.

Database Schema

The projects table has the following indexes:
  • by_userId: Lookup projects by user ID
  • by_userId_createdAt: Lookup projects by user ID and creation date (for sorting)
// convex/schema.ts
projects: defineTable({
  name: v.string(),
  userId: v.string(),
  framework: frameworkEnum,
  modelPreference: v.optional(v.string()),
  createdAt: v.optional(v.number()),
  updatedAt: v.optional(v.number()),
})
  .index("by_userId", ["userId"])
  .index("by_userId_createdAt", ["userId", "createdAt"])

Example Usage

Creating a project with the UI

import { api } from '@/convex/_generated/api';
import { useAction } from 'convex/react';
import { useState } from 'react';

function NewProjectForm() {
  const [input, setInput] = useState('');
  const createProject = useAction(api.projects.createWithMessage);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    try {
      const result = await createProject({ value: input });
      console.log('Created project:', result.id);
      // Navigate to project page
    } catch (error) {
      console.error('Failed to create project:', error);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
        placeholder="Describe your app..."
      />
      <button type="submit">Create Project</button>
    </form>
  );
}

Listing user projects

import { api } from '@/convex/_generated/api';
import { useQuery } from 'convex/react';

function ProjectList() {
  const projects = useQuery(api.projects.list);

  if (!projects) return <div>Loading...</div>;

  return (
    <div>
      {projects.map((project) => (
        <div key={project._id}>
          <h3>{project.name}</h3>
          <p>Framework: {project.framework}</p>
          {project.previewAttachment && (
            <img src={project.previewAttachment.url} alt="Preview" />
          )}
        </div>
      ))}
    </div>
  );
}

Deleting a project

import { api } from '@/convex/_generated/api';
import { useMutation } from 'convex/react';

function DeleteProjectButton({ projectId }: { projectId: string }) {
  const deleteProject = useMutation(api.projects.deleteProject);

  const handleDelete = async () => {
    if (confirm('Are you sure you want to delete this project?')) {
      await deleteProject({ projectId });
    }
  };

  return (
    <button onClick={handleDelete}>
      Delete Project
    </button>
  );
}

Build docs developers (and LLMs) love