
A simple open-source collaborative whiteboard server running on Node.js.
WBO is an online collaborative whiteboard that allows many users to draw simultaneously on a large virtual board. The board is updated in real time for all connected users, and its state is always persisted. It can be used for many different purposes, including art, entertainment, design, teaching.
A demonstration server is available at wbo.ophir.dev
| The anonymous board |
| collaborative diagram editing |
|
| teaching math on WBO |
| drawing art |
|
If you have your own web server, and want to run a private instance of WBO on it, you can. It should be very easy to get it running on your own server.
If you use the docker containerization service, you can easily run WBO as a container.
An official docker image for WBO is hosted on dockerhub as lovasoa/wbo: .
You can run the following bash command to launch WBO on port 5001, while persisting the boards outside of docker:
mkdir wbo-boards # Create a directory that will contain your whiteboards
chown -R 1000:1000 wbo-boards # Make this directory accessible to WBO
docker run -it --publish 5001:80 --volume "$(pwd)/wbo-boards:/opt/app/server-data" lovasoa/wbo:latest # run wbo
You can then access WBO at http://localhost:5001.
The official Docker image does not force an IP source. By default the application uses WBO_IP_SOURCE=remoteAddress. If you run the container behind a trusted proxy or CDN, set -e WBO_IP_SOURCE=... explicitly, for example X-Forwarded-For, Forwarded, or CF-Connecting-IP.
Alternatively, you can run the code with node.js directly, without docker.
First, download the sources:
git clone https://github.com/lovasoa/whitebophir.git
cd whitebophir
Then install node.js (v22 or superior) if you don't have it already, then install WBO's dependencies:
npm install --production
Finally, you can start the server:
PORT=5001 npm start
This will run WBO directly on your machine, on port 5001, without any isolation from the other services. You can also use an invokation like
PORT=5001 HOST=127.0.0.1 npm start
to make whitebophir only listen on the loopback device. This is useful if you want to put whitebophir behind a reverse proxy.
By default, WBO launches its own web server and serves all of its content at the root of the server (on /).
If you want to make the server accessible with a different path like https://your.domain.com/wbo/ you have to setup a reverse proxy.
Set WBO_BASE_PATH=/wbo so generated links, redirects, and canonical URLs point at the external subfolder.
See instructions on our Wiki about how to setup a reverse proxy for WBO.
WBO is available in multiple languages. The translations are stored in server/http/translations.json.
If you feel like contributing to this collaborative project, you can translate WBO into your own language.
WBO supports authentication using Json Web Tokens. Pass the token as a token query parameter, for example http://myboard.com/boards/test?token={token}.
The AUTH_SECRET_KEY variable in configuration.mjs should be filled with the secret key for the JWT.
WBO evaluates board access as three capabilities:
canOpen: the user may load or connect to the board.canEdit: the user may send normal board changes.canClear: the user may use the Clear tool, which wipes all content from the board.JWT role strings can define these capabilities. They are declared in the JWT payload:
{
"iat": 1516239022,
"exp": 1516298489,
"roles": ["editor"]
}
If AUTH_SECRET_KEY is not set, any valid board URL has canOpen.
If AUTH_SECRET_KEY is set, canOpen requires a valid token. You can restrict which board names a token may open by adding :<boardName> to a claim:
{
"roles": ["editor:board-a", "moderator:board-b", "reader:board-c"]
}
reader:<boardName> grants canOpen for that board.editor:<boardName> grants canOpen for that board and is edit-capable on read-only boards.moderator:<boardName> grants canOpen for that board, is edit-capable on read-only boards, and grants canClear.For example, http://myboard.com/boards/mySecretBoardName?token={token} with:
{
"iat": 1516239022,
"exp": 1516298489,
"roles": ["moderator:mySecretBoardName"]
}
If a token contains any board-scoped claims, it can only open the boards named in those claims.
Phase 1 of the capability refactor does not add new permission types, board owners, administrators, sharing controls, or permission management UI. Existing JWT claim syntax remains unchanged.
Board visibility and board editability are separate.
editor and moderator claims still grant canEdit.canEdit because there is no authenticated edit-capable claim.canClear is never granted.moderator claims grant canClear.Read-only state is stored on the persisted board SVG root as data-wbo-readonly:
<svg id="canvas" ... data-wbo-readonly="true">
Legacy .json board files may still use __wbo_meta__.readonly; when they are loaded, WBO migrates that metadata into the stored SVG format.
editor or moderator claims for users who should edit read-only boards.reader:<boardName> for users who should only view a read-only board.WBO_HISTORY_DIR. The filename is board-${boardName}.svg.data-wbo-readonly attribute in that file.false to make the board writable.When you start a WBO server, it loads its configuration from several environment variables.
You can see a list of these variables in configuration.mjs.
Some important environment variables are :
WBO_HISTORY_DIR : configures the directory where the boards are saved. Defaults to ./server-data/.WBO_BASE_PATH : optional external URL path prefix, such as /wbo, for deployments mounted under a reverse-proxy subfolder.WBO_HTML_HEAD_SNIPPET_PATH : optional path to an HTML snippet inserted raw before </head> on rendered HTML pages. This is useful for adding user analytics scripts or similar trusted snippets. The file is read once at server startup; relative paths resolve from the server working directory.WBO_MAX_EMIT_COUNT : the general per-IP socket write limit profile. Use compact entries such as *:250/5s anonymous:125/5s. Increase this if you want smoother drawings, at the expense of making denial-of-service bursts cheaper for clients. The default is *:250/5s.WBO_MAX_CONSTRUCTIVE_ACTIONS_PER_IP : the constructive per-IP write limit profile. Use compact entries such as *:40/10s anonymous:20/10s.WBO_MAX_DESTRUCTIVE_ACTIONS_PER_IP : the destructive per-IP write limit profile. Use compact entries such as *:190/60s anonymous:95/60s.WBO_IP_SOURCE : which request attribute to trust for client IP based limits and logs. Supports remoteAddress, X-Forwarded-For, Forwarded, or a custom header such as CF-Connecting-IP. The default is remoteAddress.AUTH_SECRET_KEY : If you would like to authenticate your boards using jwt, this declares the secret key.If a stored board SVG becomes unreadable, WBO quarantines the broken file and falls back to any readable board state it can still load.
If you experience an issue or want to propose a new feature in WBO, please open a github issue.
If you are self-hosting a WBO instance, you may want to monitor its load, the number of connected users, request latency, and board lifecycle events.
WBO now uses OpenTelemetry for metrics, logs, and traces on the server side.
Configure a standard OTLP exporter with the usual OTEL_* environment variables.
Example:
docker run \
-e OTEL_SERVICE_NAME=whitebophir-server \
-e OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318 \
lovasoa/wbo
Common settings:
OTEL_SERVICE_NAMEOTEL_RESOURCE_ATTRIBUTESOTEL_EXPORTER_OTLP_ENDPOINTOTEL_EXPORTER_OTLP_METRICS_ENDPOINTOTEL_EXPORTER_OTLP_LOGS_ENDPOINTOTEL_EXPORTER_OTLP_TRACES_ENDPOINTOTEL_EXPORTER_OTLP_HEADERSSocket connection replay is reported with wbo.socket.connection_replay and
wbo.socket.connection_replay.gap. The replay outcome attribute distinguishes
empty replays, sent replay batches, stale baselines, future baselines, and
internal errors.
Traces default to a 5% parent-based sample rate when no standard
OTEL_TRACES_SAMPLER* setting is provided. For short debugging sessions, force
full trace capture explicitly:
OTEL_TRACES_SAMPLER=parentbased_traceidratio \
OTEL_TRACES_SAMPLER_ARG=1.0 \
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318 \
npm start
If no OTLP endpoint is configured, WBO still emits canonical server log lines to stdout/stderr but does not attempt remote export.
To download a preview of a board in SVG format you can got to /preview/{boardName}, e.g. change https://wbo.ophir.dev/board/anonymous to https://wbo.ophir.dev/preview/anonymous. The renderer is not 100% faithful, but it's often good enough.