TypeScript quickstart
In this quick start guide, we will write our first script in TypeScript. Windmill uses Bun, Nodejs and Deno as the available TypeScript runtimes.
This tutorial covers how to create a simple "Hello World" script in TypeScript through Windmill web IDE, with the standard mode of handling dependencies in TypeScript (Lockfile per script inferred from imports). See the dedicated pages to develop scripts locally and other methods of handling dependencies in TypeScript.
Scripts are the basic building blocks in Windmill. They can be run and scheduled as standalone, chained together to create Flows or displayed with a personalized User Interface as Apps.
Scripts consist of 2 parts:
- Code: for TypeScript scripts, it must have at least a main function.
- Settings: settings & metadata about the Script such as its path, summary, description, JSON Schema of its inputs (inferred from its signature).
When stored in a code repository, those 2 parts are stored separately at <path>.ts and <path>.script.yaml.
This is a simple example of a script built in TypeScript with Windmill:
import { WebClient } from '@slack/web-api';
type Slack = {
token: string;
};
export async function main(slack: Slack, channel: string, message: string): Promise<void> {
// Initialize the Slack WebClient with the token from the Slack resource
const web = new WebClient(slack.token);
// Use the chat.postMessage method from the Slack WebClient to send a message
await web.chat.postMessage({
channel: channel,
text: message
});
}
In this quick start guide, we'll create a script that greets the operator running it.
From the Home page, click +Script. This will take you to the first step of script creation: Metadata.
Settings

As part of the settings menu, each script has metadata associated with it, enabling it to be defined and configured in depth.
- Summary (optional) is a short, human-readable summary of the Script. It will be displayed as a title across Windmill. If omitted, the UI will use the
pathby default. - Path is the Script's unique identifier that consists of the script's owner, and the script's name. The owner can be either a user, or a group (folder).
- Description is where you can give instructions through the auto-generated UI to users on how to run your Script. It supports markdown.
- Language of the script.
- Script kind: Action (by default), Trigger, Approval, Error handler or Preprocessor. This acts as a tag to filter appropriate scripts from the flow editor.
This menu also has additional settings on Runtime, Generated UI and Triggers.
Now click on the code editor on the left side.
Code
Windmill provides an online editor to work on your Scripts. The left-side is the editor itself. The right-side previews the UI that Windmill will generate from the Script's signature - this will be visible to the users of the Script. You can preview that UI, provide input values, and test your script there.

There are two options for runtimes in TypeScript:
As we picked TypeScript (Bun) for this example, Windmill provided some TypeScript boilerplate. Let's take a look:
// there are multiple modes to add as header: //nobundling //native //npm //nodejs
// https://www.windmill.dev/docs/getting_started/scripts_quickstart/typescript#modes
// import { toWords } from "number-to-words@1"
import * as wmill from "windmill-client"
// fill the type, or use the +Resource type to get a type-safe reference to a resource
// type Postgresql = object
export async function main(
a: number,
b: "my" | "enum",
//c: Postgresql,
//d: wmill.S3Object, // https://www.windmill.dev/docs/core_concepts/persistent_storage/large_data_files
//d: DynSelect_foo, // https://www.windmill.dev/docs/core_concepts/json_schema_and_parsing#dynamic-select
e = "inferred type string from default arg",
f = { nested: "object" },
g: {
label: "Variant 1",
foo: string
} | {
label: "Variant 2",
bar: number
}
) {
// let x = await wmill.getVariable('u/user/foo')
return { foo: a };
}
In Windmill, scripts need to have a main function that will be the script's
entrypoint. There are a few important things to note about the main.
- The main arguments are used for generating
- the input spec of the Script
- the frontend that you see when running the Script as a standalone app.
- Type annotations are used to generate the UI form, and help pre-validate inputs. While not mandatory, they are highly recommended. You can customize the UI in later steps (but not change the input type!).
Also take a look at the import statement lines that are commented out. In TypeScript, dependencies and their versions are contained in the script and hence there is no need for any additional steps. The TypeScript runtime is Bun, which is 100% compatible with Node.js without any code modifications. You can use npm imports directly in Windmill. The last import line imports the Windmill client, that is needed for example, to access variables or resources. We won't go into that here.
Back to our "Hello World". We can clear up unused import statements, change the
main to take in the user's name. Let's also return the name, maybe we can use
this later if we use this Script within a flow or app and need to pass its result on.
export async function main(name: string) {
console.log("Hello world! Oh, it's you %s? Greetings!", name);
return { name };
}