Python plugins are a planned SaaS / cloud tier feature.
The code infrastructure exists and is tested, but Python plugins are disabled by default on the local app because they run with full system access (file system, network, subprocesses) and cannot be safely sandboxed on a user's machine.
For local use: add JSON recipe files to
user_transforms/instead. JSON recipes can chain any built-in transform and cover the vast majority of real-world use cases with no security exposure.See
internal/SECURITY.md→ Python Plugin Roadmap Decision for the full rationale, andpattern_catalog/EXTENDING_TRANSFORMS.mdfor the security model and troubleshooting guide.
If you are developing or testing plugins on your own machine and understand the risk, set the environment variable before starting the app:
FUNSCRIPT_PLUGINS_ENABLED=1 streamlit run ui/streamlit/app.py
# or
FUNSCRIPT_PLUGINS_ENABLED=1 python cli.py list-transforms --user-onlyVerify your plugin loaded:
python cli.py validate-plugins --verboseOnly enable this flag for plugins you wrote yourself or have reviewed.
A malicious .py file in this directory would execute with your full user
permissions at app startup.
A plugin file must expose one of:
TRANSFORM— a singlePhraseTransforminstanceTRANSFORMS— a list ofPhraseTransforminstances
from dataclasses import dataclass, field
from pattern_catalog.phrase_transforms import PhraseTransform, TransformParam
@dataclass
class _MyTransform(PhraseTransform):
def _transform(self, actions: list, p: dict) -> list:
# actions is a deep copy — mutate freely.
# p is a dict of {param_key: resolved_value} built from self.params defaults
# plus any caller-supplied overrides.
for a in actions:
a["pos"] = max(0, min(100, int(a["pos"] * p["scale"])))
return actions
TRANSFORM = _MyTransform(
key = "my_scale",
name = "My Scale",
description = "Multiply every position by a scale factor.",
structural = False,
params = {
"scale": TransformParam(
label = "Scale",
type = "float",
default = 0.8,
min_val = 0.0,
max_val = 2.0,
step = 0.05,
help = "Multiplier applied to every position value.",
),
},
)keymust be unique and must not clash with any built-in catalog key. Clashing keys are skipped with a warning.structural = Trueif your transform returns a different number of actions or changes their timestamps (e.g. tempo changes). Callers use this flag to decide whether to do an in-place position update or a full slice replacement.- A broken plugin (import error, exception during load) is skipped with a stderr message; it does not abort the app.
- Files named
example_*.pyare committed as templates and are always skipped, even whenFUNSCRIPT_PLUGINS_ENABLED=1. Your own files are gitignored.
user_transforms/README.md— JSON recipe transforms (safe, no flag needed)pattern_catalog/EXTENDING_TRANSFORMS.md— full security model and troubleshootinginternal/SECURITY.md— threat analysis and Python plugin roadmap decision
© 2026 Liquid Releasing. Licensed under the MIT License. Written by human and Claude AI (Claude Sonnet).