# Coline Sites

> Put a Coline app on the web at its own address, with visitors, data rules and live updates, in one command.

Source: https://docs.coline.app/sites

A site is a Coline app with a public website. It gets its own address, `name.coline.space` or your own domain, and anyone can open it without an account. Its data lives in a Coline workspace: the team sees every record, gets notified about new ones, and can ask Kairo about them.

Sites are part of SDK 2.1 (`@colineapp/sdk` 2.1.2, `@colineapp/ui` 2.1.1 and `create-coline-app` 2.9.2).

## Quickstart

No account is needed to build or deploy a site.

```bash
npx create-coline-app my-site --template site
cd my-site && npm install
npm run dev          # http://127.0.0.1:3000, data in memory
npx coline-app deploy
```

`deploy` prints the live address and a claim link. A site deployed without an account stays up for 72 hours after its last deploy. Open the claim link, sign in, and pick a workspace to keep it. See [deploy and claim](/sites/deploy).

## What's in a site

The `site` template is a guestbook:

```
my-site/
  app.config.ts      the app: permissions, the site, collections and tools
  app.test.ts        the same rules Coline enforces, in memory
  site/main.tsx      the website (React)
  site/app.css       Tailwind is built in
  site/public/       files served at the site root (favicon, images)
  AGENTS.md          the contract, for coding agents
```

The site surface declares the page, its routes and what visitors can do:

```ts
import { z } from "zod/v4";
import { defineApp, defineTool } from "@colineapp/sdk/v2";

const entry = z.object({ name: z.string().max(60), message: z.string().max(500), signedAt: z.iso.datetime() });
type Entry = z.infer<typeof entry>;

const sign = defineTool({
  name: "guestbook.sign",
  description: "Sign the guestbook.",
  input: z.object({ name: z.string().min(1).max(60), message: z.string().min(1).max(500) }),
  effect: "write",
  execute: async (input, { coline }) => {
    const entry = await coline.storage
      .collection<Entry>("entries")
      .insert({ ...input, signedAt: new Date().toISOString() });
    return { output: { id: entry.id } };
  },
});

export default defineApp({
  key: "guestbook",
  name: "Guestbook",
  permissions: ["site.serve", "storage.app", "ai.tools"],
  hosting: { default: "coline" },
  surfaces: {
    site: {
      entry: "site/main.tsx",
      assets: "site/public",
      title: "Guestbook",
      description: "Leave a note.",
      routes: [{ path: "/" }, { path: "/entries/:id", head: { title: "An entry" } }],
      visitors: { signIn: true, providers: ["google", "coline"] },
      tools: ["guestbook.sign"],
    },
  },
  collections: {
    entries: { access: { read: "public", update: "owner", delete: "owner" }, schema: entry },
  },
  tools: [sign],
});
```

The page is built from Coline's own components and talks only to its own site. The React hooks are typed from the app itself, so collection names, record shapes and tool inputs are checked at compile time:

```tsx
import { createSiteHooks } from "@colineapp/sdk/site/react";
import { Button, Card, CardContent, ColineSiteProvider, Input } from "@colineapp/ui";
import type app from "../app.config";

const { useCollection, useTool } = createSiteHooks<typeof app>();

function Guestbook() {
  const entries = useCollection("entries", { orderBy: "createdAt", order: "desc" }); // live
  const sign = useTool("guestbook.sign");
  // <Button onClick={() => sign.run({ name: "Ada", message: "Hello" })}>Sign</Button>
}
```

## How it works

* **Every site is a Coline app.** The same app can also have a home screen inside Coline, file types and Kairo tools. Its records are workspace records.
* **Visitors are their own kind of user.** They are guests by default. A site can offer [sign-in with Google or Coline](/sites/visitors), require it, or admit only visitors the team approves. Visitors never act as a workspace member or as the app.
* **Collections say who can do what.** Each collection declares which visitors may read, create, update and delete, and a `schema` that every visitor write must match. A collection without rules is invisible to visitors. See [data and rules](/sites/data).
* **Tools are the site's server code.** Visitors can run the tools a site lists. They run on Coline as the app, with the app's permissions, and never at more than the `write` effect.
* **Endpoints answer HTTP on the site's own paths**, for webhooks, JSON APIs and feeds. See [endpoints](/sites/endpoints).
* **Visitors can upload files** when the site accepts them. See [files](/sites/files).
* **Pages update live.** Collections refresh when records change, for every visitor.
* **Coline serves the page.** Each route gets server-rendered `<title>`, description and link preview tags, a sitemap and `robots.txt`. The page itself is client-rendered React.
* **It looks like Coline.** Pages use `@colineapp/ui`, Coline's components, in light and dark. See [design](/sites/design).
* **Browser agents can use the site.** With `agentTools: true`, the site's tools are offered to AI agents in the visitor's browser.

## Next

<Cards>
  <Card title="Data and rules" description="Collections, schemas, visitor rules, the browser client, React hooks and site tools." href="/sites/data" />

  <Card title="Design" description="Coline's components and tokens on a site, in light and dark." href="/sites/design" />

  <Card title="Visitors and sign-in" description="Guests, Google and Coline sign-in, access modes and approvals." href="/sites/visitors" />

  <Card title="Files" description="Visitor uploads, public and private files, and storage limits." href="/sites/files" />

  <Card title="Endpoints and agents" description="HTTP endpoints on the site's paths, and tools for browser agents." href="/sites/endpoints" />

  <Card title="Deploy and claim" description="Deploying with and without an account, claiming, and the CLI." href="/sites/deploy" />

  <Card title="Limits and plans" description="Requests, data, storage, domains and what an unclaimed site can use." href="/sites/limits" />
</Cards>
