Types
What flows through a port.
Every port in Weft has a concrete type. The compiler uses types to validate connections, drive parallel processing, render Runner UI, and steer the AI builder. There is no Any type. If you need an escape hatch for genuinely opaque JSON, use JsonDict.
Primitives
String: text. Free-form strings, URLs, emails, prompts, identifiers, stringified JSON, date strings, credentials.Number: numeric values. Counts, temperatures, coordinates, money, percentages, durations.Boolean: true or false. Toggles, flags, on/off switches.Null: the absence of a value. Rarely written by hand; used in unions likeString | Null.
Rule of thumb: if a user-facing input is a yes/no question, use Boolean. If it is a fixed set of choices, use String with options. Do not store flags as strings, the Runner UI depends on the type to pick the right form control.
There is no Date type and no Credential type. Dates are stored as String (typically ISO format). Credentials are stored as String as well, but the catalog's Credential node marks its config field as type password, which is what triggers the automatic stripping on export and publish.
Media
Image: a single image.Audio: a single audio clip.Video: a single video.Document: a PDF, a text file, a spreadsheet, anything document-shaped.Media: shorthand for "any of the four".
Media types carry a JSON handle with {url, mimeType, filename}. See Files and media for how files are uploaded, stored, and served.
Collections
List[T] is a list of T. Lists nest freely:
Dict[String, T] is a map from string keys to values of type T:
For genuinely unstructured JSON, use JsonDict rather than deeply nested Dict types. It is the one escape hatch in the system, and it exists so you do not have to write Dict[String, Dict[String, Dict[String, ...]]].
Unions
A union says "this port carries one of these types at runtime":
The compiler accepts a connection if the source is assignable to any variant of the target's union. Narrowing also works: if the catalog says a port is String | Number and you know in your project it will only ever be String, you can declare the port as just String in your code and the compiler will enforce it.
Type variables
Some nodes work on "whatever you pass in, give me the same shape back". For those, the catalog declares type variables (T, T1, T2). The same variable name on an input and an output means "these two ports share a type, resolved at connection time".
This is how you get generics in Weft. The same node type works on strings, numbers, images, lists, or whatever, without losing type safety downstream.
Required, optional, and Null
A port is required by default. To mark it optional, add ? after the type:
Required ports stop the node from running if they receive null. Optional ports let the node run and pass null through to its code. String? is equivalent to String | Null. Both forms are accepted, use whichever is clearer in context.
Do not over-type with | Null defensively. Null propagation already prevents nulls from reaching required ports, because upstream nodes skip instead. Only add | Null when the upstream node can genuinely produce null as a valid value, not as a skip signal.
MustOverride
A few nodes do not know their output type ahead of time. The catalog marks their output as MustOverride, and you are required to declare the real type in your project.
LlmInference is the most common case. Its response output is MustOverride because the LLM could return a plain string, parsed JSON, or a structured object depending on what you asked for. You tell the compiler which:
In the graph view, a MustOverride port shows up in red. Right-click to set its type, or declare it in the code. Either way works, the result is the same.
Why all this matters
Types in Weft are not decoration. They do real work:
- Connection validation. Wrong type at an edge is a compile error. You find it while building, not at 3am.
- Parallel processing. A
List[T]flowing into aTinput is the compiler's signal to split the list into parallel lanes. See parallel processing. - Runner UI generation. The Runner picks form controls based on the type. Boolean becomes a toggle, Image becomes an upload, String becomes a text box. Use the wrong type and the Runner falls back to a text input.
- AI builder steering. Tangle relies on types to know what nodes can connect to what, which cuts wasted generation.
What's next
- Connections: how types are checked at each edge.
- Null propagation: how required vs optional becomes runtime behavior.
- Parallel processing: how
List[T]turns into parallel lanes.