ScratchPy Studio is a complete Scratch-style visual programming environment that writes, saves and runs genuine Python. Drag a block, and a real line of code appears in the panel next to it. Press the green flag, and that code actually runs.
It is a single file — scratchpy_studio.py — and it needs nothing but the
Python standard library.
python scratchpy_studio.py👉 Try the browser demo — no install, nothing to download
index.html is a standalone version of the same idea: one file, no
dependencies, no build step. 95 blocks, drag and snap, the Python appearing live
beside you, Run working right there in the page — and you can drop a .py file
on it and watch the whole program turn into blocks. The desktop app adds pip,
virtual environments, files, the web blocks and the MCP server.
Most block editors are toys with their own private runtime. This one is a code
generator: everything you build lands in a .py file you can open, read, edit
and hand in.
| 🧩 177 blocks | Hat blocks, C-shaped loops, hexagonal booleans, reporter ovals that drop into slots — the real Scratch 3 shapes and colours |
| 🐍 Real Python, live | The generated source updates as you drag. No hidden interpreter |
print, input, errors and a stop button, all wired to the built-in console |
|
| 👆 Click a block to try it | A loose block runs on its own and reports what it printed in a bubble underneath |
| 🌐 Talks to the web out of the box | GET, POST, headers, JSON, downloads — using urllib from the standard library, so there is nothing to install |
| 🔊 It talks | A Sound category that speaks, plays notes and beats drums — using the voice and speaker the computer already has |
📥 Import any .py |
Turn a program you already have into blocks — loops, functions, try/except, f-strings and all |
| 📦 Every PyPI package | pip dashboard installs anything and turns it into blocks automatically |
| 📦 Makes a real app | One button turns your blocks into a double-clickable .exe — upload any picture and it becomes the icon |
| ⬆️ Updates itself | Looks for a newer version once a day, shows you what changed, and installs it when you say so |
| 🤖 Works with AI | Built-in MCP server so an assistant can build blocks alongside you |
| 🎨 Looks the part | Because half the point of Scratch is that it looks inviting |
Every category lives in one continuous list, the way Scratch does it. Clicking a category glides the drawer to that section instead of swapping the list out, and scrolling by hand moves the highlight along with you.
The rest of the app moves too, in small ways that are meant to be felt rather than watched: the highlight slides between categories, blocks flash softly where they click together, a deleted block shrinks away into the palette, report bubbles pop in, and the green flag has a gentle heartbeat while your program runs. A full rebuild of the whole drawer takes about 40 ms, so none of it gets in your way — and Settings has a switch to turn all of it off.
Blocks lying loose on the canvas are a scratch pad. Click one and it runs on its own, with a little bubble underneath showing what it printed.
- Click a loose block or stack → it runs from there down.
- Click a reporter (the oval ones) → the bubble shows its value.
- Click a hat → its whole script runs, the same as the green flag.
- Blocks that sit inside a script under a hat are left alone, so nothing runs by accident while you are building.
Mistakes are explained rather than dumped — the bubble shows
ZeroDivisionError: division by zero and the full traceback goes to the console.
Custom blocks, variables and packages all work, because the piece is compiled
with the same imports and definitions as the rest of the tab. Variables start
from their starting values each time, and anything still running after 15
seconds is stopped.
The Web category is built in. No pip install requests, no venv, no
waiting — it is urllib from the standard library dressed up as blocks.
| Block | What it does |
|---|---|
text from [url] |
The page or API answer as text |
JSON from [url] |
The answer already turned into records and lists |
send (GET▾) to [url] |
The all-purpose API sender — GET, POST, PUT, PATCH, DELETE, HEAD |
send (POST▾) to [url] with JSON { } |
Send a record as a JSON body |
post form { } to [url] |
The kind of form a web page would send |
send header [name] as [value] |
Set it once; every request after it carries the header — this is where an API key goes |
status code of [url] · [url] is working |
200, 404, or 0 when nothing answers |
download [url] to file [path] |
Save a picture or a file |
[base] with values { } |
Builds ...?q=cats&page=2 safely |
web safe [text] |
Percent-encodes anything for a URL |
A 404 still hands you the body, so you can read the error message an API sends
back. Only http:// and https:// addresses are accepted — a redirect cannot
be talked into reading a local file.
Run → Try a web request, or the button at the top of the Web category:
Pick a method, paste a URL, add a header, press Send. You get the status code, how long it took, and the reply with JSON pretty-printed. Make a block from this then drops the matching blocks straight into your workspace.
The tester runs the same helper functions your blocks compile to, so what you test is exactly what your program will do.
Every computer already has a voice and a speaker — the trouble is that each one
reaches them a different way. Windows has the voices behind Narrator, macOS has
say, Linux has espeak. The Sound category hides the difference, so one
block works on all three.
| Block | What it does |
|---|---|
say [hello] out loud |
Speaks, and waits until the sentence is finished |
start saying [hello] |
Speaks while the program carries on |
set voice to (▾) |
The dropdown lists the voices this computer really has |
set speaking speed to (0) |
−10 is very slow, 10 is very fast |
save speech [hello] to [speech.wav] |
Keeps it as a real sound file |
play note (C4▾) for (0.5) beats |
Middle C is C4 or 60; F#3 and Bb4 work too |
play drum (snare▾) for (0.25) beats |
Twelve drums, synthesised from scratch |
play (440) Hz for (0.5) seconds |
Any frequency, plus sine, square, saw, triangle and noise |
set tempo to (120) beats a minute · rest for (0.25) beats |
Music timing, the way Scratch does it |
play sound [sound.wav] · start sound [sound.wav] |
Your own files — .wav everywhere, .mp3 on Windows and macOS |
stop all sounds · set volume to (100) percent |
Silence, and loudness |
Tones, notes and drums are worked out one sample at a time and written as real
.wav files, so there is no synthesiser to install either. Each one is
calculated once and then reused, so a note inside a loop stays fast.
The Sound tab on the right is a place to make a noise now, before you have built anything:
- Test my speakers plays three notes. If you hear nothing, the problem is the mute switch or the output device, not your program — and it says so.
- Type a sentence, pick a voice, drag the speed and volume, press Say it.
- A little keyboard for trying notes and a drum.
- Put these blocks in my project drops blocks that do exactly what you just heard, sliders and all.
It runs the same helper functions your blocks compile to, so what you hear in the tab is what your program will do.
Using
pyttsx3instead? Its catch is that nothing is heard untilrunAndWait()is called. ScratchPy now ships hand-written blocks for it that always do both — as it does forgttsandplaysound.
Build .exe on the toolbar turns the tab you are on into an application anyone can double-click — no Python installed, nothing to set up.
Choose a picture and it becomes the icon. ScratchPy reads the PNG itself,
with nothing but zlib — every colour type, every bit depth, every line filter,
transparency and all. It centres the picture in a square, box-filters it down to
seven sizes and writes a proper multi-size .ico (and .icns on macOS). GIF
goes through Tk; a JPEG needs Pillow, and ScratchPy offers to install it rather
than pretending it can.
The one thing here that is not standard library is PyInstaller. ScratchPy checks
whether it is there, offers to fetch it with the same pip the Packages tab uses,
and shows the whole build log as it happens. The finished app lands in dist/
next to your project, and Open the folder takes you to it.
- One single file — easier to hand to someone, slower to start.
- Show a console window — leave it on if your program prints or asks questions; turn it off only if it opens a window of its own.
- Build tools work in the system temp folder, not in your project, because OneDrive and Dropbox lock files while they sync and PyInstaller trips over it.
Press Import .py and pick any file. The whole program becomes blocks.
Loops, conditions, functions, try/except, with open(...), f-strings and
comparisons all become proper blocks. Classes, decorators and anything else
exotic are kept word for word inside "python code" blocks, so no program is
ever refused and nothing is ever silently lost. ScratchPy also notices which
packages the file imports and offers to install them and build blocks for them.
Six sample programs were imported, turned back into Python and run: every one produced byte-identical output to the original. ScratchPy's own 6,700-line source imports into 1,793 blocks that still compile.
Type a name, press Install. ScratchPy inspects the package in a sandboxed subprocess and builds a set of blocks for it — and gives each library its own colour so your palette never turns into soup.
Packages → Browse packages opens a shelf of around 85 libraries worth trying, each with a plain description and an honest note about how well it fits block programming:
| Ready made blocks | ScratchPy has hand-written blocks for it |
| Fits nicely | its functions turn into blocks cleanly |
| Opens a window | works, but draws in a window of its own |
| Needs hardware | works, but wants a device plugged in |
| Hard to use as blocks | installs fine, but its ideas do not map well |
Anything already installed, or already turned into blocks, is marked. Type any
name at all and Look it up on PyPI fetches its real description, version and
requires-python, and tells you whether your Python can run it — so you
find out before installing, not after.
- Popular libraries (requests, numpy, pandas, matplotlib, pillow, pygame, turtle) also get hand-written, friendlier blocks.
- Any importable module works — including standard library ones like
turtleandstatistics. - Remove blocks takes a library out of the palette; Uninstall removes the package itself. Blocks you added stay put between sessions.
Flip the switch in Settings and every pip install, every introspection and
every run happens inside a .venv beside your project instead of touching the
Python installed on your computer.
ScratchPy speaks the Model Context Protocol, so Claude Desktop, Claude Code or any other MCP client can work in the same project you have open.
python scratchpy_studio.py --mcp myproject.spy| Tool | What the assistant can do |
|---|---|
write_python |
Hand it Python — it becomes blocks in a tab |
read_blocks |
Read a readable outline of what you have built |
read_code |
Read the Python your blocks generate |
run |
Run a tab and get the output back |
project_overview · set_variable · delete_file · import_python_file |
Project bookkeeping |
list_packages · install_package · add_package_blocks · remove_package_blocks |
pip and block packs |
list_block_types |
Every block ScratchPy knows and the Python each one makes |
The editor watches the project file, so anything the assistant changes shows up in your workspace a second or two later. You can literally watch the blocks appear.
git clone https://github.com/ZDStudios/scratchpy-studio.git
cd scratchpy-studio
python scratchpy_studio.pyThat is the whole install. No pip, no virtualenv, no build step.
No Python at all? Take one from the latest release:
| Platform | File | How to run it |
|---|---|---|
| Windows | ScratchPyStudio.exe |
Double-click it |
| macOS | ScratchPy-Studio-macOS.zip |
Unzip, then right-click → Open the first time |
| Linux | ScratchPy-Studio-Linux.zip |
Unzip, chmod +x ScratchPyStudio, run it |
Each build carries its own Python, so your block programs run even on a machine that has none. (Installing packages with pip still wants a normal Python.)
Once a day, quietly, ScratchPy asks GitHub whether there is a newer version. If there is, it says so — once — and offers to go and get it.
Install it now does the whole thing:
- Running from source? ScratchPy is a single
.pyfile, so it downloads the new one, checks it compiles and really is ScratchPy at the version it claims, keeps your old copy asscratchpy_studio.previous.py, and swaps it over. - Running the app? The new build is downloaded, checked against the size GitHub
published, and moved into place — the old one is kept beside it as
.previous. On Windows a running.execannot be written over, but it can be renamed, which is exactly what happens. - Then Restart now closes tidily (asking about unsaved work first) and opens the version you just installed.
Nothing is downloaded, and nothing is replaced, until you press the button.
The rules it will not bend: https only, and only github.com — checked before the request and again after the redirect GitHub sends downloads through. A file that does not compile, does not contain ScratchPy, or carries a different version from the release it came from is thrown away rather than installed.
Help → Check for updates asks straight away. Settings has a switch for the daily look, and the popup has Skip this one if you would rather stay put.
On a school or office network that inspects secure traffic, the download hosts may be unreachable even when github.com is not. ScratchPy says so in plain words and points you at a browser, which will get through.
The version sits next to the name in the purple bar, and Settings → About this copy shows the exact file it is running from, when that file was last changed, and a Check for updates button that asks GitHub. From a terminal:
python scratchpy_studio.py --versionScratchPy Studio 1.0.2
Running from the source file:
C:\...\scratchpy_studio.py
last changed 05 Aug 2026, 12:23
Python 3.14.6, Tk 8.6, Windows 11
125 blocks loaded
Handy when you have both a checkout and a downloaded app on the same machine and want to know which one you just opened.
python scratchpy_studio.py --selftest # build everything head-less, compile every block
python scratchpy_studio.py --make-icons # write .png / .ico / .icns
python scratchpy_studio.py --mcp file.spy # run as an MCP serverpython build_apps.py| Run it on | You get |
|---|---|
| Windows | dist/ScratchPyStudio.exe — one file, double-click, no Python needed |
| macOS | dist/ScratchPy Studio.app plus a .zip beside it |
| Linux | dist/ScratchPyStudio plus a .desktop launcher and icon |
build_apps.py installs PyInstaller if it is missing, draws the icon and
bundles it into the app.
PyInstaller cannot cross-compile — a Mac app has to be built on a Mac. To get all three without owning all three machines, use the included workflow: open the Actions tab, choose Build ScratchPy Studio apps and press Run workflow. It builds on Windows, macOS and Linux at once and attaches all three as downloads.
Every block type is one BlockSpec: a shape, a row of mark-up describing its
label and inputs, and a template for the Python it produces.
B("control_repeat", "control", "c", "repeat %n(times,10)",
"for _ in range(int({times})):\n {BODY0}")That single definition gives you the orange C-shaped block, its editable number slot, and the loop it compiles to. Adding a block is one line; a package pack is a list of them generated by introspecting the module.
The compiler walks the blocks under each hat, collects the imports and runtime
helpers they need, works out which variables need a global declaration, and
writes a tidy module with a main() and an if __name__ == "__main__": guard.
python scratchpy_studio.py --selftestBuilds the entire interface head-lessly, then compiles every one of the 177 blocks, checks the block definitions are well formed, round-trips a project through save and load, exercises the Python importer and the MCP server, and finally runs the generated example program and checks its output.
index.html is a self-contained demo you can open by
double-clicking it, or visit at
zdstudios.github.io/scratchpy-studio.
It shares the desktop app's ideas in about 3,400 lines of HTML, CSS and JavaScript with no dependencies and no build step:
- 95 blocks across eight categories — the same Scratch 3 shapes, drawn as SVG paths from the same puzzle geometry
- one continuous drawer that glides between sections
- drag, snap, C-shaped mouths, reporters that drop into slots
- the Python it makes, updating live beside you — copy it or save it as a
.py - a small interpreter so Run actually works in the page, and clicking a loose block still shows its answer in a bubble
- it reads Python too — see below
Each block is described once, and that single description drives all three things: how it is drawn, the Python it generates, and how it runs. It works with a mouse, a pen or a finger.
Press Import .py, paste a program, or just drop a .py file onto the page.
A small tokeniser and recursive-descent parser — written from scratch in
JavaScript, since browsers have no ast module — reads the file and builds the
blocks.
It understands assignments, if/elif/else, while, for over range and
over lists, def with parameters and returns, break, continue, f-strings,
comparisons, and/or/not, list and string methods, and the usual built-ins.
Functions become real custom blocks with their own define hat and call block.
Anything it does not recognise is kept word for word in a "python" block, so no
file is ever refused.
Six programs were tested three ways: run by real Python, imported into blocks and run by the demo's own interpreter, then turned back into Python and run again. All three produce identical output every time.
What the demo leaves out: pip, virtual environments, files, the web blocks and the MCP server. Those need a machine, not a tab — they are in the desktop app.
| Name | What it holds |
|---|---|
<project folder>/*.py |
your generated Python, one file per tab |
scratchpy_blocks/ |
cached block packs for packages you added |
scratchpy_assets/ |
the generated icon files |
scratchpy_settings.json |
your preferences (venv, autosave, zoom) |
.venv/ |
only if you switch the venv on in Settings |
Saving a project (a .spy file) puts the generated .py files next to it.
Until you save, they go next to the application.
F5 |
Run |
Esc |
Stop |
Ctrl S / Ctrl O / Ctrl N |
Save / Open / New |
Ctrl I |
Import a Python file |
Ctrl Z |
Undo |
Ctrl , |
Settings |
| Click a loose block | Run just that block and see what it printed |
| Drag a block onto the palette | Delete it |
| Right-click the canvas | Clean up, delete all |
MIT licensed · built with nothing but the Python standard library













