Skip to main content
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’s save (or saveConfig.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, standalone transcription — are not included; they have no resolver and stay fully Velt-hosted.

Table of Contents

  1. Comments
  2. Reactions
  3. Recordings
  4. Notifications
  5. Activity
  6. Attachments
  7. Shared building blocks
  8. 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, attachment name/url (when the attachment resolver is active), targetTextRange.text, and any fieldsToRemove. Each comment whose PII is withheld gets isCommentResolverUsed = true; attachments get isAttachmentResolverUsed = true.
  • from / assignedTo / resolvedByUserId are copied-not-moved (sent to both).
  • A save to your provider only fires when the comment PII actually changed and the action maps to a ResolverActions value (COMMENT_ANNOTATION_ADD / COMMENT_ADD / COMMENT_UPDATE / COMMENT_DELETE, or a draft). Pure status / priority / assignment changes do not call save.
  • Truthy-gating: empty-string commentText etc. are not sent to your provider (and are not withheld from Velt either). additionalFields is the exception — it uses !== undefined, so 0 / "" / false are 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 icon is never sent to Velt (stripped on the frontend → your DB); everything else is kept and isReactionResolverUsed set true.
  • from is copied-not-moved. Per-element reactions[].from is reduced to { userId } (when user provider active) only inside Velt’s DB — it is not part of the Partial payload.
  • position’s value is never sent to Velt — written as null on 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 of attachment (written as null), and the urls inside attachments (Velt keeps only { attachmentId, name } stubs). from is reduced to { userId }; isRecorderResolverUsed set true.
  • recordingEditVersions per-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 / recordedTime are 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 the PartialNotification PII is never sent to Velt at all; it lives in your backend and is fetched on read, then merged into both the notification and its raw form, setting isNotificationResolverUsed = true.
  • The only write-side reduction is actionUser → { userId } (when user provider active).
  • isUnread, forYou, and the rendered displayHeadlineMessage are computed on the client (from notificationViews / 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 the ActivityRecord 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
  • entityData and entityTargetData objects 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, taggedUserContacts from both entityData and entityTargetData.
    • reaction — removes icon, from, metadata only 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, attachments only when both the activity resolver and the recorder resolver are active; otherwise only user objects are reduced to { userId }.
  • 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 / entityTargetData PII 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.fieldsToRemove are 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 and deleted from the Velt record. On read it’s restored by wholesale replacement.
  • This means if you list entityData or entityTargetData in fieldsToRemove, 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.
Universal
  • displayMessage is always recomputed on the client (from the template + values) and stored in neither DB.
  • actionUser and any user objects in changes and displayMessageTemplateData are reduced to { userId } whenever a user resolver 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 returned url (plus the structural fields above) is stored on the Attachment record.

6.B — Handed to your storage provider (upload payload) / returned

6.C — Attachment strip rules

  • No annotation-style Partial<X>. The JSON request is exactly { attachment: { attachmentId, name, mimeType }, metadata, event }; the File is 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). The metadata envelope is the one whose your-DB copy is reduced via getClientMetadata().

BaseMetadata

The metadata 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 / pageInfo dropped. folderId included only when truthy.

Location & Version

PageInfo

TargetElement

TargetTextRange

CursorPosition

CommentAnnotationViews

Reaction (per element)

The element shape of ReactionAnnotation.reactions[], kept in Velt’s DB.

Comment (per-thread comment)

The element shape of CommentAnnotation.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