This page focuses on what you need while customizing UI. The complete canonical reference is Data models.
This list is generated from the Velt SDK and is complete for what it covers: a name that isn’t listed here doesn’t exist; don’t guess names. The comments + notifications + core data layer is covered exhaustively (every documented field, the hook/event that surfaces it, and the API that mutates it); other-feature entities (recorder, presence, cursor, CRDT, suggestions) are summarized, and internals (deep anchor/xpath structures, transcription, full document-user shape) are documented as absences rather than fully enumerated.
## Absences & limits.
How to read each table:
- Field: the property name as it appears on the object you receive.
- Type: its shape.
- Meaning: what it holds.
- Exposed by: the read hook (or event payload) that carries it live.
- Writable via: the API that changes it.
read-onlymeans there is no client API to set it (server-derived or auto-generated).
Hooks for hook signatures, Events for event names/payloads, APIs for imperative element methods, Component config for custom-data prop shapes (customStatus/customPriority/customCategory/customReactions).
The two foundational entities are CommentAnnotation (a thread: one pin, with a list of comments) and Comment (a single reply inside that thread). Most other entities hang off these.
CommentAnnotation (thread)
One comment pin / thread. The root object returned byuseCommentAnnotations(). Its comments array holds the individual replies.
MultiThreadCommentAnnotation
The grouping pin that holds multiple comment threads at one anchor, used when multi-thread mode is on (enableMultiThreadMode()). Each child CommentAnnotation points back to it via CommentAnnotation.multiThreadAnnotationId. It is not a thread itself: it is a container whose commentAnnotationsMap holds the child threads.
All fields are read-only from the SDK (set at placement / managed); children are mutated through the normal comment APIs on each
CommentAnnotation.
Comment (reply)
A single message inside a thread’scomments array. Not fetched on its own as a reactive stream: read it from the parent CommentAnnotation.comments, or imperatively via useGetComment() (await getComment({annotationId}) → Comment[]).
No per-edit author identity.CommentrecordsisEdited/editedAtbut not who edited it: see Absences.
User / UserContact
User is the identified user (yourself, or any from/assignedTo/to actor on a thread). UserContact is a lighter shape used for mention/assignee lists.
User fields: userId (your id), name, email, photoUrl, color, textColor (avatar colors), initial, plan, groupId, organizationId, type, isAdmin, isReadOnly, isAnonymous, isGuest, userSnippylyId (internal id used as the key in views), contacts (UserContact[]).
UserContact fields: userId, name, email (required), photoUrl, groupId, visibility ('group' \| 'private'), color, textColor, initial.
SelectedUserContact wraps a UserContact with isOrganizationContact, isDocumentContact, documentAccessType. The assignUser API takes a UserContact as assignedTo. All user identity originates from identify(); the SDK adds color/textColor/userSnippylyId/initial.
Reaction / ReactionAnnotation
AReactionAnnotation is the container pinned to a comment (or element); it holds a reactions array of individual Reactions.
ReactionAnnotation fields: annotationId, commentAnnotationId (the comment it’s attached to), reactions (Reaction[]), from (User), targetElement/targetElementId, position (CursorPosition), location/locationId, type (string), annotationIndex (number, 1-based), pageInfo (PageInfo), icon/iconUrl/iconEmoji, lastUpdated, metadata, involvedUserIds (server-derived), and context (typed Context: { access: {[key]: string|number}, accessFields?: string[] }: not a free-form any bag, unlike CommentAnnotation.context).
Reaction fields: variant (emoji variant), from (User), lastUpdated.
The emoji set is defined by the
customReactions prop (ReactionMap: { [reactionId]: { url?, svg?, emoji? } }). Reaction add/delete/toggle requests reference a reactionId and optional customReaction (ReactionItem).
Attachment
A file on a comment.
Mutate via
useAddAttachment (addAttachment({annotationId,files})), useDeleteAttachment, useGetAttachment. allowedFileTypes prop whitelists types.
CustomStatus / CustomPriority / CustomCategory
Definition objects you supply via props/APIs to populate the status/priority/category controls. They share a base{id, color, name} (CustomFilter).
The
type on CustomStatus drives lifecycle semantics (terminal ≈ resolved/done). See Component config for the exact prop shapes and examples. Right-tool note: statuses/priorities are a small fixed workflow vocabulary; for arbitrary per-thread labels prefer customCategory or the thread context bag rather than overloading status.
Views
Read-receipt / view-tracking data. Surfaced onCommentAnnotation.views and through the views feature.
CommentAnnotationViews = { views: { [userSnippylyId]: { timestamp } }, comments: { [commentId]: { views: {...} } }, metadata? }. So per-thread and per-comment “seen by whom and when” is keyed by userSnippylyId (the internal user id on User). Views also has documentViews and locationViews (aggregated by user and by date).
Location
A sub-document scope. Threads, reactions, presence and cursors all carry alocation.
Location fields: id, locationName, version ({id, name}), plus arbitrary custom keys ([key: string]: any): see storage patterns. Set via useSetLocation/useSetLocations/useSetRootLocation (or the location/locationId props on comment surfaces). Filterable in the sidebar via the location filter. The SDK derives a numeric locationId hash from the Location object.
PageInfo
Page metadata captured when an annotation is created, plus per-feature deep-link URLs. Surfaced onCommentAnnotation.pageInfo and ReactionAnnotation.pageInfo (read); written via useSetPageInfo() ({ setPageInfo }) / cleared via useClearPageInfo().
All fields are optional. Read-only except via
useSetPageInfo (you supply the override; the SDK auto-fills screenWidth/deviceInfo).
CustomAnnotationDropdownData / CustomAnnotationDropdownItem
The shape behind a thread’sCommentAnnotation.customList: a custom dropdown attached to an annotation via createCustomListDataOnAnnotation(data) (see APIs).
CustomAnnotationDropdownItem={ id: string, label: string }: a single selectable option.CustomAnnotationDropdownData={ type: 'multi' | 'single', placeholder?: string, data: CustomAnnotationDropdownItem[] }: the dropdown definition (typecontrols single vs multi-select). The selected items land onCommentAnnotation.customListasCustomAnnotationDropdownItem[].
Not the same asComment.customList. That one is a per-reply autocomplete list (AutocompleteReplaceData[]), set viacreateCustomListDataOnComment. The annotation-level dropdown isCustomAnnotationDropdownItem[].
TargetTextRange
The anchored text selection on a text/inline comment (CommentAnnotation.targetTextRange). Most fields are xpath internals (commonAncestorContainer, commonAncestorContainerFXpath, commonAncestorContainerCFXpath, commonAncestorContainerAnchor, occurrence) used to re-resolve the selection in the DOM. The one field you will typically use:
text(string): the actual selected text the comment was anchored to. Read this to show “commented on: …”. The remaining fields are anchor-resolution internals; treat them as opaque.
AgentData
Identity + output of an AI agent that authored a thread/reply. Present onCommentAnnotation.agent and Comment.agent when sourceType === 'agent'.
Read-only from the SDK: written upstream by the agent; there is no client API to author or edit
agent. The agent-suggestion UI renders only when agent is present, type === 'suggestion', and the viewer is an admin.
Notification
A single notification row. Read withuseNotificationsData(query?) → Notification[].
NotificationRawData (the notification resolver input)
When you customize notification copy through the notification resolver (or templates), the SDK builds a raw notification object first:NotificationRawData: which is the shape the resolver round-trip is formed from. This is what your resolver receives. Key fields you can read/branch on:
This is the raw, server-shaped input: distinct from the
Notification row above (the resolved object you read in the UI via useNotificationsData).
Counts via useUnreadNotificationsCount() → { forYou, all }. Settings via useNotificationSettings() (settings, setSettings, setSettingsInitialConfig). Panel tabs configured by tabConfig (forYou/documents/all/people). Notification content itself is not mutated by a client write API: customize copy through templates/resolvers, behavior through props/settings.
PresenceUser
A user currently present on the document. Read withusePresenceData(query?) → GetPresenceDataResponse (or the deprecated usePresenceUsers() → PresenceUser[]).
All presence fields are read-only (server-/SDK-managed); identity comes from
identify(). Change events: usePresenceEventCallback('multipleUsersOnline' \| 'userStateChange'). Inactivity thresholds set via VeltPresence props (inactivityTime, offlineInactivityTime).
CursorUser
A user’s live cursor. Read withuseCursorUsers() → CursorUser[].
All read-only (auto-generated live). Change event:
onCursorUserChange on VeltCursor. Avatars on cursors via the avatarMode prop.
RecordedData
A finished recording attached to a comment. Read withuseGetRecording() (imperative) or from Comment.recorders[]; also delivered by useRecorderAddHandler().
Mutate via
useDeleteRecording / useGetRecording. Recorder change events via useRecorderEventCallback(...) and the recorder events in Events (recordingDone, transcriptionDone, …). Recording media URLs are server-set (read-only).
ActivityRecord
A single activity-feed event. Read withuseAllActivities(config?) → ActivityRecord[].
Scope/filter via
ActivitySubscribeConfig (documentIds, featureTypes, actionTypes, userIds, maxDays, …). Reading is reactive; activities are written by the features themselves (custom activities are created server-side / via REST, not through a React mutation hook).
LiveStateData
Arbitrary application state synced across collaborators. Read/write withuseLiveStateData / useSetLiveStateData (and useUiState for shared UI state).
This is the general-purpose escape hatch for syncing custom data that has no home on any other entity. Single-editor-mode state (
SingleEditorLiveStateData: editor, requestEditorAccess) rides the same feature; access events are in Events under Live State Sync.
Custom-data storage patterns
Four places hold arbitrary application data. Pick by lifetime and scope:
For larger/looser app state with no entity home, use
LiveStateData (above). The context bag is the most common choice for “attach my record id to this thread.”
Absences & limits
State these as confident “not supported” verdicts:- Per-edit editor identity is not stored.
CommenthasisEditedandeditedAtbut no field for who edited a reply. “Who edited this reply” cannot be shown client-side from the model. Only the original author (Comment.from) is available. - No per-reply
createdBy-vs-editedBydistinction.fromis the author; there is no separate last-editor user object onCommentorCommentAnnotation(onlystatusUpdatedByUserIdandresolvedByUserIdexist, scoped to those specific actions). agent,suggestion,involvedUserIds,mentionedUserIds,views/viewedByare read-only. They are written upstream/server-side; no client API mutates them. UI that displays them is feasible; UI that edits them is not (for suggestions, use the Suggestion element API, not a field write).- Notification content has no client write API. You cannot mutate a
Notificationobject directly. Change copy through message templates / the notification resolver, and behavior throughtabConfig/ settings, not by writing the entity. This forces a template/resolver (server-side) path for custom notification text. - Presence/cursor fields are fully managed.
onlineStatus, positions, colors, timestamps are auto-generated; you influence them only through identity (identify) and config props (inactivityTime,avatarMode, …), never by writing the field. - Activity records are not mutated from React. Reading is reactive (
useAllActivities); creating/updating custom activities and deleting records is a server-side/REST path, andimmutablerecords reject REST mutation entirely. contextis set, not patched per-field client-side. It is provided wholesale (provider/addContext/prop) and hashed intocontextId; there is no React hook to mutate one key of an existing thread’scontextin place (server REST is the path for editing stored context).

