# Publishing Functions via CLI

> A complete guide to publishing your Aerostack function to the community hub using the CLI direct-push workflow.

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](/cli)
- Logged in with `aerostack login` (you'll need your account API key from [app.aerostack.dev/account/keys](https://app.aerostack.dev/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.

- my-image-resizer
  - aerostack.json  // Required — metadata and config
  - index.ts  // Required — your function logic
  - schema.ts  // Optional — Drizzle tables
  - README.md  // 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.

```json title="aerostack.json"
{
  "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.

```typescript title="index.ts"

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

1. **Authenticate**

   ```bash
   aerostack login
   # Prompts: Enter your API key: ac_secret_xxxx
   ```

2. **Push to create a draft**

   ```bash
   aerostack functions push ./my-image-resizer/index.ts
   # Or from inside the function directory:
   aerostack functions push index.ts
   ```

   Each **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://app.aerostack.dev/functions/edit/<id>
   ```

3. **Publish the draft**

   Use the ID from the push output (or from **My Functions** in the Admin):

   ```bash
   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`.

4. **Optional: Finalize in the Admin**

   After pushing, your function is a **Draft**. To add a full README and tags before going live:

   1. Go to the **Admin URL** from the push output (or [Admin](https://app.aerostack.dev) → **My Functions**)
   2. Open the draft and click **Edit**
   3. Add README, screenshots, and usage examples
   4. Click **Publish** — it goes live immediately

---

## First-time vs updating

- **First time**: `aerostack functions push [file]` creates a new draft. Then run `aerostack functions publish <id>` or publish from the Admin.
- **Updating an existing function**: Each `aerostack functions push` **creates a new draft** (it does not update the same function by slug). To update an existing function:
  - **Admin**: Open the function in [Admin](https://app.aerostack.dev) → My Functions → Edit, change code/metadata, Save. Optionally publish again if you want a new version snapshot.
  - **CI**: Use the [GitHub Actions](/hub/publish-github) or [GitLab CI](/hub/publish-gitlab) 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/aerostack/example-functions
cd example-functions/rate-limiter
aerostack publish .
```
