xdg-desktop-portal Explained: How Sandboxed Apps Reach Your Files
A Flatpak with no filesystem permission at all can still open your documents, because you picked them. The mechanism that makes that work is xdg-desktop-portal, and it is also why screen sharing on Wayland either works perfectly or not at all.
The problem
A sandboxed application has no access to your files, your screen, or your printer. That is the point. But it still needs to open documents, and hardcoding access defeats the sandbox.
Portals invert the flow. The application asks, a dialog drawn outside the sandbox appears, the user chooses, and the application receives access to that one thing.
Permission by user action rather than by configuration. The application never sees your directory listing, only the file you handed it.
Two pieces
# the frontend, one of these
xdg-desktop-portal
# the backend, matching your desktop
xdg-desktop-portal-gnome
xdg-desktop-portal-kde
xdg-desktop-portal-wlr
xdg-desktop-portal-hyprland
xdg-desktop-portal-gtk
xdg-desktop-portal is the D-Bus service applications talk to. It implements the interfaces and routes requests.
The backend draws the actual dialogs and implements the desktop-specific parts. Which one you need depends on your desktop environment, and this is where nearly every portal problem comes from.
# what is installed
ls /usr/share/xdg-desktop-portal/portals/
rpm -qa 'xdg-desktop-portal*' # Fedora
dpkg -l 'xdg-desktop-portal*' # Debian and Ubuntu
# what is running
systemctl --user status xdg-desktop-portal xdg-desktop-portal-'*'
| Desktop | Backend |
|---|---|
| GNOME | xdg-desktop-portal-gnome |
| KDE Plasma | xdg-desktop-portal-kde |
| Sway, river, other wlroots | xdg-desktop-portal-wlr |
| Hyprland | xdg-desktop-portal-hyprland |
| Anything else | xdg-desktop-portal-gtk |
Install one that matches, not several. Multiple backends claiming the same interface produce the wrong dialog or no dialog, and the failure is silent.
The gtk backend is the generic fallback. It works everywhere and gives you GTK dialogs, which look out of place on KDE but function. On a minimal window manager it is usually the right choice, sometimes alongside wlr for screen capture specifically.
Which interfaces exist
busctl --user introspect org.freedesktop.portal.Desktop /org/freedesktop/portal/desktop | grep portal
| Portal | What it provides |
|---|---|
FileChooser | Open and save dialogs |
ScreenCast | Screen and window capture |
Screenshot | Still capture |
RemoteDesktop | Input injection, remote control |
Print | Printing |
Notification | Desktop notifications |
OpenURI | Open a link or file in the default handler |
Settings | Read the theme and colour scheme |
Inhibit | Prevent sleep or screen lock |
GlobalShortcuts | Keyboard shortcuts outside the window |
Camera | Webcam access |
Secret | Keyring access |
Not every backend implements every interface. wlr handles screen capture and little else, which is why a Sway setup frequently needs wlr and gtk together.
Screen sharing, the common failure
On X11, any application could read the whole screen. That was convenient and a serious security problem.
Wayland removed the ability entirely. No application can read the screen, including a legitimate one. Capture goes through the ScreenCast portal: the application requests, the compositor shows a picker, and the stream arrives over PipeWire.
# the chain that has to be intact
systemctl --user status pipewire pipewire-pulse wireplumber
systemctl --user status xdg-desktop-portal
ls /usr/share/xdg-desktop-portal/portals/
When a browser share fails, work through it in order:
No window picker appears. The backend is missing or is the wrong one for your compositor.
Picker appears, stream is black. Usually PipeWire, or a browser that needs its Wayland flags. Check wireplumber is running.
No share option at all. For Firefox, MOZ_ENABLE_WAYLAND=1. For Chromium, the WebRTC PipeWire capturer flag.
# Firefox on Wayland
MOZ_ENABLE_WAYLAND=1 firefox
# Chromium, check the flag state
chromium --enable-features=WebRTCPipeWireCapturer
# test the portal directly, without a browser
gdbus call --session \
--dest org.freedesktop.portal.Desktop \
--object-path /org/freedesktop/portal/desktop \
--method org.freedesktop.portal.Screenshot.Screenshot "" "{}"
If that produces a screenshot, the portal works and the problem is in the application. If it does not, the portal is your problem. Our Wayland screenshots guide covers the capture side more fully.
The document portal
The clever part, and the reason a Flatpak with zero filesystem access can open files.
# what a sandboxed app actually sees
flatpak run --command=ls org.example.App /run/user/1000/doc/
# grants that exist
flatpak documents
flatpak documents org.example.App
When you pick a file, the portal exposes that file alone through a fuse filesystem at /run/user/$UID/doc/, with a per-application view. The application sees a path it can open. It cannot see the rest of the directory, and it cannot reach anything it was not given.
# revoke everything for one application
flatpak permission-remove documents org.example.App
# see all stored permissions
flatpak permissions
Grants persist. They accumulate over months and nothing prompts you to review them. Worth clearing occasionally, and our Flatpak permissions guide covers the wider permission model, including why an application granted --filesystem=home bypasses all of this.
Configuration
# ~/.config/xdg-desktop-portal/portals.conf
[preferred]
default=gtk
org.freedesktop.impl.portal.ScreenCast=wlr
org.freedesktop.impl.portal.Screenshot=wlr
org.freedesktop.impl.portal.FileChooser=gtk
This is how you route interfaces to different backends, which is exactly the Sway case: wlr for capture, gtk for everything else.
# after editing
systemctl --user restart xdg-desktop-portal
wlr also has its own config for capture behaviour:
# ~/.config/xdg-desktop-portal-wlr/config
[screencast]
output_name=DP-1
max_fps=30
chooser_type=simple
chooser_cmd=slurp -f %o -or
chooser_cmd with slurp gives you region selection rather than whole-output capture, which is the setup most people want once they know it exists.
Debugging
# watch the portal work
systemctl --user restart xdg-desktop-portal
journalctl --user -f -u 'xdg-desktop-portal*'
# verbose
systemctl --user stop xdg-desktop-portal
G_MESSAGES_DEBUG=all /usr/libexec/xdg-desktop-portal -r -v
# watch the D-Bus traffic
dbus-monitor --session "interface='org.freedesktop.portal.FileChooser'"
That last one is the most informative when an application claims a dialog failed. You see the request, the backend it went to, and the reply.
Common causes, in order:
No backend installed. The frontend runs, nothing answers.
Wrong backend for the desktop. Dialogs appear and behave oddly, or one interface silently does nothing.
Multiple backends. Conflicting claims on the same interface.
Not restarted after installing one. The service caches which backends exist at startup.
Environment variable wrong. Backends key off XDG_CURRENT_DESKTOP:
echo "$XDG_CURRENT_DESKTOP"
# sway or GNOME or KDE
An unset or unexpected value means no backend recognises the session. On a bare window manager, setting it explicitly in the session startup is the fix.
Portals outside Flatpak
A misconception worth correcting: portals are not a Flatpak feature. They are a desktop interface that Flatpak makes heavy use of.
A natively installed Firefox on Wayland needs the ScreenCast portal for screen sharing, because the restriction is Wayland’s and applies to everything. Snap uses portals. Native applications use them for the Settings interface to follow your light or dark theme.
# read the theme preference the portal way
gdbus call --session \
--dest org.freedesktop.portal.Desktop \
--object-path /org/freedesktop/portal/desktop \
--method org.freedesktop.portal.Settings.Read \
org.freedesktop.appearance color-scheme
1 means prefer dark. This is how an application follows your system theme without knowing which desktop it is on, and it is why dark mode switching now works in applications that used to need manual configuration.
A minimal working setup
GNOME or KDE: your distribution installed the right backend already. If capture fails, check PipeWire rather than portals.
Sway or another wlroots compositor:
sudo apt install xdg-desktop-portal xdg-desktop-portal-wlr xdg-desktop-portal-gtk
# ~/.config/xdg-desktop-portal/portals.conf
[preferred]
default=gtk
org.freedesktop.impl.portal.ScreenCast=wlr
org.freedesktop.impl.portal.Screenshot=wlr
# in your sway config, so backends see the session
exec dbus-update-activation-environment --systemd \
WAYLAND_DISPLAY XDG_CURRENT_DESKTOP=sway
That dbus-update-activation-environment line is the one people miss. Services started by D-Bus activation do not inherit your compositor’s environment unless you push it to them, and without it the backend cannot find the display.
Frequently Asked Questions
What is xdg-desktop-portal?
A D-Bus service that lets sandboxed applications request things they cannot do themselves, such as opening a file or capturing the screen. The dialog runs outside the sandbox, the user chooses, and the application receives access only to what was chosen. It is permission by user action rather than by configuration.
Why does screen sharing not work in my browser on Wayland?
Wayland gives no application the ability to read the screen, so capture goes through the ScreenCast portal and PipeWire. If the portal or its backend for your desktop is missing, there is nothing to pick a window with and the share fails or shows a black frame.
Why does my file chooser dialog look wrong in a Flatpak app?
Because the dialog is drawn by the portal backend, not the application. If the GTK backend is installed on a KDE system you get a GTK dialog inside a Qt app. Installing the backend that matches your desktop fixes the appearance and the theming.
Do I need a portal if I do not use Flatpak?
Increasingly yes. Wayland screen capture, global shortcuts and the remote desktop path all go through portals regardless of how the application was installed, so a native browser on Wayland needs one for screen sharing just as a Flatpak does.
Which portal backend should I install?
The one matching your desktop: xdg-desktop-portal-gnome on GNOME, -kde on KDE Plasma, -wlr for wlroots compositors such as Sway, and -hyprland for Hyprland. Install exactly one that matches, plus the gtk backend as a generic fallback if a specific one does not cover everything.
Why does a Flatpak app remember file access after I closed it?
Because the document portal keeps the grant, exposing the chosen file through a per-application view in a fuse filesystem. You can list existing permissions with flatpak documents and revoke them, which is worth doing occasionally since grants accumulate silently.