The Coline App SDK: apps, tools, file types and surfaces that run inside Coline workspaces, and public websites with Coline Sites. TypeScript, with an in-memory test workspace. MIT.
TypeScript SDK for building apps on Coline.
npm install @colineapp/sdk
import { ColineApp, ui, actions, createHandler } from "@colineapp/sdk";
const app = new ColineApp({
key: "com.acme.todos",
name: "Acme Todos",
description: "Track todos inside Coline.",
permissions: ["app.home.read", "files.read", "files.write"],
hosting: { mode: "external", baseUrl: "https://your-app.example.com" },
});
// Define a file type your app owns
app.defineFileType({
typeKey: "todo",
name: "Todo",
description: "A todo item",
storage: "coline_document",
indexable: true,
homeRenderMode: "cached",
});
// Render the app's home screen
app.onHomeRender((ctx) =>
ui.stack([
ui.heading("Todos"),
ui.text(`Workspace: ${ctx.workspace.name}`),
ui.button("New Todo", {
action: actions.createFile({
name: "Untitled Todo",
typeKey: "todo",
document: { status: "open" },
}),
}),
]),
);
// Render an individual file
app.onFileRender((ctx) =>
ui.stack([
ui.heading(ctx.file.title ?? "Todo"),
ui.badge(String(ctx.document["status"] ?? "open")),
]),
);
The SDK gives you one handler that routes all Coline traffic:
const handler = createHandler({
app,
secret: process.env.COLINE_DELIVERY_SECRET!,
onWebhook: (event) => {
console.log("Webhook:", event.type);
},
});
Mount it on any framework:
// Hono
const server = new Hono();
server.all("/coline/*", (c) => handler(c.req.raw));
// Next.js App Router (app/coline/[...path]/route.ts)
export const GET = handler;
export const POST = handler;
// Cloudflare Workers / Bun / Deno
export default { fetch: handler };
// Express (with raw body parsing)
app.all("/coline/*", async (req, res) => {
const response = await handler(toWebRequest(req));
res.status(response.status).json(await response.json());
});
Start your app, then go to coline.app/developers/apps/new.
http://localhost:3100)GET /coline/manifestThat's it. The console pulls your manifest, permissions, file types, and notification channels directly from the SDK — no copy-pasting JSON.
For store-listed apps, submit for review from the app detail page. For private apps, install directly into your workspace.
createHandler() handles these automatically:
| Route | Method | Purpose |
|---|---|---|
/coline/manifest | GET | Returns your app manifest (used by the developer console) |
/coline/render/home | POST | Renders your app's home screen |
/coline/render/file | POST | Renders a file owned by your app |
/coline/actions | POST | Handles UI action callbacks (button clicks, etc.) |
/coline/events | POST | Receives webhook events |
All POST routes are verified using your delivery secret.
For server-to-server calls (creating files, sending notifications, etc.):
import { ColineApiClient } from "@colineapp/sdk";
const client = new ColineApiClient({
baseUrl: "https://coline.app",
apiKey: process.env.COLINE_API_KEY!,
});
// Fluent scoped handles
const ws = client.workspace("acme");
const app = ws.app("com.acme.todos");
// Create a file
const { file } = await app.createFile({
name: "Buy milk",
typeKey: "todo",
document: { status: "open" },
});
// Update it
await app.file(file.id).updateDocument({ status: "done" });
// Send a notification
await app.sendNotification({
channelKey: "updates",
title: "Todo completed",
body: "Buy milk is done!",
recipients: [{ userId: "user_123" }],
});
Verify and handle webhook events with typed payloads:
import { webhooks } from "@colineapp/sdk";
// In your webhook handler:
const event = await webhooks.constructEvent(rawBody, request.headers, secret);
switch (event.type) {
case "message.created":
console.log(event.data.plaintext);
break;
case "task.created":
console.log(event.data.title, event.data.priority);
break;
}
Or use createHandler({ onWebhook }) for automatic verification.
Build native Coline UI with the ui helper. Components render directly inside Coline's workspace — no iframe, no custom CSS.
import { ui, actions } from "@colineapp/sdk";
ui.heading("Title", { level: 1 }); // h1–h4
ui.text("Body text");
ui.text("Error occurred", { tone: "danger" });
ui.badge("Active", { tone: "positive" }); // default, muted, positive, warning, danger
ui.link("Read more", "https://docs.example.com", { external: true });
ui.stack([child1, child2], { gap: "md" }); // vertical
ui.row([badge1, badge2], { gap: "sm" }); // horizontal
ui.divider();
ui.button("Save", { action: actions.custom("my-app.save") });
ui.button("Open", { action: actions.openFile("file_123") });
ui.button("Create", {
action: actions.createFile({ name: "New Todo", typeKey: "todo" }),
});
ui.input({ name: "title", label: "Title", placeholder: "Enter a title…" });
ui.input({ name: "notes", label: "Notes", type: "textarea" });
ui.input({ name: "amount", label: "Amount", type: "number", required: true });
ui.select({
name: "priority",
label: "Priority",
options: [
{ value: "low", label: "Low" },
{ value: "medium", label: "Medium" },
{ value: "high", label: "High" },
],
defaultValue: "medium",
});
ui.table({
columns: [
{ key: "name", label: "Name" },
{ key: "status", label: "Status" },
{ key: "date", label: "Date", align: "right" },
],
rows: [
{ name: "Sprint planning", status: "Done", date: "Jan 15" },
{ name: "Design review", status: "Open", date: "Jan 20" },
],
});
ui.codeBlock("const x = 42;", { language: "typescript" });
ui.image({ src: "https://...", alt: "Screenshot", width: 800, height: 400 });
ui.fileCard({ title: "Q4 Report", subtitle: "Updated yesterday", fileId: "file_123" });
ui.userChip({ label: "Alice", userId: "user_123" });
ui.emptyState({
title: "No items yet",
description: "Create your first item.",
action: actions.custom("my-app.create"),
});
For direct spec-driven access:
import { createAppPlatformClient } from "@colineapp/sdk";
const client = createAppPlatformClient({
baseUrl: "https://coline.app",
apiKey: process.env.COLINE_API_KEY!,
});
const { data } = await client.GET("/api/v1/apps");
pnpm sdk:generate:openapi