Element Web

Glossy Matrix client for the web and desktop

Matrix & XMPP ★ 13.5k stars Easy setup AGPL-3.0

Element Web is the flagship Matrix client, providing a polished interface for chat, voice, and video over the Matrix protocol. It can be self-hosted alongside a homeserver for a fully owned communication stack.

Key features

  • Full-featured Matrix client
  • End-to-end encryption
  • Voice and video calls
  • Customizable branding

Pros & cons

Strengths

  • Most mature Matrix client
  • Active development

Trade-offs

  • Heavier than minimal clients
  • Best paired with own homeserver

Element Web replaces

Last reviewed Aug 26, 2026 · 807 words

Serve Element Web from a different hostname than your homeserver. That is the one rule the project's own documentation is loudest about, and the one new Matrix operators break most often: Element on chat.example.com, Synapse on matrix.example.com, never both on the same origin. A homeserver serves user-uploaded content, and a web client sharing that origin gives any uploaded HTML file a path to your session. Get that right and the rest of hosting Element is 128 MB of RAM and one JSON file.

The client is the easy tenth of a Matrix stack

Element Web is static files. An nginx process inside the container serves them, your browser runs them, and every hard part of Matrix lives elsewhere: in the homeserver (Synapse for the full feature set, Conduit if you want something that idles in well under 100 MB), in federation, and in the TURN server for calls. So the catalogue's "Easy" rating is accurate for this component and misleading for the project you are actually undertaking, which is running a homeserver. Budget an evening for Element and a weekend for the rest of the Matrix category. The 128 MB in the catalogue is generous, too: the nginx in that container idles at a fraction of it, and the real memory cost of Element is in the browser tab, which on a busy account with many encrypted rooms can reach several hundred megabytes.

Two files and you are serving it

services:
  element:
    image: vectorim/element-web:latest
    ports:
      - "8080:80"
    volumes:
      - ./config.json:/app/config.json:ro
    restart: unless-stopped
{
  "default_server_config": {
    "m.homeserver": {
      "base_url": "https://matrix.example.com",
      "server_name": "example.com"
    }
  },
  "disable_custom_urls": true,
  "disable_guests": true,
  "brand": "Example Chat"
}

Put a reverse proxy with TLS in front of port 8080; Element needs a secure context for its encryption code, like any app built on the Web Crypto API. default_server_config makes your homeserver the preselected option on the login screen, disable_custom_urls removes the "other homeserver" field so your users cannot wander off, and disable_guests closes guest access. Those three keys are most of what people mean by "our own Element". The rest of config.json is documented in the repository and can stay at defaults. Element fetches the file on page load rather than at container start, so applying a change is a restart of the container plus a hard refresh in the browser; if an edit seems to have no effect, the browser cache is the usual reason.

Voice and video are a separate project

One-to-one calls in Element use WebRTC, and WebRTC across NAT needs a TURN relay. Without one, calls between two people on the same LAN work and calls between anyone else fail silently after ringing, which is a maddening thing to debug. Run coturn with a shared secret, hand that secret to Synapse in its turn_uris and turn_shared_secret settings, and open UDP 3478 plus a relay port range on the firewall. Group calls go through Element Call, which is yet another service. None of this is Element Web's fault, but people install the client, see the call button, and assume it will work.

Why bother when app.element.io exists

The hosted client at app.element.io talks to any homeserver, including yours, so self-hosting the client is optional in a way that self-hosting the server is not. Three things still push me to do it. A pinned homeserver and a custom brand make onboarding a family or a small team obvious: one URL, one login form, nothing to explain. Your users' browsers fetch the client from you rather than from a third party. And you control the update cadence, which matters because Element ships often and a release occasionally changes behaviour you relied on.

The cost is that you now own updates. Pin the image tag rather than latest if people depend on it, read the release notes, and remember that a cached old build in someone's browser can usually talk to a new homeserver but not always the reverse. Element is also the heaviest Matrix client by a distance; the catalogue is right that minimal clients such as Cinny load faster, though none of them match Element's coverage of encryption key backup and device verification, which are the features that stop a family losing their message history.

What I'd do

Synapse on matrix.example.com, Element Web on chat.example.com behind Caddy, the four-key config.json above, and coturn on a public IP from day one so nobody ever asks why calls fail. Pin the Element tag, update monthly alongside Synapse, and treat the client as what it is: a light, replaceable front end on top of the part that actually holds your messages.

Compare Element Web

26 head-to-head comparisons.

Similar matrix & xmpp apps