1---
2title: Functions, templates & VMs
3description: Effect-native canvas Functions, marketplace templates, sandboxes, and cloud agents on Railway.
4---
5
6A [`Railway.Function`](/providers/railway/function) is an Effect
7program on Railway's canvas Function runtime: a **single TypeScript
8file** on Bun. No GitHub repo. No Docker. No registry. Distinct from
9Effect-native [`Service({ main })`](/railway/compute/services),
10which uploads a generated Dockerfile. Cap is 96KB once encoded.
11
12Marketplace [`Template`](/providers/railway/template)s deploy a
13serialized config into a Project. [`Sandbox`](/providers/railway/sandbox)
14is an ephemeral Linux VM. [`CloudAgent`](/providers/railway/cloudagent)
15is a coding-agent VM.
16
17## Declare an Effect-native Function
18
19A Function is a class. Props describe the canvas Function. The Effect
20is the program that runs in it.
21
22```typescript
23// src/ping.ts
24import * as Railway from "alchemy/Railway";
25import * as Effect from "effect/Effect";
26import * as HttpServerResponse from "effect/http/HttpServerResponse";
27import { Site } from "./project.ts";
28
29export default class Ping extends Railway.Function<Ping>()(
30 "Ping",
31 {
32 project: Site,
33 main: import.meta.url,
34 },
35 Effect.gen(function* () {
36 return {
37 fetch: Effect.succeed(HttpServerResponse.text("ok")),
38 };
39 }),
40) {}
41```
42
43`main: import.meta.url` is the bundle entrypoint. Alchemy bundles
44this file with Rolldown into one JS file and writes it as the
45Function's `startCommand`. Railway does **not** pull a registry
46image.
47
48## Schemaless RPC
49
50Return methods next to `fetch`. Call `enableRailwayRpc()` in the constructor
51(the canvas start command is capped at 96KB, so the dispatcher is
52opt-in). Another Function or Service binds the class and calls
53them over `{name}.railway.internal` with a shared token. Public
54`*.up.railway.app` traffic to `/__rpc__/*` gets 401 even with the
55token — this is not a public API.
56
57```typescript
58export default class Query extends Railway.Function<Query>()(
59 "Query",
60 { project: Site, main: import.meta.url },
61 Effect.gen(function* () {
62 Railway.enableRailwayRpc();
63 return {
64 greet: (name: string) => Effect.succeed(`hello ${name}`),
65 };
66 }),
67) {}
68```
69
70```typescript
71export default class Api extends Railway.Service<Api>()(
72 "Api",
73 { project: Site, main: import.meta.url },
74 Effect.gen(function* () {
75 const query = yield* Railway.bindFunction(Query);
76 return {
77 fetch: query
78 .greet("sam")
79 .pipe(Effect.map((greeting) => HttpServerResponse.text(greeting))),
80 };
81 }),
82) {}
83```
84
85`bindService` is the same stub for a tagged Service. Calls are
86`POST http://{dnsName}:{port}/__rpc__/{method}` on the private mesh.
87Do not send `rpcToken` to the browser.
88
89## Inline source
90
91Pass inline `source` (or `path` to a `.ts` file) instead of `main`
92when the Function is not an Effect class. Alchemy generates a
93`*.up.railway.app` domain unless `http: false` or a cron schedule is
94set.
95
96```typescript
97const site = yield* Railway.Project("Site");
98const ping = yield* Railway.Function("Ping", {
99 project: site,
100 source: `
101 Bun.serve({
102 hostname: "0.0.0.0",
103 port: Number(process.env.PORT ?? 3000),
104 fetch() { return new Response("ok"); },
105 });
106 `,
107});
108```
109
110`path` is the CLI `--path` equivalent. The file is read at
111plan/reconcile.
112
113```typescript
114const ping = yield* Railway.Function("Ping", {
115 project: site,
116 path: "./fn.ts",
117});
118```
119
120## Cron Function
121
122`cronSchedule` runs the Function on a cron expression (`--cron`).
123HTTP domains are skipped unless `http: true` is set explicitly.
124
125```typescript
126const job = yield* Railway.Function("Cleanup", {
127 project: site,
128 source: `console.log("tick");`,
129 cronSchedule: "0 * * * *",
130});
131```
132
133## Marketplace templates
134
135`templateId` is a marketplace UUID or code (`postgres`). Pass a
136Project to deploy into it, or omit `project` and Alchemy creates one.
137
138```typescript
139const site = yield* Railway.Project("Site");
140const db = yield* Railway.Template("Postgres", {
141 templateId: "postgres",
142 project: site,
143});
144```
145
146## Sandboxes
147
148An ephemeral Linux VM. Create it, `execSandbox` commands, snapshot
149with checkpoints, destroy it when the task is done. Sandboxes are
150Priority Boarding.
151
152```typescript
153const box = yield* Railway.Sandbox("Box", {
154 environment: site,
155 idleTimeoutMinutes: 5,
156});
157yield* Railway.execSandbox({
158 sandboxId: box.sandboxId,
159 environmentId: box.environmentId,
160 command: "echo hello",
161});
162```
163
164## Cloud agents
165
166A persistent coding-agent VM. Sleep keeps the disk; wake re-runs the
167entrypoint. Variables are create-only and may use
168[`Railway.ref`](/providers/railway/ref).
169
170```typescript
171const agent = yield* Railway.CloudAgent("Coder", {
172 environment: site,
173 variables: {
174 DATABASE_URL: Railway.ref(db, "DATABASE_URL"),
175 },
176});
177```
178
179## Where next
180
181[Services](/railway/compute/services) is the container when you need
182Docker (`main`). Functions stay on the canvas runtime.
183The [`Function`](/providers/railway/function),
184[`Template`](/providers/railway/template),
185[`Sandbox`](/providers/railway/sandbox), and
186[`CloudAgent`](/providers/railway/cloudagent) references list every
187prop.