How to develop a DocSpace plugin – complete step-by-step tutorial

21 May 2025By Serge

In a world where speed and collaboration are everything, productivity tools need to keep up. The DocSpace plugins offer a modern way to enhance how users manage and interact with documents. In this guide, we’ll walk you through how to create your own DocSpace plugin using real examples from our official plugin samples.

How to develop a DocSpace plugin – complete Step-by-Step Tutorial

About ONLYOFFICE DocSpace Plugins SDK

ONLYOFFICE DocSpace Plugins SDK is an npm package based on TypeScript engines which provides interfaces allowing you to create your own plugins and embed them into the DocSpace portal. The plugin development lifecycle includes planning, development, testing and using.

To illustrate how to use our Plugins SDK, we created a repository with the  plugin samples. Let’s dive into the entire process of plugin development based on our plugin samples.

Planing

Define the plugin’s purpose, think about where you will access it and what third-party services you need to connect.

Step 1. Install all the necessary packages and programs

To install the @onlyoffice/docspace-plugin-sdk npm package globally, use the following command:

 npm i -g @onlyoffice/docspace-plugin-sdk

Step 2. Design the way your plugin will work

Choose the service that allows you to add the necessary functionality to your DocSpace.

For example, in our plugin samples, we use:

  • AssemblyAI to convert speech from audio and video files into text;
  • ConvertAPI to convert documents, spreadsheets, presentations, and forms into PDF;
  • Draw.io  to create, edit, and insert professional-looking diagrams.

Note! Please make sure that the service documentation is available, check its license, availability of API methods, etc. For some services, the user must obtain an API key to start using the plugin.

Think about where to implement the plugin, what the plugin’s structure will be, how the user will interact with the plugin’s components, etc. Make a list of the required plugin types and items depending on this information. For more information, read the Plugin types and Plugin items sections of the Plugins SDK documentation.

For example, for the speech-to-text plugin, we use the following plugin interfaces:

  • IPlugin. Required for each plugin. It contains the plugin PluginStatus variable, used to embed the plugin into the DocSpace.
  • ApiPlugin. Required as we implement a third-party service.
  • ISettingsPlugin and ISettings. Used to add settings block for plugin’s configuration. The users will access this block from Settings -> Integration -> Plugins to adjust the plugin’s parameters.
  • IContextMenuPlugin and IContextMenuItem. Used to implement a context menu action. In the speech-to-text plugin, it will be available for video and audio files allowing users to convert content into text.

The list of interfaces may be longer. For example, in the draw\.io plugin:

  • IMainButtonPlugin and IMainButtonItem. Used to implement the main button action. In the draw\.io plugin, we use the Action button -> More menu elements in the My documents section or in the selected room to create draw\.io diagrams.
  • IFilePlugin and IFileItem. Used to interact with the specified file types. In this case, with the .drawio files.

Come up with the plugin’s structure. All the required files are described here. Everything else can be organized however you prefer. For example, in the draw\.io plugin, we put the code for each plugin type in separate folders, while in the speech-to-text plugin, this code is placed in the src folder.

(no title)

Choose a name for your plugin and write a description for it.

 Developing

Now that all the preparation work is done, you can start developing your plugin.

Step 1. Create a plugin template

Create a plugin template and configure its settings which will be displayed in the DocSpace plugin settings:

To create a plugin template, open the terminal and run the following npx command:

 npx create-docspace-plugin

If the npx command is not avaibale, install the @onlyoffice/docspace-plugin-sdk npm package globally using the following command:

npm i -g @onlyoffice/docspace-plugin-sdk

Configure the plugin in the terminal by specifying settings in the dialog. All the settings are described here.

For the speech-to-text plugin, you can use the following values:

(no title)

You can change all the specified parameters later in the package.json file.

You can also create a plugin in any project by adding the @onlyoffice/docspace-plugin-sdk npm package as a dependency and specifying all the necessary fields in the package.json file.

Step 2. Configure the plugin entry point

The index.ts plugin entry point will be created automatically in the src folder during the template creation step. This file is required.

This file contains all the basic methods of the plugin types that you selected in the previous step. You can change this file at any time.

If you create a plugin yourself, without a template, for the plugin entry point, you can use the code from our ready-made plugin samples. It will work perfectly.

Step 3. Add plugin icons

Create the assets folder in the root plugin folder and add there all the plugin icons. The number of icons and their sizes will depend on the plugin types you implement. For the speech-to-text plugin, we need the following icons:

  • The default plugin type requires a logo image. It is equal to the logo parameter from the package.json file. The logo will be displayed in the DocSpace plugin settings. The required icon size is 48×48 px. Otherwise, it will be compressed to this size.(no title)
  • The context menu plugin uses an icon on the Convert to text button. The required icon size is 16×16 px. Otherwise, it will be compressed to this size.

(no title)

This icon can be also used for the main button icon. For example, in the draw\.io plugin, the same icon is used for the context and main button menu.

(no title)

The draw\.io plugin also uses the specific file icon near the .drawio files, which are created with the file plugin type. The preferred icon size for the table format is 32×32 px.

(no title)

Step 4. Configure the plugin’s interface elements

For example, the draw\.io plugin contains two main UI elements – the modal window and the diagram editor. Create the files for configuring each element. For your convenience, you can put these files into a separate DrawIO folder.

In the  Dialog.ts file, configure the modal window settings. Specify the IFrame UI component that is used to embed the draw\.io website into a modal window:

  export const frameProps: IFrame = {
    width: "100%",
    height: "100%",
    name: "test-drawio",
    src: "",
  }

Create the IBox container to add the iframe to it:

 const body: IBox = {
    widthProp: "100%",
    heightProp: "100%",


    children: [
      {
        component: Components.iFrame,
        props: frameProps,
      },
    ],
  }

Configure the modal window properties:

 export const drawIoModalDialogProps: IModalDialog = {
    dialogHeader: "",
    dialogBody: body,
    displayType: ModalDisplayType.modal,
    fullScreen: true,
    onClose: () => {
      const message: IMessage = {
        actions: [Actions.closeModal],
      }
      return message
    },
    onLoad: async () => {
      return {
        newDialogHeader: drawIoModalDialogProps.dialogHeader || "",
        newDialogBody: drawIoModalDialogProps.dialogBody,
      }
    },
    autoMaxHeight: true,
    autoMaxWidth: true,

In the Editor.ts file, configure the diagram editor. Create the DiagramEditor function with the following parameters: 

Parameter Type Example Description
ui string “default” Defines the editor’s ui theme.
dark string “auto” Defines the editor’s dark theme.
off boolean false Specifies if the offline mode is active or not.
lib boolean false Specifies if the libraries are enabled or not.
lang string “auto” Defines the editor’s language.
url string `https://embed.diagrams.net` Defines the URL to the editor.
showSaveButton boolean true Specifies if the **Save** button will be displayed in the editor.

Then specify methods to work with diagrams:

Method Description
startEditing Starts the editor with the given data.
getData Returns the diagram’s data.
getTitle Returns the diagram’s title.
getFormat Returns the diagram’s format.
getFrameId Returns the editor’s frame ID.
getFrameUrl Returns the URL to the iframe.
handleMessage Handles the given message.
initializeEditor Posts the *load* message to the editor.
save Saves the given data.

The full code for the *DiagramEditor* can be found here

Step 5. Create plugin types

Now that the default plugin is ready, you can start coding other plugin types.

Each plugin type has specific plugin items. Define the context menu item that will be displayed when you right-click on audio or video files:

export const contextMenuItem: IContextMenuItem = {
  key: "speech-to-text-context-menu-item",
  label: "Convert to text",
  icon: "speech-to-text-16.png",
  onClick: assemblyAI.speechToText,
  fileType: [FilesType.video],
  withActiveItem: true,
}

(no title)

You can add more plugin types. For example, the draw\.io plugin can be accessed from the main button menu, so we need to specify the main button item:

const mainButtonItem: IMainButtonItem = {
  key: "draw-io-main-button-item",
  label: "Draw.io",
  icon: "drawio.png",
  onClick: (id: number) => {
    drawIo.setCurrentFolderId(id)


    const message: IMessage = {
      actions: [Actions.showCreateDialogModal],
      createDialogProps: {
        title: "Create diagram",
        startValue: "New diagram",
        visible: true,
        isCreateDialog: true,
        extension: ".drawio",
        onSave: async (e: any, value: string) => {
          await drawIo.createNewFile(value)
        },
        onCancel: (e: any) => {
          drawIo.setCurrentFolderId(null)
        },
        onClose: (e: any) => {
          drawIo.setCurrentFolderId(null)
        },
      },
    }
    return message
  },
  // items: [createItem],
}

When the main button item is clicked, the modal window appears where you can type the diagram’s name and open an empty .drawio file.

(no title)

For the draw\.io plugin, you also need to configure the file plugin type which works when the user opens the specific .drawio file:

Define the file item that is represented as a file with the specific extension (.drawio) and icon:

 export const drawIoItem: IFileItem = {
     extension: ".drawio",
     fileTypeName: "Diagram",
     fileRowIcon: "drawio-32.svg",
     fileTileIcon: "drawio-32.svg",
     devices: [Devices.desktop],
     onClick,
   }

Define the onClick event which will execute the editDiagram method each time the .drawio file is opened:

  const onClick = async (item: File) => {
     return await drawIo.editDiagram(item.id)
   }

(no title)

Step 6. Create the settings plugin type

Configure the settings plugin type to provide users with the administrator settings.

Create a container where the plugin settings will be placed:

 const descriptionText: TextGroup = {
     component: Components.text,
     props: {
       text: "To generate API token visit https://www.assemblyai.com",
       color: "#A3A9AE",
       fontSize: "12px",
       fontWeight: 400,
       lineHeight: "16px",
     },
   }
   
   const descGroup: BoxGroup = {
     component: Components.box,
     props: {children: [descriptionText]},
   }
   
   const parentBox: IBox = {
     displayProp: "flex",
     flexDirection: "column",
     // marginProp: "16px 0 0 0",
     children: [tokenGroup, descGroup],
   }

In the settings description, indicate that it is necessary to generate an API token in order to be able to work with the plugin.

Configure the administrator settings with the ISettings object:

const adminSettings: ISettings = {
     settings: parentBox,
     saveButton: userButtonComponent,
     onLoad: async () => {
       assemblyAI.fetchAPIToken()
   
       tokenInput.value = assemblyAI.apiToken
   
       if (!assemblyAI.apiToken) {
         return {
           settings: parentBox,
         }
       }


       plugin.setAdminPluginSettings(adminSettings)
   
       return {settings: parentBox}
     }

Specify the onLoad event which defines which plugin settings will be displayed when the settins block is loaded.

Each settings item is determined in separate files (buttons, token).

(no title)

Step 7. Create the main plugin code file

Create a file in the src folder with the main plugin code. This file is required. Refer to the documentation of a third-party service to write this code.

Let’s see how the AssemblyAI.ts file is organized in detail:

Define the AssemblyAI class with all the necessary variables and methods:

  • Variables and their description:

apiURL

Defines the API URL.

apiURL = ""

currentFileId

Defines the current file ID.

const currentFileId: numbernull | number = null

apiToken

Defines the API token.

 apiToken = ""
  • Methods and their description

createAPIUrl

Creates the API URL.

 createAPIUrl = () => {
     const api = plugin.getAPI()


     this.apiURL = api.origin.replace(/\/+$/, "")


     const params = [api.proxy, api.prefix]


     if (this.apiURL) {
       for (const part of params) {
         if (!part) {
           continue
         }
         const newPart = part.trim().replace(/^\/+/, "")
         if (newPart) {
           if (this.apiURL.length !== 0 && this.apiURL[this.apiURL.length - 1] === "/") {
             this.apiURL += newPart
           } else {
             this.apiURL += `/${newPart}`
           }
         }
       }
     }
   }

setAPIUrl

Sets the API URL.

 setAPIUrl = (url: string) => {
     this.apiURL = url
   }

getAPIUrl

Retuns the API URL.

 getAPIUrl = () => {
     return this.apiURL
   }

setAPIToken

Sets the API token.

   setAPIToken = (apiToken: string) => {
     this.apiToken = apiToken
   }

getAPIToken

Returns the API token.

   getAPIToken = () => {
     return this.apiToken
   }

fetchAPIToken

Fetches the API token.

   fetchAPIToken = async () => {
     const apiToken = localStorage.getItem("speech-to-text-api-token")
  
     if (!apiToken) {
       return
     }


     this.setAPIToken(apiToken)
     plugin.updateStatus(PluginStatus.active)
   }

saveAPIToken

Saves the API token.

   saveAPIToken = (apiToken: string) => {
     localStorage.setItem("speech-to-text-api-token", apiToken)
  
     let status
     if (apiToken) {
       status = PluginStatus.active
     } else {
       status = PluginStatus.hide
     }
     plugin.updateStatus(status)
   }

setCurrentFileId

Sets the ID to the current file.

 setCurrentFileId = (id: number | null) => {
     this.currentFileId = id
   }

uploadFile

Uploads a file which will be transcribed.

uploadFile = async (apiToken: string, path: string, data: Blob) => {
     console.log(`Uploading file: ${path}`)
  
     const url = "https://api.assemblyai.com/v2/upload"
  
     try {
       const response = await fetch(url, {
         method: "POST",
         body: data,
         headers: {
           "Content-Type": "application/octet-stream",
           "Authorization": apiToken,
         },
       })
  
       if (response.status === 200) {
         const responseData = await response.json()
         return responseData["upload_url"]
       }
       console.error(`Error: ${response.status} - ${response.statusText}`)
       return null
     } catch (error) {
       console.error(`Error: ${error}`)
       return null
     }
   }

transcribeAudio

Transcribes the audio file.

transcribeAudio = async (apiToken: string, audioUrl: string) => {
     console.log("Transcribing audio... This might take a moment.")
  
     const headers = {
       "authorization": apiToken,
       "content-type": "application/json",
     }
     const response = await fetch("https://api.assemblyai.com/v2/transcript", {
       method: "POST",
       body: JSON.stringify({audioUrl}),
       headers,
     })
  
     const responseData = await response.json()
     const transcriptId = responseData.id
  
     const pollingEndpoint = `https://api.assemblyai.com/v2/transcript/${transcriptId}`
  
     while (true) {
       const pollingResponse = await fetch(pollingEndpoint, {headers})
       const transcriptionResult = await pollingResponse.json()
  
       if (transcriptionResult.status === "completed") {
         return transcriptionResult
       } else if (transcriptionResult.status === "error") {
         throw new Error(`Transcription failed: ${transcriptionResult.error}`)
       } else {
         await new Promise((resolve) => {
           setTimeout(resolve, 3000)
         })
       }
     }
   }

speechToText

Implements the plugin functionality.

speechToText = async (id: number) => {
     if (!this.apiToken) {
       return
     }


     this.setCurrentFileId(null)
  
     if (!this.apiURL) {
       this.createAPIUrl()
     }


     const response = await fetch(`${this.apiURL}/files/file/${id}`)
     const data = await response.json()
     const {viewUrl, title, folderId, fileExst} = data.response
  
     const file = await fetch(viewUrl)
  
     const fileBlob = await file.blob()
  
     const uploadUrl = await this.uploadFile(this.apiToken, viewUrl, fileBlob)
  
     const transcript = await this.transcribeAudio(this.apiToken, uploadUrl)
  
     const blob = new Blob([transcript.text], {
       type: "text/plain;charset=UTF-8",
     })
  
     const newFile = new File([blob], "blob", {
       type: "",
       lastModified: Date.now(),
     })
  
     const formData = new FormData()
     formData.append("file", newFile)
  
     const newTitle = `${title.replaceAll(fileExst, "")} text`
  
     try {
       const sessionRes = await fetch(
         `${this.apiURL}/files/${folderId}/upload/create_session`,
         {
           method: "POST",
           headers: {
             "Content-Type": "application/json;charset=utf-8",
           },
           body: JSON.stringify({
             createOn: new Date(),
             fileName: `${newTitle}.txt`,
             fileSize: newFile.size,
             relativePath: "",
           }),
         },
       )
       const response = await sessionRes.json()
       const sessionData = response.response.data
  
       const data = await fetch(`${sessionData.location}`, {
         method: "POST",
         body: formData,
       })


       const jsonData = await data.json()
       const {id: fileId} = jsonData.data
  
       return fileId
     } catch (e) {
       console.log(e)
     }


     return {
       actions: [Actions.showToast],
       toastProps: [{type: ToastType.success, title: ""}],
     } as IMessage
   }

Declare the AssemblyAI class instance:

const assemblyAI = new AssemblyAI()

Export the created plugin instance:

export default assemblyAI

Testing

To check the plugin’s functionality and fix any potential bugs, test the plugin:

  • Build the prepared plugin following the instructions here

The dist folder will be created in the root plugin folder and the plugin archive will be placed in it. This archive is the completed plugin that can be uploaded to the DocSpace portal.

  •  Upload your plugin to the DocSpace portal and thoroughly test its appearance and functionality.

Note! Please note that you can upload your own plugins only in the server DocSpace version

If any bugs occur, fix the source code of your plugin and repeat the procedure of building and testing.

Now that your plugin is tested and works properly, you can add it to the DocSpace server version and start using it.

The DocSpace plugin offers a powerful, user-friendly approach to document management and collaboration. By integrating  with users’ preferred platforms, it removes common obstacles and improve productivity across different workflows. If you have any questions about DocSpace plugins, don’t hesitate to ask our developers on ONLYOFFICE forum (registration required). You can also request a feature or report a bug by posting an issue on GitHub or share your DocSpace plugins with us and other users. Best of luck in your exploratory endeavors!

Create your free ONLYOFFICE account

View, edit and collaborate on docs, sheets, slides, forms, and PDF files online.