
Security News
Frontier AI Is Now Critical Infrastructure
The Fable shutdown shows how quickly model access can become a business continuity risk for AI-dependent engineering teams.
@useatlas/plugin-sdk
Advanced tools
Type definitions and helpers for authoring Atlas plugins. Plugins extend Atlas with new datasources, context providers, interaction surfaces, actions, and sandbox backends.
import { createPlugin } from "@useatlas/plugin-sdk";
import { z } from "zod";
export const myPlugin = createPlugin({
configSchema: z.object({ url: z.string() }),
create: (config) => ({
id: "my-datasource",
types: ["datasource"] as const,
version: "1.0.0",
config,
connection: {
create: () => makeConnection(config.url),
dbType: "postgres",
},
}),
});
Register in atlas.config.ts:
import { defineConfig } from "@atlas/api/lib/config";
import { myPlugin } from "./my-plugin";
export default defineConfig({
plugins: [myPlugin({ url: process.env.MY_DB_URL! })],
});
Atlas has five plugin types. Each extends AtlasPluginBase with variant-specific fields.
AtlasDatasourcePlugin)Connect to a database. Provides a connection factory, SQL dialect hints, and optional entity definitions.
A datasource plugin must provide at least one connection factory: create() for a static config-defined connection, createFromConfig() for a DB-stored (admin-registered) per-workspace connection, or both. A plugin with only createFromConfig() is an adapter-only plugin — registered for use but with no static datasource (the multi-tenant SaaS model, where every connection is added per workspace).
| Field | Type | Required | Description |
|---|---|---|---|
connection.create() | () => PluginDBConnection | Promise<PluginDBConnection> | One of create/createFromConfig | Factory that returns a connection with query() and close() from the plugin's config-time config (static / self-host) |
connection.createFromConfig() | (config) => PluginDBConnection | Promise<PluginDBConnection> | One of create/createFromConfig | Factory that builds a connection from a DB-stored per-(workspace, install) config (adapter-only / SaaS) |
connection.dbType | PluginDBType | Yes | Database type identifier (postgres, mysql, clickhouse, snowflake, duckdb, or custom) |
entities | EntityProvider | No | Semantic layer fragments merged into the table whitelist at boot |
dialect | string | No | SQL dialect guidance injected into the agent system prompt |
Reference: clickhouse
AtlasContextPlugin)Inject knowledge into the agent. Context plugins load additional system prompt fragments, semantic layer extensions, or external metadata.
| Field | Type | Required | Description |
|---|---|---|---|
contextProvider.load() | () => Promise<string> | Yes | Returns context string appended to the agent system prompt |
contextProvider.refresh() | () => Promise<void> | No | Cache invalidation hook |
Reference: yaml-context
AtlasInteractionPlugin)Add communication surfaces. Interaction plugins mount HTTP routes or manage non-HTTP transports (stdio, SSE).
| Field | Type | Required | Description |
|---|---|---|---|
routes | (app: Hono) => void | No | Mount Hono routes for webhooks, OAuth, etc. Optional for non-HTTP transports |
AtlasActionPlugin)Enable agent side-effects. Action plugins provide AI SDK tools with approval controls.
| Field | Type | Required | Description |
|---|---|---|---|
actions | PluginAction[] | Yes | Array of action definitions |
actions[].name | string | Yes | Tool name |
actions[].tool | ToolSet[string] | Yes | AI SDK tool definition |
actions[].actionType | string | Yes | Category identifier (e.g. jira:create) |
actions[].reversible | boolean | Yes | Whether the action can be undone |
actions[].defaultApproval | ActionApprovalMode | Yes | auto, manual, or admin-only |
actions[].requiredCredentials | string[] | Yes | Config fields needed at runtime |
AtlasSandboxPlugin)Provide code isolation for the explore tool. Sandbox plugins create backends that execute shell commands in an isolated environment.
| Field | Type | Required | Description |
|---|---|---|---|
sandbox.create(semanticRoot) | (string) => PluginExploreBackend | Yes | Factory that creates an explore backend |
sandbox.priority | number | No | Higher = tried first. Built-in: Vercel=100, E2B=90, Daytona=85, nsjail=75, sidecar=50, just-bash=0. Plugin default: 60 |
security | object | No | Informational metadata about isolation guarantees |
Reference: nsjail, vercel-sandbox
All plugins share these fields from AtlasPluginBase:
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique identifier (e.g. clickhouse-datasource) |
type | PluginType | Yes | One of datasource, context, interaction, action, sandbox |
version | string | Yes | SemVer version |
name | string | No | Human-readable display name |
config | TConfig | No | Plugin-specific configuration |
initialize(ctx) | (AtlasPluginContext) => Promise<void> | No | Called once at server boot. Throw to block startup |
healthCheck() | () => Promise<PluginHealthResult> | No | Periodic health probe |
teardown() | () => Promise<void> | No | Graceful shutdown (LIFO order) |
onUninstall(workspaceId) | (string) => Promise<void> | void | No | Per-workspace uninstall hook — revoke external webhook subscriptions / OAuth grants the plugin registered for that workspace |
hooks | PluginHooks | No | Agent lifecycle and HTTP hooks |
schema | Record<string, PluginTableDefinition> | No | Declarative table definitions for the internal DB |
register → initialize(ctx) → healthCheck() → ... → teardown()
└── onUninstall(workspaceId) (per-workspace, at uninstall time)
plugins array in atlas.config.ts. Config is validated at factory call time (before server starts).AtlasPluginContext. Throw to signal fatal initialization failure. Context provides:
ctx.db — Internal Postgres (auth/audit DB). Null when DATABASE_URL is not set.ctx.connections — Connection registry for analytics datasources.ctx.tools — Tool registry for adding agent tools.ctx.logger — Pino-compatible child logger scoped to the plugin.ctx.config — Resolved Atlas configuration.{ healthy: false, message } to signal degradation. Never throw. Results surface in the plugins component of GET /api/health — a failing probe shifts the top-level status to degraded (HTTP 200, never 503).teardown() to release process-level state — third-party connections, drained queues, timers. Note: teardown() runs on server shutdown, not on a per-workspace uninstall (uninstall is a DB-row removal, not a process event) — per-workspace cleanup belongs in onUninstall.onUninstall(workspaceId) fires when a workspace uninstalls the plugin (both the marketplace DELETE route and WorkspaceInstaller.uninstall), before Atlas removes the install row and credential stores — so the plugin can still authenticate against the external platform. Use it to revoke webhook subscriptions, OAuth grants, or any other external state registered for that workspace. Attribution rule: never revoke a subscription you can't positively attribute to the uninstalling workspace (recorded id, metadata tag, or workspace marker in the callback URL) — the credential may be shared with other workspaces or out-of-band tooling. Best-effort: a thrown error is logged with the plugin id + workspaceId and the uninstall proceeds (each invocation also runs against a 15s host-side deadline); never rely on it for load-bearing cleanup. It does not fire on datasource disconnects or a workspace purge — only the two plugin-uninstall paths above. See packages/api/src/lib/integrations/jira/lazy-builder.ts for a reference implementation (revokes only workspace-attributed Jira dynamic webhook subscriptions).v1.1 note:
AtlasPluginContextwill gainexecuteQuery,conversations, andactionsfields for full host-level decoupling. Currently, interaction plugins that need these inject them via config callbacks.
DELETE /api/v1/admin/marketplace/:id removes a plugin from a workspace. The cleanup contract:
| State | Survives uninstall? | Notes |
|---|---|---|
workspace_plugins row | No (deleted) | Canonical "is this plugin installed?" record. |
scheduled_tasks rows tagged with the plugin's catalog_id | No (deleted) | Scoped by (plugin_id, org_id) so cleanup never crosses workspaces. scheduled_task_runs cascade via FK. Cleanup runs in a separate statement after the install row is removed; partial failure leaves the uninstall committed and is recorded as a cleanupFailed: true audit event. |
plugin_<pluginId>_* tables (declared via schema) | Yes (retained) | Reinstall picks up where it left off — cached digest history, sync cursors, etc. Hard-reset only via workspace purge. |
| In-process hook registrations | Not detached on uninstall — teardown() runs only at server shutdown | Hooks become inert for the uninstalled workspace because dispatch checks workspace_plugins for the installation. |
| Webhook subscriptions registered with external platforms | Yes — unless your onUninstall(workspaceId) revokes them | Atlas has no visibility into external state. Implement onUninstall to revoke the subscription — it fires before the install row and credentials are removed, so your plugin can still authenticate. Revoke only subscriptions attributable to the uninstalling workspace. An un-revoked webhook keeps delivering events to a workspace that no longer has the plugin installed. Note the hook fires only on plugin uninstalls — datasource disconnects and workspace purges skip it. |
If your plugin creates scheduled_tasks rows, set plugin_id = $catalogId and org_id = $orgId on insert so the uninstall cleanup picks them up. Untagged tasks (plugin_id IS NULL) are treated as user-created and survive uninstall. See the authoring guide for the full lifecycle.
Use createPlugin() with a Zod schema for typed, validated configuration:
import { createPlugin } from "@useatlas/plugin-sdk";
import { z } from "zod";
const configSchema = z.object({
url: z.string().url(),
poolSize: z.number().int().positive().optional(),
});
export const myPlugin = createPlugin({
configSchema,
create: (config) => ({
id: "my-datasource",
types: ["datasource"] as const,
version: "1.0.0",
config,
connection: { create: () => connect(config.url), dbType: "postgres" },
}),
});
The factory validates config at call time — invalid config fails fast during startup, not at first use. The configSchema accepts any object with a parse(input) method (Zod schemas satisfy this).
For simple plugins without config validation, use definePlugin():
import { definePlugin } from "@useatlas/plugin-sdk";
export default definePlugin({
id: "my-context",
types: ["context"],
version: "1.0.0",
contextProvider: { async load() { return "Extra context..."; } },
});
Hooks use a matcher + handler pattern inspired by Better Auth. Matchers are optional — omit to always fire.
hooks: {
beforeQuery: [{
matcher: (ctx) => ctx.connectionId === "warehouse",
handler: (ctx) => {
// Return { sql } to rewrite, throw to reject, return void to pass through
return { sql: ctx.sql.replace(/SELECT \*/, "SELECT id, name") };
},
}],
afterQuery: [{
handler: (ctx) => {
console.log(`Query took ${ctx.durationMs}ms, returned ${ctx.result.rows.length} rows`);
},
}],
}
| Hook | Context | Mutation | Description |
|---|---|---|---|
beforeQuery | { sql, connectionId? } | { sql } | Fires before SQL execution. Return to rewrite, throw to reject |
afterQuery | { sql, connectionId?, result, durationMs } | — | Fires after SQL execution |
beforeExplore | { command } | { command } | Fires before explore command. Return to rewrite, throw to reject |
afterExplore | { command, output } | — | Fires after explore command |
onRequest | { path, method, headers } | — | HTTP-level: fires before routing |
onResponse | { path, method, status } | — | HTTP-level: fires after response |
Plugins declare internal database tables via the schema property:
schema: {
slack_installations: {
fields: {
team_id: { type: "string", required: true, unique: true },
bot_token: { type: "string", required: true },
installed_at: { type: "date" },
},
},
},
Run migrations with the CLI:
atlas migrate # Preview migration SQL
atlas migrate --apply # Apply to internal DB
Tables are prefixed with the plugin ID to avoid collisions.
$InferServerPluginExtract plugin types on the client side without importing server code:
import type { $InferServerPlugin } from "@useatlas/plugin-sdk";
import type { clickhousePlugin } from "@useatlas/clickhouse";
type CH = $InferServerPlugin<typeof clickhousePlugin>;
// CH["Config"] → { url: string; database?: string }
// CH["Type"] → "datasource"
// CH["DbType"] → "clickhouse"
// CH["Actions"] → never (not an action plugin)
// CH["Security"] → never (not a sandbox plugin)
Works with both createPlugin() factory functions and definePlugin() direct objects.
Available inferred fields: Config, Type, Id, Name, Version, DbType (datasource only), Actions (action only), Security (sandbox only).
atlas plugin list # List installed plugins from atlas.config.ts
atlas plugin create <name> --type <type> # Scaffold a new plugin (datasource|context|interaction|action|sandbox)
atlas plugin add <package-name> # Install a plugin package via bun
atlas plugin create generates a plugins/<name>/ directory with src/index.ts, src/index.test.ts, package.json, and tsconfig.json.
import {
isDatasourcePlugin,
isContextPlugin,
isInteractionPlugin,
isActionPlugin,
isSandboxPlugin,
} from "@useatlas/plugin-sdk";
if (isDatasourcePlugin(plugin)) {
// plugin is AtlasDatasourcePlugin — connection, entities, dialect available
}
Note: These are workspace packages (
@useatlas/*) within the monorepo. Use them as reference when authoring your own plugins.
| Plugin | Type | Package | Description |
|---|---|---|---|
| clickhouse | Datasource | @useatlas/clickhouse | ClickHouse HTTP transport adapter |
| mysql | Datasource | @useatlas/mysql | MySQL pool-based adapter |
| snowflake | Datasource | @useatlas/snowflake | Snowflake callback-based adapter |
| duckdb | Datasource | @useatlas/duckdb | DuckDB in-process adapter |
| yaml-context | Context | @useatlas/yaml-context | YAML semantic layer context provider |
| mcp | Interaction | @useatlas/mcp | MCP server lifecycle (stdio + SSE) |
| chat | Interaction | @useatlas/chat | Chat SDK bridge — Slack, Teams, Discord, etc. via unified adapter |
| jira | Action | @useatlas/jira | Create JIRA tickets from analysis |
| Action | @useatlas/email | Send email reports via Resend | |
| nsjail | Sandbox | @useatlas/nsjail | Linux namespace isolation via nsjail |
| sidecar | Sandbox | @useatlas/sidecar | HTTP-isolated container sidecar |
| vercel-sandbox | Sandbox | @useatlas/vercel-sandbox | Firecracker microVM via @vercel/sandbox |
| daytona | Sandbox | @useatlas/daytona | Daytona managed cloud sandbox |
| e2b | Sandbox | @useatlas/e2b | E2B Firecracker microVM (managed) |
The definitive type definitions are in src/types.ts. Factory functions and type guards are in src/helpers.ts.
FAQs
Type definitions and helpers for authoring Atlas plugins
We found that @useatlas/plugin-sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
The Fable shutdown shows how quickly model access can become a business continuity risk for AI-dependent engineering teams.

Security News
AI agents are pulling packages into environments no scanner is watching, creating exposure before security teams can see it.

Security News
GitHub Actions checkout now blocks risky pull_request_target checkouts by default to help prevent pwn request supply chain attacks.