Defining Contracts
Learn how to define type-safe contracts for your Temporal workflows and activities.
Overview
Contracts are the foundation of temporal-contract. They define the interface for your workflows, including inputs, outputs, and activities, all using Zod schemas for validation.
Basic Contract Structure
typescript
import { defineContract } from "@temporal-contract/contract";
import { z } from "zod";
export const myContract = defineContract({
taskQueue: "my-task-queue",
// Global activities available to all workflows
activities: {
log: {
input: z.object({
level: z.enum(["info", "warn", "error"]),
message: z.string(),
}),
output: z.void(),
},
},
// Workflow definitions
workflows: {
myWorkflow: {
input: z.object({
userId: z.string(),
amount: z.number().positive(),
}),
output: z.object({
success: z.boolean(),
transactionId: z.string().optional(),
}),
// Workflow-specific activities
activities: {
processPayment: {
input: z.object({
userId: z.string(),
amount: z.number(),
}),
output: z.object({
transactionId: z.string(),
}),
},
},
},
},
});Contract Elements
Task Queue
The task queue name that workers will listen on:
typescript
taskQueue: "orders";Global Activities
Activities that are available to all workflows in the contract:
typescript
activities: {
sendEmail: {
input: z.object({
to: z.string().email(),
subject: z.string(),
body: z.string()
}),
output: z.object({ sent: z.boolean() }),
},
}Workflows
Each workflow must define:
input: Zod schema for workflow parametersoutput: Zod schema for workflow return valueactivities: Workflow-specific activities (optional)
typescript
workflows: {
processOrder: {
input: z.object({ orderId: z.string() }),
output: z.object({ status: z.string() }),
activities: { /* ... */ }
}
}Workflow-Specific Activities
Activities that are only available within a specific workflow:
typescript
workflows: {
processOrder: {
// ...
activities: {
chargeCard: {
input: z.object({ amount: z.number() }),
output: z.object({ success: z.boolean() }),
},
},
},
}Schema Validation
All inputs and outputs are validated using Zod schemas:
typescript
input: z.object({
email: z.string().email(), // Email validation
age: z.number().int().positive(), // Integer validation
status: z.enum(["active", "inactive"]), // Enum validation
metadata: z.record(z.string()).optional(), // Optional fields
});Type Inference
TypeScript automatically infers types from your schemas:
typescript
const contract = defineContract({
workflows: {
processOrder: {
input: z.object({
orderId: z.string(),
amount: z.number(),
}),
output: z.object({
success: z.boolean(),
}),
activities: {},
},
},
});
// Types are automatically inferred:
// input: { orderId: string; amount: number }
// output: { success: boolean }Complex Schemas
Use Zod's full power for complex validations:
typescript
const AddressSchema = z.object({
street: z.string(),
city: z.string(),
zipCode: z.string().regex(/^\d{5}$/),
});
const OrderSchema = z.object({
items: z
.array(
z.object({
productId: z.string(),
quantity: z.number().int().positive(),
}),
)
.min(1),
shippingAddress: AddressSchema,
billingAddress: AddressSchema.optional(),
total: z.number().positive(),
});
export const orderContract = defineContract({
taskQueue: "orders",
workflows: {
processOrder: {
input: OrderSchema,
output: z.object({
orderId: z.string(),
status: z.enum(["pending", "completed", "failed"]),
}),
activities: {},
},
},
});Reusable Schemas
Define schemas once and reuse them:
typescript
const PaymentInput = z.object({
amount: z.number().positive(),
currency: z.enum(["USD", "EUR", "GBP"]),
});
const PaymentOutput = z.object({
transactionId: z.string(),
status: z.enum(["success", "failed"]),
});
export const contract = defineContract({
taskQueue: "payments",
workflows: {
processPayment: {
input: PaymentInput,
output: PaymentOutput,
activities: {
chargeCard: {
input: PaymentInput,
output: PaymentOutput,
},
},
},
},
});Multiple Workflows
A contract can define multiple workflows:
typescript
export const ecommerceContract = defineContract({
taskQueue: "ecommerce",
workflows: {
processOrder: {
input: z.object({ orderId: z.string() }),
output: z.object({ success: z.boolean() }),
activities: {
/* ... */
},
},
processRefund: {
input: z.object({ orderId: z.string(), reason: z.string() }),
output: z.object({ refunded: z.boolean() }),
activities: {
/* ... */
},
},
updateInventory: {
input: z.object({ productId: z.string(), delta: z.number() }),
output: z.object({ newQuantity: z.number() }),
activities: {
/* ... */
},
},
},
});Best Practices
1. Keep Contracts Focused
Group related workflows in the same contract:
typescript
// ✅ Good - related workflows together
export const orderContract = defineContract({
taskQueue: "orders",
workflows: {
createOrder: {
/* ... */
},
cancelOrder: {
/* ... */
},
updateOrder: {
/* ... */
},
},
});
// ❌ Avoid - mixing unrelated workflows
export const contract = defineContract({
taskQueue: "everything",
workflows: {
processOrder: {
/* ... */
},
sendEmail: {
/* ... */
},
generateReport: {
/* ... */
},
},
});2. Use Descriptive Names
typescript
// ✅ Good - clear and descriptive
workflows: {
processOrderPayment: { /* ... */ },
cancelOrderAndRefund: { /* ... */ },
}
// ❌ Avoid - vague names
workflows: {
process: { /* ... */ },
handle: { /* ... */ },
}3. Document Complex Schemas
typescript
/**
* Order processing workflow
*
* Handles the complete order lifecycle including:
* - Payment processing
* - Inventory reservation
* - Shipping coordination
*/
processOrder: {
input: z.object({
orderId: z.string().describe('Unique order identifier'),
customerId: z.string().describe('Customer UUID'),
items: z.array(OrderItemSchema).describe('List of items to purchase'),
}),
// ...
}See Also
- Client Usage - Using contracts with the client
- Worker Usage - Implementing contracts in workers
- Core Concepts - Understanding the contract-first approach