Repository navigation
Self Hosting
This page covers running your own draw.io instance: plain static hosting, the official Docker image, the .war on a servlet container, and the desktop app. It then describes the configuration hooks a deployment uses, the server endpoints the client calls and which of them exist in this repository, offline use, Content-Security-Policy and keeping an instance up to date. For end-user documentation of the Docker image see Run your own draw.io server with Docker.
| Option | Server code | Suits |
|---|---|---|
| Static files on any web server, including GitHub Pages | None | Editing with device and browser storage, no sign-in to cloud storage |
Docker image jgraph/drawio
|
Tomcat with the .war
|
Most self-hosters: configured with environment variables, optional Google Drive, OneDrive and GitLab |
The .war on your own servlet container |
The servlets in src/main/server/java
|
Existing Java infrastructure |
| Desktop app | None | Single users, fully offline |
In all cases the editor runs in the browser. Diagrams are stored where the user chooses (their device, the browser, or a cloud storage service they sign in to); the instance itself does not store diagrams.
src/main/webapp is a complete static site. Copy it to any web server, or serve it from a clone, and open index.html. All paths are relative, so a sub-path works too. No build is needed because the minified bundles are committed (see Building if you change the source).
The README's GitHub Pages route is the simplest example: fork the repository, publish the dev branch with GitHub Pages and open https://<your-user>.github.io/drawio/src/main/webapp/index.html (step by step in Getting started).
Without the servlets you lose the features that need a server: signing in to Google Drive, OneDrive, GitHub and GitLab, the /proxy for loading files, images and fonts from other hosts, and anything listed as "not in this repository" under Server endpoints.
jgraph/docker-drawio publishes the jgraph/drawio image. Its build clones this repository, runs ant war with JDK 11 and deploys the result as the root application of tomcat:9.0-jdk11-temurin. Images are tagged latest and with the draw.io version (for example jgraph/drawio:32.3.0), so you can pin a release.
docker run -it --rm --name="draw" -p 8080:8080 -p 8443:8443 jgraph/drawioThen open http://localhost:8080/ or, with the generated self-signed certificate, https://localhost:8443/. Adding ?offline=1 disables the cloud storage integrations.
At startup the container's docker-entrypoint.sh turns environment variables into configuration: it rewrites js/PreConfig.js, appends to js/PostConfig.js, writes the OAuth credentials into files under WEB-INF, and adjusts Tomcat's server.xml. Whatever you do not set stays disabled: the entrypoint always sets sync=manual, EXPORT_URL = null and EditorUi.enableLogging = false, turns off Dropbox, GitHub and Trello, and turns off Google Drive, OneDrive and GitLab unless their credentials are given.
The variables that matter most (the README has the full list and details):
| Variable | Purpose |
|---|---|
DRAWIO_SERVER_URL |
Public URL of the deployment, with a trailing slash. A sub-path also sets the Tomcat context path |
DRAWIO_BASE_URL |
The same without the trailing slash; derived from DRAWIO_SERVER_URL if unset |
DRAWIO_VIEWER_URL, DRAWIO_LIGHTBOX_URL
|
Your own viewer script and lightbox host, see Global variables |
DRAWIO_CONFIG, DRAWIO_CONFIG_FILE
|
The editor configuration as JSON, inline or from a mounted file; written to window.DRAWIO_CONFIG (see Configuration) |
DRAWIO_LANG |
Default UI language when the URL has no lang and the user has not chosen one |
DRAWIO_CSP_HEADER |
Replaces the default Content-Security-Policy that the entrypoint injects as a <meta> tag |
ENABLE_DRAWIO_PROXY |
1 enables the /proxy endpoint (off by default) |
DRAWIO_GOOGLE_CLIENT_ID, its secret and DRAWIO_GOOGLE_APP_ID
|
Google Drive with your own OAuth client |
DRAWIO_MSGRAPH_CLIENT_ID, its secret and DRAWIO_MSGRAPH_TENANT_ID
|
OneDrive with your own Azure app |
DRAWIO_GITLAB_ID, DRAWIO_GITLAB_SECRET, DRAWIO_GITLAB_URL
|
GitLab, including self-managed GitLab |
LETS_ENCRYPT_ENABLED, PUBLIC_DNS, KEYSTORE_PASS, KEY_PASS
|
TLS: Let's Encrypt or the self-signed certificate |
DRAWIO_USE_HTTP |
1 builds the OAuth redirect URIs with http:// and drops the Secure cookie flag. Insecure; only for setups without TLS where you accept that risk |
The image runs as the non-root user tomcat. To run it under another UID, that user needs group 0, otherwise the entrypoint skips all runtime configuration; a read-only root file system is not supported because the configuration is written into the web application at startup. The EXPORT_URL and DRAWIO_SELF_CONTAINED variables of older images no longer have any effect.
How to register the OAuth apps for each storage service is described in Storage backends and in the Docker README.
Download draw.war from the releases page, or build it with ant war (Building). Then:
- Deploy it to a container that implements the
javax.servletAPI withweb.xmlversion 3.1, such as Tomcat 9 (the version the Docker image uses). Containers built on thejakarta.servletnamespace (Tomcat 10 and later) cannot load these servlets unchanged. - Configure the client by editing
js/PreConfig.jsandjs/PostConfig.jsin the deployed application, as described below. - If you use the cloud storage integrations, put your OAuth client IDs and secrets into the matching files in
WEB-INF(google_client_id,msgraph_client_id,gitlab_client_id,github_client_idand the secret file next to each). The repository ships them with placeholder text; the servlets read them throughSecretFacade.getSecret. - Set environment variables for the servlets as needed:
| Variable | Read by | Effect |
|---|---|---|
ENABLE_DRAWIO_PROXY |
ProxyServlet |
1 enables /proxy; otherwise it answers 404 |
DRAWIO_PROXY_ALLOWED_PORTS |
Utils |
Comma-separated ports the proxy may connect to. Without it, only URLs without an explicit port are allowed |
ALLOW_INTERNAL_10_NET |
Utils |
1 lets the proxy reach 10.x.x.x addresses. Loopback, link-local and the other private and reserved ranges stay blocked |
EXPORT_URL |
ExportProxyServlet |
Base URL that /export and /service/* forward to. There is no export service in this repository |
DRAWIO_USE_HTTP |
OAuth servlets | See the Docker table above |
DRAWIO_MEMCACHED_ENDPOINT |
CacheFacade |
Shares the OAuth state cache between several instances through a memcached JCache provider, whose jar you must add to WEB-INF/lib (it is not shipped) |
Set DRAWIO_BASE_URL in PreConfig.js to the public URL of the application (for example https://example.com/draw when draw.war is deployed under its own name); otherwise the links the editor generates point at app.diagrams.net. DRAWIO_SERVER_URL defaults to the directory of the page and usually needs no change.
draw.io Desktop is an Electron application for Windows, macOS and Linux built from jgraph/drawio-desktop, which includes this repository as a git submodule. It needs no server and works offline. See Desktop app.
A deployment is configured through two small scripts in src/main/webapp/js that you edit (or, with Docker, that the entrypoint writes). In production mode bootstrap.js loads them on every host except *.draw.io and *.diagrams.net; in development mode (?dev=1) it always loads them.
| File | Runs | Use it for |
|---|---|---|
| js/PreConfig.js | After bootstrap.js has parsed urlParams, before Init.js and any application code |
Setting global variables such as DRAWIO_BASE_URL, EXPORT_URL and DRAWIO_CONFIG, and setting or forcing URL parameters (urlParams[...]) |
| js/PostConfig.js | After app.min.js (in development mode, after all sources), before App.main runs. shapes-14-6-5.min.js, stencils.min.js and extensions.min.js load later, inside App.main
|
Overriding defaults that Init.js has already filled in, static flags such as Editor.enableCustomGitLabUrl, and prototype methods |
The repository's PreConfig.js sets DRAWIO_PUBLIC_BUILD = true, EXPORT_URL = null (PDF export through the print dialog), placeholders for DRAWIO_BASE_URL, DRAWIO_VIEWER_URL, DRAWIO_LIGHTBOX_URL and DRAWIO_CONFIG, DRAW_MATH_URL = 'math4/es5' and urlParams['sync'] = 'manual'. Its PostConfig.js sets ICONSEARCH_PATH and ICON_SERVICE_PATH to null.
Init.js fills in most variables with window.X = window.X || default, so setting one of them to null in PreConfig.js has no effect: the default replaces it. To disable such a variable, set it to null in PostConfig.js, which is why ICONSEARCH_PATH is cleared there. EXPORT_URL is the exception: Init.js only applies its default when the variable is undefined, so null in PreConfig.js turns server export off.
A typical PreConfig.js for an instance at https://drawio.example.com:
window.DRAWIO_PUBLIC_BUILD = true;
window.DRAWIO_BASE_URL = 'https://drawio.example.com';
window.DRAWIO_LIGHTBOX_URL = 'https://drawio.example.com';
window.DRAWIO_VIEWER_URL = 'https://drawio.example.com/js/viewer-static.min.js';
window.EXPORT_URL = null;
window.DRAW_MATH_URL = 'math4/es5';
window.DRAWIO_CONFIG = {"defaultFonts": ["Helvetica", "Verdana"]};
urlParams['sync'] = 'manual';These are the window variables read at startup, mostly defaulted in js/diagramly/Init.js and js/grapheditor/Init.js (the draw.io Init.js runs first). Set them in PreConfig.js, or in PostConfig.js to clear them as explained above.
Deployment URLs
| Variable | Default on your host | Used for |
|---|---|---|
DRAWIO_BASE_URL |
https://app.diagrams.net |
Absolute base URL of the deployment, without a trailing slash. Used for the links the editor generates and for some script and image URLs in exported HTML. Set it, or those point at app.diagrams.net |
DRAWIO_SERVER_URL |
The directory of the page, with a trailing slash | Base for the server endpoints (proxy, save, the OAuth servlets) and for loading js/extensions.min.js and js/orgchart.min.js
|
DRAWIO_LIGHTBOX_URL |
https://viewer.diagrams.net |
Host of the lightbox (?lightbox=1) used for lightbox links and in exported and embedded HTML |
DRAWIO_VIEWER_URL |
null (then js/viewer-static.min.js on the lightbox or base host) |
Viewer script referenced by exported and embedded HTML |
EXPORT_URL |
null from PreConfig.js
|
Server-side export service. null means none: PDF export uses the print dialog. It cannot be set from a URL parameter (GHSA-ff67-v9r9-6877) |
PROXY_URL |
DRAWIO_SERVER_URL + 'proxy' |
Loading files, images and fonts from other hosts, except those that Editor.prototype.isCorsEnabledForUrl or the cors URL parameter lets the browser fetch directly |
SAVE_URL |
DRAWIO_SERVER_URL + 'save' |
Server echo for downloads, only used with save=remote or in browsers that cannot save files themselves |
REALTIME_URL, RT_WEBSOCKET_URL
|
DRAWIO_SERVER_URL + 'cache', wss://<host>/rt
|
Real-time collaboration services, not part of this repository |
NOTIFICATIONS_URL |
null |
Notifications, only used on draw.io's own hosts |
ICONSEARCH_PATH, ICON_SERVICE_PATH
|
null from PostConfig.js
|
Icon search in the shape search box |
VSS_CONVERT_URL |
https://convert.diagrams.net/VsdConverter/api/converter |
Fallback conversion service for Visio files the browser cannot convert itself. Set it to null in PostConfig.js to keep such files local |
DRAW_MATH_URL |
math4/es5 |
MathJax |
DRAWIO_LOG_URL |
'' |
Client error and event logging. Logging (EditorUi.enableLogging) is only on for draw.io's own hosts, so nothing is sent from yours |
Configuration, plugins and paths
| Variable | Purpose |
|---|---|
DRAWIO_CONFIG |
Editor configuration object passed to Editor.configure, see Configuration
|
ALLOW_CUSTOM_PLUGINS |
false by default: only the plugins in App.pluginRegistry are loaded. true also allows other same-origin plugins added through the plugins setting, see Plugins
|
PLUGINS_BASE_PATH |
Prefix for the paths of the built-in plugins and of stored plugin paths that start with /
|
mxLanguage |
Forces the UI language. Set unconditionally it overrides the lang URL parameter and the user's choice, see Configuration
|
mxLoadSettings |
false stops the stored preferences from being loaded |
SHAPES_PATH, STENCIL_PATH, STYLE_PATH, CSS_PATH, IMAGE_PATH, GRAPH_IMAGE_PATH, TEMPLATE_PATH, RESOURCES_PATH, RESOURCE_BASE, mxBasePath, mxImageBasePath
|
Locations of shapes, stencils, styles, images, templates, language files and mxGraph resources. The relative defaults fit the layout of src/main/webapp
|
DOM_PURIFY_CONFIG |
DOMPurify configuration for sanitising labels and other HTML. Do not loosen it, see Security |
MAX_REQUEST_SIZE, MAX_AREA
|
Limits for the data sent to server endpoints and for the area of exported images |
DRAWIO_PUBLIC_BUILD |
Set by this repository's PreConfig.js; changes what development mode loads and the preference keys of the sketch theme |
Storage services (details in Storage backends): DRAWIO_GOOGLE_CLIENT_ID, DRAWIO_GOOGLE_APP_ID, DRAWIO_MSGRAPH_CLIENT_ID, DRAWIO_MSGRAPH_TENANT_ID, DRAWIO_GITHUB_ID, DRAWIO_GITHUB_URL, DRAWIO_GITHUB_API_URL, DRAWIO_GITLAB_ID, DRAWIO_GITLAB_URL, DRAWIO_DROPBOX_ID. Their defaults are draw.io's own app registrations, which only work on draw.io's hosts.
The viewer bundles have their own defaults, compiled in by the build: PROXY_URL, STYLE_PATH, SHAPES_PATH, STENCIL_PATH, DRAW_MATH_URL, GRAPH_IMAGE_PATH, mxImageBasePath and mxBasePath point at https://viewer.diagrams.net unless the page sets them before loading the script. The committed viewer.min.js and viewer-static.min.js also fetch stencil XML from STENCIL_PATH at runtime. To serve the viewer entirely from your own host, rebuild the bundles with ant app (the public build embeds stencils.min.js in both, see Building) and set the variables above to your host on pages that load the viewer. See Embedding for the viewer API.
Each storage integration has a URL parameter. To change the default for all users, set it in PreConfig.js. A plain assignment forces the value; checking for null first keeps it overridable from the URL:
// Forced off for everyone
urlParams['db'] = '0'; // Dropbox
urlParams['tr'] = '0'; // Trello
urlParams['gh'] = '0'; // GitHub
// Off by default, but ?gapi=1 still works
if (urlParams['gapi'] == null) urlParams['gapi'] = '0'; // Google Drive
if (urlParams['od'] == null) urlParams['od'] = '0'; // OneDrive
if (urlParams['ms365'] == null) urlParams['ms365'] = '0'; // Microsoft 365
if (urlParams['gl'] == null) urlParams['gl'] = '0'; // GitLabWithout these, the editor loads the Google API script from apis.google.com on every start, even though sign-in cannot work without your own OAuth client.
Broader switches, applied in Init.js: offline=1, local=1, stealth=1 and lockdown=1 all turn off every cloud storage integration. offline=1 also stops plugins from loading, local=1 also stops the use of localStorage, and stealth=1 and lockdown=1 also disable the features that use external services (data governance lockdown). plugins=0 disables plugins. See Configuration.
The client calls the following endpoints. Paths are relative to DRAWIO_SERVER_URL unless they are absolute. The public .war maps its servlets in WEB-INF/web.xml.
| Endpoint | Used for | In the public .war
|
|---|---|---|
/proxy (PROXY_URL) |
Fetching URLs on other hosts: opening a diagram from a URL, images and fonts for export | Yes, ProxyServlet. Answers 404 unless ENABLE_DRAWIO_PROXY=1; refuses private and reserved addresses |
/google, /microsoft, /gitlab, /github2, /dropbox
|
OAuth code-for-token exchange of the storage integrations | Yes, the *AuthServlet classes, reading credentials from WEB-INF
|
/export, /service/*
|
Server-side export, when EXPORT_URL points here |
Yes, ExportProxyServlet, which only forwards to the service named by the EXPORT_URL environment variable |
/embed2.js |
Viewer script for embedded diagrams | Yes, EmbedServlet2
|
/log |
Client error logging | Yes, LogServlet; the client does not send logs from your host (see DRAWIO_LOG_URL) |
/i/* |
Redirect for Imgur image links to app.diagrams.net | Yes, ImgurRedirectServlet
|
/embed.js |
Legacy embed script | Mapped in web.xml, but the servlet class is not in this repository, so requests fail |
/save (SAVE_URL) |
Download echo | No |
/cache, wss://<host>/rt, notifications
|
Real-time collaboration and notifications | No. PreConfig.js sets sync=manual
|
iconSearch2, api/icons
|
Icon search | No. PostConfig.js clears the paths |
https://convert.diagrams.net/... (VSS_CONVERT_URL) |
Fallback conversion of Visio files | External service; clear it to keep files local |
Every other request is for a static file under src/main/webapp. Remove mappings you do not use from web.xml; each one is server code reachable from the internet.
draw.io registers its service worker only where Editor.enableServiceWorker is true: on *.diagrams.net and *.draw.io, or on any host when the URL contains offline=1 or enableSW=1 (pwa=0 prevents it everywhere). On your own host, offline=1 is therefore what turns it on.
An installable offline application is not available from a deployment of this repository. The committed service-worker.js is generated for app.diagrams.net and its precache list includes files that are not in this repository (under connect/ and some plugins). Installation is all-or-nothing and fails when any listed file returns an error, so on your host the service worker does not install. The public build has no step that regenerates the file. offline=1 is still useful because it turns off the cloud storage integrations and plugins.
If you maintain your own service-worker.js, keep in mind that it is cache-first and keys every entry by the revision in its MANIFEST. A changed bundle is only fetched again when its revision changes.
For offline use, give users the desktop app. To remove a service worker from a browser, open clear.html on the same host, or use the browser's developer tools (Application > Service workers).
index.html does not set a Content-Security-Policy, so it is up to the deployment. The Docker image injects one as a <meta> tag from PreConfig.js (override it with DRAWIO_CSP_HEADER).
The policies draw.io uses itself are in the CSP-POLICIES block at the top of js/diagramly/Devel.js. Open ?dev=1&print-csp=1 and the console prints each of them; the one labelled app.diagrams.net is the production policy and a good starting point. Remove the hosts of integrations you have disabled and add your own. What the code requires:
| Directive | Needs | Why |
|---|---|---|
script-src |
'self'; https://apis.google.com only with Google Drive |
The bundles are loaded as same-origin scripts. Production mode does not need 'unsafe-eval': App.main sets mxStencilRegistry.allowEval = false. Development mode does need it |
style-src |
'self' 'unsafe-inline', plus https://fonts.googleapis.com for Google Fonts |
The UI sets inline styles throughout |
img-src, media-src, font-src
|
* (or your allowed hosts), data: and blob:
|
Diagrams embed images and fonts from anywhere and as data URIs |
connect-src |
'self' and the APIs of the storage services you enable (for example https://*.googleapis.com, https://graph.microsoft.com, https://api.github.com, your GitLab host) |
Storage APIs and resource loading use XMLHttpRequest and fetch
|
frame-src |
'self' plus Google and Microsoft sign-in hosts if those integrations are on |
Pickers and sign-in dialogs |
frame-ancestors |
The hosts allowed to embed your instance | Only effective as an HTTP header |
Prefer sending the policy as an HTTP header: a <meta> tag added by PreConfig.js does not cover bootstrap.js and PreConfig.js themselves, and frame-ancestors is ignored in a <meta> tag.
- Serve the instance over HTTPS. The OAuth integrations require it unless you set
DRAWIO_USE_HTTP, which is insecure. - Leave
ENABLE_DRAWIO_PROXYoff unless you need it. If you turn it on, keep the default address filtering. - Keep
ALLOW_CUSTOM_PLUGINSfalse unless you control every script on the origin. - Do not loosen
DOM_PURIFY_CONFIG; diagrams are untrusted input. - Keep the OAuth secrets in
WEB-INFreadable only by the container. - Remove servlet mappings you do not use.
The threat model and the rules for code that renders diagrams are in Security.
SECURITY.md says that only the latest version is supported and fixes are not backported. Plan to follow releases:
- Watch the repository's releases on GitHub (each release is tagged
vX.Y.Zand carriesdraw.war), or pull the matching Docker tag. - Read the ChangeLog before upgrading.
- Keep local changes in
PreConfig.js,PostConfig.js,DRAWIO_CONFIGor plugins rather than in the core files, so that upgrading means replacing the application and re-applying a few small files. - The bundle file names do not change between releases (
app.min.jsand so on). Serve them with caching headers that make browsers revalidate, or users keep running the old version after an upgrade. - The running version is shown in the Help menu (
EditorUi.VERSION) and is in theVERSIONfile at the root of the repository.
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