Skip to main content
Live. This area is documented as current, user-reliable behavior.

Goal

Build and operate a StackShift Functions project as the fastest path to one endpoint, one webhook, one cron, or one worker without learning the platform internals first.

Prerequisites

  • A GitHub-backed project or Docker image project that contains an api/ directory
  • Node.js handler files under api/ using JavaScript or TypeScript
  • At least one healthy node running the Phase 4 agent

Workflow

1
Create function files under api/ and export HTTP methods such as GET, POST, PUT, PATCH, DELETE, or HEAD.
2
Add stackshift.functions.json when you need async, queue, schedule, timeout, retry, or memory configuration.
3
Deploy the project with workload type functions; StackShift builds .stackshift/functions-runtime.mjs and a functions manifest into the image.
4
Caddy sends public traffic to the agent functions gateway, and the gateway cold-starts the project container only when a request arrives.
5
Use @stackshift/functions from inside a function to enqueue async work by function path or explicit queue name.
6
Use the Functions overview to inspect HTTP endpoints, queues, schedules, runs, retries, and replayable failures.

Project layout

A StackShift Functions project is detected from an api/ directory when the repo does not have a stronger full-stack framework signal. During build, StackShift copies or compiles those handlers into .stackshift/functions/api, writes .stackshift/functions-manifest.json, and starts the generated runtime with node .stackshift/functions-runtime.mjs. Use file paths to define routes. Static route segments win over dynamic route segments, so api/users/me.js is matched before api/users/[id].js.
Minimal layout
api/
  hello.js
  users/
    [id].ts
stackshift.functions.json
package.json

HTTP handlers

Export named HTTP methods from a handler file. HEAD falls back to GET when a dedicated HEAD export is not present. CommonJS-style default objects are also supported after build, but named ESM exports are the clearest path.
api/hello.js
export async function GET(request, context) {
  return Response.json({
    message: 'Hello from StackShift Functions',
    requestId: context.requestId,
  })
}

export async function POST(request) {
  const body = await request.json()
  return Response.json({ received: body }, { status: 201 })
}
api/users/[id].ts
export async function GET(_request: Request, context: { params: { id: string } }) {
  return Response.json({ userId: context.params.id })
}

Function configuration

stackshift.functions.json is optional. Add it when a function needs a non-default timeout, memory size, retry count, async trigger, explicit queue trigger, or schedule trigger. Config keys use source paths from the repo, not compiled .stackshift paths.
  • HTTP timeout must be positive and within the supported HTTP limit.
  • Async, queue, and scheduled functions may use longer timeouts than HTTP handlers.
  • Supported memory values are 128m, 256m, 512m, and 1g.
  • Retries apply to non-HTTP work and are capped by the platform.
  • Use one explicit queue name per function in v1. background: true remains supported and maps to the built-in _async queue.
stackshift.functions.json
{
  "functions": {
    "api/hello.js": {
      "timeout": 10,
      "memory": "128m"
    },
    "api/process-image.js": {
      "background": true,
      "timeout": 300,
      "memory": "512m",
      "retries": 3
    },
    "api/thumbnail.js": {
      "queue": "images",
      "timeout": 300,
      "retries": 5
    },
    "api/cleanup.js": {
      "schedule": "0 * * * *",
      "timeout": 60
    }
  }
}

Async invocation and queues

Use @stackshift/functions when one function needs to enqueue another function for async work. In deployed workloads, the helper submits a durable run through the private StackShift async endpoint so retries and replay stay available after the original HTTP request has finished. Use invoke(functionPath, payload) for function-to-function async work or enqueue(queueName, payload) when you want a stable queue name for producers outside the target function file. The built-in _async queue backs legacy background: true functions.
Queue work from an HTTP handler
import { enqueue, invoke } from '@stackshift/functions'

export async function POST(request, context) {
  const payload = await request.json()

  await invoke('api/process-image.js', payload, {
    requestId: context.requestId,
  })

  await enqueue('images', payload, {
    requestId: context.requestId,
  })

  return Response.json({ queued: true }, { status: 202 })
}

Schedules

Scheduled functions export run(context) and must be configured with a schedule. StackShift syncs schedules from the generated manifest after a successful deployment and enqueues durable function runs on the declared cadence. New jobs are scheduled for their next real due time, not forced to run immediately after deploy.
  • Supported aliases: @hourly, @daily, and @weekly.
  • Supported intervals: @every 15m, @every 2h, and similar positive durations.
  • Supported simple cron forms include */10 * * * *, 0 * * * *, and 0 */2 * * *.
  • When a scheduled function is removed from the manifest, StackShift disables the stale scheduled job.
  • Failed scheduled runs appear in the project Runs view and can be replayed from there.
api/cleanup.js
export async function run(context) {
  console.log('cleanup started', {
    scheduledAt: context.scheduledAt,
    requestId: context.requestId,
  })

  // remove expired records, rotate caches, or sync external state
}

Routing and cold starts

In Phase 4, functions are no longer just a file-based runtime inside an always-running project container. Public requests enter through Caddy, Caddy forwards to the local agent functions gateway, and the gateway starts or reuses the project function container. The generated runtime exposes a health endpoint for readiness, normal HTTP route dispatch, and reserved internal invoke endpoints. The agent tracks idle time, active request count, startup timeout, and max concurrency for each functions deployment, while durable jobs drive non-HTTP retries and replayable execution.
  • Caddy adds X-StackShift-Functions-Runtime so the gateway knows which project runtime to use.
  • The gateway cold-starts app_<runtimeID> when the function is inactive and waits for __stackshift/health.
  • Idle containers are stopped after the configured idle timeout and restarted on the next request.
  • Reserved internal invoke paths require X-StackShift-Internal-Token before any cold start is attempted.

Environment variables

Most projects do not need to set any functions-specific environment variables. StackShift injects the async helper URL, per-project async token, and project ID during deployment. The runtime still exposes internal invoke defaults for local development and compatibility.
  • PORT: the port used by the generated runtime; defaults to the project runtime port, commonly 3000.
  • STACKSHIFT_FUNCTIONS_ASYNC_URL: private StackShift endpoint used by @stackshift/functions to create durable async runs in deployed environments.
  • STACKSHIFT_FUNCTIONS_ASYNC_TOKEN: private per-project token used to authorize async enqueue requests.
  • STACKSHIFT_PROJECT_ID: injected project identifier used by the helper when submitting async or queue work.
  • STACKSHIFT_FUNCTIONS_INVOKE_TOKEN: private token still used by the internal gateway/runtime invoke path.
  • STACKSHIFT_FUNCTIONS_INVOKE_URL: optional local override for unusual topologies. Leave it unset in normal production; the compatibility default remains http://127.0.0.1:$PORT/.stackshift/functions/invoke inside the runtime container.
  • FUNCTIONS_ROOT and FUNCTIONS_MANIFEST: generated runtime paths. Override only for custom runtime debugging.

Production URL guidance

api.stackshift.cloud is the StackShift control-plane API. It is not the public endpoint your users hit, and it should not be used as a browser-facing Functions origin. In deployed workloads, the async helper talks to a private StackShift endpoint using the injected async URL and token while public HTTP traffic continues to flow through your project domain and the functions gateway. On the current production agent install, Caddy runs as a host/systemd service and can reach the functions gateway on 127.0.0.1:<FUNCTIONS_GATEWAY_PORT>. If Caddy is moved into Docker, the gateway binding and Caddy upstream target must be reviewed together because Docker Caddy cannot use its own 127.0.0.1 to reach the host agent.
  • Do not expose STACKSHIFT_FUNCTIONS_ASYNC_TOKEN or STACKSHIFT_FUNCTIONS_INVOKE_TOKEN to browsers, client bundles, or public logs.
  • Do not point your frontend directly at the private async helper endpoint.
  • Do keep normal platform API clients pointed at https://api.stackshift.cloud/api/v1 when they are calling the public StackShift API.

Operational checklist

  • Confirm the project workload type is functions after detection or manual override.
  • Check build logs for manifest warnings about missing configured source files, duplicate queue names, or invalid schedules.
  • Check deployment logs for gateway registration, Caddy route updates, and cold-start errors.
  • Use the Functions overview for HTTP endpoints, queues, schedules, and runs before dropping down to raw durable-jobs internals.
  • Use function logs for [stackshift-functions] entries including request ID, function path, status, duration, trigger type, run ID, and timeout state.
  • Replay failed schedule and queue runs or retry failed async runs from the project Runs view.

Expected result

The project runs as a managed functions workload: public HTTP routes are routed through Caddy and the agent gateway, async and queue work is durable, schedules run on their declared cadence, and failed non-HTTP runs can be retried or replayed from the project surface.

Common failures

  • A function file exports no HTTP method and has no configured background, queue, or schedule trigger.
  • A scheduled function is configured but does not export run.
  • Two functions are configured with the same explicit queue name.
  • STACKSHIFT_FUNCTIONS_ASYNC_URL is pointed at the wrong host or the async token is missing, so helper-based enqueue requests are rejected.
  • The app listens on the wrong port or overrides PORT without matching the project runtime port.
  • Caddy is containerized while the functions gateway is bound to host 127.0.0.1; the current production install expects host/systemd Caddy.

Deploy from GitHub

Use the repository-backed project flow when you want StackShift to detect the app, build from source, and let you override runtime behavior before the first deploy.

Builds, deployments, and logs

Understand the project execution lifecycle: build output, deploy state, rollback behavior, and where to inspect logs.

Project environment, domains, and previews

Configure the project surfaces that most often decide whether a deployment works after it builds, including runtime shape, domains, previews, and storage.

Project troubleshooting

Common project-side failure modes, especially when the app built successfully but does not come up healthy.