Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/pingdotgg/lawn/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The Projects API manages project structures within teams. Projects organize videos and inherit permissions from their parent team. All project functions are defined in convex/projects.ts.

Functions

create

Creates a new project within a team.
import { useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';

function CreateProjectButton({ teamId }) {
  const createProject = useMutation(api.projects.create);
  
  const handleCreate = async () => {
    const projectId = await createProject({
      teamId,
      name: 'New Project',
      description: 'Project description',
    });
    console.log('Created project:', projectId);
  };
  
  return <button onClick={handleCreate}>Create Project</button>;
}
teamId
Id<'teams'>
required
The team to create the project in
name
string
required
Project name
description
string
Project description (optional)
projectId
Id<'projects'>
The ID of the created project
Permissions: Requires member role in the team. Team must have an active subscription. Implementation: convex/projects.ts:6

list

Lists all projects in a team with video counts.
import { useQuery } from 'convex/react';
import { api } from '../convex/_generated/api';

function ProjectsList({ teamId }) {
  const projects = useQuery(api.projects.list, { teamId });
  
  return (
    <div>
      {projects?.map(project => (
        <div key={project._id}>
          <h3>{project.name}</h3>
          <p>{project.description}</p>
          <p>{project.videoCount} videos</p>
        </div>
      ))}
    </div>
  );
}
teamId
Id<'teams'>
required
The team to list projects from
projects
array
Array of project objects with video counts
_id
Id<'projects'>
Project ID
teamId
Id<'teams'>
Parent team ID
name
string
Project name
description
string | undefined
Project description
videoCount
number
Number of videos in the project
Permissions: Requires team membership. Implementation: convex/projects.ts:24

listUploadTargets

Lists all projects the user can upload to, optionally filtered by team slug.
import { useQuery } from 'convex/react';
import { api } from '../convex/_generated/api';

function UploadDestinationPicker({ teamSlug }) {
  const targets = useQuery(api.projects.listUploadTargets, { teamSlug });
  const [selected, setSelected] = useState(null);
  
  return (
    <select value={selected} onChange={(e) => setSelected(e.target.value)}>
      <option value="">Select destination...</option>
      {targets?.map(target => (
        <option key={target.projectId} value={target.projectId}>
          {target.teamName} / {target.projectName}
        </option>
      ))}
    </select>
  );
}
teamSlug
string
Filter by team slug (optional)
targets
array
Array of upload target objects (excludes projects where user has viewer role)
projectId
Id<'projects'>
Project ID
projectName
string
Project name
teamId
Id<'teams'>
Team ID
teamName
string
Team name
teamSlug
string
Team slug
role
'member' | 'admin' | 'owner'
User’s role (viewers excluded)
Permissions: Requires authentication. Only returns projects where user has upload permissions (member or higher). Implementation: convex/projects.ts:52

get

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

function ProjectDetail({ projectId }) {
  const project = useQuery(api.projects.get, { projectId });
  
  return (
    <div>
      <h1>{project?.name}</h1>
      <p>{project?.description}</p>
      <p>Your role: {project?.role}</p>
    </div>
  );
}
projectId
Id<'projects'>
required
The project to retrieve
project
object
Project object with user’s role
role
'owner' | 'admin' | 'member' | 'viewer'
User’s role in the project’s team
Permissions: Requires access to the project (through team membership). Implementation: convex/projects.ts:100

update

Updates project name and/or description.
import { useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';

function EditProjectForm({ projectId }) {
  const updateProject = useMutation(api.projects.update);
  const [name, setName] = useState('');
  const [description, setDescription] = useState('');
  
  const handleSubmit = async () => {
    await updateProject({ projectId, name, description });
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <textarea value={description} onChange={(e) => setDescription(e.target.value)} />
      <button type="submit">Save</button>
    </form>
  );
}
projectId
Id<'projects'>
required
The project to update
name
string
New project name (optional)
description
string
New project description (optional)
Permissions: Requires member role in the project’s team. Implementation: convex/projects.ts:108

remove

Deletes a project and all associated data (videos, comments, share links).
import { useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';

function DeleteProjectButton({ projectId }) {
  const removeProject = useMutation(api.projects.remove);
  
  const handleDelete = async () => {
    if (confirm('Delete this project and all its videos? This cannot be undone!')) {
      await removeProject({ projectId });
    }
  };
  
  return <button onClick={handleDelete}>Delete Project</button>;
}
projectId
Id<'projects'>
required
The project to delete
Permissions: Requires admin role in the project’s team. Implementation: convex/projects.ts:125

Usage Examples

Project Selector with Video Counts

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

function ProjectSelector({ teamId, onSelect }) {
  const projects = useQuery(api.projects.list, { teamId });
  
  return (
    <div className="project-selector">
      <h3>Select a project</h3>
      {projects?.map(project => (
        <button
          key={project._id}
          onClick={() => onSelect(project._id)}
          className="project-option"
        >
          <div>
            <h4>{project.name}</h4>
            {project.description && <p>{project.description}</p>}
          </div>
          <span className="video-count">{project.videoCount} videos</span>
        </button>
      ))}
    </div>
  );
}

Project Dashboard

import { useQuery, useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';

function ProjectDashboard({ projectId }) {
  const project = useQuery(api.projects.get, { projectId });
  const videos = useQuery(api.videos.list, { projectId });
  const updateProject = useMutation(api.projects.update);
  const removeProject = useMutation(api.projects.remove);
  
  const canEdit = project?.role && ['member', 'admin', 'owner'].includes(project.role);
  const canDelete = project?.role && ['admin', 'owner'].includes(project.role);
  
  return (
    <div>
      <header>
        <h1>{project?.name}</h1>
        <p>{project?.description}</p>
        {canEdit && <button onClick={() => setEditing(true)}>Edit</button>}
        {canDelete && (
          <button onClick={() => removeProject({ projectId })}>Delete</button>
        )}
      </header>
      
      <div className="stats">
        <div>Total Videos: {videos?.length || 0}</div>
        <div>In Review: {videos?.filter(v => v.workflowStatus === 'review').length}</div>
        <div>Completed: {videos?.filter(v => v.workflowStatus === 'done').length}</div>
      </div>
      
      <VideoGrid videos={videos} />
    </div>
  );
}

Multi-Team Upload Destination

import { useQuery, useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';

function UniversalUploader() {
  const targets = useQuery(api.projects.listUploadTargets);
  const createVideo = useMutation(api.videos.create);
  const [selectedProject, setSelectedProject] = useState(null);
  
  const handleUpload = async (file: File) => {
    if (!selectedProject) {
      alert('Please select a project');
      return;
    }
    
    await createVideo({
      projectId: selectedProject,
      title: file.name,
      fileSize: file.size,
      contentType: file.type,
    });
  };
  
  return (
    <div>
      <select 
        value={selectedProject} 
        onChange={(e) => setSelectedProject(e.target.value)}
      >
        <option value="">Choose destination...</option>
        {targets?.map(target => (
          <option key={target.projectId} value={target.projectId}>
            {target.teamName}{target.projectName}
          </option>
        ))}
      </select>
      
      <input type="file" onChange={(e) => handleUpload(e.target.files[0])} />
    </div>
  );
}

Create Project with Redirect

import { useMutation } from 'convex/react';
import { api } from '../convex/_generated/api';
import { useRouter } from 'next/navigation';

function CreateProjectForm({ teamId }) {
  const createProject = useMutation(api.projects.create);
  const router = useRouter();
  const [name, setName] = useState('');
  const [description, setDescription] = useState('');
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    
    try {
      const projectId = await createProject({ teamId, name, description });
      router.push(`/project/${projectId}`);
    } catch (error) {
      if (error.message.includes('active subscription')) {
        alert('Your team needs an active subscription to create projects');
      } else {
        alert('Failed to create project: ' + error.message);
      }
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        placeholder="Project name"
        value={name}
        onChange={(e) => setName(e.target.value)}
        required
      />
      <textarea
        placeholder="Description (optional)"
        value={description}
        onChange={(e) => setDescription(e.target.value)}
      />
      <button type="submit">Create Project</button>
    </form>
  );
}

Project Organization

Projects inherit all permissions from their parent team:
  • Team viewers can view projects and videos
  • Team members can create and edit videos in projects
  • Team admins can create, edit, and delete projects
  • Team owners have full control
Projects are automatically deleted when their parent team is deleted.

Build docs developers (and LLMs) love