Publishing Functions via CLI
The CLI lets you publish a function from your terminal using the functions subcommand. You push source code to create a draft, then publish by ID to make it live. Point the CLI at a file (and optional aerostack.json in the same directory or project root); it creates a new draft on the Aerostack Hub each time you push.
Prerequisites
Before you publish:
- Installed the Aerostack CLI
- Logged in with
aerostack login(you’ll need your account API key from aerocall.ai/account/keys) - A function directory structured like the examples below
Account Key vs Project Key — Publishing to the community hub requires an account key (prefix ac_), not a project key. Project keys are scoped to a single project deployment.
Function Structure
Every publishable function lives in its own directory. The only required file is aerostack.json plus at least one source file.
- Required — metadata & config
- Required — your function logic
- Optional — Drizzle tables
- Recommended — shown on Hub
The aerostack.json file
This file defines your function’s identity, version, and capabilities. It is read by both the CLI and the Hub.
{
"name": "image-resizer",
"version": "1.0.0",
"description": "Resize and transform images on the edge using Cloudflare Workers.",
"category": "media",
"language": "typescript",
"runtime": "cloudflare-worker",
"license": "MIT",
"tags": ["image", "media", "resize", "cdn"],
"entrypoint": "index.ts",
"routePath": "/api/image-resize",
"routeExport": "imageResizerRoute",
"npmDependencies": [],
"envVars": ["IMAGE_MAX_WIDTH", "IMAGE_QUALITY"],
"drizzleSchema": false
}aerostack.json field reference
| Field | Type | Required | Description |
|---|---|---|---|
name | string | ✅ | Slug-friendly name. Becomes the Hub URL: hub.aerostack.dev/functions/you/image-resizer |
version | string | ✅ | SemVer string e.g. 1.0.0 |
description | string | ✅ | Short description shown in search results |
category | string | ✅ | One of: auth, payments, media, email, ai, database, utility, analytics, notifications, storage |
language | string | ✅ | typescript or javascript |
runtime | string | ✅ | cloudflare-worker (default) |
license | string | ✅ | SPDX identifier e.g. MIT, Apache-2.0 |
tags | string[] | — | Search keywords, max 10 |
entrypoint | string | ✅ | Path to main source file relative to the function directory |
routePath | string | — | Default HTTP route when installed into a project, e.g. /api/image-resize |
routeExport | string | — | Named export from your adapter, e.g. imageResizerRoute |
npmDependencies | string[] | — | npm packages the consumer must install |
envVars | string[] | — | Environment variables the consumer must set |
drizzleSchema | boolean | — | Set true if you export a Drizzle table schema |
Category casing matters — Use all lowercase. The category must exactly match one of the supported values, or publishing will be rejected with a validation error.
Writing Your Function
Your index.ts should export a Hono route handler. This is the convention the CLI bundles and the Hub displays.
import { Hono } from 'hono';
export const imageResizerRoute = new Hono<{ Bindings: { DB: D1Database } }>();
imageResizerRoute.get('/resize', async (c) => {
const url = c.req.query('url');
const width = parseInt(c.req.query('width') ?? '800');
if (!url) {
return c.json({ error: 'url query param required' }, 400);
}
// Your image resizing logic using Cloudflare Images API
return c.json({
original: url,
resized: `${url}?width=${width}&format=webp`,
width,
});
});
imageResizerRoute.get('/health', (c) =>
c.json({ module: 'image-resizer', ok: true })
);Keep core.ts separate — For complex functions, split pure business logic into core.ts and keep your HTTP adapter in index.ts. This makes your function easier to test and easier for consumers to use without Hono.
Publishing Steps
Authenticate
aerostack login
# Prompts: Enter your API key: ac_secret_xxxxPush to create a draft
aerostack functions push ./my-image-resizer/index.ts
# Or from inside the function directory:
aerostack functions push index.tsEach push creates a new draft function. The command outputs the function ID, slug, and a link to the Admin. Save the ID — you need it to publish from the CLI.
Example output:
Pushing function 'image-resizer' to Aerostack...
Pushed successfully!
Slug: image-resizer
Status: draft
Admin URL: https://admin.aerocall.ai/functions/edit/<id>Publish the draft
Use the ID from the push output (or from My Functions in the Admin):
aerostack functions publish <id>Alternatively, open the Admin URL from the push output, add a README and tags, and click Publish there. The function then appears on the Hub and can be installed with aerostack functions install.
Optional: Finalize in the Admin
After pushing, your function is a Draft. To add a full README and tags before going live:
- Go to the Admin URL from the push output (or Admin → My Functions)
- Open the draft and click Edit
- Add README, screenshots, and usage examples
- Click Publish — it goes live immediately
First-time vs updating
- First time:
aerostack functions push [file]creates a new draft. Then runaerostack functions publish <id>or publish from the Admin. - Updating an existing function: Each
aerostack functions pushcreates a new draft (it does not update the same function by slug). To update an existing function:- Admin: Open the function in Admin → My Functions → Edit, change code/metadata, Save. Optionally publish again if you want a new version snapshot.
- CI: Use the GitHub Actions or GitLab CI workflow with
POST /api/community/functions/ci/publish; that endpoint upserts by slug (updates the same function).
Version snapshots are permanent — Once a version is published, its code snapshot is preserved in the history. Consumers who installed an earlier version can still reference it. You can only push forward — you cannot overwrite an older version string.
Common Errors
| Error | Cause | Fix |
|---|---|---|
INVALID_CATEGORY | Category not in allowed list | Check spelling and use lowercase |
MISSING_ENTRYPOINT | entrypoint file not found | Verify the path is relative to the function directory |
EMPTY_CODE | Entrypoint file is empty | Add at least one exported function |
DUPLICATE_SLUG | Slug already exists under a different account | Rename your function |
UNAUTHORIZED | API key invalid or expired | Run aerostack login again |
VERSION_CONFLICT | Version string already published | Bump version in aerostack.json |
Full Example Project
A minimal publishable function you can clone and modify:
git clone https://github.com/aerocall/example-functions
cd example-functions/rate-limiter
aerostack publish .