Skip to content

Artifacts Publishing Pattern

Purpose

Publish static HTML content accessible via public URL. Generate reports, dashboards, and shareable documents that users can view in a browser.

When to Use

Use CaseExample
ReportsPurchase analysis, research results, comparison tables
DashboardsStatus pages, metrics visualization
Shareable contentDocuments, presentations, portfolios
NotificationsGenerate a report and send its URL to the user
User deliverablesAnything user needs to view in browser

Don’t use artifacts for temporary data (use notes), structured JSON storage (use notes), binary files, frequently changing content, or authenticated content.

Structure

[generate-data] → [create-html] → [upload-artifact] → [share-url]

Implementation

Direct Upload Pattern

When agent generates HTML in memory:

{
"id": "generate-and-upload",
"type": "agent-directive",
"directive": "Generate HTML report from analysis data.\n\n**Data:**\n{{note:analysis-results}}\n\nCreate responsive HTML with Tailwind CDN.\n\nUpload using:\nartifacts({ action: \"upload\", name: \"report.html\", content: \"<html>...\" })\n\nSave the returned `url` as report_url.",
"completionCondition": "Report uploaded and URL obtained",
"inputSchema": {
"type": "object",
"required": ["report_url", "uploaded"],
"properties": {
"report_url": { "type": "string" },
"uploaded": { "type": "boolean" }
}
},
"connections": { "success": "notify-user" }
}

Token-based Upload Pattern

When agent creates files locally first:

{
"id": "upload-via-token",
"type": "agent-directive",
"directive": "Upload the report file.\n\n1. Get token: artifacts({ action: \"token\", ttlMinutes: 30 })\n2. Read file from {{report_file_path}}\n3. POST to the uploadUrl with content\n4. Save response url",
"inputSchema": {
"properties": {
"report_url": { "type": "string" },
"uploaded": { "type": "boolean" }
}
}
}

Conditional Based on Capabilities

Route based on agent’s file access:

{
"id": "check-file-access",
"type": "condition",
"condition": {
"operator": "eq",
"left": { "contextPath": "can_create_files" },
"right": true
},
"connections": {
"true": "generate-file-then-upload",
"false": "generate-and-upload-direct"
}
}

MCP Tool Usage

Upload Artifact

artifacts({
action: "upload",
name: "purchase-report.html",
content: `<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-50 p-8">
<h1 class="text-2xl font-bold">Report</h1>
</body>
</html>`,
executionId: "exec-123", // Optional: link to workflow
});

Response:

{
"success": true,
"data": {
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"url": "https://{MOIRA_HOST}/a/a1b2c3d4...",
"name": "purchase-report.html",
"size": 1234,
"expiresAt": "2025-02-28T10:00:00.000Z"
}
}

Update Artifact

artifacts({
action: "update",
uuid: "a1b2c3d4...",
content: "<html>...updated...</html>",
name: "report-v2.html", // Optional
});

Check Quota

artifacts({
action: "stats",
});
// Returns: totalArtifacts, totalSize, storageLimit, countLimit,
// storageUsedPercent, countUsedPercent

List Artifacts

artifacts({
action: "list",
limit: 20,
});

Quotas and Limits

Storage, file-count, per-file-size, and default-expiration policies are configured by the server and can include per-user quota overrides. Use stats for the effective storage and count quotas, handle the boundary reported by upload errors, and treat each returned expiresAt as authoritative. The ttlMinutes input schema states the accepted upload-token lifetime range.

Check quota before upload with artifacts({ action: "stats" }). Clean up old artifacts if approaching limits.

HTML Generation Tips

Use Tailwind CDN

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-50 min-h-screen">
<!-- Content -->
</body>
</html>

Responsive Layout

<div class="container mx-auto px-4 py-8">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<!-- Cards -->
</div>
</div>

Product Card

<div class="bg-white rounded-lg shadow-md p-6">
<h3 class="text-lg font-semibold">Product Name</h3>
<p class="text-gray-600 mt-2">Description</p>
<div class="mt-4 flex justify-between items-center">
<span class="text-2xl font-bold text-green-600">$999</span>
<a
href="https://store.com/product"
class="bg-blue-500 text-white px-4 py-2 rounded"
target="_blank"
rel="noopener noreferrer"
>
Buy Now
</a>
</div>
</div>

Comparison Table

<table class="w-full border-collapse">
<thead>
<tr class="bg-gray-100">
<th class="border p-3 text-left">Product</th>
<th class="border p-3 text-left">Price</th>
<th class="border p-3 text-left">Rating</th>
</tr>
</thead>
<tbody>
<tr class="hover:bg-gray-50">
<td class="border p-3">Product A</td>
<td class="border p-3">$599</td>
<td class="border p-3">4.5/5</td>
</tr>
</tbody>
</table>

Integration with Notifications

Combine with a channel-neutral user notification:

{
"id": "notify-with-link",
"type": "user-notification",
"message": "Report ready: {{report_url}}",
"format": "plain",
"connections": { "default": "end", "error": "end" }
}

Real Example

From a purchase analysis workflow:

{
"id": "create-report",
"type": "agent-directive",
"directive": "Create HTML report with product recommendations.\n\n**Analysis data:**\n{{note:purchase-{{executionId}}-03-analysis}}\n\nGenerate responsive HTML:\n- Product cards with images, prices, links\n- Comparison table\n- Recommendation summary\n\nUpload: artifacts({ action: \"upload\", name: \"purchase-{{executionId}}.html\", content: html, executionId: \"{{executionId}}\" })",
"inputSchema": {
"properties": {
"report_url": { "type": "string" },
"uploaded": { "type": "boolean" }
},
"required": ["report_url"]
}
}

Best Practices

Descriptive Names

// Good
artifacts({ action: "upload", name: "purchase-analysis-2025-01-31.html", content: htmlContent });
// Bad
artifacts({ action: "upload", name: "report.html", content: htmlContent });
artifacts({
action: "upload",
name: "report.html",
content: htmlContent,
executionId: context.executionId,
});

Benefits:

  • Track which execution created which artifact
  • Query artifacts by execution
  • Clean up when execution archived

Handle Errors

{
"id": "upload-report",
"type": "agent-directive",
"directive": "Upload report. Check quota first. If upload fails, inform user.",
"connections": {
"success": "notify-user",
"error": "handle-upload-error"
}
}

Anti-patterns

Using Artifacts for Data Storage

Use notes for JSON data, artifacts for presentable HTML only.

Not Checking Quotas

Always check stats before large uploads. Handle quota exceeded errors.

Non-responsive HTML

Use Tailwind or media queries. Fixed-width layouts break on mobile.