Companion to the Self-Hosting Overview. That guide’s field-inventory section lists the field names split between Velt’s DB and your DB. This page adds Type, Example value, Description, and Notes for every field, covers both sides of the split, and expands every nested structural object into its own sub-table. Ground-truthed against the SDK models and the resolver strip logic, not just the guide.
How to read this
When a customer registers a data provider (Velt.setDataProviders({ … })), the split happens
on the frontend, before anything is written:
- Velt’s DB keeps the structural remainder — IDs, positions, locations/targets, statuses, timestamps, relationships, and flags.
- Your DB receives the
Partial<X>PII payload the SDK strips on the device and hands to your provider’ssave(orsaveConfig.url) — then merges back on read so the UI renders identically to a fully-Velt-hosted setup.
🔒 Privacy guarantee
Anything not stored in Velt’s DB never leaves the frontend device for Velt’s network — not in transit, not at rest. The PII is stripped on the device before any request to Velt is made and is sent only to your DB (or recomputed entirely on the client). Velt never receives it, transmits it, or stores it.
Note vocabulary
Features covered:comment,reaction,recorder,notification,activity,attachment(+ shared building blocks). Features without a self-hosting data-provider split —cursor,presence,huddle,selection,tag,area,arrow,rewriter, standalonetranscription— are not included; they have no resolver and stay fully Velt-hosted.
Table of Contents
- Comments
- Reactions
- Recordings
- Notifications
- Activity
- Attachments
- Shared building blocks
- Summary matrix & field counts
1. Comments (comment)
Model: CommentAnnotation. PII payload: PartialCommentAnnotation.
1.A — Stored in Velt’s DB (CommentAnnotation, structural remainder)
1.B — Stored in your DB (PartialCommentAnnotation)
1.C — Comment strip rules
- The only PII stripped on the frontend (and therefore never sent to Velt) is: per-comment
commentText/commentHtml, attachmentname/url(when theattachmentresolver is active),targetTextRange.text, and anyfieldsToRemove. Each comment whose PII is withheld getsisCommentResolverUsed = true; attachments getisAttachmentResolverUsed = true. from/assignedTo/resolvedByUserIdare copied-not-moved (sent to both).- A
saveto your provider only fires when the comment PII actually changed and the action maps to aResolverActionsvalue (COMMENT_ANNOTATION_ADD/COMMENT_ADD/COMMENT_UPDATE/COMMENT_DELETE, or a draft). Pure status / priority / assignment changes do not callsave. - Truthy-gating: empty-string
commentTextetc. are not sent to your provider (and are not withheld from Velt either).additionalFieldsis the exception — it uses!== undefined, so0/""/falseare copied. - Delete sends only
{ apiKey, documentId, organizationId, folderId? }.
2. Reactions (reaction)
Model: ReactionAnnotation. PII payload: PartialReactionAnnotation.
2.A — Stored in Velt’s DB (ReactionAnnotation)
2.B — Stored in your DB (PartialReactionAnnotation)
2.C — Reaction strip rules
- Only
iconis never sent to Velt (stripped on the frontend → your DB); everything else is kept andisReactionResolverUsedsettrue. fromis copied-not-moved. Per-elementreactions[].fromis reduced to{ userId }(whenuserprovider active) only inside Velt’s DB — it is not part of thePartialpayload.position’s value is never sent to Velt — written asnullon every write to Velt’s DB regardless of self-hosting.- An unchanged save (deep-compare vs. cache) skips stripping entirely (icon not re-processed, flag not set).
3. Recordings (recorder)
Model: RecorderAnnotation. PII payload: PartialRecorderAnnotation.
3.A — Stored in Velt’s DB (RecorderAnnotation)
3.B — Stored in your DB (PartialRecorderAnnotation)
3.C — Recorder strip rules
- Never sent to Velt:
transcription(entire object → your DB), the value ofattachment(written asnull), and the urls insideattachments(Velt keeps only{ attachmentId, name }stubs).fromis reduced to{ userId };isRecorderResolverUsedsettrue. recordingEditVersionsper-version PII is withheld the same way; non-PII per-version fields (recordedTime,waveformData,displayName,boundedTrimRanges,boundedScaleRanges) are sent to Velt.- Top-level
displayName/waveformData/recordedTimeare sent to Velt (not part of the your-DB payload). - Recording files stay on Velt unless you also set
recorder.storage(the bring-your-own storage scope). The recorder metadata resolver and the recording file storage are independent toggles.
4. Notifications (notification)
Model: Notification / NotificationRawData. PII payload: PartialNotification. Custom
notifications only (notificationSource === 'custom'); standard comment/recording notifications
resolve through their own feature providers. This is read-only enrichment — there is no
write-side strip and no save.
4.A — Stored in Velt’s DB (structural notification envelope)
4.B — Stored in your DB (PartialNotification)
4.C — Notification strip rules
- No write-side strip / no
save. For custom notifications thePartialNotificationPII is never sent to Velt at all; it lives in your backend and is fetched on read, then merged into both thenotificationand its raw form, settingisNotificationResolverUsed = true. - The only write-side reduction is
actionUser → { userId }(whenuserprovider active). isUnread,forYou, and the rendereddisplayHeadlineMessageare computed on the client (fromnotificationViews/notifyUsers*/ the templates) and stored in neither DB — omitted from the tables above.- Resolution order is notification → user → comment (notification PII fills userIds that the user resolver then enriches).
- Delete calls your provider with
{ notificationId, organizationId }.
5. Activity (activity)
Model: ActivityRecord. PII payload: PartialActivityRecord. Append-only — there is no
delete. Activity records can carry PII from several features at once.
5.A — Stored in Velt’s DB (ActivityRecord)
5.B — Stored in your DB (PartialActivityRecord)
5.C — Activity strip rules
The activity resolver runs a strip-on-write / resolve-on-read cycle: PII is pulled out of theActivityRecord before it ships to Velt and is merged back from your DB on read. How much is
removed depends on featureType.
Built-in featureType (comment / reaction / recorder) — partial strip
entityDataandentityTargetDataobjects are kept on the Velt record; only specific PII fields inside them are removed. Structural identifiers (annotation/comment/target IDs) are deliberately preserved so the read path can match resolved data back.- Per feature (matches the per-feature partial tables above):
- comment — removes
commentText,commentHtml,attachments(name/url),from,to,taggedUserContactsfrom bothentityDataandentityTargetData. - reaction — removes
icon,from,metadataonly when both the activity resolver and the reaction resolver are active; otherwise only user objects are reduced to{ userId }and the rest of the structure survives. - recorder — removes
from,transcription,attachment,attachmentsonly when both the activity resolver and the recorder resolver are active; otherwise only user objects are reduced to{ userId }.
- comment — removes
changes['commentText']is removed from the Velt record (→ your DB) only when the activity resolver is active; if only the comment resolver is registered it is preserved on the Velt side to avoid unrestorable loss.- Comment
entityData/entityTargetDataPII is handled by the comment resolver’s own store, not duplicated into the activity resolver payload. - Any top-level keys you additionally list in
config.fieldsToRemoveare moved wholesale to your DB on top of this automatic strip.
featureType === 'custom' — wholesale removal by config
- There is no feature-aware partial stripping for custom activities. The only fields removed are the
top-level keys you list in
fieldsToRemove: each listed key is moved wholesale to your DB anddeleted from the Velt record. On read it’s restored by wholesale replacement. - This means if you list
entityDataorentityTargetDatainfieldsToRemove, the entire field disappears from the Velt record (not field-by-field). If you don’t list them, they’re left untouched — there is no automatic stripping for custom.
displayMessageis always recomputed on the client (from the template + values) and stored in neither DB.actionUserand any user objects inchangesanddisplayMessageTemplateDataare reduced to{ userId }whenever auserresolver is active.- Append-only: no delete.
6. Attachments (attachment)
Stored model: Attachment — embedded on comments (comments[].attachments) and recordings
(attachments). Upload payload: ResolverAttachment / AttachmentResolverMetadata. There is no
Partial<X> strip and no get — attachments are binary files. When you register an attachment
(or recorder.storage) provider, Velt hands you the raw File; you return { url }.
6.A — Stored in Velt’s DB (Attachment record — kept, minus the binary bytes)
The binary bytes are never sent to Velt when you self-host storage; they go straight to your bucket and only the returnedurl(plus the structural fields above) is stored on theAttachmentrecord.
6.B — Handed to your storage provider (upload payload) / returned
6.C — Attachment strip rules
- No annotation-style
Partial<X>. The JSONrequestis exactly{ attachment: { attachmentId, name, mimeType }, metadata, event }; theFileis destructured out and sent as binary — straight to your storage, never to Velt. - Delete sends
{ attachmentId, metadata: { apiKey, documentId, organizationId, folderId? }, event }. - Comment attachments (
DataProviders.attachment) and recording files (DataProviders.recorder.storage) are independent storage scopes — omit either to keep that scope on Velt-managed storage.
7. Shared building blocks
These nested objects are embedded inside the feature records above and are kept verbatim in Velt’s DB (unless a sub-field is noted as never sent to Velt). Themetadata envelope is the one whose
your-DB copy is reduced via getClientMetadata().
BaseMetadata
Themetadata field present on every feature payload. Full version kept in Velt’s DB; client-facing
subset sent to your DB.
Client-facing transform (getClientMetadata):clientDocumentId → documentId,clientOrganizationId → organizationId;veltFolderId/parentVeltFolderId/pageInfodropped.folderIdincluded only when truthy.
Location & Version
PageInfo
TargetElement
TargetTextRange
CursorPosition
CommentAnnotationViews
Reaction (per element)
The element shape ofReactionAnnotation.reactions[], kept in Velt’s DB.
Comment (per-thread comment)
The element shape ofCommentAnnotation.comments[], kept in Velt’s DB with per-comment PII never sent to Velt.
Transcription (your-DB recorder payload)
Full object sent to your DB (never sent to Velt) when the recorder resolver is active.8. Summary matrix & field counts
Where does each field-class live?
Anything in the right-hand column with
— under “Velt’s DB” is never sent to Velt — it is
stripped on the frontend and goes only to your DB (or is recomputed on the client).
Field counts (persisted fields only)
Resolver flags (set in Velt’s DB; never sent from your side)
isCommentResolverUsed · isReactionResolverUsed · isRecorderResolverUsed ·
isNotificationResolverUsed · isActivityResolverUsed · isAttachmentResolverUsed
