The Messages API handles all message-related operations including creating messages, managing fragments (generated code), and handling attachments.
Data Model
Message
Unique identifier for the message
The text content of the message
Who sent the message: USER or ASSISTANT
Message type: RESULT, ERROR, or STREAMING
Message status: PENDING, STREAMING, or COMPLETE
The project this message belongs to
Timestamp when the message was created (milliseconds)
Timestamp when the message was last updated (milliseconds)
Fragment
Unique identifier for the fragment
The message this fragment belongs to
E2B sandbox URL where the code runs
Display title for the fragment
File structure with paths as keys and content as values
Additional metadata about the fragment (optional)
Framework used: NEXTJS, ANGULAR, REACT, VUE, or SVELTE
Attachment
_id
Id<'attachments'>
required
Unique identifier for the attachment
The message this attachment belongs to
Attachment type: IMAGE, FIGMA_FILE, or GITHUB_REPO
Image width in pixels (optional)
Image height in pixels (optional)
Related import ID if from Figma/GitHub (optional)
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...",
});
The project ID to get messages for
Returns: Array of messages sorted by creation date (ascending)
The fragment associated with this message (if any)
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...",
});
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...",
});
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...",
});
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...",
});
The fragment ID to retrieve
Returns: Object containing:
The associated message object
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...",
});
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...",
});
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",
});
Message role: USER or ASSISTANT
Message type: RESULT, ERROR, or STREAMING
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",
});
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",
});
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 },
});
Display title for the fragment
File structure (paths as keys, content as values)
Framework: NEXTJS, ANGULAR, REACT, VUE, or SVELTE
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,
});
Attachment type: IMAGE, FIGMA_FILE, or GITHUB_REPO
Image width in pixels (optional)
Image height in pixels (optional)
Related import ID (optional)
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",
});
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",
});
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,
},
],
});
The project ID (as string)
Optional array of attachment objects
Attachment type (optional, defaults to IMAGE)
attachments[].sourceMetadata
Source metadata (optional)
Returns: Object containing:
The newly created message ID
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>
);
}