Repository navigation
Storage Backends
How draw.io opens, saves and synchronises files in the different storage locations (Google Drive, OneDrive, GitHub, the device, the browser and so on), and what a self-hosted deployment needs to make the cloud integrations work. For developers who self-host, debug load and save problems, or want to add a storage location.
All the classes named here live in src/main/webapp/js/diagramly/. The OAuth servlets are in src/main/server/java/com/mxgraph/online/.
| Layer | Class | Responsibility |
|---|---|---|
| Application |
App (App.js), a subclass of EditorUi
|
Holds one client per provider (this.drive, this.oneDrive, this.m365, this.gitHub, this.gitLab, this.dropbox, this.trello), the current storage mode (this.mode) and the current file. Maps the URL hash to a provider and dispatches create, open, pick and save to it. |
| Client |
DrawioClient (DrawioClient.js) and one subclass per provider |
Authentication, the current user (setUser fires userChanged), token persistence (getPersistentToken / setPersistentToken, keyed by cookieName) and the provider's REST API: getFile, insertFile, saveFile, pickFile, pickFolder, getLibrary, logout. |
| File |
DrawioFile (DrawioFile.js) and one subclass per provider |
One open document: its data, title, hash, descriptor (provider metadata such as the etag), modified state, autosave, save, conflict handling and merge. |
DrawioFile and DrawioClient both extend mxEventSource. Libraries (custom shape libraries) reuse the file classes: each provider has a *Library subclass of its file class.
| Provider | App.MODE_* |
Hash prefix | Client | File / library classes | Default |
|---|---|---|---|---|---|
| Google Drive |
MODE_GOOGLE ('google') |
G |
DriveClient |
DriveFile, DriveLibrary
|
on (gapi=0 disables) |
| OneDrive |
MODE_ONEDRIVE ('onedrive') |
W |
OneDriveClient |
OneDriveFile, OneDriveLibrary
|
on (od=0 disables) |
| Microsoft 365 (SharePoint) |
MODE_M365 ('m365') |
M |
OneDriveClient in SharePoint mode |
OneDriveFile with isSP
|
on except in the desktop app and embed mode (ms365=0 disables) |
| GitHub |
MODE_GITHUB ('github') |
H |
GitHubClient |
GitHubFile, GitHubLibrary
|
on (gh=0 disables) |
| GitLab |
MODE_GITLAB ('gitlab') |
A |
GitLabClient (extends GitHubClient) |
GitLabFile (extends GitHubFile), GitLabLibrary
|
on (gl=0 disables) |
| Dropbox |
MODE_DROPBOX ('dropbox') |
D |
DropboxClient |
DropboxFile, DropboxLibrary
|
off (db=1 enables) |
| Trello |
MODE_TRELLO ('trello') |
T |
TrelloClient |
TrelloFile, TrelloLibrary
|
off (tr=1 enables) |
| Device |
MODE_DEVICE ('device') |
none | none |
LocalFile, LocalLibrary
|
on |
| Browser |
MODE_BROWSER ('browser') |
L |
none |
StorageFile, StorageLibrary
|
on (browser=0 hides it) |
| Embed host |
MODE_EMBED ('embed') |
E |
none | EmbedFile |
embed mode only |
App.MODE_ATLAS also exists and is used by the Atlassian integrations, which are not part of this repository.
Deprecations announced in this repository's discussions:
-
Dropbox: jgraph/drawio#5740, stops working after 31 December 2027. The client only loads with
db=1,DropboxFile.prototype.isEditablereturnsfalse(files open read-only, use Save As to move them), and Dropbox is no longer offered as a save target. -
GitLab: jgraph/drawio#5773, stops working after 31 December 2027. GitLab is no longer offered as a save target in the save dialog (
addStorageEntryinSaveDialog, Dialogs.js).
The part of the URL after # names the open file. App.prototype.loadFile(id) reads the first character. The letters for cloud providers go through App.prototype.getModeForChar and getServiceForName to a client, whose getFile(id, success, error) returns a DrawioFile. Each file class writes its own hash back with getHash().
| Prefix | Meaning | Example |
|---|---|---|
G |
Google Drive file ID (DriveFile.getHash returns 'G' + id) |
#G<fileId> |
W |
OneDrive item, <driveId>/<itemId> URI-encoded (OneDriveFile.getIdOf) |
#W<driveId>%2F<itemId> |
M |
SharePoint item via the Microsoft 365 client, same ID format | #M<driveId>%2F<itemId> |
H |
GitHub org/repo/ref/path, URI-encoded as a whole |
#Hjgraph%2Fdrawio-diagrams%2Fdev%2Fdiagrams%2Fschema.xml |
A |
GitLab group/project/ref/path, URI-encoded as a whole |
#Amygroup%2Fmyproject%2Fmain%2Fdocs%2Fflow.drawio |
D |
Dropbox path relative to the app folder | #Dflow.drawio |
T |
Trello attachment: card ID and attachment ID joined by |$|
|
#T<cardId>%7C%24%7C<attachmentId> |
L |
Browser storage, by file title | #LMy%20Diagram.drawio |
U |
Load from a URL: directly for the same origin and the hosts in Editor.prototype.isCorsEnabledForUrl (plus the cors URL parameter), otherwise through PROXY_URL. Opens as a local copy (LocalFile with no mode). |
#Uhttps%3A%2F%2Fjgraph.github.io%2Fdrawio-diagrams%2Fdiagrams%2Fbulb.xml |
R |
Raw diagram data in the URL: XML, or compressed with Graph.compress
|
#R<compressed data> |
E |
Embed mode: reloads the current file from the host via remoteInvoke('getDraftFileContent')
|
#E |
create= |
Creates a file from a JSON descriptor (see the create URL parameter), handled by executeCreateObject
|
#create=%7B...%7D |
P |
URL parameters as URI-encoded JSON, parsed in bootstrap.js before anything else | #P%7B%22lightbox%22%3A%221%22%7D |
_CONFIG_ |
Compressed configuration JSON stored into the editor configuration (App.main) |
#_CONFIG_<compressed json> |
S |
No longer supported for files: shows a deprecation alert |
A second # followed by a URI-encoded JSON object can follow the file ID (for example #G<id>#%7B"pageId"%3A"..."%7D). EditorUi.prototype.getDiagramId strips it and getHashObject parses it. The user-facing reference is Supported location hash properties.
Libraries passed in the clibs URL parameter use the same scheme (App.prototype.loadLibraries): L (StorageLibrary), U (UrlLibrary), R (RemoteLibrary, fetched from the embedding host), S (a desktop file path, DesktopLibrary), and G, H, T, D, W through the signed-in client's getLibrary.
-
App.prototype.loadFile(id)→peer.getFile(id, success, error). The client downloads the content and descriptor and constructs a file object, for examplenew OneDriveFile(ui, data, meta). -
EditorUi.prototype.fileLoaded(file)closes the previous file, clears the model and the undo history, callssetCurrentFile(file)andfile.open(), then firesfileLoadedon the editor. -
DrawioFile.prototype.opencallsui.setFileData(data, file), stores a deep copy of the pages as the shadow pages (setShadowPages), installs the change listeners (installListeners) and, ifisSyncSupported(), callsstartSync(). -
EditorUi.prototype.setFileDataparses the<mxfile>, wraps each<diagram>in aDiagramPage, selects the page from the hash object orpage-idparameter, and callsEditor.prototype.setGraphXmlfor the current page. The format is described in File format.
installListeners listens to mxEvent.CHANGE on the model and to page format, background, grid, math and other view settings. Each change calls DrawioFile.prototype.fileChanged, which:
- sets
modified(and its copyshadowModified) withsetModified(true); - if
isAutosave()is true, shows "Saving..." and schedulesautosave(autosaveDelay, maxAutosaveDelay, ...); - otherwise shows the unsaved-changes status (
addUnsavedStatus).
isAutosave() is !inConflictState && ui.editor.autosave. Subclasses narrow it: LocalFile autosaves only with a file handle, RemoteFile never. autosaveDelay defaults to 1500 ms (500 ms for OneDriveFile and StorageFile) and can be changed with the autosaveDelay configuration key. maxAutosaveDelay (30 s) forces a save while the user keeps editing. isAutosaveRevision() asks for a new provider revision at most every maxAutosaveRevisionDelay (5 min).
-
DrawioFile.prototype.save(revision, success, error, unloading, overwrite)refuses read-only files (isEditable()), and files with an invalid checksum unlessoverwriteis set. It then callsupdateFileData(), which serialises the pages withEditorUi.prototype.getFileData(viacreateData) intofile.data. - The subclass's
saveFile(title, revision, success, error, unloading, overwrite)setssavingFile = true, takes the current etag unless overwriting, and calls the client, for exampleOneDriveClient.prototype.saveFile(file, success, error, etag). Changes made during the save are tracked throughshadowModified. - On success the subclass stores the new descriptor and calls
fileSaved(savedData, lastDesc, success, error). This clears the conflict flags and makes the saved pages the new shadow pages. -
handleFileSuccess/handleFileErrorupdate the status bar.afterSave(fn)queues work until the current save has finished.
Every provider uses optimistic locking. The saved revision is compared with the one the file was opened from:
| File class | Version token | Conflict signal (isConflict) |
|---|---|---|
DriveFile |
desc.etag, sent as If-Match (DriveClient.createUploadRequest) |
HTTP 412 |
OneDriveFile |
meta eTag, sent as If-Match
|
HTTP 412 or 409 |
GitHubFile |
blob sha in the contents API request |
HTTP 409 |
GitLabFile |
as GitHub | HTTP 400 |
LocalFile (browser) |
lastModified of the File System Access handle |
compared before writing |
LocalFile (desktop) |
mtimeMs from fs.stat, see Desktop app
|
compared before writing |
StorageFile |
hash of the stored data (getEtag) |
saveFile reloads the stored copy first and merges it if the hash differs |
On a conflict the file sets inConflictState. What happens next depends on DrawioFile.SYNC, which is the sync URL parameter:
-
auto(the default inDrawioFile.js; the publicPreConfig.jsoverrides it, see below):DrawioFileSync.fileConflictfetches the latest version, merges it and retries the save. -
manual:handleConflictErrorshowsshowConflictDialog, with Overwrite (save withoverwrite = true) or Merge (synchronizeFile, then save). After a failed autosave, the status bar shows a conflict message; clicking it runssynchronizeFile. -
none:showCopyDialog, with Make a copy or Overwrite.
synchronizeFile → updateFile → getLatestVersion (the subclass reloads the file) → mergeFile(latestFile). mergeFile does a three-way merge on pages, using the shadow pages as the common ancestor:
-
ui.diffPages(shadow, remotePages)computes the remote changes.diffPagesandpatchPagesare in DiffSync.js. - If the file is modified,
ui.diffPages(shadow, ui.pages)computes the local changes. - It applies the remote patch to a copy of the shadow and compares
getHashValueForPagesof the result with the remote pages. A mismatch means the diff is not reproducible:checksumErrorsetsinvalidChecksumand saving stops until the file is reloaded or overwritten. - Otherwise it applies the patch to the current document with
DrawioFile.prototype.patch. WithDrawioFile.LAST_WRITE_WINS, local changes win for values and styles. The remote pages become the new shadow.
As the README says, draw.io built from this repository does not support real-time collaborative editing. The public PreConfig.js sets urlParams['sync'] = 'manual', so startSync never creates a DrawioFileSync. The sync classes (DrawioFileSync, P2PCollab, DrawioFilePolling) are in the source, but they rely on backend services that are not part of this repository. In manual mode, concurrent edits by two users are handled by the conflict detection and merge described above.
When EditorUi.enableDrafts is on (drafts=0 turns it off), DrawioFile.prototype.saveDraft keeps unsaved content as .draft_<id> items in IndexedDB (EditorUi.prototype.openDatabase, database database). The same database holds browser-storage files (object stores files and filesInfo), with a fallback to localStorage (StorageFile.getFileContent).
Providers are switched on and off by URL parameters, which a deployment can force in PreConfig.js (urlParams['od'] = '0';). The parameters that matter here:
| Parameter | Effect |
|---|---|
gapi=0, od=0, ms365=0, gh=0, gl=0
|
Disable a provider that is on by default |
db=1, tr=1
|
Enable Dropbox or Trello. A stored mode for either is ignored unless it is enabled (App.prototype.init). |
mode=google|onedrive|github|gitlab|dropbox|trello|device|browser |
Preselects the storage. Otherwise the last mode stored in localStorage (.mode, see App.getStoredMode) is used. |
offline=1, local=1, stealth=1, lockdown=1, demo=1
|
Turn off all cloud providers (Init.js sets gapi, db, od, gh, gl, tr and ms365 to 0) |
browser=0, noDevice=1
|
Hide the browser or device options |
sync=none|manual|auto |
Conflict handling, see above |
The provider-related keys of the editor configuration (DRAWIO_CONFIG) are autosaveDelay, oneDriveInlinePicker and enableCustomGitLabUrl. The full parameter list is Supported URL parameters.
In embed mode (embed=1) the cloud clients are not created unless they are enabled explicitly (gapi=1, od=1, gh=1, gl=1, tr=1, ms365=1). The host application stores the file, see Embedding.
The client IDs built into the code are those of the draw.io deployment. A self-hosted instance that wants a cloud integration must register its own OAuth app with the provider and configure it in two places:
-
Client side: a global set in
PreConfig.js(loaded before the app, see Configuration). -
Server side: the client ID and secret, read by the auth servlet.
SecretFacade.getSecretreads them from files inWEB-INF/.
The auth servlets all extend AbsAuth and implement the authorisation-code flow with a server-side code exchange:
- The client calls
<redirect URI>?getState=1. The servlet stores a random state token in its cache and sets anauth-statecookie. - The client opens the provider's authorise URL in a popup. The
stateparameter carriescId(client ID),domain(window.location.host),tokenand, under a sub-path,path. - The provider redirects to the servlet with
code. The servlet checks the state token against the cookie (CSRF protection), postsclient_id,client_secret,codeand (except for GitHub)redirect_urito the token endpoint, and returns a page that passes the access token towindow.opener(for exampleonOneDriveCallback). - If the provider returns a refresh token, the servlet keeps it in an
auth-token<clientId>cookie scoped to the servlet path. Google and OneDrive only ask for one when the user ticks Remember me. Later token refreshes go through the same servlet. The clients keep the access token in memory only.
| Provider | Redirect URI to register | Client-side global |
WEB-INF/ files |
Servlet |
|---|---|---|---|---|
| Google Drive | https://<host>/google |
DRAWIO_GOOGLE_CLIENT_ID, DRAWIO_GOOGLE_APP_ID (project number, used by the Google Picker) |
google_client_id, google_client_secret
|
GoogleAuthServlet |
| OneDrive | https://<host>/microsoft |
DRAWIO_MSGRAPH_CLIENT_ID, optional DRAWIO_MSGRAPH_TENANT_ID
|
msgraph_client_id, msgraph_client_secret, optional msgraph_tenant_id
|
MSGraphAuthServlet |
| GitHub |
https://<host>/github2 (callback URL of a GitHub App) |
DRAWIO_GITHUB_ID; for GitHub Enterprise also DRAWIO_GITHUB_URL, DRAWIO_GITHUB_API_URL
|
github_client_id, github_client_secret, optional github_auth_url
|
GitHubAuthServlet |
| GitLab | https://<host>/gitlab |
DRAWIO_GITLAB_ID, DRAWIO_GITLAB_URL
|
gitlab_client_id, gitlab_client_secret, optional gitlab_auth_url
|
GitlabAuthServlet |
| Dropbox (deprecated) | https://<host>/dropbox |
DRAWIO_DROPBOX_ID |
dropbox_client_id, dropbox_client_secret
|
DropboxAuthServlet |
The servlet mappings are in the public web.xml. The WEB-INF/*_client_id and *_client_secret files in the repository are placeholders. Notes per provider:
-
Google Drive: scopes are
drive.file,drive.installanduserinfo.profile(DriveClient.prototype.scopes). Also add your origin as an authorised JavaScript origin. The docker-drawio README walks through the Google console. - OneDrive: see Setting up a OneDrive project.
-
Microsoft 365 (SharePoint): the second
OneDriveClient(this.m365) redirects to<DRAWIO_SERVER_URL>ms365, and no servlet in this repository serves that path. Disable it withms365=0when you self-host. -
GitHub:
GitHubClientuses the client ID of a GitHub App and sends noredirect_uri, so GitHub uses the callback URL registered with the app.GitHubClient.prototype.showAuthorizeDialogand the repository picker link to draw.io's own GitHub App installation page. Change these links if you register your own app. -
GitLab: the scopes are
api read_repository write_repository. A GitLab URL other thanhttps://gitlab.com(fromDRAWIO_GITLAB_URLor thegitlabURL parameter) is refused unlessEditor.enableCustomGitLabUrlis true (configuration keyenableCustomGitLabUrl). -
Trello: authenticates in the browser with Trello's
client.jsandTrelloClient.prototype.key. It has no servlet.
Common to all servlets:
-
Multiple apps:
AbsAuth.Configaccepts several client IDs and secrets in one file, separated by/:::/, in the same order in both files. -
Host name: the redirect URI is built as
https://+ thedomainfrom the state + the servlet path, so register it exactly as users reach the site. -
HTTP:
DRAWIO_USE_HTTP=1in the server environment buildshttp://redirect URIs. This is insecure; use it only for local testing. -
Sub-path deployments: Google, OneDrive, GitLab and Dropbox send the redirect path (for example
/drawio/microsoft). The servlet only accepts a path that ends with its own path, and the cookies are scoped to the context path plus the servlet path (AbsAuth.getCookiePath). Register the full sub-path URI with the provider.
The docker-drawio image does all of this from environment variables (DRAWIO_GOOGLE_*, DRAWIO_MSGRAPH_*, DRAWIO_GITLAB_*, DRAWIO_SERVER_URL). It writes the globals into PreConfig.js and the IDs and secrets into WEB-INF/, and disables Dropbox, GitHub and Trello. See Self-hosting.
There is no plugin API for storage providers. Adding one means changing the sources (see Building). Follow an existing provider. GitLabClient / GitLabFile, which subclass the GitHub classes, show how little a variant needs to override.
-
Client:
MyClient = function(ui) { DrawioClient.call(this, ui, 'myAuthInfo'); }withmxUtils.extend(MyClient, DrawioClient). Implement:-
getFile(id, success, error), which passes a file object tosuccess; -
insertFile(title, data, success, error, asLibrary, folderId)andsaveFile(file, success, error, ...); -
pickFile(fn),pickFolder(fn),pickLibrary(fn),getLibrary(id, success, error)andlogout(); - the
extensionproperty.
Call
setUser(new DrawioUser(id, email, name))after signing in so thatuserChangedfires. -
-
File:
MyFile = function(ui, data, meta) { DrawioFile.call(this, ui, data); this.meta = meta; }withmxUtils.extend(MyFile, DrawioFile). Override:-
getHash()(a new prefix letter plus the URI-encoded ID),getId(),getMode()andgetTitle(); -
saveFile(title, revision, success, error, unloading, overwrite): maintainsavingFile, send the current etag, callfileSavedon success, and setinConflictStateon a conflict; -
isConflict(err)andgetLatestVersion(success, error), needed for merging; -
getDescriptor/setDescriptorandgetDescriptorEtag/setDescriptorEtag; - optionally
isRenamable/rename,isAutosaveOptionalandgetFileUrl.
-
-
Library:
MyLibraryextendsMyFile, asOneDriveLibrarydoes. -
App wiring in App.js:
- add an
App.MODE_*constant; - create the client in
App.prototype.initand listen touserChangedthere; - map the prefix in
getModeForChar, and the mode ingetServiceForName,getTitleForServiceandisModeEnabled; - dispatch creation in
createFile, and handle the mode inpickFolder,pickLibrary,saveLibraryandloadLibraries; - show the user in
toggleUserPanelandupdateHeader.
- add an
-
UI:
- storage choices in
StorageDialog,SplashDialog,NewDialogandSaveDialog(Dialogs.js); - the Open from, Import from and library menus in
Menus.prototype.init(Menus.js); - the logo and name in
AuthDialog.getServiceInfo.
- storage choices in
-
Server: if the provider needs a client secret, add an
AbsAuthsubclass. OverridegetConfig()(secret file names,REDIRECT_PATH,AUTH_SERVICE_URL) andprocessAuthResponse(calls yourwindow.openercallback). Add the servlet and a mapping toweb.xml. -
Build: add the new source files to the build (Building) and to
Devel.jsfordev=1. Add the provider's API hosts to your Content-Security-Policy.
- Setting up a OneDrive project
- Self-hosting and Configuration
- File format
- Embedding, where the host application is the storage
- Desktop app, where local files are read and written through Electron
- Architecture and Code map
- Security
Describes the dev branch of jgraph/drawio as of release 32.3.0 (October 2026). Internal JavaScript APIs change between releases; check the code of the version you use. · Questions: Discussions · Bugs: Issues · Vulnerabilities: report privately · User docs: drawio.com/docs
Get started
Concepts
Guides
- Self-hosting
- Configuration
- Storage backends
- OneDrive app registration
- Embedding
- Diagrams in GitHub
- Plugins
- Extending the editor
- Shapes and stencils
- Import and export
- Desktop app
- Internationalization
Reference
Project
Elsewhere