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.

Try the Intrasheets demo

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 width and height the 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 html or body.

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 sub is the user.
  • beforeLoad opens a document to its owner, to invited users, or to everyone when it is public.
  • beforeChange refuses 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.io and /api to it. With a path prefix, set client.path and client.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.