Skip to main content

Glue

Glue is a fork of Eel by Chris Knott and contributors.

Glue Documentation

A little and opinionated Python library for making desktop apps with HTML, CSS, and JavaScript — plus full access to Python. It hosts a local window, then lets Python and JavaScript call each other. No new UI framework.

Presentation — cover Presentation — pitch and simplest app Presentation — familiar window Presentation — Frontend ↔ Backend Presentation — why it feels easy

Contents


Install

pip install glue-ui

That installs the PyPI project glue-ui. You still import glue in Python (the import name is unchanged).

From a clone of this repo:

pip install .

Editable / development (code changes apply without reinstalling):

pip install -e .

Directly from GitHub (no local clone required):

pip install "git+https://github.com/alepodj/Glue.git@main"

Optional extras:

pip install "glue-ui[jinja2]"   # Jinja2 templates
pip install "glue-ui[build]"    # PyInstaller for packaging
pip install "glue-ui[splash]"   # Transparent PNG/APNG/GIF startup splash
# From a clone: pip install ".[jinja2]" / ".[build]" / ".[splash]"

Quick start

Put your frontend in a folder named ui/ and index.html (the defaults), then:

import glue

glue.init()    # Override ui/ with any path: glue.init('frontend')
glue.start()   # Override index.html: glue.start('main.html')

By default Glue opens a PyWebView native window. If that isn’t available, it falls back to Chrome/Chromium in app mode (--app), then Edge on Windows only.

Include the bridge on every html page:

<script type="text/javascript" src="/glue.js"></script>

Put your backend logic in one Python file or split it across any folders you like — Glue does not require a fixed backend layout. You own the imports and paths; your start point is the Python script you run (often at the project root).

Security note

By default Glue binds to localhost and only accepts Glue WebSocket clients from the loopback interface. Every @glue.exposed Python function is callable by any page that can open that socket — treat exposed functions as your trust boundary. Do not put secrets under the frontend folder (ui/). Use all_interfaces=True only on trusted networks (it allows remote clients to call exposed functions).


Demo

The examples/00 - presentation app is a full walkthrough of the idea. Run it with:

cd "examples/00 - presentation"
python presentation.py

Call both ways

Python → available in JavaScript

#python
@glue.expose
def add(a, b):
    return a + b
//javascript
let sum = await glue.add(1, 2)();

JavaScript → available in Python

//javascript
glue.expose(js_random);
function js_random() {
  return Math.random();
}
#python
n = glue.js_random()()          # wait for the value
glue.js_random()(print)         # or use a callback

Complex values travel as JSON over a WebSocket.

If a JS bundler renames functions, expose them with an explicit name:

glue.expose(someFunction, "my_javascript_function");

Hello, World!

Full example: examples/01 - hello_world

ui/index.html

<!doctype html>
<html lang="en">
  <head>
    <title>Hello, World!</title>
    <script src="/glue.js"></script>
    <script src="/app.js" defer></script>
  </head>
  <body>Hello, World!</body>
</html>

ui/app.js

glue.expose(say_hello_js);
function say_hello_js(source) {
  console.log(`Hello from ${source}`);
}

say_hello_js("JavaScript World!");
glue.say_hello_py("JavaScript World!");

hello_world.py

import glue

glue.init()

@glue.expose
def say_hello_py(x):
    print('Hello from %s' % x)

say_hello_py('Python World!')
glue.say_hello_js('Python World!')

glue.start()

From the example directory, run python hello_world.py. Python→JS calls made before the page WebSocket connects are queued and flushed when the bridge is ready (the HTTP server is already up before the window opens).


Return values

Python and the UI talk over a WebSocket. Glue gives you two ways to get a result back.

Callback

glue.js_random()(lambda n: print('Got', n))

Synchronous wait (after glue.start(), or with block=False)

n = glue.js_random()()

In JavaScript, use async / await:

let n = await glue.py_random()();

Python waits up to 10 seconds for a JS result by default (js_result_timeout on glue.init).


Settings

Optional durable JSON for themes, defaults, and other app state that should survive restarts. Python only — expose your own wrappers with @glue.expose if the UI needs it.

The default location is opt-in via app_name. Without it, Glue does not invent a path; you can still load/save any file with an explicit path.

import glue

glue.init(app_name='myapp')   # unlocks ~/.myapp/myapp.json (all OS)

data = glue.settings()        # {} if the file does not exist yet — nothing created on disk
data['theme'] = 'dark'
glue.save_settings(data)      # first save creates ~/.myapp/ and writes myapp.json
# Custom file — works with or without app_name
data = glue.settings('/path/to/prefs.json')
glue.save_settings(data, '/path/to/prefs.json')
# or after settings(path), save_settings(data) reuses that path
  • Structure of the JSON object is yours.
  • glue.settings_path() returns the default path when app_name is set.
  • Passing app_name alone does not create a folder or an empty file.

App options

Pass keyword arguments to glue.start():

Option Default Source Hosts Notes
(pages) 'index.html' Glue all Which HTML file(s) to open first (positional args), they must come before keyword args
mode 'auto' Glue all How to show the UI — see Hosts
host 'localhost' Glue all Hostname the local HTTP server binds to
port 8000 Glue all Server port (0 = pick a free port)
block True Glue all Keep start() running until the app exits. False returns so you can run your own loop (skips PyWebView in auto; PyWebView needs True)
jinja_templates None Glue all Subfolder of the UI root for Jinja2 templates
close_callback None Glue all Called when a window/WebSocket closes (cleanup / last-window logic)
all_interfaces False Glue all Listen on 0.0.0.0 and allow remote WebSocket clients (trusted networks only)
disable_cache True Glue all Send no-store so the browser doesn’t cache UI assets during development
default_path 'index.html' Glue all Page served for the root URL / (homepage)
app new Bottle Glue all Optional custom Bottle app (e.g. add auth/session middleware). Omit to let Glue create one
shutdown_delay 1.0 Glue all Seconds to wait after the last window closes before the Python process exits
splash False Glue webview / chrome / edge / custom True discovers a PNG/APNG/GIF named splash in the UI or project root; a path selects an explicit image. Requires glue-ui[splash]
splash_min_duration 1.0 Glue webview / chrome / edge / custom Minimum visible seconds; readiness and this duration must both complete before fade-out (0 disables the minimum)
geometry {} Glue webview / chrome / edge Per-page size/position when opening multiple files. Keys = page paths; values = {'size': (w, h), 'position': (x, y)} (either key optional). Overrides global size / position for that page
app_mode True Glue chrome/edge Open Chromium in --app (desktop-like, less browser chrome)
cmdline_args ['--disable-http-cache'] Glue chrome/edge/custom Chrome/Edge: extra flags appended to the browser command. custom: the full Popen argv (executable + args)
frameless True PyWebView webview Hide the OS title bar; Glue draws its in-page title bar (False = stock OS chrome)
easy_drag False PyWebView webview Allow dragging a frameless window from any empty area of the page
shadow True on Windows PyWebView webview Draw a drop shadow around the window
debug False PyWebView webview Enable DevTools / shortcuts (F12, F5, context menu) for debugging
confirm_close False PyWebView webview Ask “close this window?” before quitting
fullscreen False PyWebView webview Start covering the whole screen
minimized False PyWebView webview Start in the taskbar/dock (hidden until restored)
maximized False PyWebView webview Start maximized to the work area
on_top False PyWebView webview Keep the window above other windows
min_size (200, 100) PyWebView webview Smallest (width, height) the user can resize to
icon ui/favicon.ico if present PyWebView webview / chrome / edge PyWebView: app icon in the taskbar/dock (.ico / .icns). Chrome/Edge: caption icon comes from the page favicon — Glue injects <link rel="icon" href="/favicon.ico?v=…"> into served HTML when ui/favicon.ico exists
gui auto PyWebView webview Force a specific web engine backend ('edgechromium', 'qt', 'gtk', …)
menu [] PyWebView webview Native menu bar (Menu / MenuAction from PyWebView); empty = no menus
webview_options {} PyWebView webview Escape hatch for other PyWebView kwargs; first-class kwargs win on conflict
size None → 1280×720 both webview / chrome / edge Initial content size as (width, height) pixels
position None both webview / chrome / edge Initial screen position as (left, top); omit to leave placement to the host (usually centered)
centered None → splash default both webview / chrome / edge Explicitly center the window. Automatically enabled with splash; use False to opt out. Explicit global position and per-page positions override the automatic default. On Windows PyWebView this uses the primary work area; Chrome/Edge/macOS/Linux rely on available-screen or host placement
title 'Glue' both webview Text in the native/in-page title bar (Chrome/Edge use the page <title>)
resizable True both webview Allow the user to resize; on Windows frameless, Glue adds edge grips

Source Annotations:

  • Glue = Glue’s own API
  • PyWebView = A PyWebView window option that Glue exposes on glue.start() for convenience
  • both = Glue has always offered this as a first-class glue.start() option, and when the host is PyWebView, Glue also maps it into PyWebView’s kwargs

Hosts Annotations:

  • Which window host honors the option (all, webview, chrome/edge, …).

Defaults:

  • What you get when you omit the argument (Glue’s opinionated window setup). Pass an explicit value to override; that also wins over the same key in webview_options. Glue sets SHOW_DEFAULT_MENUS=False so stock Edit menus stay off unless you build your own. Advanced window control: glue.get_webview_windows().

Startup splash

Install glue-ui[splash], then place a PNG, APNG, or GIF named splash in your project root or UI folder:

import multiprocessing
import glue

def main():
    glue.init()
    glue.start(splash=True, splash_min_duration=1.0)
    # Or select a file: glue.start(splash='branding/launch.gif')

if __name__ == '__main__':
    multiprocessing.freeze_support()
    main()

Glue renders the image in an independent, frameless GLFW window with transparent pixels. Images taller than 500px are scaled down to that height while preserving aspect ratio; this limit is fixed and not configurable. The splash fades after the initial page loads, connects, and paints, while honoring the minimum duration. The main app centers automatically when a splash is enabled; pass centered=False or position=(x, y) to override it. The guarded entrypoint is required by Windows multiprocessing and works with frozen applications. Missing optional dependencies or unavailable compositor transparency disable only the splash; the main app continues starting.


Hosts

How Glue opens your UI on any OS.

Default order for mode='auto':

  1. PyWebView — native window (Windows / macOS / Linux); full control via PyWebView APIs / glue.get_webview_windows()
  2. Chrome/Chromium — app mode (--app) on all platforms
  3. Edge — Windows only, if Chrome is missing

size / position / centered / geometry apply on PyWebView as window kwargs, and on Chrome/Edge via /glue.js (resizeTo / moveTo on page load). Splash-enabled apps center automatically unless centered=False or an explicit position is supplied. On Windows PyWebView, centered=True calculates the primary work-area position; elsewhere Glue omits coordinates or uses screen.avail* so the host/browser centers. An explicit per-page geometry position overrides centering. When ui/favicon.ico is present, Glue injects a favicon <link> into served HTML so Chrome/Edge --app windows can show it in the caption.

PyWebView window options are first-class on glue.start() (plus webview_options escape hatch).

mode Behavior
'auto' PyWebView → Chrome → Edge (Windows); see above
'webview' PyWebView only
'chrome' / 'edge' Force that Chromium browser
None Server only (tests, custom frontends)
'custom' Raw Popen(cmdline_args) — the full process argv (executable + args)

Try 01 - hello_world for auto launch, or 02 - hello_world_chrome to force Chrome.


Async Python

Glue uses Bottle + Gevent. Prefer glue.sleep() and glue.spawn() over time.sleep().

If you need Gevent monkey-patching, do it before importing Glue:

from gevent import monkey
monkey.patch_all()
import glue
import glue
glue.init()

def ticker():
    while True:
        print("tick")
        glue.sleep(1.0)

glue.spawn(ticker)
glue.start(block=False)

while True:
    print("main")
    glue.sleep(1.0)

With block=False, mode='auto' skips PyWebView (its GUI loop needs the main thread) and uses Chrome/Edge instead.


Package with PyInstaller

  1. Use a clean virtualenv with only what you need
  2. pip install "glue-ui[build]" (or pip install ".[build]" from a clone)
  3. From your app folder: python -m glue your_script.py ui
  4. Check dist/, then ship with --onefile --noconsole when ready

Extra PyInstaller flags pass through, for example:

python -m glue file_access.py ui --exclude numpy --onefile --noconsole

See the PyInstaller docs for more.


Examples

Example What it shows
00 - presentation Full product story (screenshots above)
01 - hello_world Minimal two-way hello
02 - hello_world_chrome Force Chrome
03 - callbacks Async callbacks
04 - sync_callbacks Blocking returns
05 - file_access Python file access from the UI
06 - input Live input bridge
07 - jinja_templates Jinja2 pages
08 - createreactapp React + TypeScript
09 - disable_cache Cache control
10 - custom_app_routes Custom Bottle routes
11 - splash Transparent GLFW startup splash
12 - scrolling Scrolling content isolated below the title bar

Project layout

my_app.py
ui/
  index.html
  css/
  js/

Frontend lives under one ui/ root (by default). Backend is normal Python. Glue glues them together.

Metadata

Release files for glue-ui 0.6.7

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for glue-ui 0.6.7
File Size Uploaded
glue_ui-0.6.7.tar.gz 51.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for glue-ui 0.6.7
File Interpreter ABI Platform
glue_ui-0.6.7-py3-none-any.whl Python 3 none any Details

Total release size: 99.3 kB

Release files / glue_ui-0.6.7.tar.gz

Download URL glue_ui-0.6.7.tar.gz
Size 51.3 kB
Tags Source
SHA-256 checksum
How to use checksums
d4ce03a01ade2e1b7ab86bda2a7928b580172979d105a04234961cd9d3661fa4
BLAKE2b-256 checksum
How to use checksums
74e2451729d693fac87b6ba02cfd381458adfeb788f1e4a6571f29ac297cccb8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 2, 2026.

Transparency log

Release files / glue_ui-0.6.7-py3-none-any.whl

Download URL glue_ui-0.6.7-py3-none-any.whl
Size 48.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
219c1782ecfae01581e0fa0befa9a03105bce6f0bac55bce5de50915adebc935
BLAKE2b-256 checksum
How to use checksums
3e569c142a99ff712bfeab829a34f988597bab3d308cf3778dc6a47cf4be5989
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 2, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.6.7 This release

2 release files

0.6.6

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page