Last time we crossed a little line without making a big deal of it. We spent the whole episode documenting our work - capturing recipes, writing the feeling first, building a portfolio that describes itself - and right at the end I said the reason all that matters is that documentation is the bridge to your work having a life beyond your own screen. Well, today we walk across that bridge. Because there's a moment in every creative coder's life when someone looks at your sketch and says "oh, could I use that for my thing?" - and what happens next depends entirely on whether you built a tool or just a pile of code that happens to work for you. Allez, that's today: how do you take something you made for yourself and shape it so a stranger can pick it up and play :-).
I want to be honest that this is more of a shift in attitude than a new pile of techniques. Everything we've built so far, we built for an audience of one - you, tonight, with all the context living in your head. The second another person is going to touch your code, a whole invisible set of questions appears. What do they have to know? What can they get wrong? What happens when they do get it wrong? A tool is basically a promise you make to a stranger: "give me this, and I'll give you that, and I won't surprise you." The whole craft of building tools for others is just taking that promise seriously.
Here's the thing that took me way too long to understand: for a tool, the function signature is more important than the code inside it. The insides you can rewrite forever. But the shape of how people call your thing - the arguments, the order, the names - that's the part they build their code against, and once they have, you can't change it without breaking them. So the signature is the actual product. The clever algorithm is just the filling.
Let me show you the difference, because it's stark. Here's a "tool" written the way we write things for ourselves - everything as a positional argument, because we remember the order:
// the SELFISH signature: fine for you tonight, misery for anyone else.
// quick - what does the 4th argument do? what's the 6th? you'd have to read the source.
function makeField(w, h, count, speed, seed, blur, palette, warp) {
// ...forty lines of lovely code...
}
// calling it looks like a phone number nobody can read:
makeField(800, 800, 3000, 0.4, 42, 2, "warm-ink", 0.8);
Nobody can read that call site. makeField(800, 800, 3000, 0.4, 42, 2, ...) - which number is the blur? What if I want to set the palette but leave everything else default? I'd have to pass all eight arguments and get their order right just to change the last one. That's the signature saying "you must memorise me perfectly or suffer." Not a promise a stranger wants.
The fix is one of the oldest tricks in the book, and it's the single most important habit for building tools others can use: take one options object, and give everything a sensible default.
// the GENEROUS signature: one options object, every knob named, every knob optional.
// the reader can set ONLY what they care about and ignore the rest.
function makeField(options = {}) {
const config = {
width: 800,
height: 800,
count: 3000,
speed: 0.4,
seed: 42,
blur: 2,
palette: "warm-ink",
warp: 0.8,
...options, // whatever the caller passed WINS over the defaults
};
// ...same lovely code, now reading from `config`...
return config;
}
Look at what that ...options spread does at the end - it lets the caller override only the keys they care about, and silently keep your defaults for everything else. See where this is going? Now the call site reads like a sentence:
// now the caller says only what they mean, in any order, and the rest just works:
makeField({ palette: "cool-mist", count: 5000 });
// width? height? speed? seed? all sensible defaults. the user didn't have to care.
makeField(); // and this ALSO works - a complete, valid piece with zero arguments.
That last line is a quiet superpower. makeField() with nothing gives a working result. A good tool does something nice on the very first call, before the reader has learned a single option. That's the difference between a tool that feels welcoming and one that feels like homework. Makes sense, right?
Let me sit on defaults for one more minute, because people rush past them and they're honestly where the kindness lives. A default is you doing the thinking so your user doesn't have to. Every default you pick well is a decision they never have to make. And the pattern above - lay out a full defaults object, then spread the user's options on top - is worth turning into a tiny reusable helper, because you'll do it in every tool you ever write.
// merge user options over your defaults - your manners, in one line.
// this pattern is the backbone of every friendly API you'll ever build.
function withDefaults(defaults, options = {}) {
return { ...defaults, ...options };
}
// so any tool's first line becomes clean and obvious:
function makeField(options) {
const config = withDefaults(
{ width: 800, height: 800, count: 3000, palette: "warm-ink" },
options
);
// ... use config ...
}
One caveat worth knowing, since it will bite you: this is a shallow merge. If a default is itself an object (say { colors: { bg: "#111", fg: "#eee" } }) and the user passes { colors: { fg: "red" } }, the whole colors object gets replaced and the user loses your bg default. For most creative tools flat options are plenty and I'd keep them flat on purpose - but if you ever nest, remember you need a deeper merge, or your defaults quietly vanish where you least expect. Keep the surface flat and you keep the promise simple.
Right, so you've got a friendly function. How does it actually get into someone else's hands? In the JavaScript world the answer is a package - a little labelled box with your code inside and a card on the front saying what it is. That card is package.json, and the whole thing is less scary than it looks. It's mostly just honesty about what you made.
{
"name": "flow-field",
"version": "1.0.0",
"description": "Seeded, reproducible flow-field art for the browser.",
"type": "module",
"main": "index.js",
"exports": { ".": "./index.js" },
"keywords": ["creative-coding", "generative", "canvas"],
"license": "MIT"
}
Every field there is a small promise. main and exports say "this is the front door - when someone imports my package, this is the file they get." version we'll come back to in a second because it's more important than it looks. license tells people whether they're even allowed to use it (we'll properly get into that shortly in the series - for now, MIT means "yes, freely"). And type: "module" just says we're using modern import/export, same as we've been writing all along.
Then the front door itself - index.js - is where you decide exactly what the outside world can see. This is a real design decision, not an afterthought. You expose the handful of things you want people to depend on, and you keep your messy internals private.
// index.js - the public face of your tool. this is your CONTRACT with the world.
// export ONLY what you're willing to support forever. hide the rest.
export { makeField } from "./field.js";
export { PALETTES } from "./palettes.js";
export { version } from "./package.json";
// note what's NOT here: helper functions, internal maths, that hacky bit
// you're embarrassed by. those stay private, so you can rewrite them freely.
That restraint is the whole game. Everything you export becomes a promise you have to keep; everything you don't export, you're free to tear up and rebuild tomorrow with nobody noticing. A small public surface is a gift to future-you. The tightest tools I love using expose maybe three things and hide fifty.
Now here's a lovely one, and it's a callback all the way to episode 24 when we made randomness reproducible with a seed. When you build a tool for yourself, the seed is a private convenience. When you build a tool for others, the seed becomes a genuine feature you advertise - because it's what lets your users share their results with each other. If my flow-field takes a seed and always draws the same thing for the same seed, then two strangers using my tool can send each other a single number and see the exact same picture. That's magic, and it's the kind of magic that makes people love a tool.
// expose the seed as a first-class part of your API, and reproducibility becomes
// a feature your users get for free: same seed in -> same art out, on any machine.
function makeField(options) {
const config = withDefaults({ seed: 42, count: 3000 }, options);
const rng = seededRandom(config.seed); // the seeded RNG from episode 24
const points = [];
for (let i = 0; i < config.count; i++) {
points.push({ x: rng() * config.width, y: rng() * config.height });
}
return { config, points }; // hand back BOTH the settings and the result
}
Notice I return the config right alongside the result. That's deliberate and it's a habit worth stealing: a good tool hands back not just what it made but the exact settings it used to make it, including any defaults the user never set. That way the caller can read off the seed, save it, share it, feed it back in later - all the documentation discipline from last episode, except now your tool does the capturing for your users. You're not just giving them a picture; you're giving them a picture they can always find their way back to.
For anything bigger than a single call, the shape that scales best is the one we built our little framework around back in episode 131: a factory that sets things up once and hands back an object with a few clear methods. This is friendlier than a bag of loose functions because it holds the reader's hand through a whole little lifecycle - create, do things, tidy up - without them having to thread state through every call themselves.
// a factory: configure once, get back a tidy object with a few clear methods.
// this is the shape from episode 131, now pointed OUTWARD at other people.
export function createField(options = {}) {
const config = withDefaults({ width: 800, height: 800, seed: 42 }, options);
const rng = seededRandom(config.seed);
let points = seedPoints(config, rng);
return {
// draw the current state into a canvas context the user gives us
render(ctx) { drawPoints(ctx, points, config); },
// let them re-roll with a new seed WITHOUT rebuilding everything
reseed(seed) { points = seedPoints(config, seededRandom(seed)); },
// and always let them read back the honest, complete settings
get settings() { return { ...config }; },
};
}
The reader's whole experience becomes three lines they can guess: const field = createField({ seed: 7 }); field.render(ctx); field.reseed(12);. No juggling internal arrays, no wondering what order to call things in. The object's method names are the documentation. When someone can use your tool correctly on the first try without reading a manual, you've designed the signature well - that's the real test, and it's harder than it sounds.
Here's a place tools quietly fail people, and it's close to my heart because I've been on the receiving end so many times. Someone passes a wrong value into your tool, and deep inside your code something explodes with a message like Cannot read properties of undefined (reading 'length'). The user has no idea what they did. They didn't write that line - you did, three files down. A tool for others owes its users a helpful error, caught at the front door, that tells them what went wrong and how to fix it.
// validate at the FRONT DOOR and fail with a message that teaches.
// your user should learn what to do next, not get a cryptic crash three files deep.
function createField(options = {}) {
const config = withDefaults({ width: 800, height: 800, count: 3000 }, options);
if (config.count < 0) {
throw new Error(
`flow-field: "count" must be 0 or more, but you passed ${config.count}. ` +
`Try something like { count: 3000 }.`
);
}
if (typeof config.seed !== "number") {
throw new Error(
`flow-field: "seed" must be a number so results are reproducible, ` +
`but you passed a ${typeof config.seed}. Try { seed: 42 }.`
);
}
// ... proceed knowing the inputs are sane ...
}
Read those messages back. Each one names your tool, says which option is wrong, shows what the user actually passed, and gives a copy-pasteable example of the right thing. That's the difference between an error that scolds and an error that teaches. It costs you four lines and it saves your user twenty minutes of confused staring. Honestly, the quality of a tool's error messages tells you more about how much its maker cared than any feature list ever will.
Not everyone who wants your art writes code. Sometimes a blogger, an artist, a friend just wants your generator living on their page with no build step, no npm, nothing to learn. The web has a beautiful answer for that: a custom element - your own HTML tag that anyone can drop in like it was always part of the language. This is how you hand your tool to people who'll never open a terminal.
// wrap your tool in a custom element so ANYONE can use it with plain HTML.
// no build step, no npm - just a tag they paste into their page.
class FlowFieldElement extends HTMLElement {
connectedCallback() {
const canvas = document.createElement("canvas");
// read options straight off the HTML attributes, with our friendly defaults
const seed = Number(this.getAttribute("seed") ?? 42);
const count = Number(this.getAttribute("count") ?? 3000);
canvas.width = canvas.height = 600;
this.appendChild(canvas);
const field = createField({ seed, count });
field.render(canvas.getContext("2d"));
}
}
customElements.define("flow-field", FlowFieldElement);
Once that script is on a page, look what the user has to write to get your art. This is it. This is the whole thing:
<flow-field seed="7" count="5000"></flow-field>
That's the entire user experience for someone who doesn't code: one tag, a couple of attributes named in plain English, done. All your seeded, reproducible, validated cleverness sits behind a tag that reads like it was born into HTML. The first time you see someone put your <flow-field> on their site, with their seed, making something you never would have made - that's a genuinly special feeling. Your tool grew a life of its own.
Remember from last episode how a seed plus a few parameters is the artwork, compressed to a few numbers? Tools for others should lean into that hard, because it gives your users a way to share their creations with zero infrastructure - no database, no accounts, no server. You just pack the settings into the URL itself. The link is the save file.
// pack the settings INTO a URL so users can share a creation with just a link.
// no server, no database - the link itself carries the whole recipe.
function stateToURL(settings) {
const params = new URLSearchParams(settings);
return `${location.origin}${location.pathname}?${params}`;
// -> ".../?seed=7&count=5000&palette=cool-mist"
}
// and read it back when the page loads, so a shared link rebuilds the exact piece:
function urlToState() {
const params = new URLSearchParams(location.search);
const out = {};
for (const [key, value] of params) out[key] = value;
return out; // feed straight into createField(...)
}
That little pair turns every creation your users make into a shareable link they can text to a friend, post on Hive, or bookmark for themselves. The friend opens the link and sees the exact same piece, because the seed made it reproducible and the URL carried the recipe. You've given your users the power to share, and it cost you two tiny functions and no backend at all. That is my favourite kind of feature - big feeling, small code.
Last big idea, and it's the grown-up one. The moment people build things on top of your tool, you've taken on a responsibility: don't yank the rug out from under them. This is what that version field in package.json is really for. There's a shared convention - we'll dig into it properly a bit later - where a version like 1.4.2 tells people, at a glance, whether an update is safe: bump the last number for fixes, the middle for new-but-compatible features, and the first number only when you break something on purpose. Change the shape of your signature and you must turn that first number.
And when you do want to change something, you don't just delete the old way and hope. You keep it working, warn about it, and give people time to move:
// changing an API? don't just break people. keep the old way, warn, and migrate gently.
function createField(options = {}) {
// we renamed "particles" to "count" - but old code still passes "particles".
if ("particles" in options) {
console.warn(
`flow-field: "particles" is deprecated and will be removed in v2. ` +
`Please rename it to "count".`
);
options.count ??= options.particles; // keep their old code WORKING for now
}
const config = withDefaults({ count: 3000 }, options);
// ...
}
See the care in that? Someone's old code that says { particles: 5000 } still works today - it just prints a gentle warning telling them what to change before the next big version. You're respecting the fact that people trusted your tool enough to build on it. That respect, more than any feature, is what turns a tool people try into a tool people rely on. Breaking your users teaches them never to depend on you again, and a tool nobody depends on isn't really a tool.
One last honest word, tying straight back to last episode. You can build the friendliest, most-defaulted, best-error-messaged tool in the world, and if the first thing a visitor sees is a blank page, they'll leave. Your README - that first screen on the project page - is your tool's front door, and it should show, in the first ten seconds, the simplest possible thing that makes something nice. Not a wall of options. Just the magic, immediately.
# flow-field
Seeded, reproducible flow-field art for the browser.
import { createField } from "flow-field";
const field = createField({ seed: 7 });
field.render(document.querySelector("canvas").getContext("2d"));
That's it - three lines and you've got art. See below for palettes, options,
and the `` HTML tag for no-code pages.
Lead with the shortest path to a smile. The exhaustive options table can live further down for the people who fall in love and want more - but the first thing anyone reads should prove your tool is worth two minutes of their evening. That's the artist-statement lesson from last episode, pointed at code: feeling first, details later. Show them the good bit before you ask them to learn anything.
Step back and feel what changed today. Every episode before this, we made art. Today we made something that makes art in other people's hands - and that's a different kind of creating, one where your cleverness multiplies through strangers you'll never meet. We did it with almost no new syntax: an options object, sensible defaults, a small public surface, honest errors, a shareable link, and the promise not to break the people who trust you. That's not really JavaScript knowledge. It's manners, encoded.
And this opens a door that's going to stay open for a good while. Once your tools are friendly enough to hand over, the natural next questions come thick and fast. How do you build one deliberately, start to finish, as a real project rather than a lesson? What happens when you put it out in the open where anyone can not just use it but change it and give their changes back? And while we're making tools solid and fast, there's a whole new engine arriving in the browser that's about to change what "fast" even means for graphics. All of that grows straight out of today - the moment your work stopped being just yours. So this week, one small thing: take any sketch you're fond of, and give it one good options object with sensible defaults, so that yourSketch() with no arguments already does something lovely. That single change is the whole spirit of building for others, in miniature. Try it, and notice how differently you think about your own code the moment you imagine a stranger calling it :-).
...options on top so callers set only what they care aboutyourTool() with zero arguments should already do something nice. A good tool is welcoming on the very first call, before the reader learns a single optionpackage.json is honesty about what you made, and index.js is where you expose ONLY the handful of things you'll support forever, and hide the messy rest so you can rewrite it freely<flow-field seed="7">) hands your tool to people who'll never open a terminal - one HTML tag, no build step. And pack state into a URL so users share creations with just a link, no server at allconsole.warn about it, and migrate gently. And make your README show the shortest path to a smile in the first ten secondsSo that's the bridge crossed - from work that lives on your screen to tools that live in other people's hands. The lovely surprise, as always, is how little of it is new code and how much of it is just care: a well-named option, a default chosen kindly, an error that explains itself, a promise kept. Give one of your own sketches a proper options object this week and hand it to a friend. The moment they make something with it that you never would have - that's when you'll feel what building tools is really for. Merci for reading, and go make something someone else can use :-).
Sallukes! Thanks for reading.
X