
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
Scripts
folder—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 scriptsFolder = new Folder(thisScriptFile.parent.fsName + "/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;
// Responsive layout handler
myPanel.onResizing = myPanel.onResize = function() {
this.layout.resize();
updateLayout(myPanel);
};
// Initial setup
populateButtons(myPanel);
updateLayout(myPanel);
if (myPanel instanceof Window) {
myPanel.minimumSize = [180, 100];
myPanel.center();
myPanel.show();
}
return myPanel;
}
function updateLayout(panel) {
var width = panel.size[0];
var height = panel.size[1];
var isHorizontal = width > height * 1.2;
// Clear main container
var container = panel.mainContainer;
while (container.children.length > 0) {
container.remove(container.children[0]);
}
if (!panel.buttonData) return;
var buttons = panel.buttonData;
// Calculate number of columns
var buttonWidth = 80;
var spacing = 5;
var availableWidth = width - 20;
var numColumns = Math.max(1, Math.floor(availableWidth / (buttonWidth + spacing)));
// Calculate total slots and rows needed
var totalScripts = buttons.length;
var totalButtons = totalScripts + 2;
var totalSlotsPerRow = numColumns;
var rowCount = Math.ceil(totalButtons / totalSlotsPerRow);
// Add all buttons in a single flow
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) {
// Add Refresh button
var refreshPanel = row.add("panel", undefined, undefined, { borderStyle: "none" });
refreshPanel.preferredSize = [buttonWidth, 25];
refreshPanel.margins = 0;
refreshPanel.graphics.backgroundColor = refreshPanel.graphics.newBrush(
refreshPanel.graphics.BrushType.SOLID_COLOR, [0, 0.5, 0, 1]
);
var refreshBtn = refreshPanel.add("button", undefined, "Refresh");
refreshBtn.preferredSize = [buttonWidth, 25];
refreshBtn.helpTip = "Refresh Script List";
refreshBtn.onClick = function() {
// Create a new Folder object to force a re-scan
panel.scriptsFolder = new Folder(panel.scriptsFolder.fsName);
// Clear existing button data to avoid caching
panel.buttonData = [];
// Re-populate the buttons
populateButtons(panel);
// Update the layout
updateLayout(panel);
};
} else if (buttonIndex <= totalScripts) {
// Add script buttons
addButtonGroup(row, buttons[buttonIndex - 1]);
} else {
// Add About button
var aboutPanel = row.add("panel", undefined, undefined, { borderStyle: "none" });
aboutPanel.preferredSize = [buttonWidth, 25];
aboutPanel.margins = 0;
aboutPanel.graphics.backgroundColor = aboutPanel.graphics.newBrush(
refreshPanel.graphics.BrushType.SOLID_COLOR, [0, 0.5, 0, 1]
);
var aboutBtn = aboutPanel.add("button", undefined, "About");
aboutBtn.preferredSize = [buttonWidth, 25];
aboutBtn.onClick = function() {
alert("Script Launcher\nCreated by Mehmet Sensoy\n\nA responsive script launcher for After Effects");
};
}
buttonIndex++;
}
// Fill remaining slots with spacers
while (slotsInThisRow < totalSlotsPerRow) {
var spacer = row.add("group");
spacer.preferredSize.width = buttonWidth;
slotsInThisRow++;
}
}
panel.layout.layout(true);
}
function populateButtons(panel) {
var folder = panel.scriptsFolder;
// Create folder if it doesn't exist
if (!folder.exists) {
folder.create();
alert("Created scripts folder at:\n" + folder.fsName);
panel.buttonData = [];
return;
}
// Get all script files
var files = folder.getFiles(function(f) {
return f instanceof File && f.name.match(/\.(js|jsx|jsxbin)$/i);
});
// Sort files alphabetically
files.sort(function(a, b) {
return a.name.localeCompare(b.name);
});
// Store button data
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
});
}
// Update the layout after populating buttons
updateLayout(panel);
}
function addButtonGroup(parent, buttonData) {
var btnGroup = parent.add("group");
btnGroup.orientation = "column";
btnGroup.alignChildren = "center";
btnGroup.spacing = 2;
btnGroup.preferredSize.width = 80;
var iconFile = new File(buttonData.icon);
if (iconFile.exists) {
// If an icon exists, show iconbutton with text below
var btn;
try {
btn = btnGroup.add("iconbutton", undefined, iconFile, { style: "toolbutton" });
btn.size = [45, 45];
} catch (e) {
btn = btnGroup.add("button", undefined, buttonData.name.length > 10 ? buttonData.name.substring(0, 7) + "…" : buttonData.name);
btn.preferredSize = [80, 25];
btn.helpTip = buttonData.name;
}
var text = btnGroup.add("statictext", undefined, buttonData.name.length > 10 ? buttonData.name.substring(0, 7) + "…" : buttonData.name);
text.alignment = "center";
text.preferredSize.width = 80;
btn.onClick = function() {
runScript(buttonData.path, buttonData.name);
};
btn.helpTip = buttonData.name;
} else {
// If no icon, show only a clickable button with the script name
var btn = btnGroup.add("button", undefined, buttonData.name.length > 10 ? buttonData.name.substring(0, 7) + "…" : buttonData.name);
btn.preferredSize = [80, 25];
btn.helpTip = buttonData.name;
btn.onClick = function() {
runScript(buttonData.path, buttonData.name);
};
}
}
function runScript(scriptPath, scriptName) {
try {
app.beginUndoGroup("Run Script: " + scriptName);
var file = new File(scriptPath);
if (file.exists) {
$.evalFile(file);
} else {
alert("Script not found:\n" + scriptPath);
}
app.endUndoGroup();
} catch (e) {
if (app.activeUndoGroup) {
app.endUndoGroup();
}
alert("Error running script:\n" + scriptPath + "\n\n" + e.toString());
}
}
// Launch the UI
buildUI(thisObj);
})(this);