Skip to main content
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.
The data layer behind every Velt feature: what Velt stores, the field names and types on each entity, the hook/event that surfaces each field at runtime, and the API that mutates it. Use this to decide whether a requirement is feasible with primitives/headless: a UI need is only achievable if the field it depends on actually exists, is exposed by a hook/event, and (if it must change) is writable via an API. Where a field or capability does not exist, this file says so plainly; see ## 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-only means there is no client API to set it (server-derived or auto-generated).
Cross-references: 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 by useCommentAnnotations(). 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’s comments 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. Comment records isEdited/editedAt but 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

A ReactionAnnotation 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 on CommentAnnotation.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 a location. 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 on CommentAnnotation.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’s CommentAnnotation.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 (type controls single vs multi-select). The selected items land on CommentAnnotation.customList as CustomAnnotationDropdownItem[].
Not the same as Comment.customList. That one is a per-reply autocomplete list (AutocompleteReplaceData[]), set via createCustomListDataOnComment. The annotation-level dropdown is CustomAnnotationDropdownItem[].

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 on CommentAnnotation.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 with useNotificationsData(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 with usePresenceData(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 with useCursorUsers()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 with useGetRecording() (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 with useAllActivities(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 with useLiveStateData / 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. Comment has isEdited and editedAt but 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-editedBy distinction. from is the author; there is no separate last-editor user object on Comment or CommentAnnotation (only statusUpdatedByUserId and resolvedByUserId exist, scoped to those specific actions).
  • agent, suggestion, involvedUserIds, mentionedUserIds, views/viewedBy are 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 Notification object directly. Change copy through message templates / the notification resolver, and behavior through tabConfig / 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, and immutable records reject REST mutation entirely.
  • context is set, not patched per-field client-side. It is provided wholesale (provider/addContext/prop) and hashed into contextId; there is no React hook to mutate one key of an existing thread’s context in place (server REST is the path for editing stored context).

Runtime-access map

Quick lookup: entity → the hook that reads it live → the event that fires on change.