
Script Launcher is a fully responsive and customizable ScriptUI panel for Adobe After Effects, designed to streamline your workflow by letting you launch your favorite scripts with a single click. Whether you’re a motion designer, VFX artist, or AE power user, this panel automatically detects .js, .jsx, and .jsxbin files in its dedicated Scripts folder and organizes them into a responsive, dynamically laid-out grid—complete with optional custom icons.
💡 Key Features:
- 🔄 Auto-refresh: Built-in refresh button to update the list when you add/remove scripts.
- 📁 Drag-and-drop ready: Just drop your scripts into the
Scriptsfolder—no manual configuration required. - 🧩 Supports icons: Add PNG icons with the same name as your scripts for a polished look.
- 📱 Fully responsive UI: Automatically adapts the number of rows and columns based on panel size and orientation.
- 🧠 Smart layout engine: Dynamically recalculates layout for smooth scaling and resizing.
- 🧪 Error handling: Graceful error messages for missing scripts or icon issues.
- 📌 Dockable panel: Works as a dockable or floating panel.
Built for power users and beginners alike, this panel turns your AE workspace into a launchpad for efficiency.
JavaScript
(function ScriptLauncher(thisObj) {
// Main entry function
function buildUI(thisObj) {
var myPanel = (thisObj instanceof Panel) ? thisObj : new Window("palette", "Script Launcher", undefined, {
resizeable: true
});
myPanel.orientation = "column";
myPanel.alignChildren = ["fill", "top"];
myPanel.spacing = 5;
myPanel.margins = 10;
// Scripts folder location
var thisScriptFile = new File($.fileName);
var parentPath = thisScriptFile.exists ? thisScriptFile.parent.fsName : Folder.desktop.fsName;
var scriptsFolder = new Folder(parentPath + "/Scripts");
// --- MAIN CONTAINER ---
var mainContainer = myPanel.add("group");
mainContainer.orientation = "column";
mainContainer.alignChildren = ["left", "top"];
mainContainer.alignment = ["fill", "fill"];
mainContainer.spacing = 5;
// Store references
myPanel.mainContainer = mainContainer;
myPanel.scriptsFolder = scriptsFolder;
myPanel.cachedColCount = 0;
// Responsive layout handler
myPanel.onResizing = myPanel.onResize = function() {
this.layout.resize();
updateLayout(myPanel, false);
};
// Initial setup
populateButtons(myPanel);
updateLayout(myPanel, true);
if (myPanel instanceof Window) {
myPanel.minimumSize = [180, 100];
myPanel.center();
myPanel.show();
}
return myPanel;
}
function updateLayout(panel, forceRebuild) {
if (!panel.buttonData) return;
var width = panel.size[0];
if (width < 50) width = 180;
var buttonWidth = 80;
var spacing = 5;
var availableWidth = width - 20;
var numColumns = Math.max(1, Math.floor(availableWidth / (buttonWidth + spacing)));
if (!forceRebuild && panel.cachedColCount === numColumns) {
return;
}
panel.cachedColCount = numColumns;
var container = panel.mainContainer;
while (container.children.length > 0) {
container.remove(container.children[0]);
}
var buttons = panel.buttonData;
var totalScripts = buttons.length;
var totalButtons = totalScripts + 2;
var totalSlotsPerRow = numColumns;
var rowCount = Math.ceil(totalButtons / totalSlotsPerRow);
var buttonIndex = 0;
var row;
for (var rowIdx = 0; rowIdx < rowCount; rowIdx++) {
row = container.add("group");
row.orientation = "row";
row.alignChildren = ["left", "top"];
row.spacing = spacing;
var slotsInThisRow = Math.min(totalSlotsPerRow, totalButtons - (rowIdx * totalSlotsPerRow));
for (var i = 0; i < slotsInThisRow; i++) {
if (buttonIndex === 0) {
createUtilityButton(row, "Refresh", [0, 0.5, 0, 1], function() {
panel.scriptsFolder = new Folder(panel.scriptsFolder.fsName);
panel.buttonData = [];
populateButtons(panel);
updateLayout(panel, true);
});
} else if (buttonIndex <= totalScripts) {
addButtonGroup(row, buttons[buttonIndex - 1]);
} else {
createUtilityButton(row, "About", [0.5, 0.5, 0.5, 1], function() {
alert("Script Launcher\n\nA responsive script launcher for After Effects");
});
}
buttonIndex++;
}
while (row.children.length < totalSlotsPerRow) {
var spacer = row.add("group");
spacer.preferredSize = [buttonWidth, 25];
}
}
panel.layout.layout(true);
}
function createUtilityButton(parent, text, colorArr, onClickFunc) {
var btn = parent.add("button", undefined, text);
btn.preferredSize = [80, 25];
btn.onClick = onClickFunc;
}
function populateButtons(panel) {
var folder = panel.scriptsFolder;
if (!folder.exists) {
folder.create();
panel.buttonData = [];
return;
}
var files = folder.getFiles(function(f) {
return f instanceof File && f.name.match(/\.(js|jsx|jsxbin)$/i);
});
if (!files) {
panel.buttonData = [];
return;
}
files.sort(function(a, b) {
return a.name.localeCompare(b.name);
});
panel.buttonData = [];
for (var i = 0; i < files.length; i++) {
var scriptFile = files[i];
var scriptName = decodeURI(scriptFile.name).replace(/\.(js|jsx|jsxbin)$/i, "");
var iconPath = folder.fsName + "/" + scriptName + ".png";
panel.buttonData.push({
path: scriptFile.fsName,
icon: iconPath,
name: scriptName
});
}
}
function addButtonGroup(parent, buttonData) {
var iconFile = new File(buttonData.icon);
if (iconFile.exists) {
try {
var btn = parent.add("iconbutton", undefined, iconFile, {
style: "toolbutton"
});
btn.size = [45, 45];
btn.onClick = function() {
runScript(buttonData.path);
};
btn.helpTip = buttonData.name;
} catch (e) {
var btn = parent.add("button", undefined, truncateText(buttonData.name));
btn.preferredSize = [80, 25];
btn.onClick = function() {
runScript(buttonData.path);
};
btn.helpTip = buttonData.name;
}
} else {
var btn = parent.add("button", undefined, truncateText(buttonData.name));
btn.preferredSize = [80, 25];
btn.onClick = function() {
runScript(buttonData.path);
};
btn.helpTip = buttonData.name;
}
}
function truncateText(str) {
return str.length > 10 ? str.substring(0, 7) + "..." : str;
}
function runScript(scriptPath) {
var file = new File(scriptPath);
if (!file.exists) {
alert("Script not found:\n" + scriptPath);
return;
}
if (app.activeViewer) {
app.activeViewer.setActive();
}
$.evalFile(file);
}
buildUI(thisObj);
})(this);