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 extending the DocumentBase class and decorating it with @BlockConfig and @WithArguments. Documents define their schema using Zod and optionally include UI configuration for form rendering.
Basic Document Definition
TypeScript
import { z } from 'zod';
import { DocumentBase } from '@loopstack/core';
import { BlockConfig, WithArguments } from '@loopstack/common';
import { Injectable } from '@nestjs/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([]),
});
@Injectable()
@BlockConfig({
configFile: __dirname + '/task.document.yaml',
})
@WithArguments(TaskDocumentSchema)
export class TaskDocument extends DocumentBase {}Key Components
@Injectable() Decorator
- Standard NestJS decorator that marks the class as injectable
- Required for dependency injection within the framework
@BlockConfig Decorator
- Links the document class to its YAML configuration via
configFile - Defines metadata and configuration for the document
@WithArguments() Decorator
- Defines the document schema and validation rules using a Zod schema
- Replaces the inline
propertiesdefinition from the previous version - Schema can be exported for reuse in workflows with
@WithState
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 totrue
Registering the Document
Documents are registered in workflows using the @Document() decorator and must be imported in your module.
workflow.ts
import { z } from 'zod';
import { WorkflowBase } from '@loopstack/core';
import { BlockConfig, Document, Tool, WithArguments, WithState } from '@loopstack/common';
import { CreateDocument } from '@loopstack/core-ui-module';
import { TaskDocument, TaskDocumentSchema } from './documents/task.document';
import { Injectable } from '@nestjs/common';
@Injectable()
@BlockConfig({
configFile: __dirname + '/task-workflow.yaml',
})
@WithArguments(z.object({
userId: z.string(),
}))
@WithState(z.object({
task: TaskDocumentSchema,
}))
export class TaskWorkflow extends WorkflowBase {
@Tool() createDocument: CreateDocument;
@Document() taskDocument: TaskDocument;
}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: {{ task.title }}
Due: {{ task.dueDate }}Capturing User Input
Combine documents with manual transitions to collect user input through forms:
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
when: manual
call:
- tool: createDocument
args:
id: savedTask
document: taskDocument
update:
content: ${ transition.payload }
assign:
task: ${ result.data.content }Complete Example
Hereβs a full example showing a workflow that generates a code file using AI:
document.ts
import { z } from 'zod';
import { DocumentBase } from '@loopstack/core';
import { BlockConfig, WithArguments } from '@loopstack/common';
import { Injectable } from '@nestjs/common';
export const FileDocumentSchema = z.object({
filename: z.string(),
description: z.string().optional(),
code: z.string(),
});
@Injectable()
@BlockConfig({
configFile: __dirname + '/file-document.yaml',
})
@WithArguments(FileDocumentSchema)
export class FileDocument extends DocumentBase {}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 |