Skip to main content
The app is the interactive part of a slate: the pages people open in a browser. It is a React Router app whose server code runs as a Cloudflare Worker. React components render the interface. Route loaders read data for a page, and route actions handle submissions that change data. Loaders and actions use the ContextLayer SDK to call connected services and query databases. The browser receives the results your route returns.

The starter

The CLI’s init command creates the app from the starter bundled with the CLI, with Tailwind and shadcn/ui set up:
  • app/routes/home.tsx is the first screen. The starter’s version is a small notes page that reads and writes the notes database.
  • app/root.tsx re-exports the SDK’s document Layout, which mounts the chrome on the hosted slate. Keep this re-export when you edit the root. The root’s error boundary shows setup guidance when a connection has no assigned account or a viewer-mode secret has no value.
  • public/slate.json is the app’s declaration.
  • databases/notes/ holds the notes database’s Kysely configuration, migrations and generated query types.

Declare resources

The app names the resources it requires in public/slate.json. ContextLayer uses this declaration to prepare the app’s resource capabilities; the file does not contain account credentials, secret values, or database contents. The starter declares one database:
public/slate.json
connections is required, even when empty. Here, notes is the database slug that the app uses in queries, and "mode": "owner" selects the slate Owner’s database for all viewers. A resource declared with "mode": "viewer" instead selects the current viewer’s resource. Resources explains the declaration fields and ownership modes.

Run locally

From the checkout root, pnpm dev starts the development server; open the local URL it prints. Locally, the app uses your own resources:
  • Connections use your personal accounts, whatever their declared mode. Assign them with the CLI’s connections command (npx @usecontextlayer/cli connections --help). Local assignments are saved in .contextlayer/connections.json.
  • Secrets come from environment variables with the declared names.
  • Databases are stored in .contextlayer/databases.
Restart pnpm dev after changing declarations, assignments, or secret values so startup can prepare the new configuration. When the app declares connections, the development server also needs your CLI login. The account connection guide walks through local assignments. Run pnpm typecheck to check the checkout’s TypeScript and pnpm build to build it for production. Local database contents and environment values are not uploaded when you deploy.

Use connected services and databases

The SDK supplies resource capabilities through React Router’s request context. A loader or action asks for the capability it needs and selects a resource by its declared slug:
  • context.get(toolsContext) returns Tools. tools.call(slug, tool, arguments) runs a tool through one of the app’s declared connections and resolves to { data, logId }. Validate data before using it.
  • database<DB>(context.get(databasesContext), slug) returns a typed Kysely instance for a declared database. context.get(databasesContext).query(slug, sql, params) runs SQL directly.

Query the notes database

This excerpt from the notes route shows the loader reading notes newest first and the action inserting a submitted note. Both select the declared notes database; the action validates the form value before inserting it. Keep the route’s rendered component alongside these server functions:
app/routes/home.tsx
The "notes" argument to database() is a database slug. The "notes" argument to selectFrom() or insertInto() is a table name inside that database. They happen to match in the starter; they are separate names.

Call a connected service

A connected-service call follows the same pattern. This excerpt assumes you have declared a Gmail connection named work-email in public/slate.json and assigned an account to it. Inside a route loader or action, obtain Tools and name that connection:
Here, work-email selects the declared connection, GMAIL_FETCH_EMAILS selects the provider tool, and the object supplies that tool’s arguments. data is the provider result; validate its shape before using it. logId identifies the tool execution. See the SDK reference for the call contract. Keep these calls in loaders and actions. Browser components submit requests to those routes and receive the data they return; they receive no ContextLayer credentials. On the hosted slate, each resource call is bound to the slate and the viewer making the request. The declaration controls which resources are available, and your route code controls which operations the interface exposes. When a declared connection has no assigned account, or a viewer-mode secret has no value, the call throws an HTTP 409 response. The starter’s root error boundary turns it into setup guidance; the SDK’s connectionRequiredSchema and secretRequiredSchema recognize it in a custom error boundary. The SDK reference documents the full API.

Deploy

Commit your changes and push main to the slate’s remote. See Deploy with a Git push.