defineFunction¶
Declares a JS Function. Returns a FunctionDefinition that the runtime dispatches on.
Signature¶
function defineFunction<I extends ContractSchema, O extends ContractSchema>(
options: DefineFunctionOptions<I, O>
): FunctionDefinition
DefineFunctionOptions¶
| Property | Type | Required | Description |
|---|---|---|---|
name |
string |
yes | Logical name, used in the manifest and Studio |
method |
"GET" \| "POST" \| "PUT" \| "PATCH" \| "DELETE" |
no* | HTTP method. Omit (together with path) for a job-only function |
path |
string |
no* | URL path, must start with /. Supports :param segments. Omit (together with method) for a job-only function |
input |
ContractSchema |
no | Request contract — validates and types input |
output |
ContractSchema |
no | Response contract — types the return value |
handler |
(input, ctx) => result \| Promise<result> |
yes | Business logic |
description |
string |
no | Human-readable description, shown in Studio |
tags |
string[] |
no | Tags for grouping in Studio |
* method and path must be provided together or both omitted. Providing one without the other throws.
HTTP vs. job-only functions¶
Every function runs as a job — callable from a Trigger, a Maestro process, a Flow, or the Orchestrator API — regardless of whether it declares HTTP semantics.
- Declare
method+pathto additionally expose the function as an HTTP endpoint. This is what localserveroutes to, and the use case for Coded Apps backends and API-style integrations. - Omit both for a job-only function. It stays in the manifest and is fully invokable as a job, but gets no HTTP route. When run as a job there is no HTTP request, so
ctx.paramsandctx.headersare empty.
handler¶
handler: async (input: HandlerInput<I>, ctx: FunctionContext) =>
| T // plain value — serialized as JSON with status 200
| FunctionResponse // { status, body?, headers? } — full control
| void // empty 200 response
The handler's input parameter is typed from the declared contract: for defineSchema<T>() it is T; for a JSON Schema literal it is the type derived from the literal. If output is declared, T must match it the same way.
Declaring contracts¶
A contract is a curated JSON Schema subset — inert data, statically extractable without executing your code. Two authoring variants feed the same runtime validation path:
TypeScript — declare the contract as a type with defineSchema<T>():
import { defineFunction, defineSchema } from "@uipath/coded-functions-js-sdk";
interface CreateOrderInput {
customerId: string;
items: { sku: string; qty: number }[];
note?: string;
}
export default defineFunction({
name: "create-order",
method: "POST",
path: "/orders",
input: defineSchema<CreateOrderInput>(),
handler: async (input) => ({ ok: true }), // input is CreateOrderInput
});
defineSchema<T>() is inert at runtime. At build time (pack/serve/run) the type is lowered to the equivalent JSON Schema literal, which is what the runtime validates and what lands in the manifest.
JavaScript — write the JSON Schema literal directly:
// @ts-check
import { defineFunction } from "@uipath/coded-functions-js-sdk";
export default defineFunction({
name: "create-order",
method: "POST",
path: "/orders",
input: {
type: "object",
properties: {
customerId: { type: "string" },
items: {
type: "array",
items: {
type: "object",
properties: { sku: { type: "string" }, qty: { type: "number" } },
required: ["sku", "qty"],
additionalProperties: false,
},
},
note: { type: "string" },
},
required: ["customerId", "items"],
additionalProperties: false,
},
handler: async (input) => ({ ok: true }), // input type is derived from the literal
});
Both variants above declare the same contract — the TS type lowers to exactly that literal.
How types map to schemas¶
| TypeScript | JSON Schema |
|---|---|
string / number / boolean |
{ "type": "string" \| "number" \| "boolean" } |
"draft" \| "sent" (literal union) |
{ "type": "string", "enum": [...] } — numeric literal unions likewise |
String / numeric TS enum |
{ "type": "string" \| "number", "enum": [...] } |
T[] |
{ "type": "array", "items": ... } |
interface / object type |
closed object — additionalProperties: false, non-optional props in required |
foo?: T |
property present, not in required |
Record<string, T> |
{ "type": "object", "additionalProperties": <schema of T> } |
unknown (value position) |
{} — accepts any JSON value (e.g. Record<string, unknown> for arbitrary job args) |
Date |
{ "type": "string", "format": "date-time" } |
A \| B (non-literal) |
{ "anyOf": [...] } |
any, bigint, tuples, functions, top-level unknown |
rejected at build time with an actionable diagnostic |
JSDoc @default¶
A TS type cannot express a default value, so the derivation reads it from JSDoc:
interface HelloInput {
/**
* Name to greet — filled in by the runtime when omitted.
* @default "World"
*/
name?: string;
}
This lowers to { "type": "string", "default": "World" }; the runtime fills the field when the caller omits it. The tag text is parsed as JSON ("World" → string, 3 → number, true → boolean).
The curated subset¶
Hand-written literals may use the full subset — including validation keywords a TS type cannot express:
| Group | Keywords |
|---|---|
| Types | type: string, number, integer, boolean, object, array, null |
| Annotations | title, description, default |
| Literals | const, enum |
| Strings | format (date-time, date, time, email, uri, uuid), minLength, maxLength, pattern |
| Numbers | minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf |
| Arrays | items, minItems, maxItems, uniqueItems |
| Objects | properties, required, additionalProperties |
| Composition | anyOf, oneOf, allOf |
No $ref/$defs, no custom keywords, no custom format functions — a contract stays inert, portable data. The literal must also be static: no spreads, computed keys, or identifier references.
Runtime validation¶
The runtime compiles the contract with ajv (ajv never appears in your project):
- Input is validated before the handler runs; failures return
400with per-field errors. - GET query strings are coerced to their schema types (
"42"→42,"true"→true). defaultvalues are filled into missing input fields.- Output (when declared) is validated after the handler; failures return
500.
Backwards compatibility: validator schemas¶
input/output may still be a Standard Schema object (zod >= 4.2, arktype >= 2.1.28, valibot >= 1.2 with @valibot/to-json-schema >= 1.5). These keep working unchanged, but require executing your module to obtain the contract — so they don't work with static tooling (Studio Web contract extraction) and need the validator as a runtime dependency. Prefer defineSchema<T>() / literals for new functions.
Examples¶
Minimal POST¶
import { defineFunction, defineSchema } from "@uipath/coded-functions-js-sdk";
interface HelloInput {
/** @default "World" */
name?: string;
}
interface HelloOutput {
message: string;
}
export default defineFunction({
name: "hello",
method: "POST",
path: "/hello",
input: defineSchema<HelloInput>(),
output: defineSchema<HelloOutput>(),
handler: async (input) => ({ message: `Hello, ${input.name}!` }),
});
Job-only (no HTTP route)¶
Omit method and path for a function invoked purely as a job — from a Trigger, Maestro, Flow, or the Orchestrator API. Run it locally with uip functions run.
import { defineFunction, defineSchema } from "@uipath/coded-functions-js-sdk";
interface ProcessInvoiceInput {
invoiceId: string;
}
interface ProcessInvoiceOutput {
status: string;
}
export default defineFunction({
name: "process-invoice",
input: defineSchema<ProcessInvoiceInput>(),
output: defineSchema<ProcessInvoiceOutput>(),
handler: async (input, ctx) => {
// Called as a job — ctx.params/ctx.headers are empty and there is no caller identity
// (ctx.user?.accessToken is undefined); use ctx.robot?.accessToken, which the platform
// provides for a deployed job.
return { status: "processed" };
},
});
GET with path parameter¶
export default defineFunction({
name: "get-invoice",
method: "GET",
path: "/invoices/:id",
handler: async (input, ctx) => {
return { id: ctx.params.id };
},
});
GET /invoices/INV-001 binds ctx.params.id to "INV-001" in local serve and when deployed
alike. The path is registered as the trigger's slug verbatim, pattern included, and the deployed
trigger resolves it by route matching. See Platform Context — path parameters
for the full pattern syntax and the matching rules.
Custom status¶
Return a FunctionResponse to control the HTTP status:
FunctionResponse with headers¶
handler: async (input) => {
return {
status: 200,
body: { ok: true },
headers: { "X-Custom-Header": "value" },
};
}
Validation¶
defineFunction throws at module load time if:
- only one of
method/pathis provided (supply both, or omit both for a job-only function) methodis not one of the five allowed HTTP methodspathdoes not start with/