@fractal-os/plugin
v0.0.105
Published
The official SDK and plugin toolkit for the Fractal. Build and publish custom tools, toolsets, collections, views, and event hooks for autonomous AI agents.
Downloads
1,169
Maintainers
Readme
@fractal-os/plugin
The official Fractal Plugin SDK — a developer toolkit for building extensions, tools, and integrations on top of the FractalOS.
Build, extend, and publish autonomous agent capabilities, persistent data schemas, user interface panels, custom error diagnostics, and system hook interceptions with a fluent, type-safe builder API.
Key Features
| Builder | Purpose |
|---------|---------|
| FractalTool | Define callable actions with input schemas and async handlers |
| FractalToolset | Group related tools under connection configurations |
| FractalCollection | Define reactive data persistence schemas with lifecycle hooks |
| FractalView | Construct reactive UI trees rendered inside the Fractal workspace |
| FractalHook | Intercept and modify pipeline events (e.g., PreToolUse, SessionStart) |
| FractalError | Create diagnostic error registries with actionable CTAs |
Installation
# Using bun (recommended)
bun add @fractal-os/plugin
# Using npm
npm install @fractal-os/plugin
# Using pnpm
pnpm add @fractal-os/pluginPeer Dependencies
This package requires [email protected] as a peer dependency (schema validation across builders):
bun add zodDirect Dependencies (installed automatically)
Installing @fractal-os/plugin also pulls:
| Package | Why |
|---------|-----|
| ai / @ai-sdk/provider | Public types for chat / model surfaces (UIMessage, language models, …) |
| @igniter-js/collections | Native collection / view builders under the hood |
| @igniter-js/core | Shared Igniter contracts |
| @standard-schema/spec | Schema interop for withSchema() |
You do not need to install these manually — they ship as direct dependencies of the plugin.
Requirements
- Bun:
>=1.3(recommended) or Node.js:>=22 - TypeScript:
>=5.5(strongly recommended for type safety)
TypeScript Support
All builders are written in TypeScript and export full type definitions out of the box.
import { FractalTool, type FractalContext } from "@fractal-os/plugin";
import { z } from "zod";
// Full type inference on input
const tool = FractalTool.create("search")
.withSchema(z.object({ query: z.string() }))
.withHandler(async ({ input, fractal }) => {
// `input` is fully typed: { query: string }
// `fractal` provides workspace context, env vars, config
const results = await performSearch(input.query);
return { results };
});API Reference
1. Defining a Tool (FractalTool)
Tools represent callable actions that AI agents use to interact with the system or external APIs.
import { z } from "zod";
import { FractalTool } from "@fractal-os/plugin";
const GreetSchema = z.object({
name: z.string().describe("The name of the user to greet")
});
export default FractalTool.create("greet")
.withDescription("Greets a user by name professionally")
.withSchema(GreetSchema)
.withHandler(async ({ input, fractal }) => {
const config = await fractal.config.get();
return `Hello, ${input.name}! Welcome, ${config.user.name}.`;
})
.build();FractalTool Methods
| Method | Description |
|--------|-------------|
| .create(name) | Initialize a new tool with a unique name |
| .withDescription(text) | Set the documentation description for AI agents |
| .withSchema(schema) | Attach a Zod schema for input validation |
| .withHandler(fn) | Define the async execution logic |
| .withRules(rules) | Attach cognitive policy rules (optional) |
| .build() | Validate and compile the tool definition |
2. Grouping Tools in a Toolset (FractalToolset)
Toolsets bundle related tools under a shared connection configuration.
import { FractalToolset } from "@fractal-os/plugin";
import greetTool from "./greet.tool";
import searchTool from "./search.tool";
export default FractalToolset.create("utilities")
.withDescription("Essential workspace helper tools")
.withConnection({ type: "custom" })
.addTool(greetTool)
.addTool(searchTool)
.build();MCP Server Integration (Auto-Discovery)
For MCP-compatible servers, tools are discovered automatically — no need to define them individually:
export default FractalToolset.create("postgres")
.withDescription("PostgreSQL database tools via MCP")
.withConnection({
type: "mcp-server::stdio",
command: "npx",
args: ["-y", "@modelcontextprotocol/server-postgres"],
env: { DATABASE_URL: process.env.DATABASE_URL! }
})
.build();Connection Types
| Type | Use Case |
|------|----------|
| mcp-server::stdio | Local MCP server subprocess |
| mcp-server::http | Remote MCP server over HTTP/SSE |
| rest-api | Standard REST API integration |
| cli | Command-line tool wrapper |
| custom | In-memory JavaScript/TypeScript handlers |
3. Creating a Collection (FractalCollection)
Collections model local files as typed records with reactive lifecycle hooks.
import { z } from "zod";
import { FractalCollection } from "@fractal-os/plugin";
const UserSchema = z.object({
id: z.string(),
username: z.string(),
role: z.enum(["admin", "developer", "user"]),
createdAt: z.string().optional()
});
export default FractalCollection.create("users")
.withSchema(UserSchema)
.withPatterns(["data/users/*.json"])
.onCreated(async ({ value, fractal }) => {
const config = await fractal.config.get();
console.log(`User ${value.username} created for ${config.user.name}`);
return value;
})
.onUpdated(async ({ newValue, previousValue, fractal }) => {
await fractal.workspace.core.collections.get("audit").create({
data: { action: "user_updated", user: newValue.username },
});
return newValue;
})
.build();Lifecycle Hooks
Public hook context omits Igniter internals (context, manager). Authors only receive document fields plus fractal:
| Hook | Trigger | Context |
|------|---------|---------|
| onCreated | After a new record is persisted | { value, fractal } — returns Document \| false |
| onUpdated | After an existing record is modified | { newValue, previousValue, fractal } — returns Document \| false |
| onDeleted | Before a record is removed | { value, fractal } — returns boolean |
| onRead | When a record is retrieved | { value, fractal } — returns Document \| false |
| onList | When records are queried | { values, fractal } — returns Document[] \| false |
Complete CRM Example
// A real-world collection with auto-timestamps and enrichment
export default FractalCollection.create("customers")
.withPatterns(["data/customers/*.json"])
.withSchema(z.object({
name: z.string(),
email: z.string().email(),
phone: z.string().optional(),
createdAt: z.string().optional(),
updatedAt: z.string().optional(),
}))
.onCreated(async ({ value }) => ({
...value,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
}))
.onUpdated(async ({ newValue }) => ({
...newValue,
updatedAt: new Date().toISOString()
}))
.build();4. Creating a Workspace View (FractalView)
Views render json-render specs (shadcn catalog) inside the Fractal workspace.
import { FractalView } from "@fractal-os/plugin";
export default FractalView.create("recent-activities")
.withTitle("Recent Activities")
.withDescription("Displays recent actions performed in the active workspace")
.withData(async (ctx) => {
const activities = await ctx.workspace.core.collections.get("activities").findMany({
take: 20,
orderBy: { createdAt: "desc" },
});
return {
columns: ["Activity", "Status"],
rows: activities.map((a) => [a.title, a.status]),
stats: { total: String(activities.length) },
};
})
.withSpec({
root: "page",
elements: {
page: {
type: "Stack",
props: {
direction: "vertical",
gap: "md",
align: null,
justify: null,
className: null,
},
children: ["heading", "table"],
},
heading: {
type: "Heading",
props: { text: "Recent Activities", level: "h1" },
children: [],
},
table: {
type: "Table",
props: {
columns: { $state: "/columns" },
rows: { $state: "/rows" },
caption: null,
},
children: [],
},
},
})
.build();Dashboard View with KPI Cards
export default FractalView.create("analytics")
.withTitle("Analytics Dashboard")
.withData(async (ctx) => {
const users = await ctx.workspace.core.collections.get("users").findMany({});
const tasks = await ctx.workspace.core.collections.get("tasks").findMany({
where: { status: "finished" },
});
return {
columns: ["ID"],
rows: [],
stats: {
totalUsers: String(users.length),
completedTasks: String(tasks.length),
},
};
})
.withSpec({
root: "page",
elements: {
page: {
type: "Stack",
props: {
direction: "vertical",
gap: "md",
align: null,
justify: null,
className: null,
},
children: ["heading", "metrics"],
},
heading: {
type: "Heading",
props: { text: "Analytics", level: "h1" },
children: [],
},
metrics: {
type: "Grid",
props: { columns: 2, gap: "md", className: null },
children: ["users", "tasks"],
},
users: {
type: "Card",
props: {
title: "Total Users",
description: { $state: "/stats/totalUsers" },
maxWidth: null,
centered: null,
className: null,
},
children: [],
},
tasks: {
type: "Card",
props: {
title: "Completed Tasks",
description: { $state: "/stats/completedTasks" },
maxWidth: null,
centered: null,
className: null,
},
children: [],
},
},
})
.build();5. Intercepting Events with Hooks (FractalHook)
Hooks intercept operations on the Fractal pipeline, enabling safety checks, audit logging, or result modification.
import { FractalHook } from "@fractal-os/plugin";
export default FractalHook.create("safety-guard")
.onType("PreToolUse")
.withHandler(async (event) => {
const cmd = event.input.toolInput?.command || "";
// Block dangerous commands
if (cmd.includes("rm -rf") || cmd.includes(":(){ :|:& };:")) {
throw new Error(
"Destructive terminal commands are forbidden in this workspace."
);
}
// Log all tool invocations for audit (Igniter Collections namespace API)
await event.fractal.workspace.core.collections.get("audit").create({
data: {
tool: event.input.toolName,
args: event.input.toolInput,
timestamp: new Date().toISOString(),
},
});
return event.output;
})
.build();Supported Event Types
| Event Type | Description | Use Case |
|------------|-------------|----------|
| SessionStart | Agent session begins | Inject session-wide context or setup |
| UserPromptSubmit | User submits a prompt | Filter or enrich user input |
| PreToolUse | Before tool invocation | Authorization, safety checks, input validation |
| PostToolUse | After tool completes | Audit logging, result transformation |
| PostToolUseFailure | Tool execution fails | Error recovery, retry logic, alerting |
| SubagentStart | Subagent spawns | Resource allocation, permission scoping |
| SubagentStop | Subagent finishes | Result collection, cleanup |
| Stop | Session stops | Final audit, resource cleanup |
| PreCompact | Context compaction | Preserve critical context before compaction |
| GenericHook | Fallback for custom events | Extensibility for future event types |
6. Dynamic Custom Errors (FractalError)
Define diagnostic error codes with actionable Call-To-Action (CTA) buttons that both developers and agents can execute.
import { z } from "zod";
import { FractalError } from "@fractal-os/plugin";
const MissingEnvSchema = z.object({
key: z.string().describe("The missing environment variable name")
});
export const WorkspaceErrors = FractalError.create()
.addError("ENV_VARIABLE_MISSING", {
status: 400,
schema: MissingEnvSchema,
message: ({ issue }) =>
`Required environment variable "${issue?.key}" is not set.`,
cta: ({ issue }) => ({
description: "Set the missing environment variable in your config.",
commands: [
{
command: `fractal config set ${issue?.key}="<value>"`,
description: "Configure the environment key"
}
]
})
})
.addError("NOT_FOUND", {
status: 404,
schema: z.object({ id: z.string() }),
message: ({ issue }) => `Resource "${issue?.id}" not found.`,
cta: ({ issue }) => ({
description: "Verify the resource ID and try again.",
commands: [
{ command: `fractal get ${issue?.id}`, description: "Look up the resource" }
]
})
})
.build();
// Usage
throw new WorkspaceErrors({
code: "ENV_VARIABLE_MISSING",
issue: { key: "API_TOKEN" }
});Merging Multiple Error Registries
import { AuthErrors } from "./auth.errors";
import { ValidationErrors } from "./validation.errors";
export const AppErrors = FractalError.create()
.merge(AuthErrors)
.merge(ValidationErrors)
.build();Context API
All handlers receive a Fractal context object with access to:
interface FractalContext {
/** Environment variable access (e.g., fractal.env.get("API_KEY")) */
env: {
get(name: string): string | undefined;
};
/** Typed config manager — async get / update of the persisted Fractal config */
config: {
get(): Promise<FractalConfig>;
update(params: FractalConfigUpdateInput): Promise<FractalConfigUpdateResult>;
};
/** Full workspace runtime — services, collections manager (`core`), store, path helper */
workspace: FractalWorkspaceRuntime;
}The fractal.workspace runtime exposes the complete Fractal service layer:
| Service | Access Pattern | Purpose |
|---------|---------------|---------|
| Toolsets | fractal.workspace.toolsets.list(...) | Discover and call tools |
| Collections (definitions) | fractal.workspace.collections.get("name") | Manage custom collection definitions |
| Core (documents) | fractal.workspace.core.collections.get("name") | CRUD documents (create / findMany / …) |
| Views | fractal.workspace.views.render("id") | Render workspace views |
| Events | fractal.workspace.events.publish(...) | Publish and query events |
| Tasks | fractal.workspace.tasks.create(...) | Create and manage tasks |
| Memories | fractal.workspace.memories.create(...) | Persist knowledge records |
| Agents | fractal.workspace.agents.list() | Manage agent configurations |
| Skills | fractal.workspace.skills.install(...) | Install community skills |
| Files | fractal.workspace.files.read(...) | File system operations |
| Chats | fractal.workspace.chats.create(...) | Chat session management |
| Templates | fractal.workspace.templates.render(...) | Render LiquidJS templates |
| Instructions | fractal.workspace.instructions.create(...) | Define behavioral rules |
| Routines | fractal.workspace.routines.create(...) | Scheduled automations |
| Projects | fractal.workspace.projects.create(...) | Project management |
| Marketplace | fractal.workspace.marketplace.search(...) | Community discovery |
Complete Skill Example
A full Fractal skill using all six builders — a "Task Manager" plugin:
skills/task-manager/
├── SKILL.md
├── .fractal/manifest.json
├── toolsets/
│ └── task-tools.toolset.ts ← FractalToolset with "create-task" + "list-tasks" tools
├── collections/
│ └── tasks.collection.ts ← FractalCollection with auto-timestamps
├── views/
│ └── task-board.view.ts ← FractalView with kanban-style table
├── hooks/
│ └── task-audit.hook.ts ← FractalHook logging all tool executions
└── errors/
└── task.errors.ts ← FractalError registry for task-specific errorsTroubleshooting
"Cannot find module 'zod'"
Ensure zod is installed as a direct dependency:
bun add zodThe plugin lists zod as a peerDependency, so you must install it separately.
TypeScript errors with builders
This package requires TypeScript >=5.5. Upgrade if you see errors:
bun add -d typescript@latestCollection hooks not firing
Ensure the collection is registered in your workspace configuration. Collections are auto-discovered from skill folders — verify the collection file exports the result of .build():
// ✅ Correct — hooks will fire
export default FractalCollection.create("users").withSchema(...).onCreated(fn).build();
// ❌ Wrong — hooks won't fire (missing .build())
export default FractalCollection.create("users").withSchema(...).onCreated(fn);View not appearing in workspace
Views must be explicitly enabled in the workspace settings. Check the Fractal workspace preferences panel, or verify the view is in a skill folder with proper .fractal/manifest.json.
"Fractal collection context is not available"
This error occurs when a FractalCollection or FractalView runs outside a properly initialized Fractal workspace. Ensure FRACTAL_WORKSPACE_ID is set and the workspace is resolved.
Toolset connection fails
For mcp-server::stdio connections, verify the command exists and args are correct. For rest-api and mcp-server::http, ensure the url is reachable and any required headers are set.
Building from Source
# Clone the repository
git clone https://github.com/tryfractal/fractal.git
cd fractal/@packages/plugin
# Install dependencies
bun install
# Build CJS, ESM, and DTS typings
bun run build
# Watch mode for development
bun run devDistribution
The package ships dual CJS + ESM builds with full TypeScript declarations:
| File | Purpose |
|------|---------|
| dist/index.js | ESM entry (import) |
| dist/index.cjs | CJS entry (require) |
| dist/index.d.ts | TypeScript declarations |
| README.md | Consumer-facing manual |
| AGENTS.md | Agent-facing operational manual |
| CHANGELOG.md | Version history (breaking changes + migration notes) |
License
MIT License — see LICENSE.
Copyright (c) 2026 tryfractal
Links
- Fractal Website
- Documentation
- Changelog
- GitHub Repository
- Issue Tracker
- Fractal Registry — publish and discover community skills
