Skip to main content
The Messages API handles all message-related operations including creating messages, managing fragments (generated code), and handling attachments.

Data Model

Message

_id
Id<'messages'>
required
Unique identifier for the message
content
string
required
The text content of the message
role
MessageRole
required
Who sent the message: USER or ASSISTANT
type
MessageType
required
Message type: RESULT, ERROR, or STREAMING
status
MessageStatus
required
Message status: PENDING, STREAMING, or COMPLETE
projectId
Id<'projects'>
required
The project this message belongs to
createdAt
number
Timestamp when the message was created (milliseconds)
updatedAt
number
Timestamp when the message was last updated (milliseconds)

Fragment

_id
Id<'fragments'>
required
Unique identifier for the fragment
messageId
Id<'messages'>
required
The message this fragment belongs to
sandboxUrl
string
required
E2B sandbox URL where the code runs
title
string
required
Display title for the fragment
files
object
required
File structure with paths as keys and content as values
metadata
object
Additional metadata about the fragment (optional)
framework
Framework
required
Framework used: NEXTJS, ANGULAR, REACT, VUE, or SVELTE

Attachment

_id
Id<'attachments'>
required
Unique identifier for the attachment
messageId
Id<'messages'>
required
The message this attachment belongs to
type
AttachmentType
required
Attachment type: IMAGE, FIGMA_FILE, or GITHUB_REPO
url
string
required
URL of the attachment
size
number
required
File size in bytes
width
number
Image width in pixels (optional)
height
number
Image height in pixels (optional)
importId
Id<'imports'>
Related import ID if from Figma/GitHub (optional)
sourceMetadata
object
Metadata from the import source (optional)

Queries

list

Get all messages for a project with fragments and attachments.
import { api } from '@/convex/_generated/api';
import { useQuery } from 'convex/react';

const messages = useQuery(api.messages.list, {
  projectId: "jh7...",
});
projectId
Id<'projects'>
required
The project ID to get messages for
Returns: Array of messages sorted by creation date (ascending)
Fragment
object | null
The fragment associated with this message (if any)
Attachment
array
All attachments for this message
Authentication: Required. User must own the project. Throws: "Unauthorized"

get

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

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

getFragment

Get the fragment for a message.
import { api } from '@/convex/_generated/api';
import { useQuery } from 'convex/react';

const fragment = useQuery(api.messages.getFragment, {
  messageId: "jg6...",
});
messageId
Id<'messages'>
required
The message ID to get the fragment for
Returns: Fragment object or null if no fragment exists Authentication: Required. User must own the project. Throws: "Message not found" or "Unauthorized"

getFragmentById

Get a fragment by its ID (public API access).
import { api } from '@/convex/_generated/api';
import { useQuery } from 'convex/react';

const fragment = useQuery(api.messages.getFragmentById, {
  fragmentId: "jd8...",
});
fragmentId
Id<'fragments'>
required
The fragment ID to retrieve
Returns: Fragment object Authentication: Public (no authentication required) Throws: "Fragment not found"

getFragmentByIdAuth

Get a fragment by ID with authorization check.
import { api } from '@/convex/_generated/api';
import { useQuery } from 'convex/react';

const result = useQuery(api.messages.getFragmentByIdAuth, {
  fragmentId: "jd8...",
});
fragmentId
Id<'fragments'>
required
The fragment ID to retrieve
Returns: Object containing:
fragment
object
required
The fragment object
message
object
required
The associated message object
project
object
required
The associated project object
Authentication: Required. User must own the project. Throws: "Fragment not found", "Message not found", or "Unauthorized"

getAttachments

Get all attachments for a message.
import { api } from '@/convex/_generated/api';
import { useQuery } from 'convex/react';

const attachments = useQuery(api.messages.getAttachments, {
  messageId: "jg6...",
});
messageId
Id<'messages'>
required
The message ID to get attachments for
Returns: Array of attachment objects Authentication: Required. User must own the project. Throws: "Message not found" or "Unauthorized"

listForUser

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

const messages = await ctx.runQuery(api.messages.listForUser, {
  userId: "user_...",
  projectId: "jh7...",
});
userId
string
required
The Clerk user ID
projectId
Id<'projects'>
required
The project ID
Returns: Array of messages with fragment and attachments fields Throws: "Unauthorized"

Mutations

create

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

const createMessage = useMutation(api.messages.create);

const messageId = await createMessage({
  projectId: "jh7...",
  content: "Make the button blue",
  role: "USER",
  type: "RESULT",
  status: "COMPLETE",
});
projectId
Id<'projects'>
required
The project ID
content
string
required
The message content
role
MessageRole
required
Message role: USER or ASSISTANT
type
MessageType
required
Message type: RESULT, ERROR, or STREAMING
status
MessageStatus
Message status (defaults to COMPLETE)
Returns: Id<"messages"> - The newly created message ID Authentication: Required. User must own the project. Throws: "Unauthorized"

updateStatus

Update a message’s status.
import { api } from '@/convex/_generated/api';
import { useMutation } from 'convex/react';

const updateStatus = useMutation(api.messages.updateStatus);

const messageId = await updateStatus({
  messageId: "jg6...",
  status: "COMPLETE",
});
messageId
Id<'messages'>
required
The message ID to update
status
MessageStatus
required
New status: PENDING, STREAMING, or COMPLETE
Returns: Id<"messages"> - The updated message ID Authentication: Required. User must own the project. Throws: "Message not found" or "Unauthorized"

updateMessage

Update a message’s content and optionally its status.
import { api } from '@/convex/_generated/api';
import { useMutation } from 'convex/react';

const updateMessage = useMutation(api.messages.updateMessage);

const message = await updateMessage({
  messageId: "jg6...",
  content: "Updated content",
  status: "COMPLETE",
});
messageId
Id<'messages'>
required
The message ID to update
content
string
required
New message content
status
MessageStatus
New status (optional)
Returns: Updated message object Authentication: Required. User must own the project. Throws: "Message not found" or "Unauthorized"

createFragment

Create or update a fragment for a message.
import { api } from '@/convex/_generated/api';
import { useMutation } from 'convex/react';

const createFragment = useMutation(api.messages.createFragment);

const fragmentId = await createFragment({
  messageId: "jg6...",
  sandboxUrl: "https://...",
  title: "Todo App",
  files: {
    "app/page.tsx": "export default...",
    "app/layout.tsx": "export default...",
  },
  framework: "NEXTJS",
  metadata: { buildTime: 1234 },
});
messageId
Id<'messages'>
required
The message ID
sandboxUrl
string
required
E2B sandbox URL
title
string
required
Display title for the fragment
files
object
required
File structure (paths as keys, content as values)
framework
Framework
required
Framework: NEXTJS, ANGULAR, REACT, VUE, or SVELTE
metadata
object
Additional metadata (optional)
Returns: Id<"fragments"> - The fragment ID (creates new or updates existing) Authentication: Required. User must own the project. Throws: "Message not found" or "Unauthorized"

addAttachment

Add an attachment to a message.
import { api } from '@/convex/_generated/api';
import { useMutation } from 'convex/react';

const addAttachment = useMutation(api.messages.addAttachment);

const attachmentId = await addAttachment({
  messageId: "jg6...",
  type: "IMAGE",
  url: "https://...",
  size: 123456,
  width: 1920,
  height: 1080,
});
messageId
Id<'messages'>
required
The message ID
type
AttachmentType
required
Attachment type: IMAGE, FIGMA_FILE, or GITHUB_REPO
url
string
required
URL of the attachment
size
number
required
File size in bytes
width
number
Image width in pixels (optional)
height
number
Image height in pixels (optional)
importId
Id<'imports'>
Related import ID (optional)
sourceMetadata
object
Source metadata (optional)
Returns: Id<"attachments"> - The newly created attachment ID Authentication: Required. User must own the project. Throws: "Message not found" or "Unauthorized"

createForUser

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

const messageId = await ctx.runMutation(api.messages.createForUser, {
  userId: "user_...",
  projectId: "jh7...",
  content: "Create a button",
  role: "USER",
  type: "RESULT",
  status: "COMPLETE",
});
userId
string
required
The Clerk user ID
projectId
Id<'projects'>
required
The project ID
content
string
required
Message content
role
MessageRole
required
Message role
type
MessageType
required
Message type
status
MessageStatus
Message status (optional, defaults to COMPLETE)
Returns: Id<"messages"> - The newly created message ID Throws: "Unauthorized"

createFragmentForUser

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

const fragmentId = await ctx.runMutation(api.messages.createFragmentForUser, {
  userId: "user_...",
  messageId: "jg6...",
  sandboxUrl: "https://...",
  title: "Todo App",
  files: { "app/page.tsx": "..." },
  framework: "NEXTJS",
});
userId
string
required
The Clerk user ID
messageId
Id<'messages'>
required
The message ID
sandboxUrl
string
required
E2B sandbox URL
title
string
required
Display title
files
object
required
File structure
framework
Framework
required
Framework
metadata
object
Additional metadata (optional)
Returns: Id<"fragments"> - The fragment ID Throws: "Message not found" or "Unauthorized"

Actions

createWithAttachments

Create a message with attachments (for message form flow).
import { api } from '@/convex/_generated/api';
import { useAction } from 'convex/react';

const createMessage = useAction(api.messages.createWithAttachments);

const result = await createMessage({
  value: "Update the UI based on this design",
  projectId: "jh7...",
  attachments: [
    {
      url: "https://...",
      size: 123456,
      width: 1920,
      height: 1080,
    },
  ],
});
value
string
required
The message content
projectId
string
required
The project ID (as string)
attachments
array
Optional array of attachment objects
attachments[].url
string
required
Attachment URL
attachments[].size
number
required
File size in bytes
attachments[].width
number
Image width (optional)
attachments[].height
number
Image height (optional)
attachments[].type
AttachmentType
Attachment type (optional, defaults to IMAGE)
attachments[].importId
Id<'imports'>
Import ID (optional)
attachments[].sourceMetadata
object
Source metadata (optional)
Returns: Object containing:
messageId
Id<'messages'>
The newly created message ID
projectId
Id<'projects'>
The project ID
value
string
The message content
Authentication: Required Throws: "You have run out of credits" Note: This action automatically checks and consumes 1 credit before creating the message.

Database Schema

The messages table has the following indexes:
// convex/schema.ts
messages: defineTable({
  content: v.string(),
  role: messageRoleEnum,
  type: messageTypeEnum,
  status: messageStatusEnum,
  projectId: v.id("projects"),
  createdAt: v.optional(v.number()),
  updatedAt: v.optional(v.number()),
})
  .index("by_projectId", ["projectId"])
  .index("by_projectId_createdAt", ["projectId", "createdAt"])
The fragments table:
fragments: defineTable({
  messageId: v.id("messages"),
  sandboxUrl: v.string(),
  title: v.string(),
  files: v.any(),
  metadata: v.optional(v.any()),
  framework: frameworkEnum,
  createdAt: v.optional(v.number()),
  updatedAt: v.optional(v.number()),
})
  .index("by_messageId", ["messageId"])
The attachments table:
attachments: defineTable({
  type: attachmentTypeEnum,
  url: v.string(),
  width: v.optional(v.number()),
  height: v.optional(v.number()),
  size: v.number(),
  messageId: v.id("messages"),
  importId: v.optional(v.id("imports")),
  sourceMetadata: v.optional(v.any()),
  createdAt: v.optional(v.number()),
  updatedAt: v.optional(v.number()),
})
  .index("by_messageId", ["messageId"])

Example Usage

Creating a message in a project

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

function MessageForm({ projectId }: { projectId: string }) {
  const [input, setInput] = useState('');
  const createMessage = useAction(api.messages.createWithAttachments);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    try {
      const result = await createMessage({
        value: input,
        projectId,
      });
      console.log('Created message:', result.messageId);
      setInput('');
    } catch (error) {
      console.error('Failed to create message:', error);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <textarea
        value={input}
        onChange={(e) => setInput(e.target.value)}
        placeholder="Describe changes..."
      />
      <button type="submit">Send</button>
    </form>
  );
}

Displaying messages with fragments

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

function MessageList({ projectId }: { projectId: string }) {
  const messages = useQuery(api.messages.list, { projectId });

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

  return (
    <div>
      {messages.map((message) => (
        <div key={message._id}>
          <p>{message.content}</p>
          {message.Fragment && (
            <div>
              <h4>{message.Fragment.title}</h4>
              <iframe src={message.Fragment.sandboxUrl} />
            </div>
          )}
          {message.Attachment.map((att) => (
            <img key={att._id} src={att.url} alt="Attachment" />
          ))}
        </div>
      ))}
    </div>
  );
}

Build docs developers (and LLMs) love