Build an Excel-like Application
ULTIMATE
intrasheets is an Excel-like interface packaged as a library: ribbon, formula bar, sheet tabs, backstage, status bar and an AI assistant, on top of Jspreadsheet and its extensions. Connected to Jspreadsheet Server, it becomes a private collaborative spreadsheet application inside your product.
Architecture
browser your infrastructure
┌──────────────────────────┐ ┌───────────────────────────────┐
│ your page │ socket.io │ @jspreadsheet/server │
│ intrasheets (library) │───────────▶│ + server-api (REST) │──▶ your database
│ your login, your UI │ REST /api │ + server-agent (AI chat) │
└──────────────────────────┘───────────▶│ + your routes (login, list) │──▶ model provider
└───────────────────────────────┘
The library renders the interface and talks to the server. Your page owns the login, the routes and the layout; your server owns the documents, the identity checks and the storage.
The library
npm install intrasheets
import intrasheets from 'intrasheets';
import 'intrasheets/dist/style.css';
Or with a script tag: https://cdn.jsdelivr.net/npm/intrasheets/dist/index.min.js and .../dist/style.css. The script build is self-contained and exposes the global intrasheets; the ES build keeps the Jspreadsheet bundle and its few dependencies as packages.
intrasheets(options)
Configures the library. Call it once, before rendering.
| Option | Description |
|---|---|
license |
Your Jspreadsheet license (required) |
client.url |
The server address for the realtime connection |
client.token |
The user's token, sent with every request |
client.path |
The socket.io path, when the server sits under a path behind a proxy, for example s/ |
client.api |
The REST base when it differs from client.url, for example https://host/s |
client.* |
Any other client extension option, such as onerror |
google |
{ clientId, apiKey } for the Google Sheets import |
onopen(guid) |
Called when the user creates or picks a document; the default navigates to /{guid} |
intrasheets.load(element, options)
Renders one document. Returns a promise of the worksheets, as jspreadsheet() returns them, and rejects when the document does not exist or the token cannot open it.
| Option | Description |
|---|---|
guid |
The document id |
width, height |
The size of the container: a number is pixels, a string any CSS length |
agent |
true shows the AI assistant; requires the AI agent on the server. Default: off |
focus |
false opens the document without focusing the grid, so the host page does not scroll to it. Default: true |
on<event> |
Any Jspreadsheet event, called after the shell's own handler with the same arguments |
intrasheets.list(element, options)
Renders the user's document list with a "Blank workbook" card and XLSX upload. Takes width and height.
Inside a page
- Without
widthandheightthe shell fills the container the page gives it. - Menus and dialogs float over the page; the backstage (File) stays inside the container.
- The Material Symbols icon font is added to the page when it is not already there.
- The library sets no global styles on
htmlorbody.
What the server provides
Besides the realtime connection, the library calls these routes on the REST base:
| Route | Used for | Provided by |
|---|---|---|
GET /api/<guid> |
Checking that the document exists and the token can open it | server-api |
POST /api/create |
New workbooks and XLSX uploads | server-api with your create handler |
DELETE /api/<guid> |
Deleting from the document list | server-api with your destroy handler |
POST /api/<guid>/thumbnail |
The picture of the document in the lists | server-api with setThumbnail / getThumbnail |
GET /api/list |
The user's documents | Your route (below) |
/api/<guid>/prompt |
The AI assistant | server-agent |
The document list is an application concern, so you add it with the extension API:
const routes = function(actions) {
actions.list = {
get: async function(Server) {
if (! this.authQuery.token) {
this.code = 401;
this.result = { error: 'authentication required' };
return;
}
this.result = await Server.options.list(this.authQuery) || [];
},
};
};
const application = function() {};
application.license = function(license, server) {
server.options.extensions.api.setAction(routes);
};
server({
// ...
list: async (auth) => adapter.list(auth),
extensions: { api, application, agent },
});
Authentication
The token you pass in client.token reaches every server hook as auth.token. A typical setup:
- Your login issues a JWT whose
subis the user. beforeLoadopens a document to its owner, to invited users, or to everyone when it is public.beforeChangerefuses read-only users.- The adapter stores the creator as the owner.
const getUser = (auth) => {
try { return jwt.verify(auth.token, process.env.JWT_SECRET); } catch (e) { return null; }
};
server({
beforeConnect: async () => true, // runs on every REST call: gate documents below
beforeLoad: async (guid, auth) => await level(guid, getUser(auth)) !== false,
beforeChange: async (guid, changes, auth) => await level(guid, getUser(auth)) >= 1,
// ...
});
beforeConnect runs on every REST request, including your login route, so keep it open and put the document rules in beforeLoad and beforeChange. See Authentication and Sharing & privacy.
The AI assistant
With agent: true, the shell shows an assistant button that opens a chat calling the AI agent. On the server, install @jspreadsheet/server-agent, set CLAUDE_API_KEY, and add agent to the extensions. Before opening it to users, set budgets and usage tracking: when your allow hook refuses a prompt, the chat shows your message.
The reference application
github.com/intrasheets/demo is a complete application built only from published packages: a React front end, the server and MongoDB in Docker.
git clone https://github.com/intrasheets/demo.git intrasheets-demo
cd intrasheets-demo
cp .env.example .env # your Jspreadsheet license
docker compose up
| Path | What it shows |
|---|---|
web/src/App.jsx |
Configuring the library once per session and routing between the list and a document |
web/src/Spreadsheet.jsx |
intrasheets.load with a size and an event handler |
web/src/Login.jsx, web/src/api.js |
A sign in that stores the token |
server/index.js |
The server: adapter, authentication hooks, extensions |
server/extension.js |
The application routes: login and the document list |
Its README has step-by-step instructions for people and for AI coding agents, with the commands to verify each step.
Production notes
- Serve your page as static files and the server on its own origin, or proxy
/socket.ioand/apito it. With a path prefix, setclient.pathandclient.api. See Nginx. - Replace the demo login with your identity provider; keep issuing tokens whose subject is the user.
- Configure images to S3 through the API extension so pictures do not live inside documents. See Images & media.
- Pin the library version in production and upgrade deliberately.