TypeScript Quickstart
In this quick start guide, we will write our first script in TypeScript. Windmill uses Deno and Bun as the TypeScript runtime.
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.
- Metadata: metadata about the Script such as its path, summary, description, jsonschema 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 "https://deno.land/x/[email protected]/mod.ts";
type Slack = {
token: string;
};
export async function main(text: string, channel: string, slack: Slack) {
const web = new WebClient(slack.token);
await web.chat.postMessage({
channel,
text,
});
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.
Metadata
Each script has metadata associated with it, enabling it to be defined and configured in depth.
- Path is the Script's unique identifier that consist of the script's owner, and the script's name. The owner can be either a user, or a group (folder).
- 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
path
by default. - Language of the script.
- Description is where you can give instructions through the auto-generated UI to users on how to run your Script. It supports markdown.
- Concurrency limits enable defining concurrency limits for scripts and inline scripts within flows to prevent exceeding the API Limit of the targeted API.
- Worker group tag to assign scripts to specific worker groups (such as nodes with GPU accelaration).
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 easily preview that UI, provide input values, and test your script there.
There are two options for runtimes in TypeScript:
Deno
As we picked TypeScript (Deno)
for this example, Windmill provided some TypeScript
boilerplate. Let's take a look:
// Ctrl/CMD+. to cache dependencies on imports hover.
// import { toWords } from "npm:number-to-words@1"
// import * as wmill from "https://deno.land/x/[email protected]/mod.ts"
// 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 = "inferred type string from default arg",
e = { nested: "object" },
//e: wmill.Base64
) {
// 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 resolution is done by deno. 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 };
}
Bun
As we picked TypeScript (Bun)
for this example, Windmill provided some TypeScript
boilerplate. Let's take a look:
// import { toWords } from "number-to-words@1"
import * as wmill from "[email protected]"
// 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 = "inferred type string from default arg",
e = { nested: "object" },
) {
// 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, The 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 };
}
Instant Preview & Testing
Look at the UI preview on the right: it was updated to match the input
signature. Run a test (Ctrl
+ Enter
) to verify everything works.
You can change how the UI behaves by changing the main signature. For example,
if you add a default for the name
argument, the UI won't consider this field
as required anymore.
main(name: string = "you")
Now let's go to the last step: the "Customise" settings.
Customize Settings
In this menu, you can:
- Give a script kind: Action (by default), Trigger, Approval or Error Handler. This acts as a tag to filter appropriate scripts from the flow editor.
- Customize the script's Arguments. The UI is generated from the Script's main function signature, but you can add additional constraints here. For example, we could use the
Customize property
: add a regex by clicking onPattern
to make sure users are providing a name with only alphanumeric characters:^[A-Za-z0-9]+$
. Let's still allow numbers in case you are some tech billionaire's kid.
We're done! Save your script. Note that Scripts are versioned in Windmill, and each script version is uniquely identified by a hash.
Run!
Now let's look at what users of the script will do. Click on the Deploy button to load the script. You'll see the user input form we defined earlier.
Fill in the input field, then hit "Run". You should see a run view, as well as your logs. All script runs are also available in the Runs menu on the left.
You can also chose to run the script from the CLI with the pre-made Command-Line Interface call.
What's next?
This script is a minimal working example, but there's a few more steps that can be useful in a real-world use case:
- Pass variables and secrets to a script.
- Connect to resources.
- Trigger that script in many ways.
- Compose scripts in [Flows][flows] or Apps.
- You can share your scripts with the community on Windmill Hub. Once submitted, they will be verified by moderators before becoming available to everyone right within Windmill.
Scripts are immutable and there is an hash for each deployment of a given script. Scripts are never overwritten and referring to a script by path is referring to the latest deployed hash at that path.
For each script, a UI is autogenerated from the jsonchema inferred from the script signature, and can be customized further as standalone or embedded into rich UIs using the App builder.
In addition to the UI, sync and async webhooks are generated for each deployment.