flow-render
Renders a Flow Launcher search-result mockup to HTML, screenshots it headlessly with Playwright, and crops the result to a transparent PNG — useful for generating plugin screenshots for READMEs and store listings without staging a real launcher window.
How it works
- A
Config(from a JSON file, or built by running a real plugin) describes the search bar and its results. renderer.pyrenderstemplates/base.htmlwith Jinja2 into a temporaryoutput.html.screenshot.pyloads that file in headless Chromium and screenshots it with a transparent background.image.pycrops to the smallest bounding box and writesoutput_<timestamp>_<id>.pngto the output directory.
Intermediate build files live in a temp directory that's cleaned up automatically
after each run — nothing is left behind wherever you happen to invoke the CLI from.
The final PNG defaults to a per-user data directory (%LOCALAPPDATA%\flow-render\output
on Windows, ~/Library/Application Support/flow-render/output on macOS,
$XDG_DATA_HOME/flow-render/output or ~/.local/share/flow-render/output on Linux) and
accumulates there across runs; override it per-run with -o/--output.
Requirements
- Python 3.10+
- Chromium via Playwright (installed automatically by the run scripts)
Install
Install as a standalone CLI with uv:
uv tool install .
playwright install chromium
flow-render -c ./example/config.json
Usage
Linux / macOS / WSL
make run CONFIG=./example/config.json
Other targets and variables:
make help
make run PLUGIN=./path/to/plugin QUERY=test
make run PLUGIN_MANAGER=1 PLUGIN=./path/to/plugin
make run CONFIG=./example/config.json SKIP_PLAYWRIGHT=1 # skip the Chromium install step
make creates .venv, installs the package with uv pip install -e ., and installs
Chromium on first run; later runs reuse the existing venv.
Windows
.\run.ps1 -c .\example\config.json
run.ps1 does the same venv + Playwright bootstrap, prompting for elevation only if
the Chromium install needs it, then forwards all arguments to the CLI.
Direct CLI
flow-render -c ./example/config.json
| Flag | Meaning |
|---|---|
-c, --config |
Path to a config JSON file |
-p, --plugin |
Path to a Flow Launcher plugin directory, or just its name (see below) |
-u, --plugin-url |
URL or local path to a plugin .zip; extracted to a temp dir and used like -p |
-q, --query |
Query to run against the plugin |
-i |
Render the plugin-manager "pm install" view for the given plugin (works with -p or -u) instead of running a query |
--empty |
Render the idle search window (placeholder text and clock, no results) without a plugin |
--clock |
Time shown by --empty, e.g. "02:42 PM" (defaults to the current time) |
-s, --css |
Stylesheet(s) to render with, e.g. win11-dark.css or win11-dark.css ad-neon.css (only applies with -p/-u/--empty; a config file's own css field takes precedence with -c) |
-o, --output |
Directory to save the rendered PNG in (defaults to a per-user data directory, see above) |
-m, --max-results |
Maximum number of results to render (only applies with -p/-u; default: 3) |
-W, --width |
Screenshot width in px (overrides any canvas size baked into the selected theme; defaults to 1280) |
-H, --height |
Screenshot height in px (overrides any canvas size baked into the selected theme; defaults to 720) |
--print-json |
Print the resolved config as JSON to the console |
--save-json |
Also save the config as JSON next to the rendered PNG in the output directory (-o) |
--hide-caret |
Hide the blinking text caret in the query box |
-s/--css names don't need the .css extension — -s win11-dark and
-s win11-dark.css are equivalent.
With -p, the plugin's ExecuteFileName is invoked as a subprocess with a
{"method": "query", "parameters": [query]} request and its results become the
rendered rows. -u is mutually exclusive with -p — the zip is extracted to a
temporary directory (its plugin.json is located automatically, even if the zip
wraps everything in a subfolder), then handled exactly like -p.
-p doesn't have to be a path: if the given value isn't an existing directory, it's
treated as a plugin name — first a matching folder in the current directory is
looked for, then (on Windows) one in Flow Launcher's own %APPDATA%\FlowLauncher\Plugins.
Both searches match the folder name exactly, or a <name>-<version> prefix (how Flow
Launcher itself names installed plugin folders) if that match is unambiguous. So from
inside %APPDATA%\FlowLauncher\Plugins, -p "Steam Search" just works; from anywhere
else, it falls back to whatever's actually installed there.
With -i, no query is run — instead it renders the plugin-manager mockup you'd see
after typing pm install <name>: query box shows pm install {Name}, and the single
result is {Name} by {Author} / {Description}, all read straight from the plugin's
plugin.json.
With --empty, no plugin is needed. It renders what Flow Launcher shows before
anything is typed: a "Type here to search" placeholder in the theme's suggestion
color, a clock, and the search glyph, with the results list collapsed:
flow-render --empty -s win11-dark --clock "02:42 PM"
Edit mode
flow-render edit -p ./plugin opens a local browser tab with a live, editable
preview of the mockup — useful for building promo/hero-shot themes (the
ad-*.css family) without hand-tuning CSS and re-running screenshots to
check the result.
flow-render edit -p ./plugin # blank starting theme
flow-render edit -p ./plugin -q "install" # preview against a query
flow-render edit -p ./plugin -s ad-neon.css # continue editing a theme
flow-render edit -p ./plugin -q "install" -m 5 # show up to 5 results
-m/--max-results works the same as on the normal command. -o, -i, -c,
-W, -H don't apply to edit (canvas size is set live in the editor instead
of -W/-H).
In the editor:
- Click any element in the preview (the window, icon, a result's title, etc.)
to select it, then adjust its translate X/Y/Z, rotate X/Y/Z, scale,
opacity, perspective, and drop shadow (offset X/Y, blur, color, opacity)
with the sliders — each has a live-editable number box and a reset (↺)
button, with wide ranges (e.g. translate up to ±2000px, rotate up to
±360°, scale up to 5x) to cover everything from subtle nudges to
dramatic hero-shot compositions. You can also drag an element directly in
the preview to set translate X/Y, and scroll the mouse wheel over it to
adjust scale — both stay in sync with the slider panel. Because these are
shared CSS selectors, editing e.g. "Title" affects every result row, not
just one.
.icon,#GlassIcon, and.Hotkeykeep their own built-in vertical centering underneath whatever you add on top. - "Perspective" (0 = off) is what actually gives translateZ/rotateX/rotateY
real 3D depth — without it, Z movement has no visible foreshortening.
Raise it for a stronger "pop toward the viewer" effect, needed for the
tilted-window look the
ad-*.csshero themes use. - Transforming anything other than the window itself (an icon, a title, etc.) lets it visually extend past the window's edges — e.g. a large translateZ or scale can make an icon "float" off the window — instead of being clipped to the window's bounds.
- "Selected result" picks which row is highlighted, for previewing the
selected-row styling on any of the plugin's actual results. "Selected row"
in the Elements list is a separate selectable target (
.selecteditem) for transforming whichever row that is — e.g. scaling it up slightly as emphasis — independently of "Result row", which affects every row. - The canvas (behind the window) is edited globally, not per element: choose
exactly one of theme default, gradient, or transparent — a transparent
canvas shows a gray/white checkerboard in the editor so it reads as
"transparent," not just "dark." A gradient can be linear (with an angle)
or radial (with a center X/Y position instead of an angle), with 2 to 6
color stops (each with its own position); "+ Add color stop" and a
per-stop ✕ button (once past 2 stops) manage
the list. The canvas's pixel bounds are outlined in the preview. Canvas
width/height set the actual final screenshot's pixel size (not just the
live preview, up to 8000px) — saved as a
/* flow-render-canvas: WxH */marker at the top of the theme, which the normal (non-edit) command reads automatically when that theme is selected with-s.-W/-Hon the command line override it. - "Window shine" overlays a gradient on top of the window's own content, for faking a glossy highlight — enable it, then pick linear (with an angle) or radial (with a center X/Y position), and up to 6 color stops. Unlike the canvas gradient, each stop has its own opacity slider, since a shine only reads as a highlight with stops that fade to fully transparent rather than a solid color.
- "+ Add layer" adds a freeform text layer (up to 4), each a sibling of the
window rather than nested inside it, so it doesn't inherit the window's
own rotation — with its own Jinja2 template (e.g.
{{ plugin.Name }}), a choice of font family (several web-safe options, or the theme's default), font size, color with its own opacity slider, weight, plus the same translate/rotate/scale/opacity/shadow controls as elements. The template stays live in the saved CSS: reusing the theme against a different plugin later updates the text automatically. "🗑 Remove layer" deactivates the currently selected layer, freeing its slot for reuse. - "Save" writes
src/flow_render/static/<name>.css, ready to use immediately with-s <name>.csson the normal (non-edit) command. - "📷 Capture screenshot" runs the real screenshot pipeline against whatever you're currently editing (saved or not) and writes it to the normal per-user output directory — no need to save first or leave the editor.
Themes
example/config.json rendered against the default styling, the two bundled Windows 11
mockups, and every theme generated from the current Flow Launcher release (see
static/themes/ below). Regenerate these with:
uv run python scripts/generate_theme_screenshots.py
Default (no theme) |
win11-dark.css |
win11-light.css |
themes/blurblack.css |
themes/blurblack-darker.css |
themes/blurwhite.css |
themes/circle-system-dark.css |
themes/circle-system-light.css |
themes/cyan-dark.css |
themes/darker.css |
themes/darker-glass.css |
themes/discord-dark.css |
themes/dracula.css |
themes/gray.css |
themes/league.css |
themes/midnight.css |
themes/nord-darker.css |
themes/pink.css |
themes/slimlight.css |
themes/sublime.css |
themes/ubuntu.css |
themes/win10system-dark.css |
themes/win10system-light.css |
themes/win11light-dark.css |
themes/win11light-light.css |
Config format
See example/config.json:
{
"keyword": "pm",
"query": "install Steam Search",
"icon": "data:image/png;base64,...",
"max_results": 1,
"selection": 0,
"css": null,
"query_suggestion": "",
"results": [
{
"title": "Steam Search by Garulf",
"subtitle": "Search and launch your Steam Games",
"icon": "data:image/png;base64,..."
}
]
}
iconaccepts a data URI or a path relative to the config file — relative paths are resolved and inlined automatically.selectionis the index of the highlighted row.- When
keywordandqueryare both empty, the window renders as Flow Launcher does before anything is typed: the "Type here to search" placeholder, a clock and the search glyph, with no results (anyresultsin the config are ignored; see--empty). The optionalclockfield sets the time shown; leave it out to use the current time. - A query with no results collapses the window to just the query box.
query_suggestionis auto-filled, when left empty, with the selected result's title — but only if the typedqueryis a case-insensitive prefix of it. The untyped remainder renders as grayed-out ghost text after the cursor (the usual autocomplete look); no match means no suggestion.max_resultscaps how many entries fromresultsactually get rendered (any beyond it are dropped from the screenshot). Whenresultshas more entries thanmax_results, a cosmetic scrollbar thumb is drawn on the results list, sized to the visible/total ratio, to suggest more results exist below (this tool renders a single static screenshot, so it's a visual cue only, not an actually scrollable list). With-p/-u, up to 20 of the plugin's results are kept for this purpose even though onlymax_resultsare actually shown.cssnames a stylesheet (or a list of stylesheets, e.g.["win11-dark.css", "ad-neon.css"]) to inline on top of the defaultstyle.css. Each is applied in order, so later entries win the cascade over earlier ones. Each is looked up relative to the working directory first, then in the bundledstatic/, where several variants ship (hero.css,hero1.css…hero4.css,style.css,win11-light.css,win11-dark.css— the last two mimic Flow Launcher's "Windows 11" theme). Templates resolve the same way, so a localbase.htmloverrides the bundled one.- For product-page hero shots,
hero-win11-light.cssandhero-win11-dark.cssrender the launcher centered on a mock Windows 11 desktop — taskbar, start button, pinned icons, and a couple of blank cascaded windows behind it — using the realwin11-light.css/win11-dark.csslauncher chrome.hero-win11-accent.cssis the same desktop mockup with a saturated accent wallpaper instead, on the default launcher theme. - Generated Flow Launcher themes live in the bundled
static/themes/— use them with"css": "themes/dracula.css"(orthemes/win11light-dark.css, etc.). Regenerate them from the current Flow Launcher release withmake themes, which refreshes the vendored XAML inthemes/xaml/and rewrites the CSS.
Metadata
Release files for flow-render 0.15.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| flow_render-0.15.0.tar.gz | 1.7 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| flow_render-0.15.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.8 MB
Release files / flow_render-0.15.0.tar.gz
| Download URL | flow_render-0.15.0.tar.gz |
|---|---|
| Size | 1.7 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0cf853006e07f67a31a70833ee0a0013b7f783de76bd5eadeecccf66f95f72a2
|
|
BLAKE2b-256 checksum How to use checksums |
f86ca0b57165dd627183e2b3ff8ad3cb3af4763f82209cbba9a49b16fd9453c1
|
| 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 Oct 3, 2026.
Transparency logRelease files / flow_render-0.15.0-py3-none-any.whl
| Download URL | flow_render-0.15.0-py3-none-any.whl |
|---|---|
| Size | 116.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1835a04c91b74179e18746737c999a25c492d99e9d4caffd766601c18ced0ae8
|
|
BLAKE2b-256 checksum How to use checksums |
0cb2d7a44d180d44ab2a3a1413cc2625fc8d2d27f3d58b0642c8886e7cabeb95
|
| 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 Oct 3, 2026.
Transparency log