Cargo hosts two kinds of custom code alongside your resources: workers (serverless edge HTTP handlers) and apps (Vite single-page apps served on *.cargo.app). defineWorker and defineApp deploy a source bundle directory that Cargo Hosting builds on deploy.
Define a worker
import { defineWorker, secret } from "@cargo-ai/cdk";
// bundle root needs: manifest.json + package.json + package-lock.json,
// plus a TS entry `src/index.ts` (or a pre-built `index.js`)
export const webhook = defineWorker("webhook", {
path: new URL("./webhook", import.meta.url).pathname,
// env vars baked into the build; secret("NAME") reads the value at deploy
// time and stores it as a secret env var
env: {
CARGO_API_TOKEN: secret("CARGO_API_TOKEN"),
LOG_LEVEL: "info",
},
// optional: call the worker on a schedule
triggers: [
{
name: "Sync",
cron: "@every 1h",
path: "/cron/sync",
data: { mode: "incremental" },
headers: { authorization: "Bearer <something-strong>" },
},
],
});
Define an app
import { defineApp } from "@cargo-ai/cdk";
// bundle root needs: index.html + package.json + package-lock.json (a Vite app)
export const dashboard = defineApp("dashboard", {
path: new URL("./dashboard", import.meta.url).pathname,
});
The CDK validates the required bundle files exist at define time, so a missing file fails in plan.
defineWorker/defineApp are the deployable resource (the hosted slot).
Author the worker’s runtime code in TypeScript with createWorker from
@cargo-ai/worker-sdk at src/index.ts — bundles are uploaded as source and
built server-side: the hosting build runs npm ci then esbuilds the worker
entrypoint (esbuild transpiles TypeScript natively) or vite build for apps.
A pre-built index.js at the bundle root is still accepted for backwards
compatibility. Bundle sub-directories have their own package.json, so the
loader treats them as content to upload, not resource files to import.
Cron triggers
A worker can be called on a schedule: each trigger describes the full request Cargo fires — cron (or Temporal’s @every shorthand), path, method (default POST), a JSON body, and headers. Set an authorization header and guard the route with standard Hono middleware (bearerAuth/basicAuth); ticks only fire once the worker has a promoted deployment.
Triggers can be set from the worker’s page in the Cargo app, via defineWorker (above), or with the CLI/API:
cargo-ai hosting worker update --uuid <workerUuid> \
--triggers '[{"type":"cron","name":"Sync","cron":"@every 1h","path":"/cron/sync","method":"POST"}]'
Calling the Cargo API
Create a workspace API token and add it to the worker as a CARGO_API_TOKEN secret environment variable — the Cargo API host is allowed without an outboundAllowlist entry. createCargoApi(env) from @cargo-ai/worker-sdk returns the fully-typed @cargo-ai/api client:
import { createCargoApi } from "@cargo-ai/worker-sdk";
app.get("/workers", async (c) => {
const api = createCargoApi(c.env);
const { workers } = await api.hosting.worker.list();
return c.json(workers);
});
Custom integrations
A worker that serves the Custom Integration HTTP contract (createCustomIntegration from @cargo-ai/worker-sdk) can be registered in the connector catalog declaratively:
import { defineCustomIntegration } from "@cargo-ai/cdk";
export const integration = defineCustomIntegration("my-integration", {
worker: webhook,
});
Scaffolding and deploying standalone
You can also scaffold and ship bundles directly with the CLI:
cargo-ai hosting app init my-app # scaffold a Vite app
cargo-ai hosting worker init my-worker # scaffold an edge worker
cargo-ai hosting deployment create --app-uuid <uuid> --source .
cargo-ai hosting deployment promote --uuid <deployment-uuid>
Slugs are kebab-case and must start with a letter (my-worker).