Getting started

Introduction

Ferrule runs your webhooks and edge functions in the region nearest the caller and records a trace for every request, from the edge to the database.

What you get

  • Functions written in TypeScript, Python or Go, deployed with one command.
  • Traces for every request, with your log lines and queries on the same timeline.
  • Queues with retries and idempotency keys, so a retry never runs a side effect twice.
  • Preview deploys for every branch, with their own secrets and traces.

The five layers

Every request passes through the same layers in the same order. Each one adds a span to the trace, so you can see where the time went.

LayerWhat it doesSpan name
EdgeTerminates TLS in the nearest region and routes by path.edge
AuthChecks the API key and its scopes.auth
QueueStores the job with its idempotency key.queue.send
FunctionRuns your code and collects its log lines.fn.<name>
DatabaseRecords each query with its timing.db.<table>.<op>

Fictional product. Ferrule is an example made for an Inazda UI template. Commands and packages on these pages are placeholders for your own.

Where to go next

Start with the Quickstart to deploy a function and read its first trace. If you are moving an existing webhook handler, read Queues and retries first.

Getting started

Quickstart

Deploy a checkout function, call it, and read its trace. It takes about five minutes.

Install the CLI

The CLI creates projects, runs functions locally and deploys them. You need Node 20 or newer.

Terminal
npm i -g ferrule
ferrule login

Create a function

A function is a file in functions/ that exports a handler. The file name becomes the route.

functions/checkout.ts
import { ferrule } from "@ferrule/sdk";

export default ferrule.fn("checkout", async (req, ctx) => {
  const { cart } = await req.json();
  const items = await ctx.db.carts.get(cart);
  ctx.log("cart loaded", { items: items.length });
  return Response.json({ status: "created" }, { status: 201 });
});

Run it locally

Local runs record traces too, so you can read one before you deploy.

Terminal
ferrule dev
# Listening on http://localhost:8787

Deploy

  1. Commit the function and push the branch. A preview deploy starts on its own.
  2. Open the preview URL from the CLI output or the dashboard.
  3. Merge to main to promote the same build to production.

Secrets are per branch. A preview reads the secrets set for its branch, then the project defaults. Set production keys on main only.

Read the trace

Call the function, then print the trace of the last request. The slowest span is marked.

Terminal
curl -X POST http://localhost:8787/checkout -d '{"cart":"c_8Q2"}'
ferrule trace --last

Tip. Add --open to open the trace in the dashboard instead of the terminal.

Concepts

Traces

A trace is the record of one request: every layer it passed through, every log line your code wrote, and every query it ran, on one timeline.

Spans

Each layer adds a span with a start time and a duration. Spans nest: a database query inside your function is a child of the function span.

Trace tr_9f2c41
request            0ms ────────────────────────────── 41ms
  edge · sin1      0ms ███                               4ms
  auth             4ms    ██                             2ms
  queue.send       6ms      ██                           3ms
  fn.checkout      9ms        ██████████████████████    31ms
    db carts.get  12ms          ████                     6ms
    db orders     27ms                     ██            3ms

Log lines

Anything you pass to ctx.log is stored with the span that wrote it and the time since the request began.

Structured fields

Pass an object as the second argument. Fields are searchable in the dashboard, so prefer IDs over sentences.

Retention

PlanKept forExport
Hobby7 daysJSON
Pro30 daysJSON, OpenTelemetry
ScaleSet per projectJSON, OpenTelemetry, stream

Concepts

Queues and retries

Queues hold work that has to happen once, even when a downstream service is having a bad minute.

Send a job

functions/checkout.ts
await ctx.queue.send("orders", { cart, email }, {
  idempotencyKey: req.headers.get("idempotency-key"),
  retries: 3,
  backoff: "exponential", // 2s, 8s, 32s
});

Idempotency keys

A key makes the job safe to send twice. If a job with the same key already succeeded, the second send returns the first result instead of running again.

Keys expire after 24 hours. Use your own order or payment ID as the key, not a random value made at send time.

Dead-letter queue

A job that fails every retry moves to the queue's dead-letter list with its body, headers and each attempt. Requeue one job or all of them from the CLI.

Terminal
ferrule dlq list orders
ferrule dlq requeue orders --all

Reference

SDK

Every SDK passes your handler a request and a context. The context is the same in TypeScript, Python and Go.

The context

MemberWhat it is
ctx.log(msg, fields?)Writes a log line into the current span.
ctx.dbDatabase client. Each query becomes a child span.
ctx.queue.send(name, body, opts?)Sends a job. Options: idempotencyKey, retries, backoff, delay.
ctx.envSecrets for the current branch.
ctx.trace.idThe ID of the current trace, for your own logs or support tickets.

Install

Terminal
npm i @ferrule/sdk          # TypeScript
pip install ferrule         # Python
go get github.com/example/ferrule-go

Reference

CLI

The CLI covers the whole loop: create, run, deploy, read traces and replay requests.

Commands

CommandWhat it does
ferrule initCreates a project with one example function.
ferrule devRuns functions locally with traces.
ferrule deploy [dir]Deploys a folder. Without git, makes a preview.
ferrule trace --lastPrints the trace of the latest request.
ferrule replay <trace>Sends a past request to a preview and compares both responses.
ferrule dlq list <queue>Lists dead-lettered jobs.

Global flags

FlagDefaultMeaning
--projectcurrent folderWhich project to act on.
--branchcurrent git branchWhich preview or secrets to use.
--jsonoffMachine-readable output.

Project

Changelog

What changed, newest first. Example entries for the template.

October 2026

Replays from the CLI. ferrule replay sends a past request to any preview and prints both responses side by side.

September 2026

Query plans in traces. Slow database spans carry their plan, folded until you open it.

Go SDK, first stable release. Same context as the TypeScript and Python SDKs.

August 2026

Readable dead-letter queues. Failed jobs keep their body, headers and every attempt.

Per-branch secrets. Previews read their own secrets, so a test key never reaches production.

Ferrule is a fictional product made for an Inazda UI template.

Template by Inazda