Build an app
Scaffold, define, test and push your first Coline app.
A Coline app installs into a workspace as a set of capabilities: tools that Kairo and people can run, file types with their own previews and editors, home screens, schedules and more. Coline supplies sign-in, members, permissions, files, search, notifications, realtime, AI and billing, so an app doesn't build any of that. Apps run on Coline by default, with no servers to deploy.
The packages are open source under MIT: @colineapp/sdk, @colineapp/ui and create-coline-app.
The current release is SDK 2.1: @colineapp/sdk 2.1.3, @colineapp/ui 2.1.4, @colineapp/app-runtime 2.1.0 and create-coline-app 2.9.5. SDK 2 brought the app model on this page: one defineApp for permissions, surfaces, tools, file types, schedules, jobs, endpoints and connected accounts, with every call checked against what the workspace granted. SDK 2.1 adds Coline Sites: any app can also be a public website, with visitor sign-in (Google or Coline), typed collections, uploads, endpoints on the site's own paths, React hooks and Coline's components in light and dark.
Before you start
You need Node.js and npm. To push, sign in with npx coline-app login as an admin of the workspace. In CI, use a workspace API key with the apps.write scope instead.
1. Scaffold
npx create-coline-app my-app
cd my-app && npm install && npm run checkPick a template with --template:
| Template | You get |
|---|---|
backendless (default) | A hosted React app with a file type, a Kairo tool and UI examples |
editor | A file type with a React block editor: autosave, conflict handling, previews and search |
integration | A GitHub OAuth connection, a webhook endpoint, a resumable sync job, an hourly schedule and a dashboard |
external-backend | Tools and screens served by your own Node server, signed by Coline |
site | A public website with visitor rules, a site tool and a home screen for the team. Deploys without an account; see Sites |
Every project includes an AGENTS.md and an agent skill with the full app contract, so coding agents can build with it.
2. Define the app
app.config.ts is the whole contract: permissions, surfaces, tools, file types and handlers.
import { z } from "zod/v4";
import { defineApp, defineFileType, defineTool, ui } from "@colineapp/sdk/v2";
const todoFileType = defineFileType({
metadata: {
typeKey: "todos.todo",
name: "Todo",
indexable: true,
surfaces: { preview: { tier: "tree" }, inline: { tier: "tree" } },
},
runtime: {
renderPreview: (ctx) =>
ui.stack([ui.heading(ctx.file.name), ui.badge(String(ctx.document.status ?? "open"))]),
renderInline: (ctx) => ui.text(ctx.file.name),
index: {
title: (doc) => String(doc.title ?? "Todo"),
body: (doc) => String(doc.notes ?? ""),
},
},
});
const createTodo = defineTool({
name: "todos.create_todo",
description: "Create a todo with a title.",
input: z.object({ title: z.string().min(1) }),
effect: "write", // enforced as a ceiling at runtime
execute: async (input, ctx) => {
const file = await ctx.coline.files.create({
typeKey: "todos.todo",
name: input.title,
document: { title: input.title, status: "open" },
});
return {
output: { fileId: file.fileId },
card: ui.reference({ kind: "file", id: file.fileId }),
};
},
});
export default defineApp({
key: "todos",
name: "Todos",
permissions: ["files.read", "files.write", "ai.tools", "search.index"],
hosting: { default: "coline" },
surfaces: { home: { tier: "tree" } },
files: [todoFileType],
tools: [createTodo],
handlers: {
renderHome: async (ctx) => {
const { files } = await ctx.coline.files.list({ typeKey: "todos.todo" });
return ui.stack([
ui.heading("Todos"),
...files.map((file) => ui.fileCard({ title: file.name, fileId: file.fileId })),
]);
},
},
});One definition produces two artifacts. The manifest says what the app may do; it's what reviewers and workspace admins approve. The runtime bundle is what the app does. Every call it makes is checked against the manifest, the workspace's grants, the acting person's access and the tool's declared effect.
3. Test
@colineapp/sdk/testing runs the app against an in-memory workspace with the same API and permission checks as production.
import { expect, it } from "vitest";
import { createTestWorkspace } from "@colineapp/sdk/testing";
import app from "./app.config";
it("creates a todo through the Kairo tool", async () => {
const ws = createTestWorkspace(app);
const result = await ws.invokeTool("todos.create_todo", { title: "Ship it" });
expect(result.card).not.toBeNull();
expect(ws.files.byType("todos.todo")).toHaveLength(1);
});npm run check validates types, the manifest, the logic bundle and the React bundle without uploading anything.
4. Push
npx coline-app login # once: opens a browser, pick the workspace
npx coline-app pushThe CLI builds the logic bundle, ships client source for React surfaces, and uploads a version. It lands as a draft that you can install privately in your workspace right away, or submit for review to list in the App Store. See develop and publish.