Back home

Organising JavaScript in Webflow: Exploring Scalable Patterns

May 20, 2025

Keeping JavaScript clean and maintainable inside Webflow can be… tricky. Over the last few projects, I’ve been exploring different ways to modularise my scripts — not just to stay sane, but to make things clearer for other developers (and future me) down the line.

In this article my goal is to explain the concepts and uses cases as I explored them

The Problem

As projects grow in complexity — especially ones that lean heavily on GSAP for interactions — your codebase can get real messy, real fast. Think of a slider that uses Flip, InertiaPlugin, Observer, maybe even some ScrollTrigger magic. It’s not unusual to rack up a few hundred lines of JavaScript just to get something polished and accessible.

Now Webflow technically allows up to 10,000 characters per embed, but cramming everything into one block just feels wrong. What I wanted was something closer to the structure I’m used to in VS Code — small files, reusable utilities, named exports. Something clean.

The Options I Tried

I cycled through a few approaches trying to find the right balance between flexibility, maintainability, and Webflow-friendliness:

  • Slater: A solid option, but it introduces a paid dependency clients aren’t always keen to commit to — especially when it’s not essential.
  • GitHub + Finsweet’s Dev Starter + JSDelivr: Great for version control and team workflows. But a bit too heavy for solo projects or handover scenarios where the next dev might not be comfortable with NPM or Git. Plus it's quite time consuming to push small tweaks and fixes to the code, which could lead to developers cutting corners or just simply being blocked all together.
  • Tim Ricks' pageFunctions Pattern: This one I really liked — especially on older Lumos setups. But the trade-off was losing the ability to easily import/export functions across files. That modularity is something I’ve come to love. It's since been removed from newer versions of Lumos

What I Landed On

What if we treat window as a kind of shared namespace?

Finsweet already does a similar approach with Attributes. If you have any attribute on the page, open the console and type fsAttributes. You'll get an object with data about the attributes that are on the page.

That was the inspiration for this approach. Passing functions around different embeds so we could keep things nice and tidy.

Let’s say you have a changeSlide() function that handles a GSAP timeline. In a typical setup, you’d keep it in its own file and import it into something like initSlider.js. Why not mimic that in Webflow by doing:

window.functions = window.functions || {};

window.functions.changeSlide = () => {
  // GSAP stuff here
};


Then, in another embed:

window.functions.initSlider = () => {
  nextBtn.addEventListener('click', window.functions.changeSlide);
};

It’s not quite the same as proper module imports, but it gives you some of that clarity back — and avoids bloating any one embed.

Managing Execution Order

This brings up the tricky part: timing.

Webflow scripts execute in order as they appear in the DOM, but there’s no real guarantee that your changeSlide embed will run before your initSlider one unless you wrap everything in the right event listeners.

What I started doing was splitting my embeds into two types:

  • Modules: Just utility functions added to window.functions. No immediate execution.
  • Initialisers: Code that actually runs — after all modules are available.

To make this bulletproof, I experimented with dispatching custom events:

// In the module embed
window.functions = window.functions || {};
window.functions.changeSlide = () => { ... };
document.dispatchEvent(new CustomEvent('module:slider:ready'));

// In the init embed
document.addEventListener('module:slider:ready', () => {
  window.functions.initSlider();
});

This way, each piece of functionality can load independently and safely hook into the right moment.

So… Is it good in production?

It depends.

For heavier components like a complex slider or custom GSAP sequence, this structure makes life easier. If you’re building a one-off interaction or a smaller effect, it’s absolutely overkill.

Something else to keep in mind is the skill level of the team that will maintain the project. The goal of this is to make the code more readable and maintainable. But if you’re handing it off to a non-technical team or a junior Webflow developer, it’s best to think hard about if this is the right approach and if you do decide to go ahead with it, comment the code extremely clearly.

Overall what I’ve found works best is a hybrid: use the event-based structure for large features that need clear separation, and keep smaller stuff self-contained.

Writings

Launch day isn't the finish line

Good projects get messy. Great projects get cleaned up. How should we handle technical debt and real-world refactoring?

Useful GSAP utilities

A practical, code-heavy dive into GSAP’s utility functions—keyframes, pipe, clamp, normalize, and interpolate—and why they’re so much more than just shortcuts for animation math.

Using Functions as Property Values in GSAP (And Why You Probably Should)

GSAP lets you pass _functions_ as property values. I've known this for a while but never really explored it particularly deeply. Over the last couple of weeks I've been testing, experimenting and getting creative with it to deepen my understanding.

Building a Scroll-Based Image Sequencer with GSAP

An exploration in building a scroll-driven image sequence animation using GSAP and HTML5 canvas. Using Avif file compression with the Avif CLI, hosting strategies (Webflow vs AWS), GSAP and the quirks of working with canvas.