How to Add Commenting To Your SpreadJS Spreadsheet (a build-vs-buy breakdown) August 2026
SpreadJS commenting: build vs. buy (August 2026). See what 6 subsystems cost to build and how Velt covers them in 3 days or less.

Adding SpreadJS collaboration features to a financial model sounds like a contained project until you start listing what users actually expect: threaded comments anchored to the right cell after a sort or filter, @mentions that route to real inboxes, approval states, an immutable audit trail. Building all of that from scratch runs 4 to 6 weeks of baseline engineering before your first comment ships. This post lays out what each subsystem costs and when it makes more sense to buy.
TLDR:
- Building comment infrastructure for SpreadJS from scratch takes 4 to 6 weeks before your first comment ships, across 6 distinct subsystems.
- SpreadJS virtualizes its grid, so coordinate-based comment overlays break on scroll. You need data ID anchoring, not pixel positions.
- Building a compliant audit trail alone takes 3 to 6 months, covering write-once storage, SHA-256 hash chaining, and tiered retention.
- Build in-house only if you need full ownership of the real-time layer or your collaboration model won't map to a thread-and-resolution pattern.
- Velt is review and approval infrastructure for SpreadJS: it binds comment threads to
locationIdvalues built from cell references, ships comments, approval workflows, presence, notifications, audit trails, and recording, and integrates in 3 days or less (tracked internally).
Why SpreadJS Spreadsheet Teams Weigh Building vs Buying Commenting
SpreadJS teams need review and approval infrastructure to close the gap between co-editing and sign-off. SpreadJS is a complete enterprise JavaScript spreadsheet solution used for financial reports, dashboards, and budgeting and forecasting models. If you're building on top of it, you're probably in FP&A, financial reporting, or budgeting territory, where reviewers are already using Slack for budget sign-offs to approve line items, approval decisions are scattered across threads with no version context, and nobody has a clean audit trail of who signed off on what. That is the review bottleneck that review and approval infrastructure is designed to close.
Velt for SpreadJS is that infrastructure: not a UI widget you drop in, but a system that ships contextual comments, approval workflows, presence, notifications, audit trails, and recording as a single integrated unit.
The build vs buy collaboration software question comes up here because SpreadJS v19 added a real-time collaboration server add-on for co-editing (using Operational Transformation) and native threaded cell comments with @mentions and resolve states. So your team gets co-editing and basic threaded comments. What you do not get is approval workflows, audit trails, or a notifications pipeline. And the native threaded comments bind to row and column indices, not stable data IDs, which means they can drift when SpreadJS virtualizes rows out of view during scrolling or filtering. Building the rest yourself looks scoped until you list the actual subsystems: thread state, stable cell anchoring, user identity, notifications, and approval routing. Buying means assessing whether review and approval infrastructure fits SpreadJS's virtualization model without fighting it.
Both paths are real options. The rest of this post lays out what each one actually costs.
What "Commenting" Actually Means on a SpreadJS Spreadsheet
Commenting sounds like a simple feature until you map what it actually requires in a production spreadsheet context. A textarea on a cell is table stakes. What users expect when you add review infrastructure to your spreadsheet product, after working in Google Sheets or tools like Datarails, is substantially more: threaded discussions tied to specific cells, @mentions with notifications, resolution tracking, and feedback that survives model updates.
Thread persistence and cell binding
SpreadJS virtualizes its grid for performance. Rows and columns are painted on demand, which means there's no stable DOM element to anchor a comment to. SpreadJS native threaded comments use row and column indices to identify cells (threadedCommentManager.add(row, col)), so they share this same fragility: scroll the sheet far enough, apply a filter, or sort the data, and the thread can no longer find its cell without custom reconciliation logic. Coordinate-based overlays drift the same way. The correct approach is binding each comment thread to a stable data ID, the row key plus column key, instead of a positional reference. Velt handles this natively: threads bind to data IDs instead of coordinates, so they survive layout changes without any custom positioning logic on your end.
Version-aware anchoring in financial models
A budget model typically runs through 6 to 10 revision cycles before sign-off. Without version-aware anchoring, a comment left on an assumption in cycle 3 becomes detached noise by cycle 7 because the row moved, the label changed, or the section was restructured entirely.
That's the failure mode that pushes teams off in-app comments and back onto Slack threads, where feedback at least stays readable even if it loses all cell context.
Velt's anchoring uses data IDs instead of coordinates, so threads stay attached to the correct cell identifier as the model evolves. The comment from cycle 3 is still readable, still resolved or open, and still attached to the right line item in cycle 8.
The Hidden Engineering Cost of Building It Yourself for a SpreadJS Spreadsheet
Teams building review infrastructure from scratch spend 4 to 6 weeks on baseline infrastructure before writing a single line of business logic. For a SpreadJS spreadsheet product, that baseline covers six distinct subsystems.
Here's what that actually means in practice: each subsystem below represents a separate engineering workstream, a separate failure mode, and a separate ongoing maintenance obligation.
Subsystems required before the first comment ships
- Real-time sync layer: a WebSocket server or third-party messaging service with conflict resolution and reconnection logic, scoped to SpreadJS's cell and range model.
- Cell-level thread storage: a schema that attaches threaded comments to row and column identifiers and survives model version changes without orphaning threads.
- @mentions and notification pipeline: user lookup, in-app delivery, email fallback, and read-state tracking across every comment author and reviewer.
- Permission model: controls over who can comment on which sheets or ranges, accounting for finance, leadership, and external auditor access tiers.
- Resolution and approval state: discrete states (open, in review, resolved, approved) with timestamp and user attribution on every transition.
- Financial software audit trail: a write-once log of every comment, edit, resolution, and approval action with user attribution for SOX compliance requirements and SEC contexts.
The ongoing maintenance burden
None of these are one-time builds. WebSocket infrastructure needs scaling, monitoring, and incident response. Notification pipelines need deliverability maintenance. Permission models need updates as org structures change. Building compliant audit trail infrastructure alone typically takes 3 to 6 months, covering write-once storage, SHA-256 hash chaining per event, tiered retention across hot and cold storage, and indexed query layers. Velt ships all of it as pre-built infrastructure.
Every sprint spent here is a sprint not spent on formula engine extensions, pivot enhancements, or data connectors. Those are the features that set a financial planning product apart. Comment infrastructure is not.
How Velt's Commenting Works on a SpreadJS Spreadsheet
Velt's review and approval infrastructure binds comment threads to locationId values constructed from the cell reference itself, not from pixel position. Coordinate-based overlays break silently. Velt's review and approval infrastructure sidesteps this entirely by binding comment threads to locationId values constructed from the cell reference itself, not from pixel position.
Velt location binding in a virtualized grid
The integration pattern is straightforward: listen to SpreadJS cell click events, then call client.setLocation() with a stable ID built from the sheet name, row index, and column index. Every comment the user posts gets anchored to that cell's thread, and it re-attaches correctly when the cell scrolls back into view or the model updates.
Complete integration example
// App.jsx — SpreadJS + Velt integration (React)
// Install: npm install @mescius/spread-sheets @mescius/spread-sheets-react @veltdev/react @veltdev/client
import { useEffect, useRef } from 'react';
import { SpreadSheets, Worksheet } from '@mescius/spread-sheets-react';
import '@mescius/spread-sheets/styles/gc.spread.sheets.excel2013white.css';
import {
VeltProvider,
VeltComments,
VeltCommentsSidebar,
useVeltClient,
} from '@veltdev/react';
function SpreadJSWithComments() {
const { client } = useVeltClient();
const spreadRef = useRef(null);
useEffect(() => {
if (!client) return;
// Identify the current user — replace with your own auth system
client.identify({
userId: 'user-001',
name: 'Finance Reviewer',
email: 'reviewer@example.com',
});
// Set the document context — one documentId per spreadsheet model
client.setDocument({
documentId: 'budget-model-q1-2026',
documentName: 'Q1 2026 Budget Model',
});
}, [client]);
function handleWorkbookInit(spread) {
spreadRef.current = spread;
const sheet = spread.getActiveSheet();
// Seed sample financial data
sheet.setValue(0, 0, 'Line Item');
sheet.setValue(0, 1, 'Q1 Budget');
sheet.setValue(0, 2, 'Q1 Actual');
sheet.setValue(1, 0, 'Revenue');
sheet.setValue(1, 1, 500000);
sheet.setValue(1, 2, 480000);
sheet.setValue(2, 0, 'COGS');
sheet.setValue(2, 1, 150000);
sheet.setValue(2, 2, 145000);
sheet.setValue(3, 0, 'Gross Margin');
sheet.setValue(3, 1, 350000);
sheet.setValue(3, 2, 335000);
// Bind Velt location to the clicked cell
// Comments will anchor to the cell ID, not pixel coordinates
sheet.bind(
GC.Spread.Sheets.Events.CellClick,
function (sender, args) {
if (!client) return;
const sheetName = spread.getActiveSheet().name();
const cellId = `${sheetName}-r${args.row}-c${args.col}`;
client.setLocation({
locationId: cellId,
locationName: `${sheetName} - Row ${args.row + 1}, Col ${args.col + 1}`,
});
}
);
}
return (
<div style={{ display: 'flex', height: '100vh' }}>
<div style={{ flex: 1, position: 'relative' }}>
<SpreadSheets workbookInitialized={handleWorkbookInit}>
<Worksheet />
</SpreadSheets>
{/* Displays comment pins anchored to the active cell location */}
<VeltComments />
</div>
{/* Sidebar shows all threads across the document */}
<VeltCommentsSidebar />
</div>
);
}
export default function App() {
return (
<VeltProvider apiKey="YOUR_API_KEY">
<SpreadJSWithComments />
</VeltProvider>
);
}
What Velt handles automatically after this setup
Once this is in place, Velt's review and approval infrastructure manages the following without any custom engineering on your end:
- Real-time thread sync across all active sessions, so reviewers see each other's comments the moment they're posted.
- @mention resolution and in-app notifications that route feedback to the right person without leaving the spreadsheet.
- Comment visibility controls scoped to public, org-private, or named reviewers, so sensitive financial data stays in front of the right eyes.
- A full, immutable audit trail of every comment, edit, and resolution, with timestamps and user attribution attached to each event.
From Commenting to Review and Approval: What Teams Actually Need
Commenting is where the request starts, not where it ends. Once SpreadJS users can leave threaded feedback on cells, finance and operations teams immediately ask for the next layer: formal sign-off, assignment routing, and a timestamped record of who approved which version of the model. That is the approval layer of Velt's review and approval infrastructure, and it builds directly on the same cell-level anchoring.
Assignment and per-cell approval queues
Velt's approval workflow SDK builds directly on the cell-level anchoring from the previous section. A finance lead can assign a specific thread to the CFO for sign-off. The CFO sees an "Assigned to me" filter in the sidebar, resolves the thread, and that action is written to the audit trail automatically. No separate approval tool required.
Audit trail as a native output
The audit trail Velt generates captures every comment, approval state change, and resolution with timestamps and user attribution. It is written synchronously, before the API response returns, so there is no compliance gap between when an action occurs and when the record exists. For SpreadJS products in FP&A or financial reporting contexts, that record is the evidentiary proof that humans reviewed specific model versions. This is the 'one system, not two' architecture that defines Velt's review and approval infrastructure: the review workflow and the audit trail are the same system, not separate tools stitched together.
Stensul cut email review cycles from 8 days to 3 using Velt's review and approval infrastructure. The underlying system is the same one a SpreadJS financial tool needs: contextual comments anchored to specific artifacts, formal approval state transitions tied to exact versions, and an automatically generated, immutable audit trail. The sequence of review steps and the demand for a durable approval record are the same regardless of what is being reviewed.
Build vs. Buy: A Direct Comparison for SpreadJS Spreadsheet Teams
| Capability | Build in-house | Velt |
|---|---|---|
| Time to production | 4 to 6 weeks for baseline infrastructure | Teams ship in 3 days or less (tracked internally) |
| Ongoing maintenance | Full ownership: WebSocket infra, schema migrations, notification deliverability | Maintained by Velt; teams receive updates without re-engineering |
| Cell-level thread anchoring | Custom row/column ID system required; virtualised DOM adds complexity | Native via setLocation() with data IDs; no coordinate math |
| Real-time sync | Must build or source separately (WebSocket server, conflict resolution) | Included via Yjs/CRDT infrastructure |
| Audit trail | 3 to 6 months to build compliant write-once storage with hash chaining | Generated automatically for every comment, approval, and resolution action |
| Approval workflows | Separate state machine, routing logic, and UI components required | Included; configurable assign-to UI, resolution tracking, Declarative Approval Engine |
| Self-hosting | Full control; team owns infra and compliance posture | Supported via DataProviders for comments, annotations, recordings, and activity logs |
Building in-house makes sense when your team needs complete ownership of the real-time connection layer, your review model is highly bespoke (a novel multi-user formula authoring experience that does not map to a thread-and-resolution pattern, for instance), or you have dedicated infrastructure capacity and a multi-quarter runway. One signal worth tracking: Liveblocks added cell comment pins and multiplayer editing for Handsontable grids in May 2026, signaling that purpose-built grid collaboration is now a space multiple vendors are entering. For SpreadJS teams whose core differentiation lives in the calculation engine and data connectors, spending that runway on review and approval infrastructure is the trade-off worth weighing carefully.
Final Thoughts on SpreadJS Review and Approval Infrastructure
Most SpreadJS teams underestimate the comment layer until they start building it. Stable cell anchoring in a virtualized grid, a write-once audit trail, and approval routing are not small additions. They are the kind of work that quietly consumes quarters. If your product lives in FP&A or financial reporting, that is time better spent on the features your users actually chose you for. Book a demo to see how Velt fits your setup.
FAQ
How does Velt handle comment anchoring in SpreadJS's virtualized grid?
Velt binds comment threads to locationId values built from the cell reference (sheet name, row index, column index), not pixel coordinates. When SpreadJS virtualizes rows out of view during scrolling, filtering, or resizing, threads re-attach to the correct cell identifiers when those cells come back into view, with no custom positioning logic required on your end.
SpreadJS collaboration: build the comment layer yourself or use Velt?
Building from scratch means owning six subsystems before the first comment ships: real-time sync, cell-level thread storage, @mention notifications, permission controls, approval state tracking, and audit trail infrastructure. That baseline runs 4 to 6 weeks of engineering time. Teams using Velt ship a working comment layer in 3 days or less (tracked internally), because Velt handles all six subsystems as pre-built infrastructure.
What is version-aware comment anchoring in a SpreadJS financial model?
Version-aware anchoring means comment threads stay attached to the correct cell identifier as the model evolves across revision cycles, instead of becoming detached when rows move, labels change, or sections restructure. Velt achieves this by binding threads to data IDs instead of coordinates, so a comment left on an assumption in cycle 3 is still readable, still open or resolved, and still attached to the right line item in cycle 8.
Can I add approval workflows on top of SpreadJS commenting without building a separate system?
Yes. Velt's approval workflows build directly on the same cell-level anchoring used for comments. A finance lead can assign a specific thread to a named reviewer, that reviewer sees an "Assigned to me" filter in the sidebar, and every state transition (open, in review, approved, rejected) writes to the audit trail automatically. The Declarative Approval Engine also supports multi-step pipelines defined via REST API for more formal sign-off chains, with no separate approval or logging system required.
How does Velt's MAC pricing compare to MAU or MAR models for a SpreadJS product with many read-only users?
For financial reporting tools where most users are read-only viewers, MAC pricing is typically a fraction of total monthly active users, making it more cost-predictable than MAU-based billing or Liveblocks' MAR model, which scales with document count and not user activity. Users who open the spreadsheet but never comment, resolve a thread, or trigger an approval action are not billed. For financial reporting tools where most users are read-only viewers, MAC pricing is typically a fraction of total monthly active users, making it more cost-predictable than MAU-based billing or Liveblocks' MAR model, which scales with document count and not user activity.