Skip to main content
Source of truth: exhaustive for what it covers. Generated from Velt data-model metadata.
  • If a name isn’t on this page, it doesn’t exist: don’t use it (a missing identifier resolves to undefined).
  • Never invent or guess a name; use only identifiers that appear verbatim here or that you verified against ground truth and added here.
  • Scope: exhaustive for the comments + notifications + core data layer (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 out-of-scope 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 customer-useful field:
  • 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. The guide tells customizers to use the resolver; this is what it 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 a React mutation hook in this guide).

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.