Quick answer: Construct 3 supports full JavaScript scripting alongside event sheets. Add .js files to the Scripts folder, use ES module import/export syntax, and access game objects through the runtime API. Call JavaScript from event sheets using the Script action, and call event sheet functions from JavaScript using runtime.callFunction(). Debug with the browser’s developer tools (F12) during preview.
Construct 3’s event sheets are powerful for visual game logic, but some systems are cleaner, more maintainable, or simply faster to write in JavaScript. Complex data transformations, API integrations, pathfinding algorithms, procedural generation, and reusable utility libraries all benefit from scripted code. Since 2019, Construct 3 has provided first-class JavaScript support that integrates directly with the event sheet system. This guide covers everything: script file setup, the runtime API, bridging between scripts and event sheets, ES modules, external libraries, and debugging.
Script Files vs. Inline Code
Construct 3 offers two ways to write JavaScript. Script files are standalone .js files in your project’s Scripts folder. They are ES modules by default, which means they support import and export and run in strict mode. This is the recommended approach for any non-trivial code. Inline code uses the “Execute JavaScript” action in an event sheet (via the Browser object) to run a snippet. Inline code is convenient for one-liners but becomes unreadable for anything longer than a few lines.
To create a script file, right-click the Scripts folder in the Project Bar and select “Add script.” Construct 3 creates a new .js file with a template. Each event sheet can have an associated script file — right-click the event sheet and select “Add script” to create a paired script that receives the event sheet’s runtime context automatically.
// scripts/main.js - Script file associated with an event sheet
// This runs when the associated event sheet's layout starts
const scriptsInEvents = {
// Called when the layout associated with this script starts
async OnBeforeLayoutStart(runtime) {
// Set up event listeners
runtime.addEventListener("tick", () => onTick(runtime));
}
};
function onTick(runtime) {
const player = runtime.objects.Player.getFirstInstance();
if (!player) return;
// Access instance variables
const speed = player.instVars.Speed;
const dt = runtime.dt;
// Custom movement logic
if (runtime.keyboard.isKeyDown("ArrowRight")) {
player.x += speed * dt;
}
}
self.C3.Plugins.Script_Interface.Exps = scriptsInEvents;
The key difference: script files have full IDE support (autocomplete, syntax highlighting, import/export), while inline code is a string inside an action with no tooling. Use script files for anything you plan to maintain or reuse.
The Runtime API
The runtime object is your gateway to everything in the Construct 3 project from JavaScript. It provides access to global variables, object types, instances, layouts, layers, and game events. Here are the most commonly used APIs:
// Accessing global variables
const score = runtime.globalVars.Score;
runtime.globalVars.Score = 100;
// Getting object instances
const player = runtime.objects.Player.getFirstInstance();
const allEnemies = runtime.objects.Enemy.getAllInstances();
// Reading and writing instance variables
player.instVars.HP -= 10;
const isAlive = player.instVars.HP > 0;
// Object position and properties
player.x = 200;
player.y = 150;
player.angleDegrees = 90;
player.opacity = 0.5;
player.isVisible = false;
// Creating and destroying instances
const bullet = runtime.objects.Bullet.createInstance(
"GameLayer", // layer name
player.x, // x position
player.y // y position
);
bullet.instVars.Direction = player.angleDegrees;
// Destroy an instance
enemy.destroy();
// Layout and layer access
const currentLayout = runtime.layout.name;
runtime.goToLayout("GameOver");
const uiLayer = runtime.layout.getLayer("UI");
uiLayer.isVisible = true;
The runtime also exposes timing information. runtime.dt returns the delta time in seconds since the last frame — use this for frame-rate-independent movement and animations. runtime.gameTime returns the total elapsed game time. runtime.fps gives the current frames per second.
For iterating over all instances of an object type, use getAllInstances() which returns an array. You can filter, sort, and transform this array using standard JavaScript array methods — something that would require multiple event sheet conditions and picking logic to accomplish visually.
// Find the nearest enemy to the player
function findNearestEnemy(runtime) {
const player = runtime.objects.Player.getFirstInstance();
const enemies = runtime.objects.Enemy.getAllInstances();
if (enemies.length === 0) return null;
let nearest = null;
let minDist = Infinity;
for (const enemy of enemies) {
const dx = enemy.x - player.x;
const dy = enemy.y - player.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < minDist) {
minDist = dist;
nearest = enemy;
}
}
return nearest;
}
Bridging Event Sheets and JavaScript
The real power of Construct 3 scripting comes from combining event sheets and JavaScript. Use event sheets for what they do best (collision handling, spawning, layout flow) and JavaScript for what code does best (data processing, algorithms, API calls).
Calling JavaScript from event sheets: Use the “Run script” action on any event sheet. This executes JavaScript code with access to the runtime, localVars, and event sheet context. For reusable functions, define them in a script file and call them via the Script Interface.
// Event sheet action: Run script
// This runs inline JavaScript with runtime access
// Example: Sort inventory by item value
const inv = JSON.parse(runtime.globalVars.InventoryJSON);
inv.sort((a, b) => b.value - a.value);
runtime.globalVars.InventoryJSON = JSON.stringify(inv);
Calling event sheet functions from JavaScript: If you have a function defined in an event sheet (using the Function object), you can call it from JavaScript using runtime.callFunction(). This is useful when your JavaScript code needs to trigger visual effects, spawn objects, or perform actions that are easier to express in event sheets.
// Call an event sheet function from JavaScript
// Event sheet has a function "SpawnExplosion" that takes x, y params
runtime.callFunction("SpawnExplosion", player.x, player.y);
// Event sheet has a function "GetDamageMultiplier" that returns a value
const mult = runtime.callFunction("GetDamageMultiplier", weaponType);
const finalDamage = baseDamage * mult;
This bidirectional bridge means you never need to choose one system exclusively. A common pattern is to handle all user input and collision detection in event sheets, but delegate complex calculations (damage formulas, loot tables, procedural generation) to JavaScript functions.
ES Modules and Code Organization
Construct 3 script files are ES modules, which means you can split your code across multiple files and use import/export to share functionality. This is essential for keeping larger projects maintainable.
// scripts/utils/math.js
export function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
}
export function lerp(a, b, t) {
return a + (b - a) * clamp(t, 0, 1);
}
export function distanceBetween(x1, y1, x2, y2) {
const dx = x2 - x1;
const dy = y2 - y1;
return Math.sqrt(dx * dx + dy * dy);
}
// scripts/systems/combat.js
import { clamp } from "./utils/math.js";
export class CombatSystem {
constructor(runtime) {
this.runtime = runtime;
}
calculateDamage(attacker, defender) {
const base = attacker.instVars.Attack;
const defense = defender.instVars.Defense;
const variance = 0.8 + Math.random() * 0.4; // 80%-120%
const raw = (base - defense / 2) * variance;
return Math.floor(clamp(raw, 1, 9999));
}
applyDamage(target, damage) {
target.instVars.HP = clamp(
target.instVars.HP - damage,
0,
target.instVars.MaxHP
);
// Trigger visual feedback via event sheet function
this.runtime.callFunction(
"ShowDamageNumber", target.x, target.y, damage
);
}
}
// scripts/main.js - Import and use
import { CombatSystem } from "./systems/combat.js";
let combat;
runOnStartup(async runtime => {
runtime.addEventListener("beforeprojectstart", () => {
combat = new CombatSystem(runtime);
});
});
For external libraries that are not ES modules (like a physics library or a utility package), add them to your project as a script file and set the “Script type” property to “Classic script” instead of “Module.” Classic scripts load in the global scope, so their exports are available on the window object. Your module scripts can then access them via globalThis or window.
Debugging JavaScript in Construct 3
Debugging is where JavaScript in Construct 3 truly shines compared to event sheets. The browser’s developer tools give you breakpoints, step-through execution, variable inspection, and the console — all standard web development tools that work perfectly with Construct 3 scripts.
Press F12 during preview to open developer tools. Your script files appear in the Sources panel. Click on any line number to set a breakpoint. When execution hits that line, it pauses and you can inspect every variable in scope, step through code line by line, and evaluate expressions in the console.
// Debugging techniques
// 1. Console logging with context
console.log("Player state:", {
x: player.x,
y: player.y,
hp: player.instVars.HP,
state: player.instVars.State
});
// 2. Conditional breakpoints (set in DevTools)
// Right-click a line number > "Add conditional breakpoint"
// Example condition: enemy.instVars.HP <= 0
// 3. Performance profiling
console.time("pathfinding");
const path = findPath(startX, startY, endX, endY);
console.timeEnd("pathfinding");
// Output: "pathfinding: 2.45ms"
// 4. Watching for unexpected values
if (isNaN(player.x) || isNaN(player.y)) {
console.error("Player position is NaN!");
debugger; // Pauses execution here if DevTools is open
}
// 5. Table output for arrays
const enemies = runtime.objects.Enemy.getAllInstances();
console.table(enemies.map(e => ({
uid: e.uid,
x: Math.round(e.x),
y: Math.round(e.y),
hp: e.instVars.HP
})));
One important gotcha: when you set breakpoints in Construct 3 script files, the breakpoints may not survive a preview restart because the script URLs can change. Use the debugger statement in your code for persistent breakpoints that always trigger when DevTools is open. Remove them before exporting your game.
"Use event sheets for the 'what' (spawn enemy, play sound, go to layout) and JavaScript for the 'how' (calculate optimal spawn position, mix audio dynamically, determine which layout based on player progress)."
Related Issues
If your event sheet conditions are not triggering as expected when combined with JavaScript, see Fix: Construct 3 Event Sheet Conditions Not Working. For issues with variables resetting when layouts restart, check Fix: Construct 3 Global Variable Reset on Layout Restart. If your JavaScript calls external APIs and hits CORS errors, see Fix: Construct 3 AJAX Request CORS Error. And for performance issues in code-heavy projects, see Fix: Construct 3 Performance Low FPS Lag.
Start with event sheets. Add JavaScript when the event sheet version gets painful. You will know when.