Creating Documents
Documents are structured data containers in Loopstack that enable user input, data validation, and form rendering in the Loopstack Studio. They’re essential for capturing and displaying structured information within your workflows.
Creating a Document
A document is created by decorating a class with @Document and implementing DocumentInterface. Documents define their content schema using the @Input decorator with a Zod schema, and optionally include UI configuration in a YAML file for form rendering.
Basic Document Definition
TypeScript
import { z } from 'zod';
import { Document, Input } from '@loopstack/common';
export const TaskDocumentSchema = z.object({
title: z.string().min(1, "Title is required"),
description: z.string().optional(),
dueDate: z.string().datetime(),
priority: z.enum(['low', 'medium', 'high']).default('medium'),
assignees: z.array(z.string()).default([]),
});
@Document({
configFile: __dirname + '/task.document.yaml',
})
export class TaskDocument {
@Input({
schema: TaskDocumentSchema,
})
content: z.infer<typeof TaskDocumentSchema>;
}Key Components
@Document Decorator
- Links the document class to its YAML configuration via
configFile - Defines metadata and configuration for the document
@Input() Decorator
- Defines the document’s content schema and validation rules using a Zod schema
- The
schemaproperty accepts a Zod object schema - The decorated
contentproperty holds the document’s data at runtime
YAML Config
ui.form.order- Controls the display order of fields in the Studioui.form.properties- Defines how each field is rendered in the frontendwidget- Specifies the input type (text, textarea, select, date, etc.)readonly- Makes fields display-only when set totruecollapsed- Collapses array fields by default when set totrueitems- Configures rendering of individual items in array fieldsui.actions- Defines interactive buttons that trigger workflow transitions
Registering the Document
Documents are registered in workflows using the @InjectDocument() decorator and must be provided in your module.
workflow.ts
import { z } from 'zod';
import {
Workflow,
InjectDocument,
InjectTool,
Input,
State,
Runtime,
} from '@loopstack/common';
import { CreateDocument } from '@loopstack/core-ui-module';
import { TaskDocument, TaskDocumentSchema } from './documents/task.document';
@Workflow({
configFile: __dirname + '/task-workflow.yaml',
})
export class TaskWorkflow {
@InjectTool() createDocument: CreateDocument;
@InjectDocument() taskDocument: TaskDocument;
@Input({
schema: z.object({
userId: z.string(),
}),
})
args: {
userId: string;
};
@State({
schema: z.object({
task: TaskDocumentSchema.optional(),
}),
})
state: {
task?: z.infer<typeof TaskDocumentSchema>;
};
@Runtime()
runtime: any;
}Using Documents in Workflows
Creating Document Instances
Use the createDocument tool to instantiate documents in your workflow:
title: "Task Management Workflow"
description: A workflow for creating and managing tasks.
transitions:
- id: initialize_task
from: start
to: task_created
call:
- tool: createDocument
args:
id: task
document: taskDocument
update:
content:
title: "Review quarterly reports"
description: "Analyze Q4 performance metrics"
dueDate: "2025-12-31T23:59:59Z"
priority: "high"
assignees: ["alice@example.com", "bob@example.com"]Using Template Variables
Documents can use workflow arguments and state variables with template syntax:
transitions:
- id: create_personalized_task
from: start
to: task_created
call:
- tool: createDocument
args:
id: task
document: taskDocument
update:
content:
title: "Task for {{ args.userId }}"
priority: "medium"Storing Document Output in State
Use assign to store document data in workflow state for later use:
transitions:
- id: generate_task
from: start
to: task_generated
call:
- tool: aiGenerateDocument
args:
llm:
provider: openai
model: gpt-4o
response:
document: taskDocument
prompt: |
Create a task for reviewing the quarterly report.
assign:
task: ${{ result.data.content }}
- id: display_task
from: task_generated
to: end
call:
- tool: createDocument
args:
id: summary
document: messageDocument
update:
content:
role: assistant
parts:
- type: text
text: |
Created task: {{ state.task.title }}
Due: {{ state.task.dueDate }}Capturing User Input
Combine documents with manual transitions to collect user input through forms. Use trigger: manual to pause the workflow and wait for user interaction, and access the submitted data via runtime.transition.payload:
transitions:
- id: display_form
from: start
to: awaiting_input
call:
- tool: createDocument
args:
id: taskForm
document: taskDocument
update:
content:
title: ""
priority: "medium"
- id: process_input
from: awaiting_input
to: task_saved
trigger: manual
call:
- tool: createDocument
args:
id: taskForm
document: taskDocument
update:
content: ${{ runtime.transition.payload }}
assign:
task: ${{ result.data.content }}Available UI Widgets
Documents support various widget types for different data formats:
| Widget | Description | Best For |
|---|---|---|
text | Single-line text input | Short text, names, titles, email, url, numbers |
textarea | Multi-line text input with fixed height | Descriptions, notes, long text |
textarea-expand | Multi-line text input with expandable height | Long-form content that varies in length |
select | Dropdown selection | Single choice from predefined options |
radio | Radio button group | Single choice with visible options |
checkbox | Boolean checkbox | Yes/no, true/false values |
switch | Toggle switch | Enable/disable, on/off states |
slider | Numeric slider | Numeric values within a defined range |
code-view | Code display field | Read-only code snippets or formatted text |