Creating Documents
Documents are typed data objects displayed in the Loopstack Studio UI. They have a Zod schema for validation and a YAML config for rendering.
Basic Document
import { z } from 'zod';
import { Document } from '@loopstack/common';
export const NotesSchema = z.object({
text: z.string(),
});
@Document({
schema: NotesSchema,
uiConfig: __dirname + '/notes.ui.yaml',
})
export class NotesDocument {
text: string;
}# notes.ui.yaml
type: document
ui:
widgets:
- widget: form
options:
properties:
text:
title: Notes
widget: textarea
rows: 8The @Document Decorator
@Document({
schema: NotesSchema, // Zod schema for validation
uiConfig: __dirname + '/notes.ui.yaml', // Path to UI YAML config
})schema— Zod schema that validates document contentuiConfig— Path to YAML file defining how the document renders in the UI
Saving Documents
Use this.repository.save() inside workflow transition methods. Reference document classes directly — no injection needed.
// Create a new document
await this.repository.save(NotesDocument, { text: 'Hello!' });
// Create/update with a specific ID
await this.repository.save(NotesDocument, { text: 'Updated content' }, { id: 'notes-1' });
// With meta options
await this.repository.save(NotesDocument, { text: 'Hidden note' }, { id: 'hidden', meta: { hidden: true } });Save Options
| Option | Type | Description |
|---|---|---|
id | string | Custom ID — use for updating existing documents |
meta.hidden | boolean | Hide from the UI |
Built-in Document Types
These are available without creating custom documents:
| Document | Source | Key Fields |
|---|---|---|
ClaudeMessageDocument | @loopstack/claude-module | role, content |
LinkDocument | @loopstack/core | label, workflowId, href, status |
MessageDocument | @loopstack/core | role, content |
MarkdownDocument | @loopstack/core | markdown |
PlainDocument | @loopstack/core | text |
ErrorDocument | @loopstack/core | error |
import { ClaudeMessageDocument } from '@loopstack/claude-module';
import { LinkDocument, MarkdownDocument } from '@loopstack/core';
await this.repository.save(ClaudeMessageDocument, {
role: 'assistant',
content: 'Hello! How can I help?',
});
await this.repository.save(MarkdownDocument, {
markdown: '# Report\n- Item 1\n- Item 2',
});YAML UI Configuration
Form Widget
The form widget renders document fields as an editable form:
type: document
ui:
widgets:
- widget: form
options:
order: [name, description, items]
properties:
name:
title: Name
description:
title: Description
widget: textarea
items:
title: Items
collapsed: true
items:
title: Item
actions:
- type: button
transition: submit
label: 'Submit'Available Widget Types
Use these in options.properties.<field>.widget:
| Widget | Description |
|---|---|
text | Single-line text input (default) |
textarea | Multi-line text area |
select | Dropdown select |
radio | Radio button group |
checkbox | Checkbox |
switch | Toggle switch |
slider | Numeric slider |
code-view | Code editor with syntax highlighting |
Property Options
| Option | Type | Description |
|---|---|---|
title | string | Display label |
widget | string | Widget type |
placeholder | string | Placeholder text |
rows | number | Visible rows (textarea) |
readonly | boolean | Read-only field |
hidden | boolean | Hide the field |
disabled | boolean | Disable interaction |
collapsed | boolean | Collapse arrays/objects by default |
items | object | UI config for array items |
Document Actions
Buttons that trigger wait: true transitions in the workflow:
actions:
- type: button
transition: confirm # Must match the method name
label: 'Confirm'Tags
Categorize documents for filtering and searching:
type: document
tags:
- message
- importantTags are used by tools like claudeGenerateText with messagesSearchTag to collect documents as conversation history.
Structured Output Example
Documents work with ClaudeGenerateDocument for AI-generated structured data:
export const FileDocumentSchema = z
.object({
filename: z.string(),
description: z.string(),
code: z.string(),
})
.strict();
@Document({
schema: FileDocumentSchema,
uiConfig: __dirname + '/file-document.yaml',
})
export class FileDocument {
filename: string;
description: string;
code: string;
}# file-document.yaml
type: document
ui:
widgets:
- widget: form
options:
order: [filename, description, code]
properties:
filename:
title: File Name
readonly: true
description:
title: Description
readonly: true
code:
title: Code
widget: code-viewUsed in a workflow:
const result = await this.claudeGenerateDocument.call({
claude: { model: 'claude-sonnet-4-6' },
response: { document: FileDocument },
prompt: 'Generate a Hello World script in Python',
});Registry References
- prompt-structured-output-example-workflow — FileDocument with code-view widget for AI-generated code
- meeting-notes-example-workflow — MeetingNotesDocument and OptimizedNotesDocument with form widgets and action buttons
- test-ui-documents-example-workflow — Demonstrates all core UI document types: MessageDocument, ErrorDocument, MarkdownDocument, PlainDocument