Spire.OfficeJs
Spire.OfficeJs

Spire.OfficeJs (3)

With the rapid advancement of enterprise digital transformation, front-end document processing has emerged as a core requirement for modern web applications. As a robust enterprise-level framework, Angular is extensively adopted in complex scenarios—including OA systems, document platforms, and education management systems—thanks to its strong typing, component-based architecture, and efficient state management.

This article demonstrates how to integrate Spire.OfficeJS into an Angular application to implement core functions such as local file uploads, online document editing, format conversion, and file downloads.

Content Overview


What is Spire.OfficeJS

Spire.OfficeJS is an enterprise-level online document editing solution comprising four core modules: Spire.WordJS, Spire.ExcelJS, Spire.PresentationJS, and Spire.PDFJS. It enables browser-based preview, editing, annotation, and format conversion for Word, Excel, PPT, and PDF files—all without requiring local Office software or any other plug‑ins. Additionally, it delivers essential enterprise capabilities including cloud‑native architecture, cross‑platform compatibility, and high‑level security.

Key Features:

  • Modular Design: Each of the four core modules handles a distinct document type, enabling granular Word editing, Excel data calculation and charting, visual PowerPoint design, and rapid PDF preview—together covering the full spectrum of document processing.
  • Pure Front-End Rendering: Powered by a custom WebAssembly engine, it eliminates back-end document processing, reducing server load and boosting response speeds.
  • Multi-Format Compatibility: Supports mainstream file formats such as DOCX, XLSX, PPTX, PDF, WPS, and cross-format export (e.g., Word to PDF).
  • Enterprise-Grade Security: Supports encrypted storage of static files to protect sensitive document data.
  • Easy to Integrate: Seamlessly compatible with mainstream front-end frameworks (Angular, Vue, React) and enables rapid integration via minimal configuration.

Prerequisites & Angular Project Setup

Ensure your development environment meets the following requirements before integration:

1. Install Node.js and npm

  • Download: Visit the official Node.js download page and install the appropriate version for your OS.
  • Verify Installation: Run the following commands in CMD to confirm success (version numbers will appear if installed correctly):
node -v
npm -v

Verify Node.js and npm versions via command line

2. Install Angular CLI

Angular CLI streamlines project creation and management. Install it globally via npm:

npm install -g @angular/cli
  • After installation, run the following command to check the Angular CLI version and confirm successful installation:
ng version

Verify the installed Angular CLI version information via command line

3. Initialize an Angular Project

  • Open the CMD and navigate to the target directory (example: F:\angular).
  • Create a new Angular project with the following command (skip Git initialization for simplicity):
ng new spireOfficeJS --skip-git
  • Configure the project wizard as follows (for compatibility):
    • Would you like to create a "zoneless" app without zone.js? No
    • Stylesheet format: CSS
    • Would you like to enable SSR/SSG? No

Configuration options when creating a project with Angular CLI

  • Wait for dependencies to finish installing. After the project is successfully created, the directory structure will appear as shown:

Folder structure of the newly created Angular project

4. Validate Project Initialization

Open the project with VS Code, and run the following command in the terminal to start the development server:

npm run start

Visit http://localhost:4200/ in your browser. If the Angular default welcome page (with "Hello, spireOfficeJS") appears, initialization is successful.

Angular default welcome page displayed in the browser


Integrating Spire.OfficeJS Online Document Editor

1. Deploy Spire.OfficeJS Static Resources

  • Download the Spire.OfficeJS product package and extract it.
  • Create a spire.cloud folder in the project's public directory (path: public/spire.cloud).
  • Copy the entire web folder from the extracted Spire.OfficeJS product package to the public/spire.cloud/ directory.
  • Confirm the core script SpireCloudEditor.js is located at: public/spire.cloud/web/editors/spireapi/SpireCloudEditor.js

Confirm the file path of SpireCloudEditor.js in VS Code

Note: This path must be completely consistent with the path in the subsequent configured office-js.ts, otherwise the editor cannot be loaded.


2. Configure State Management

We use NgRx Signals to synchronize file upload data (file object + binary data) for access by the editor component.

(1) Install dependencies

Run the following installation command in the VS Code terminal:

npm install @ngrx/effects @ngrx/signals @ngrx/store

(2) Create state management store

  • Create a store folder in the src/app/ directory and create a new index.ts file.
  • Add the following code to index.ts to define file state and operations:
import { signalStore, withState, withMethods, patchState } from '@ngrx/signals';

// Define file state interface
interface FileState {
  file: File | null;          // Uploaded file object
  fileUint8Data: Uint8Array | null; // File binary data
}

// Initial state
const initialState: FileState = {
  file: null,
  fileUint8Data: null,
};

// Create global Store
export const fileStore = signalStore(
  { providedIn: 'root' },
  withState(initialState),
  withMethods((store) => ({
    // Update file object
    setFileData(data: File | null): void {
      patchState(store, { file: data });
    },
    // Update file binary data
    setFileUint8Data(uint8Data: Uint8Array | null): void {
      patchState(store, { fileUint8Data: uint8Data });
    }
  }))
);

3. Develop Core Components (Upload + Editor)

(1) Create components

Run the following commands in your terminal to create a file upload component and an editor integration component:

ng g c spire/uploadFile
ng g c spire/officeJS

This generates two folders in src/app/spire/: upload-file/ and office-js/.

Verify the newly generated component directory in VS Code

(2) Configure the upload component (upload-file)

This component handles file selection (drag-and-drop or click), converts files to binary format, stores data in the global state, and navigates to the editor page.

  • Define the upload interface style (upload-file.css):
:host {
  display: block;
  min-height: 100vh;
}

.upload-main {
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: #f5f5f5;
}

.upload-container {
  width: 80%;
  max-width: 600px;
  padding: 40px;
  background-color: white;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  text-align: center;
}

.drop-area {
  border: 2px dashed #ccc;
  border-radius: 6px;
  padding: 40px;
  margin-bottom: 20px;
  transition: all 0.3s;
}

.drop-area.highlight {
  border-color: #4CAF50;
  background-color: #f0fff0;
}

button {
  background-color: #4CAF50;
  color: white;
  border: none;
  padding: 10px 20px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;
  margin-top: 10px;
}

button:hover {
  background-color: #45a049;
}

#fileInput {
  display: none; /* Hide the native file selection box */
}
  • Set the upload interface structure (upload-file.html), supporting both drag-and-drop upload and click-to-select file:
<main class="upload-main">
  <div class="upload-container">
    <h2>Upload Your File</h2>
    <div class="drop-area" id="dropArea">
      <p>Drag and drop your file to the browser</p>
      <p>or</p>
      <button id="browseBtn" #browseBtn (click)="handleButtonClick($event)">Click to select your file</button>
      <input type="file" id="fileInput" #fileInput (change)="handleDrop($event)">
    </div>
  </div>
</main>
  • Implement upload logic (upload-file.ts), handling file drag-and-drop, selection, binary conversion, and state storage:
import { ViewChild, ElementRef, Component, AfterViewInit, inject } from '@angular/core';
import { Router } from '@angular/router';
import { fileStore } from '../../store/index';

@Component({
  selector: 'app-upload-file',
  imports: [],
  templateUrl: './upload-file.html',
  styleUrl: './upload-file.css',
})
export class UploadFile implements AfterViewInit {
  constructor(private router: Router) { }
  // Inject state management store
  store = inject(fileStore);

  // Bind HTML elements
  @ViewChild('browseBtn') browseBtn!: ElementRef<HTMLButtonElement>;
  @ViewChild('fileInput') fileInput!: ElementRef<HTMLInputElement>;

  file: File | null = null; // Uploaded file object
  fileUint8Data: Uint8Array | null = null; // File binary data

  // Execute after component view initialization is complete
  ngAfterViewInit() {
    this.init();
  }

  // Initialize drag-and-drop event listening
  init() {
    // Prevent default browser behavior for drag-and-drop
    ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
      document.addEventListener(eventName, this.preventDefaults, false);
    });

    // Listen for file drop events
    document.addEventListener('drop', (e) => {
      this.handleDrop.call(this, e);
    }, false);
  }

  // Prevent default events
  preventDefaults(e: Event) {
    e.preventDefault();
    e.stopPropagation();
  }

  // Trigger native file input when select file is clicked
  handleButtonClick(e: Event) {
    e.preventDefault();
    this.fileInput.nativeElement.click();
  }

  // Handle file selection (drag-and-drop or click)
  async handleDrop(e: any) {
    // Retrieve file object
    if (e.target && e.target.files) {
      this.file = e.target.files[0];
    } else if (e.dataTransfer && e.dataTransfer.files) {
      this.file = e.dataTransfer.files[0];
    }

    // Convert file to Uint8Array binary format
    this.fileUint8Data = await this.handleFile(this.file) as Uint8Array;

    // Update global state
    this.store.setFileData(this.file);
    this.store.setFileUint8Data(this.fileUint8Data);

    // Navigate to editor
    this.openDocument();
  }

  // Convert file to Uint8Array binary data
  handleFile(file: any) {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = () => {
        const arrayBuffer = reader.result as ArrayBuffer;
        const uint8Array = new Uint8Array(arrayBuffer);
        resolve(uint8Array);
      };
      reader.onerror = (error) => reject(error);
      reader.readAsArrayBuffer(file); // Read file in ArrayBuffer format
    });
  }

  // Navigate to the editor page (route navigation)
  openDocument() {
    this.router.navigate(['spire']);
  }
}

(3) Configure the editor component (office-js)

The editor component loads the Spire.OfficeJS script, initializes the editor, and configures editing permissions.

  • Define the editor container (office-js.html):
<div class="form">
    <div id="iframeEditor">
    </div>
</div>
  • Implement core logic such as editor script loading, configuration initialization, file parsing, and event listening (office-js.ts):
import { Component, AfterViewInit, inject } from '@angular/core';
import { Router } from '@angular/router';
import { fileStore } from '../../store/index';

// Declare global SpireCloudEditor (from Spire.OfficeJS script)
declare const SpireCloudEditor: any;

@Component({
  selector: 'app-office-js',
  imports: [],
  templateUrl: './office-js.html',
  styleUrl: './office-js.css',
})
export class OfficeJS implements AfterViewInit {
  constructor(private router: Router) { };
  // Inject state management store
  store = inject(fileStore);

  // Get file data from store
  file = this.store.file() as File;
  fileUint8Data = this.store.fileUint8Data() as Uint8Array;
  originUrl = window.location.origin; // Current project domain name
  Editor: any; // Editor instance
  config: any; // Editor configuration
  Api: any;    // Editor API

  // Execute after component view initialization is complete
  ngAfterViewInit() {
    this.init();
  }

  // Initialize editor (redirect to upload if no file exists)
  init() {
    if (!this.file) {
      this.router.navigate(['']);
      return;
    }
    this.loadSrcipt(); // Load editor script
  }

  // Dynamically load SpireCloudEditor.js
  loadSrcipt() {
    const script = document.createElement('script');
    // Match static resource path
    script.setAttribute('src', '/spire.cloud/web/editors/spireapi/SpireCloudEditor.js');
    script.onload = () => this.initEditor(); // Initialize editor after script loading is complete
    document.head.appendChild(script);
  }

  // Initialize editor configuration and instance
  initEditor() {
    const iframeId = 'iframeEditor'; // Match container ID in the template file
    this.initConfig();
    this.Editor = new SpireCloudEditor.OpenApi(iframeId, this.config);
    this.Api = this.Editor.GetOpenApi(); // Get editor API
    this.OnWindowReSize(); // Adapt to window size
  }

  // Configure editor settings (file information + user permissions + editor behavior)
  initConfig() {
    this.config = {
      "fileAttrs": {
        "fileInfo": {
          "name": this.file.name, // File name
          "ext": this.getFileExtension(), // File suffix
          "primary": String(new Date().getTime()), // Unique ID (timestamp)
          "creator": "",
          "createTime": ""
        },
        "sourceUrl": `${this.originUrl}/files/__ffff_192.168.3.121/${this.file.name}`,
        "createUrl": `${this.originUrl}/open`,
        "mergeFolderUrl": "",
        "fileChoiceUrl": "",
        "templates": {}
      },
      "user": {
        "id": "uid-1",
        "name": "Jonn",
        "canSave": true, // Allow file saving
      },
      "editorAttrs": {
        "editorMode": this.file.name.endsWith('.pdf') ? 'view' : "edit", // PDF = preview only
        "editorWidth": "100%",
        "editorHeight": "100%",
        "editorType": "document",
        "platform": "desktop",
        "viewLanguage": "en", // English interface
        "isReadOnly": false,
        "canChat": true,
        "canComment": true,
        "canReview": true,
        "canDownload": true, // Allow file downloads
        "canEdit": this.file.name.endsWith('.pdf') ? false : true, // Disable editing for PDFs
        "canForcesave": true,
        "embedded": {
          "saveUrl": "",
          "embedUrl": "",
          "shareUrl": "",
          "toolbarDocked": "top" // Toolbar aligned to top
        },
        // Enable WebAssembly for faster performance(Supports Word/Excel/PPT/PDF)
        "useWebAssemblyDoc": true,
        "useWebAssemblyExcel": true,
        "useWebAssemblyPpt": true,
        "useWebAssemblyPdf": true,
        // License keys (add if available)
        "spireDocJsLicense": "",
        "spireXlsJsLicense": "",
        "spirePresentationJsLicense": "",
        "spirePdfJsLicense": "",
        "serverless": {
          "useServerless": true,
          "baseUrl": this.originUrl,
          "fileData": this.fileUint8Data, // Pass binary file data
        },
        "events": {
          "onSave": this.onFileSave // Save callback event
        },
        "plugins": {
          "pluginsData": []
        }
      }
    };
  }

  // Adjust editor size to fit window
  OnWindowReSize() {
    const wrapEl = document.getElementsByClassName("form") as any;
    if (wrapEl.length) {
      wrapEl[0].style.height = window.innerHeight + "px";
      window.scrollTo(0, -1);
    }
  }

  // Extract file extension
  getFileExtension() {
    const filename = this.file.name.split(/[\\/]/).pop() as String;
    return filename.substring(filename.lastIndexOf('.') + 1).toLowerCase() || '';
  }

  // Custom save logic (can be extended according to requirements)
  onFileSave(data: any) {
    console.log('Saved data:', data);
  }
}

4. Configure Routing

Enable navigation between the upload and editor pages.

  • Modify routing rules (app.routes.ts), configure routes for the upload page and editor page:
import { Routes } from '@angular/router';
import { UploadFile } from './spire/upload-file/upload-file';
import { OfficeJS } from './spire/office-js/office-js';

export const routes: Routes = [
  { path: '', component: UploadFile }, // Default: upload page
  { path: 'spire', component: OfficeJS } // Editor page: /spire
];
  • Ensure the routing configuration takes effect (app.config.ts):
import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideBrowserGlobalErrorListeners(),
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes), // Inject routing configuration
  ]
};
  • Set route outlet (app.html) to ensure the page is rendered correctly:
<main class="app-main">
  <router-outlet /> <!-- Route outlet: render the component corresponding to the current route -->
</main>

Launch & Function Validation

1. Start the Project

Save all changes and restart the development server:

npm run start

2. Test Core Functions

  • Access the upload page: Visit http://localhost:4200/ in the browser, and the "Upload Your File" interface is displayed, supporting two upload methods:
    • Drag and drop files to the specified area in the browser;
    • Click to select file from the local (supporting formats such as Word, Excel, PPT, PDF).

File upload interface displayed in the browser

  • Edit documents online: After uploading, you’ll be redirected to the editor page (http://localhost:4200/spire). The editor supports:
    • Editing Word, Excel, and PowerPoint files (PDFs are preview-only).
    • Adding annotations, comments, and track changes.
    • A user-friendly English interface with an intuitive toolbar.

English interface and toolbar of Spire.OfficeJS online editor

  • Download / convert documents: After editing, click "File" → "Download as" on the top of the editor to export the document to various formats such as PDF, TXT, RTF, HTML, etc.

List of export format options under the file menu in the editor


Frequently Asked Questions

Q1: The editor fails to load and the page is blank?

  • Verify Path: Ensure the SpireCloudEditor.js path in office-js.ts matches the deployed static resource path.
  • Browser compatibility: Upgrade to browsers that support WebAssembly such as Chrome 100+

Q2: When installing NgRx dependencies, a peer dependency conflict is prompted?

  • Use the --legacy-peer-deps flag to force compatibility:
npm install @ngrx/signals @ngrx/store --legacy-peer-deps
  • Or adjust your Angular version to match NgRx’s compatibility requirements.

Q3: When starting the project, the browser console reports an error, indicating that the zone.js module cannot be found?

  • Reason: The "zoneless" mode was mistakenly selected during project initialization, but Spire.OfficeJS requires zone.js for async events.
  • Solution:
    • First install the zone.js dependency: npm install zone.js --save
    • Then open src/main.ts and add the import at the top of the file: import 'zone.js';
    • Finally, restart the project and confirm that the error disappears.

Download Complete Sample

Get the full Angular + Spire.OfficeJS integration project with pre-configured code and resources. Run it directly to test all features.

Click to download

Apply for a Temporary License

To remove the evaluation message from generated documents or lift feature limitations, contact us to obtain a 30-day temporary license.

As modern front-end technologies evolve, more web applications are bringing Office document viewing and editing directly into the browser to improve performance, security, and user experience. React, one of the most popular front-end frameworks, is widely used to build document-centric applications.

In this article, you'll learn how to integrate Spire.OfficeJS into a React project to handle Word, Excel, PowerPoint, and PDF files efficiently on the client side.

Page Content:

What Is Spire.OfficeJS

Spire.OfficeJS is a pure front-end document component suite developed by E-ICEBLUE, enabling the creation, viewing, and editing of Word, Excel, and PowerPoint documents, with built-in support for PDF viewing. Developers can complete the entire document workflow in the browser, from opening and editing to exporting, without installing Microsoft Office or relying on backend services.

Designed for performance and easy integration, Spire.OfficeJS fits a wide range of document-driven applications, from online platforms to education systems and admin dashboards.

Spire.OfficeJS product package includes the following components:

Core Features:

  • Multi-format support: Preview, edit, and export Word, Excel, and PowerPoint documents, with PDF preview support.
  • Online editing: Edit Office documents directly in the browser, including text, tables, images, and styles.
  • High-performance, pure front-end execution: Powered by WebAssembly, delivering lightweight loading and efficient performance with no backend involvement.
  • Flexible integration: Easily integrates with React, Vue, Angular, and can also be used conveniently with vanilla JavaScript.

Create a React Project and Integrate Spire.OfficeJS

Step 1. Install Node.js

Download and install Node.js from the official website(https://nodejs.org/en/download/). After installation, open Command Prompt (cmd) and run the following command to verify the installed version:

node -v
npm -v

screenshot of checking node.js version with cmd

Step 2. Create a New React Project with Vite

Choose a project directory, open Command Prompt (cmd), and run the following command:

npm create vite@latest officejs-demo -- --template react

A new React project will be created and initialized.

screenshot of a new created react project using cmd

Step 3. Install Dependencies Using npm

Open the "officejs-demo" project in VS Code, then run the following command to install "react-router-dom", which provides routing and navigation between pages:

npm install react-router-dom

install dependencies in vs code terminal

Step 4. Integrate Spire.OfficeJS

Download the Spire.OfficeJS package. In your React project, create a new spire.officejs folder under the public directory, then copy the web folder from the extracted package into it. Ensure that the directory path matches the path referenced in Editor.jsx.

screenshot of integrating spire.officejs to your react project

Implement File Upload and Editor Pages

1. App.jsx: Configure Routing and Global File State Management

Code:

import { createContext, useContext, useState } from 'react';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import Home from './Home';
import Editor from './Editor';

//File state context
const FileContext = createContext();

export const useFileStore = () => useContext(FileContext);

function FileProvider({ children }) {
  const [file, setFile] = useState(null);
  const [fileUint8Data, setFileUint8Data] = useState(null);

  return (
    <FileContext.Provider value={{
      file,
      fileUint8Data,
      setFileData: setFile,
      setFileUint8Data
    }}>
      {children}
    </FileContext.Provider>
  );
}

const router = createBrowserRouter([
  { path: '/', element: <Home /> },
  { path: '/editor', element: <Editor /> },
]);

function App() {
  return (
    <FileProvider>
      <RouterProvider router={router} />
    </FileProvider>
  );
}

export default App;

2. Home.jsx: Set Up File Upload, File Handling, and Editor Navigation

Explanation:

  • FileReader: To read local files from the user's device.
  • Uint8Array: The binary data format required by OfficeJS WebAssembly for data input.
  • useNavigate: A React Router hook for programmatic navigation between pages.
  • Drag and Drop: Browser-based drag-and-drop support for file uploads.

Code:

import { useRef, useEffect } from "react"
import { useFileStore } from './App';
import { useNavigate } from 'react-router-dom';

function Home() {
    const { setFileData, setFileUint8Data } = useFileStore();
    const navigate = useNavigate();

    let dropArea = null;
    let fileInput = useRef();
    let file = null;
    let fileUint8Data = null;

    useEffect(() => {
        dropArea = document;

        //Prevent the default drag-and-drop behavior
        ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
            dropArea.addEventListener(eventName, preventDefaults, false);
        });

        //Handle file drag-and-drop
        dropArea.addEventListener('drop', handleDrop, false);
    }, [])

    const preventDefaults = (e) => {
        e.preventDefault();
        e.stopPropagation();
    }

    const handleBtnClick = (e) => {
        e.preventDefault();
        fileInput.current.click();
    }

    const handleDrop = async (e) => {
        if (e.target && e.target.files)
            file = e.target.files[0]
        else if (e.dataTransfer && e.dataTransfer.files)
            file = e.dataTransfer.files[0];

        if (!file) return;

        fileUint8Data = await handleFile(file);
        setFileData(file);
        setFileUint8Data(fileUint8Data);
        openDocument();
    }

    const handleFile = (file) => {
        return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onload = () => {
                const arrayBuffer = reader.result;
                const uint8Array = new Uint8Array(arrayBuffer);
                resolve(uint8Array);
            };
            reader.onerror = (error) => reject(error);
            reader.readAsArrayBuffer(file);
        });
    }

    const openDocument = () => {
        navigate('/editor')
    }

    return (
        <div>
            <div>
                <h2>Upload Your File</h2>
                <div>
                    <p>Drag your file to the browser</p>
                     <p style={{ marginLeft: '20px' }}>or</p>
                    <button ref={fileInput} onClick={handleBtnClick}>Choose Your File</button>
                    <input
                        type="file"
                        id="fileInput"
                        ref={fileInput}
                        onChange={handleDrop}
                        style={{ display: 'none' }}
                    />
                </div>
            </div>
        </div>
    )
}

export default Home

3. Editor.jsx: Add and Integrate the OfficeJS Editor

Code:

import { useRef, useEffect } from "react"
import { useFileStore } from './App';
import { useNavigate } from 'react-router-dom';

function Editor() {
    const { file, fileUint8Data } = useFileStore();
    const navigate = useNavigate();

    const config = useRef({});
    let Editor = useRef(null);
    let Api = useRef(null);
    let originUrl = window.location.origin;

    useEffect(() => {
        if (!file) {
            navigate('/')
            return;
        }
        // Dynamically load Spire.OfficeJS
        loadScript();
    }, [])

    const loadScript = () => {
        var script = document.createElement('script');
        //Adjust according to your product package path
        script.setAttribute('src', '/spire.officejs/web/editors/spireapi/SpireCloudEditor.js');
        script.onload = () => initEditor()
        document.head.appendChild(script);
    }

    const initEditor = () => {
        let iframeId = 'iframeEditor';
        initConfig();
        Editor = new SpireCloudEditor.OpenApi(iframeId, config.value);
        window.Api = Api = Editor.GetOpenApi();
        OnWindowReSize();
    }

    const initConfig = () => {
        config.value = {
            "fileAttrs": {
                "fileInfo": {
                    "name": file.name,
                    "ext": getFileExtension(),
                    "primary": String(new Date().getTime()),
                    "creator": "User",
                    "createTime": new Date().toLocaleString()
                },
                "sourceUrl": originUrl + "/files/" + file.name,
                "createUrl": originUrl + "/open",
                "mergeFolderUrl": "",
                "fileChoiceUrl": "",
                "templates": {}
            },
            "user": {
                "id": "uid-1",
                "name": "User",
                "canSave": true,
            },
            "editorAttrs": {
                "editorMode": "edit",
                "editorWidth": "100%",
                "editorHeight": "100%",
                "editorType": "document",  // document/spreadsheet/presentation
                "platform": "desktop",
                "viewLanguage": "en",  // en/zh
                "isReadOnly": false,
                "canChat": true,
                "canComment": true,
                "canReview": true,
                "canDownload": true,
                "canEdit": true,
                "canForcesave": true,
                "embedded": {
                    "saveUrl": "",
                    "embedUrl": "",
                    "shareUrl": "",
                    "toolbarDocked": "top"
                },
                //Enable WebAssembly to improve performance
                "useWebAssemblyDoc": true,
                "useWebAssemblyExcel": true,
                "useWebAssemblyPpt": true,
                "spireDocJsLicense": "",
                "spireXlsJsLicense": "",
                "spirePresentationJsLicense": "",
                "spirePdfJsLicense": "",
                //Serverless mode: process files directly in memory without a backend
                "serverless": {
                    "useServerless": true,
                    "baseUrl": originUrl,
                    "fileData": fileUint8Data,  //The file's Uint8Array data
                },
                "events": {
                    "onSave": onFileSave
                },
                "plugins": {
                    "pluginsData": []
                }
            }
        };
    }

    const OnWindowReSize = () => {
        let wrapEl = document.getElementById("editor-container");
        if (wrapEl) {
            wrapEl.style.height = screen.availHeight + "px";
            window.scrollTo(0, -1);
            wrapEl.style.height = window.innerHeight + "px";
        }
    }

    const getFileExtension = () => {
        const filename = file.name.split(/[\\/]/).pop();
        return filename.substring(filename.lastIndexOf('.') + 1).toLowerCase() || '';
    }

    const onFileSave = (data) => {
        console.log('Saved data:', data)
        //Implement the save logic here
        //For example, send the file to a server or download it
    }

    return (
        <div id="editor-container">
            <div id="iframeEditor"></div>
        </div>
    )
}

export default Editor

4. main.jsx

Code:

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.jsx'

createRoot(document.getElementById('root')).render(
  //Note: React StrictMode causes the Spire  editor to render twice, so it's recommended to disable it
  <App />
)

5. vite.config.js - Customize the Development Server Port

Code:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  server: {
    host: '0.0.0.0',
    port: 5174
  },
  plugins: [react()],
})

Run the Project

Run the command npm run dev to start the configured project, then open your browser and visit http://localhost:5174/.

the main page of officejs project

Upload a local document by dragging or clicking "Choose Your File" to open it for editing.

screenshot of word file uploading

Go to "File > Download As" to download the document or convert it to other formats.

download and save word documents to other formts

Demo Download

Click to download

FAQs:

1. Project creation error due to incompatible npm or Node.js versions:

This usually occurs when Node.js is outdated. Update to the latest version of Node.js to resolve the issue.

2. Dependency installation failure:

  • Clear the npm cache.
  • Delete node_modules and package-lock.json, then reinstall dependencies.
  • Ensure you are in the correct project directory and do not run installation commands outside the Vite project.

3. Spire.OfficeJS files cannot be loaded:

  • Verify that the web folder has been copied to the correct directory.
  • Make sure the development server can access resources under the public directory at runtime.

Apply for License

To remove the evaluation message from generated documents or lift feature limitations, contact us to obtain a 30-day temporary license.

Spire.OfficeJS is a WebAssembly-based Office document editor that enables users to open, view, and edit Word, Excel, and PowerPoint documents directly in the browser. In this tutorial, we will walk through how to integrate Spire.OfficeJS into a Vue 3 application (Vue 3 + Vite), and build a fully client-side web application that supports online Office document editing—without relying on server-side document conversion.

By the end of this guide, you will have a runnable Vue project that allows users to upload Office documents and edit them directly in the browser using Spire.OfficeJS.

On this page:

What Is Spire.OfficeJS

Spire.OfficeJS is a web-based online Office document editing component that consists of four modules: Spire.WordJS, Spire.ExcelJS, Spire.PresentationJS, and Spire.PDFJS. It provides viewing and real-time editing capabilities for documents such as Word files, Excel spreadsheets, and PowerPoint presentations.

Spire.OfficeJS runs directly in the browser and can be deployed in any web project without installing plugins or relying on client-side software.

Key Features

  • Pure front-end rendering: Based on WebAssembly, allowing document editing without server-side conversion.
  • Rich editing capabilities: Supports document editing, comments, annotations, review, and saving.
  • Multi-format support: DOC, DOCX, XLS, XLSX, PPT, PPTX, PDF (view), and more.
  • High integrability: Can be flexibly embedded into Vue, React, Angular, or pure HTML projects.
  • High customizability: Supports toolbar configuration, user permissions, save callbacks, plugin extensions, and more.

Spire.OfficeJS is suitable for enterprise systems, document management systems (DMS), collaboration platforms, online learning systems, and form-based applications.

How Spire.OfficeJS Works

Spire.OfficeJS is built on WebAssembly-based Office rendering engines that execute directly in the browser. The simplified workflow is:

  1. A user uploads an Office document via the browser.
  2. The file is read as binary data (Uint8Array).
  3. The binary data is passed directly to the WebAssembly runtime.
  4. The document is parsed, rendered, and edited client-side.
  5. Save actions trigger callbacks for custom persistence logic.

Unlike traditional server-based Office editors, no server-side document conversion or rendering is required, significantly reducing infrastructure complexity and latency.

Preparation

Install Node.js

Download and install Node.js 22.12.0 or later from the official Node.js website. Node.js 22+ is recommended to ensure compatibility with Vite, modern ES module tooling, and WebAssembly-related workflows.

Verify the installation:

node -v
npm -v

Verify installation

Create a Vue 3 Project

Step 1: Create a project folder

Create a new folder to store the project files.

Step 2: Enter the folder via Command Line

cd /d d:\demo

Enter folder

Step 3: Initialize a Vue 3 project

npm init vue@latest

Initialize vue project

Rename the project to vue-spire and skip optional features to create a minimal Vue 3 project.

Step 4: Start the development server

cd vue-spire
npm run dev

Start development server

Integrating Spire.OfficeJS

Step 1: Download the product package

Download Spire.OfficeJS product package from our website. After extracting the package, you will find a web folder containing the editor’s static assets and WebAssembly files.

Download Spire.OfficeJS

Step 2: Copy static resources to the Public directory

In your Vue project:

  • Open the project in VS Code.
  • Create a folder: public/spire.cloud .
  • Copy the entire web folder into it. This allows the editor resources to be accessed via /spire.cloud/web/....

Copy web folder

Step 3: Install required dependencies

Install Pinia and Vue Router manually to keep the project setup explicit and easy to follow.

npm install pinia
npm install vue-router@4

Install dependencies

Step 4: Create the project structure

Create the following structure under src :

src/
├── router/
│   ├── index.js
├── stores/
│   ├── file.js
├── views/
│   ├── FileUpload.vue
│   └── Spire.OfficeJS.vue

Create project structure

Step 5: Setup application

  1. main.js — Application initialization
  2. This file initializes the Vue application and registers Pinia and Vue Router. Pinia is used to manage shared document data, while Vue Router controls page navigation between the file upload view and the document editor view.

    import { createApp } from 'vue'
    // Import Pinia
    import { createPinia } from 'pinia'
    import App from './App.vue'
    import router from './router'
    
    const app = createApp(App)
    
    // Create Pinia instance
    const pinia = createPinia()
    
    // Register Pinia and Router to the Vue application
    app.use(pinia)
    app.use(router)
    app.mount('#app')
    
  3. App.vue — Root component
  4. App.vue serves as the root container of the application. It renders different pages dynamically using RouterView, allowing the file upload page and the document editor to be loaded as separate routes without reloading the application.

    <script setup>
    import { RouterView } from 'vue-router'
    </script>
    
    <template>
        <RouterView/>
    </template>
    
  5. Router index.js — Page navigation
  6. The router defines the navigation flow of the application. The root route (/) is used for file upload, while /document loads the Spire.OfficeJS editor. This separation allows users to upload a document first and then open it in the editor with shared state preserved.

    import { createRouter, createWebHistory } from 'vue-router'
    import FileUpload from '../views/FileUpload.vue'
    import SpireOfficeJs from '../views/Spire.OfficeJS.vue'
    
    const router = createRouter({
        history: createWebHistory(),
        routes: [
            {
                path: '/',
                name: 'upload',
                component: FileUpload
            },
            {
                path: '/document',
                name: 'document',
                component: SpireOfficeJs
            },
            {
                path: '/:pathMatch(.*)*',
                redirect: '/'
            }
        ]
    })
    
  7. Pinia Store (file.js) — File state management
  8. The Pinia store is responsible for sharing file metadata and binary data between different views. The uploaded file is converted into a Uint8Array and stored here so that it can be passed directly to Spire.OfficeJS in serverless mode.

    import { ref } from 'vue'
    // Import defineStore from Pinia to define a state management store
    import { defineStore } from 'pinia'
    
    // Define a file state management store
    export const useFileStore = defineStore('file', () => {
      // Store the uploaded file object (File type)
      let file = ref(null)
      // Store the file binary data (Uint8Array format) for editor loading
      let fileUint8Data = ref(null);
    
      // Set the file object
      function setFileData(data) {
        file.value = data;
      }
      // Set the file binary data
      function setFileUint8Data(data) {
        fileUint8Data.value = data;
      }
      // Export state and methods for component usage
      return { file, fileUint8Data, setFileData, setFileUint8Data }
    })
    
  9. FileUpload.vue — File upload page
  10. FileUpload.vue is responsible for handling user-selected Office documents before they are passed to the editor. It reads the uploaded file using the browser File API and converts it into a Uint8Array, which is required by Spire.OfficeJS in serverless mode.

    <template>
        <main>
            <button @click="btnClick">Choose Your File</button>
            <label>
                <input id="input" type="file" @change="handleFileChange" style="display: none;" />
            </label>
        </main>
    </template>
    
    <script setup>
    import { useRouter } from 'vue-router'
    import { useFileStore } from '../stores/file'
    
    // Router instance: redirect to /document after successful upload
    const router = useRouter()
    // Pinia Store: store the user-uploaded file and binary data
    const fileStore = useFileStore()
    
    // Handle file upload
    async function handleFileChange(event) {
        // Get the file selected by the user through the input change event
        const selectedFile = event.target.files?.[0]
        if (!selectedFile) {
            return
        }
    
        // Save the original File object and binary data for the editor to read
        fileStore.setFileData(selectedFile)
        const buffer = await selectedFile.arrayBuffer()
        fileStore.setFileUint8Data(new Uint8Array(buffer))
    
        // Redirect to the document editing page after successful upload
        router.push('/document')
    }
    function btnClick() {
        var btn = document.querySelector('#input');
        btn.click()
    }
    </script>
    
  11. Spire.OfficeJs.vue — Online editor integration

Spire.OfficeJs.vue is the core integration component where the Spire.OfficeJS editor is initialized and rendered. It dynamically loads the Spire.OfficeJS runtime, configures editor behavior, and passes the document binary data to the WebAssembly engine using serverless mode.

<template>
    <div class="form">
        <div id="iframeEditor">
        </div>
    </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { storeToRefs } from 'pinia';
import { useFileStore } from '../stores/file.js'
import { useRouter } from 'vue-router';

const fileStore = useFileStore()
// Data stored in Pinia
const { file, fileUint8Data } = storeToRefs(fileStore)
const router = useRouter()
const config = ref({});
const isOpened = ref(false);
const editorInstance = ref(null);
const apiInstance = ref(null);
const originUrl = window.location.origin

onMounted(() => {
    // Redirect back to upload page if no file exists
    if (!file.value) {
        router.replace('/');
        return;
    }
    // Load editor script dynamically
    loadScript();
    window.addEventListener('resize', OnWindowReSize);
})

onUnmounted(() => {
    window.removeEventListener('resize', OnWindowReSize);
})

// Initialize the configuration object required by the editor
function initConfig() {
    if (!file.value) {
        throw new Error('File not found, please upload again');
    }

    if (!fileUint8Data.value) {
        throw new Error('File data not found, please upload again');
    }

    config.value = {
        "fileAttrs": {
            "fileInfo": {
                "name": file.value.name,
                "ext": getFileExtension(),
                "primary": String(new Date().getTime()),
                "creator": "Jonn",
                "createTime": "2022-04-18 11:30:43"
            },
            "sourceUrl": originUrl + "/files/__ffff_192.168.2.134/" + file.value.name,
            "createUrl": originUrl + "/open",
            "mergeFolderUrl": "",
            "fileChoiceUrl": "",
            "templates": {}

        },
        "user": {
            "id": "uid-1",
            "name": "Jonn",
            "canSave": true,
        },
        "editorAttrs": {
            "editorMode": "edit",
            "editorWidth": "100%",
            "editorHeight": "100%",
            "editorType": "document",
            "platform": "desktop", // desktop / mobile / embedded
            "viewLanguage": "en", // en / zh
            "isReadOnly": false,
            "canChat": true,
            "canComment": true,
            "canReview": true,
            "canDownload": true,
            "canEdit": true,
            "canForcesave": true,
            "embedded": {
                "saveUrl": "",
                "embedUrl": "",
                "shareUrl": "",
                "toolbarDocked": "top"
            },
            "useWebAssemblyDoc": true,
            "useWebAssemblyExcel": true,
            "useWebAssemblyPpt": true,
            "spireDocJsLicense": "",
            "spireXlsJsLicense": "",
            "spirePresentationJsLicense": "",
            "spirePdfJsLicense": "",
            "serverless": {
                "useServerless": true,
                "baseUrl": originUrl,
                "fileData": fileUint8Data.value,
            },
            "events": {
                "onSave": onFileSave
            },
            "plugins": {
                "pluginsData": []
            }
        }
    };
}

// Create and render the SpireCloudEditor instance
function initEditor() {
    let iframeId = 'iframeEditor';

    initConfig();
    isOpened.value = true;
    editorInstance.value = new SpireCloudEditor.OpenApi(iframeId, config.value); // Create editor instance
    window.Api = apiInstance.value = editorInstance.value.GetOpenApi(); // Expose OpenApi for debugging/saving
    OnWindowReSize();
}

// Get the uploaded file extension for fileInfo.ext
function getFileExtension() {
    const filename = file.value.name.split(/[\\/]/).pop();
    // Get the substring after the last dot
    return filename.substring(filename.lastIndexOf('.') + 1).toLowerCase() || '';
}

// Adjust editor container size to fit the window
function OnWindowReSize() {
    let wrapEl = document.getElementsByClassName("form");
    if (wrapEl.length) {
        wrapEl[0].style.height = screen.availHeight + "px";
        window.scrollTo(0, -1);
        wrapEl[0].style.height = window.innerHeight + "px";
    }
}

// Dynamically load the SpireCloudEditor script to avoid duplicate injection
function loadScript() {
    if (window.SpireCloudEditor) {
        initEditor()
        return
    }
    const script = document.createElement('script');
    script.setAttribute('src', '/spire.cloud/web/editors/spireapi/SpireCloudEditor.js');
    script.onload = () => initEditor()
    document.head.appendChild(script);
}

// Save callback for the Spire editor, can be connected to custom save logic
function onFileSave(data) {
    console.log('save data', data)
}

</script>

<style>
.form,
iframe,
body {
    min-height: 100vh !important;
    min-width: 100vh !important;
}
</style>

Step 6: Run the project

Start the development server:

npm run dev

Run the project

Open the browser and navigate to: http://localhost:5173/

Open localhost in browser

Upload a document and start editing it directly in the browser.

Start editing document

FAQs

Q1. Why does the editor load a blank page?

This usually occurs when static resource paths are incorrect or required WebAssembly files are missing. Ensure the web directory is correctly placed under public/spire.cloud and that SpireCloudEditor.js is accessible.

Q2. Why doesn’t the document open after uploading?

The editor requires the file to be passed as a Uint8Array. Verify that the file data is correctly read, stored in Pinia, and assigned to serverless.fileData.

Q3. Can Spire.OfficeJS run without a backend server?

Yes. When serverless.useServerless is enabled, all document loading, rendering, and editing are performed entirely in the browser using WebAssembly.

Q4. Which file formats are supported by Spire.OfficeJS?

Spire.OfficeJS supports Word (.doc, .docx), Excel (.xls, .xlsx), PowerPoint (.ppt, .pptx), and PDF (.pdf) files.

Q5. How can I save the edited document?

Use the onSave event to capture the edited document data and implement custom logic to upload, store, or download the file.

Conclusion

By following this tutorial, you have successfully integrated Spire.OfficeJS into a Vue 3 application and built a fully client-side Office document editor powered by WebAssembly. This approach eliminates server-side document conversion while providing a rich, responsive editing experience directly in the browser.

Demo Download

Click to download

page