Skip to Content
DocumentationGuidesCreating Documents

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: 8

The @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 content
  • uiConfig — 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

OptionTypeDescription
idstringCustom ID — use for updating existing documents
meta.hiddenbooleanHide from the UI

Built-in Document Types

These are available without creating custom documents:

DocumentSourceKey Fields
ClaudeMessageDocument@loopstack/claude-modulerole, content
LinkDocument@loopstack/corelabel, workflowId, href, status
MessageDocument@loopstack/corerole, content
MarkdownDocument@loopstack/coremarkdown
PlainDocument@loopstack/coretext
ErrorDocument@loopstack/coreerror
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:

WidgetDescription
textSingle-line text input (default)
textareaMulti-line text area
selectDropdown select
radioRadio button group
checkboxCheckbox
switchToggle switch
sliderNumeric slider
code-viewCode editor with syntax highlighting

Property Options

OptionTypeDescription
titlestringDisplay label
widgetstringWidget type
placeholderstringPlaceholder text
rowsnumberVisible rows (textarea)
readonlybooleanRead-only field
hiddenbooleanHide the field
disabledbooleanDisable interaction
collapsedbooleanCollapse arrays/objects by default
itemsobjectUI 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 - important

Tags 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-view

Used 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

Last updated on