Pages and sections
shipyard.trian.space/docs/edge-functions
Edge functions
Deploy TypeScript functions from a git repository to Shipyard's Deno runtime.
Edge Functions let you run serverless TypeScript or JavaScript handlers alongside your other Shipyard services. Drop .ts files into a functions/ directory in any Git repository — Shipyard detects them automatically on every push and deploys them to a Deno runtime scoped to your organization.
| Item | Value |
|---|---|
| Invoke URL | /fn/<org-slug>/<fn-name> |
| Runtime | Deno 1.x · TypeScript native |
| Deploy trigger | Git push → auto-detect |
| Custom domain | CNAME → edge runtime |
#Quick start
Three steps from zero to a running function.
#Step 1: Write a function
Create a file inside functions/ and export a default async function that accepts a Request and returns a Response. No framework, no boilerplate.
// functions/hello.ts
export default async function handler(req: Request): Promise<Response> {
const name = new URL(req.url).searchParams.get("name") ?? "world";
return new Response(`Hello, ${name}!`, {
headers: { "Content-Type": "text/plain" },
});
}#Step 2: Connect a repo with an edge function group
In the Shipyard dashboard, open your project → Edge Functions and create a new function group. Connect it to your Git repository. Shipyard will clone the repo and scan the functions/ directory on every deploy.
#Step 3: Invoke your function
Once deployed, your function is reachable at the path-based URL immediately — no DNS changes needed.
curl https://your-shipyard.example.com/fn/my-org/hello?name=ClaudeHello, Claude!#Repo structure
Shipyard's function detector (edge_fn_detector) scans your repository after every deploy. It resolves functions in this order:
- If
shipyard.jsondefines anentriesmap, use those explicit paths. - Otherwise scan the configured
dir(defaultfunctions/) for.tsand.jsfiles. - A file is registered as a function only if it contains
export default. - The function name is the filename without extension (e.g.
send-email.ts→send-email).
my-repo/
├── functions/
│ ├── hello.ts ← auto-detected (export default)
│ ├── send-email.ts ← auto-detected
│ └── utils.ts ← skipped (no export default)
├── shipyard.json ← optional overrides
└── ...#Handler signature
Every function must export a default async function with this exact signature. Shipyard passes the raw Request object from the Deno HTTP server.
export default async function handler(req: Request): Promise<Response> { … }#shipyard.json
Place a shipyard.json file at the root of your repository to override defaults. All fields are optional — an empty file or no file at all is valid.
{
"functions": {
"dir": "functions",
"runtime": "deno",
"timeout_ms": 10000,
"env": [
"DATABASE_URL",
"STRIPE_KEY"
],
"entries": {
"send-email": "functions/mailer.ts",
"resize-image": "functions/images/resize.ts"
}
}
}| Field | Type | Default | Description |
|---|---|---|---|
functions.dir | string | "functions" | Directory to scan for function files |
functions.runtime | string | "deno" | Only deno is supported currently |
functions.timeout_ms | integer | 10000 | Per-request timeout in milliseconds (max 30 000) |
functions.env | string[] | [] | Allowlist of environment variable names to inject into the runtime. Variables not on this list are not accessible inside functions. |
functions.entries | object | {} | Explicit name → path mapping. When set, overrides directory scanning. Useful for functions nested in subdirectories. |
#Environment variables
Set environment variables for your edge function group in the Shipyard dashboard under Edge Functions → Settings → Environment. Variables are encrypted at rest and injected into the Deno runtime on deploy.
Inside your function, access them via Deno.env.get(). Only variables listed in functions.env in shipyard.json (or all variables if env is omitted) are passed through.
export default async function handler(req: Request): Promise<Response> {
const dbUrl = Deno.env.get("DATABASE_URL");
// dbUrl is available because "DATABASE_URL" is in the env allowlist
return new Response("ok");
}#Invoking functions
Functions can be invoked via two URL patterns: the built-in path-based URL (always available) and a custom domain (optional, see next section).
#Path-based URL
Every function is reachable under /fn/<org-slug>/<fn-name> on your Shipyard domain — no configuration required.
https://<your-shipyard>/fn/<org-slug>/<fn-name>[/extra/path][?query]All HTTP methods are forwarded verbatim — GET, POST, PUT, PATCH, DELETE, and OPTIONS. Query parameters, request headers (excluding hop-by-hop headers), and the request body are passed through unchanged.
curl https://your-shipyard.example.com/fn/my-org/hello?name=Claudecurl -X POST https://your-shipyard.example.com/fn/my-org/send-email \
-H "Content-Type: application/json" \
-d '{"to":"[email protected]","subject":"Welcome"}'#Error responses
| Status | Meaning |
|---|---|
404 | Organization slug not found or function name not deployed |
502 | Edge runtime is not running for this organization |
504 | Function exceeded its timeout_ms limit |
#Custom domains
Attach a custom domain to your edge function group so functions are reachable directly at your domain — without the /fn/<org>/ prefix.
#Adding a domain
- Go to Edge Functions → Domains → Add Domain in the dashboard.
- Enter your custom hostname (e.g.
api.acme.com). - Add a CNAME record pointing to the Shipyard edge runtime for your organization.
- TLS is provisioned automatically via Let's Encrypt once DNS propagates.
# CNAME your custom domain to the Shipyard edge runtime
api.acme.com. IN CNAME shipyard-edge-<org-short-id>.example.com.Once the domain is active, functions are invoked at the root path — the function name becomes the first path segment.
curl https://api.acme.com/send-email#Removing a domain
Removing a domain from the dashboard immediately removes the Traefik routing rule. Existing DNS records are not affected — you must remove the CNAME yourself if you no longer need it.
#Deployment history
Every time you click Deploy in the dashboard, Shipyard creates an immutable deployment record for each function it detects. You can browse the full history, view the exact code from any past deployment, and restore any previous version in one click.
#Deployment statuses
| Status | Meaning |
|---|---|
| live | Currently serving traffic — the most recent successful deployment |
| pending | Deploy in progress (git clone + function scan running) |
| error | Deploy failed (check the error message in the history row) |
| rolled_back | Superseded by a newer deployment or a manual rollback |
#Viewing code from a past deployment
In the dashboard, expand any function in the Edge Functions panel and click History. Each row shows the commit SHA, deploy time, and status. Click View on any row to open a read-only code viewer showing the exact code bundle from that deployment.
#Restoring a previous deployment (rollback)
Click Restore on any past deployment row to make it live again. Shipyard copies the code bundle from that snapshot into a new deployment record with status live and marks the previous live deployment as rolled_back. The rollback takes effect immediately — no rebuild or git clone required.
#Deploy report
After every deploy, the dashboard shows a summary of what happened:
| Field | Meaning |
|---|---|
deployed | Functions that were detected and successfully deployed |
skipped | Files that were scanned but had no export default |
failed | Files that were detected but failed to compile or load |
deleted | Functions present in the previous deploy but no longer in the repo |
#Limits & quotas
Limits are enforced per organization. Contact support to request higher limits on paid plans.
| Limit | Free | Pro |
|---|---|---|
| Edge function groups | 1 | Unlimited |
| Functions per group | 5 | 100 |
| Custom domains per group | 0 | 10 |
| Deployment history retained | 10 per function | Unlimited |
Request timeout (timeout_ms max) | 10 000 ms | 30 000 ms |
| Max request body size | 1 MB | 10 MB |
| Concurrent requests per runtime | 50 | 500 |