Brython in HTML: run Python from one file

Brython is a JavaScript file that reads Python written inside your HTML. Load it from a CDN, call it once on page load, and the page runs Python with no install.

Brython (Browser Python) is an implementation of Python 3 that runs in the browser, with an interface to the page and its events. This guide covers it as a library in a plain HTML file.

Add a script tag, call brython(), and write Python in a tag of its own.

Try it first. Click the button and watch Python count.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Brython: a click counter written in Python</title>
<!-- 1. load Brython from a CDN, with the version pinned -->
<script src="https://cdn.jsdelivr.net/npm/brython@3.14.3/brython.min.js"></script>
<style>
  body { margin: 0; padding: 24px; font-family: system-ui, sans-serif; background: #f4f5f7; }
  button {
    font: inherit; font-weight: 600; padding: 12px 20px; border: 0; border-radius: 10px;
    background: #2563eb; color: #fff; cursor: pointer;
  }
  p { margin: 14px 0 0; color: #374151; }
</style>
</head>
<!-- 2. brython() reads every <script type="text/python"> once the page has loaded -->
<body onload="brython({debug: 1, indexedDB: false})">
<button id="btn">Click me</button>
<p id="msg">Nothing clicked yet.</p>

<!-- 3. this block is Python, not JavaScript -->
<script type="text/python">
from browser import document, bind

count = 0

@bind(document["btn"], "click")
def on_click(ev):
    global count
    count += 1
    document["msg"].text = f"Clicked {count} time(s) - counted by Python."
</script>
</body>
</html>
A button and a counter, with the logic written in Python. Edit the code and the example reruns.

The three parts a Brython page needs

The Brython documentation boils the setup down to three steps. Every working page has all three.

The three parts of a Brython page: load the script, call brython on page load, write Python in a text/python tag.
The three parts of a Brython page: load the script, call brython on page load, write Python in a text/python tag.
  1. Load the script. The README shows it from the jsDelivr CDN, with the version in the address. This guide uses version 3.14.3, the one the examples were tested with.
  2. Run brython() on page load. The docs put it in the onload attribute of the <body> tag. Passing 1 sends error messages to the browser console.
  3. Write Python in a script tag whose type is text/python.
<script src="https://cdn.jsdelivr.net/npm/brython@3.14.3/brython.min.js"></script>

<body onload="brython({debug: 1, indexedDB: false})">
  <button id="btn">Click me</button>
  <script type="text/python">
  from browser import document, bind

  @bind(document["btn"], "click")
  def on_click(ev):
      document["btn"].text = "Clicked"
  </script>
</body>

Pinning the version matters. An address without a version resolves to the latest release, so the code it serves can change later.

What happens in the browser

Nothing runs on a server. When the page loads, brython() looks for every script tag of type text/python, translates the Python to JavaScript, and runs the result.

The page loads brython.min.js from the CDN, brython finds the text/python tags, translates them to JavaScript and runs them in the visitor's browser.
The page loads brython.min.js from the CDN, brython finds the text/python tags, translates them to JavaScript and runs them in the visitor's browser.

That is why one .html file is enough. The Brython FAQ says it supports all modern browsers, including on smartphones, so the same file opens on a phone.

Reading and changing the page from Python

The browser module is how Python reaches the page. document acts like a dictionary keyed by element id, and @bind attaches an event handler to an element.

To create elements, use the html module. html.LI("text") makes a list item, and the <= operator adds a child to an element. The example below uses all three.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Brython: build page elements from Python</title>
<script src="https://cdn.jsdelivr.net/npm/brython@3.14.3/brython.min.js"></script>
<style>
  body { margin: 0; padding: 20px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  form { display: flex; gap: 8px; flex-wrap: wrap; }
  input { flex: 1 1 160px; min-width: 0; font: inherit; padding: 10px 12px; border: 1px solid #cbd1da; border-radius: 8px; }
  button { font: inherit; font-weight: 600; padding: 10px 16px; border: 0; border-radius: 8px; background: #2563eb; color: #fff; cursor: pointer; }
  ul { list-style: none; margin: 14px 0 0; padding: 0; display: grid; gap: 8px; }
  li { background: #fff; border-radius: 10px; padding: 10px 14px; box-shadow: 0 2px 8px rgba(0,0,0,.08); display: flex; justify-content: space-between; align-items: center; gap: 10px; }
  li button { background: #e5e7eb; color: #374151; padding: 4px 10px; }
  #count { margin: 12px 0 0; color: #4b5563; font-size: 14px; }
</style>
</head>
<body onload="brython({debug: 1, indexedDB: false})">
<form id="form">
  <input id="task" placeholder="Add a task" autocomplete="off">
  <button>Add</button>
</form>
<ul id="list"></ul>
<p id="count"></p>

<script type="text/python">
from browser import document, html

def update_count():
    n = len(document["list"].children)
    document["count"].text = f"{n} task(s) on the list"

def remove(ev):
    ev.target.parent.remove()          # the <li> that holds the button
    update_count()

def add(ev):
    ev.preventDefault()                # handle the form inside the page
    text = document["task"].value.strip()
    if not text:
        return
    # html.LI(...) creates an element; <= appends a child to it
    li = html.LI(text)
    button = html.BUTTON("Remove")
    button.bind("click", remove)
    li <= button
    document["list"] <= li
    document["task"].value = ""
    update_count()

document["form"].bind("submit", add)
update_count()
</script>
</body>
</html>
A task list built from Python. Add a task with Enter or the button, then remove it. The form is handled inside the page, so nothing is sent anywhere.

Two details are worth copying. The add handler calls ev.preventDefault(), so the form does not reload the page. And results go into an element with .text, because print() writes to the browser console, not onto the page.

Using the standard library

Importing from the standard library needs a second file. The FAQ says that when such an import fails, the likely reason is that brython_stdlib.js is not included in the page.

Without the brython_stdlib script an import from collections fails with an error in the console. With it, the import works.
Without the brython_stdlib script an import from collections fails with an error in the console. With it, the import works.

For version 3.14.3 the jsDelivr file listing shows brython.min.js at 1.29 MB and brython_stdlib.js at 4.57 MB. Add the second file when your Python imports from the standard library, and leave it out when it does not.

This finished example counts words with re and collections.Counter and draws a bar for each of the most common words. Edit the text and the bars update.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Brython: word frequency with the standard library</title>
<script src="https://cdn.jsdelivr.net/npm/brython@3.14.3/brython.min.js"></script>
<!-- needed for "from collections import Counter" and other standard-library imports -->
<script src="https://cdn.jsdelivr.net/npm/brython@3.14.3/brython_stdlib.js"></script>
<style>
  body { margin: 0; padding: 20px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  textarea { width: 100%; box-sizing: border-box; height: 110px; font: inherit; padding: 10px 12px; border: 1px solid #cbd1da; border-radius: 8px; resize: vertical; }
  #stats { margin: 12px 0; font-weight: 600; }
  .row { display: grid; grid-template-columns: 90px 1fr 28px; align-items: center; gap: 8px; margin: 5px 0; font-size: 14px; }
  .bar { height: 14px; border-radius: 7px; background: #2563eb; min-width: 4px; }
  .w { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
</style>
</head>
<body onload="brython({debug: 1, indexedDB: false})">
<textarea id="text">the cat saw the dog and the dog saw the bird. a bird is not a cat, and a cat is not a dog.</textarea>
<p id="stats"></p>
<div id="bars"></div>

<script type="text/python">
import re
from collections import Counter
from browser import document, html

def refresh(ev=None):
    words = re.findall(r"[a-z']+", document["text"].value.lower())
    top = Counter(words).most_common(6)
    document["stats"].text = f"{len(words)} words, {len(set(words))} different"
    box = document["bars"]
    box.clear()
    biggest = top[0][1] if top else 1
    for word, n in top:
        bar = html.DIV(Class="bar", style={"width": f"{n * 100 // biggest}%"})
        row = html.DIV(Class="row")
        row <= html.SPAN(word, Class="w")
        row <= html.DIV(bar)
        row <= html.SPAN(str(n))
        box <= row

document["text"].bind("input", refresh)
refresh()
</script>
</body>
</html>
A word-frequency chart written in Python. It loads brython_stdlib.js to import re and collections.

Limits to know before you start

Brython runs Python in a browser, so some Python habits do not carry over.

  • Only pure-Python modules. The FAQ says only modules written entirely in Python can be imported, and modules that need operating-system features cannot work.
  • External files must share the domain. A script loaded with src goes through an Ajax call and must be in the same domain as the HTML page. Inline Python avoids the question.
  • First load downloads the engine. The visitor's browser fetches the Brython file before any of your Python runs.

For plain interaction, as in the examples above, none of these get in the way.

When it does not work

Python errors go to the browser console, so open the developer tools and read the Console tab first.

Symptom Cause Fix
Console shows a SyntaxError on your Python The script tag has no type="text/python" Add the type, so Brython handles it
ModuleNotFoundError for a standard module brython_stdlib.js is not in the page Add the second script tag
KeyError naming an id document["id"] does not match any id Check the spelling in the HTML
Nothing appears when you call print() print() writes to the console Set .text on an element
import requests fails The module needs features a browser does not offer Use a pure-Python module, or do the task in the page
A src Python file does not load It must be in the same domain as the page Move the Python inline
You see no error at all The console is closed Open it, and call brython({debug: 1})
SecurityError about IDBFactory, nothing runs The page runs in a sandboxed frame that cannot open IndexedDB, and Brython caches standard-library modules there Pass indexedDB: false to brython()

A Python page is easier to show than to describe. A screenshot cannot be clicked, and an .html attachment may open as plain code on a phone. Opening an HTML file on a phone covers why.

To send the working version, paste the page into a NOS document and choose Create share link. HTML to link walks through it.

The page renders as written, scripts from jsDelivr load, and the Python runs, so the people you send it to can click and type in it themselves. If you change the code later, the same link shows the new version.

If your Python tries to reach another site, such as with requests, NOS blocks that request. Keep the logic inside the page. For wider script problems, see HTML JavaScript not working.

Questions people ask

Is Brython JavaScript or Python?

Both, in different places. Brython itself is a JavaScript file that the page loads. The code you write inside script type="text/python" tags is Python, and Brython translates it to JavaScript in the visitor's browser before running it.

Do I need to install Python to use Brython?

No. The page only needs the Brython script. The translation happens in the browser, so nothing has to be installed on your computer or on the computer of the person who opens the page.

Where does print() output go?

To the browser console, not onto the page. To show a result to the visitor, set the text of an element instead, as the examples above do with document["msg"].text.

Can I keep the Python in a separate .py file?

Yes, with the src attribute on the script tag. The Brython docs say the file is loaded with an Ajax call and must be in the same domain as the HTML page. For a single shareable file, keep the Python inline.

Can I use any Python module, such as requests?

No. The Brython FAQ says only modules written entirely in Python can be imported, and modules that need operating-system features, such as requests, cannot work in a browser. Check the Brython documentation before you plan around a module.

Keep reading