The long tail of app-development - load that skill first for the core workflow (write a function, wire gipity.yaml, deploy, test). This reference covers the function runtime's module system in depth, plus operational details you reach for on demand.

ES module syntax - what you can write

The runtime compiles your code as a real ES module (since 2026-05-29). Any valid single-file ESM parses cleanly:

// All of this works in one file:
import { z } from './schemas.js';           // relative imports (multi-file only - see below)
export const VERSION = 3;                   // top-level const, exported or not
export function validate(input) { ... }     // named exports alongside the default
const cfg = await loadConfig();             // top-level await
export default async function handler(ctx, { db }) { ... }

What does NOT work and the exact error you'll see:

Multi-file functions

When a function grows past a single file - testable helpers, shared constants, etc. - turn it into a directory:

functions/
  song-render/
    index.js          # entry - must have `export default async function ...`
    sanitize.js       # helper - `export function sanitizeRenderOptions(...)`
    constants.js      # `export const DEFAULT_MODE = 'preview'`

index.js is the entry; sibling files are reached via relative import:

// functions/song-render/index.js
import { sanitizeRenderOptions } from './sanitize.js';

export default async function songRender(ctx, { db, jobs, guid }) {
  const opts = sanitizeRenderOptions(ctx.body?.render_options);
  // ...
}

Why use multi-file:

Helpers don't see services. Only the entry module receives ctx, db, fetch, secrets, env, console, jobs, guid, and the app services (llm, image, tts, sound, music, video, transcribe, location). Inside a helper, those names are undefined. If a helper needs the DB, the entry passes it as an argument:

// functions/song-render/sanitize.js - PURE helper, no service access
export function sanitizeRenderOptions(raw) { /* validates raw, returns clean obj */ }

// functions/song-render/persist.js - needs db, so accept it as arg
export async function saveRender(db, songGuid, opts) {
  await db.query('INSERT INTO renders ...', [songGuid, opts]);
}

// functions/song-render/index.js - entry passes db down
import { sanitizeRenderOptions } from './sanitize.js';
import { saveRender } from './persist.js';
export default async function (ctx, { db }) {
  const opts = sanitizeRenderOptions(ctx.body);
  await saveRender(db, ctx.body.song_guid, opts);
}

Import rules and errors:

Limits: up to 20 files per function (shared _lib files count toward this), 256 KB total source, max 4 levels of nesting.

Pick the shape that fits: single functions/foo.js for simple handlers; functions/foo/ directory once you have a helper you'd like to unit-test.

Shared modules across functions (functions/_lib/)

When two or more functions need the same code - a schema registry, validation, SQL builders - put it in functions/_lib/ instead of copy-pasting it into each function directory (copies drift):

functions/
  _lib/
    registry.js       # shared - `export function describeObject(...)`
  record-read/
    index.js          # imports it: `import { describeObject } from '../_lib/registry.js'`
  record-write.js     # single-file functions can import it the same way

At deploy, _lib/ files are bundled into every function's payload, so editing a shared module re-deploys all functions automatically. The rules:

The function runtime supports single-file ESM (shipped 2026-05-29), multi-file functions with relative ./helper.js imports (2026-05-30), and shared _lib modules imported as ../_lib/<file>.js (2026-06-09).

Kits that ship backend pieces (gipity add records …)

Some kits install more than frontend code. A kit whose manifest declares functions: true / migrations: true copies its functions/ and migrations/ trees into the app's own functions/ and migrations/ (the records and agent-api kits are the prototypes - generic registry-driven CRUD with an audit event spine, and named API keys for agent writes). Rules:

Full guide to the records + views kits (registry-driven CRUD, field types, event spine, generated tables/kanban/forms) → the app-records skill.

Versioning & Rollback

Every update creates an immutable version:

fn_manage rollback --name my-function --version 3

Management Commands

fn_manage list                          # List all functions
fn_manage logs --name X                 # Execution logs
fn_manage delete --name X               # Delete
fn_manage rollback --name X --version N # Rollback

Rate Limits


Related Skills