# Write a work

A JavaScript handler, a small manifest, and an immutable version each time you deploy.

Source: https://littleworks.app/docs/works

A work has two parts: source code and a manifest describing its input, entry point, permissions, and data scope. The examples below illustrate the interface; deploy them only as part of an authorized task.

## Write the handler

Export one default async function. Littleworks supplies `input`, `db`, `shopify`, `context`, and `log`. Return a JSON-compatible value or `Response.json(body, { status })`.

### notes.save.js

```javascript
export default async ({ input, db }) => {
  const notes = db.collection("notes");

  const note = await notes.create({
    message: input.message,
    status: "new",
  });

  return { id: note.id };
};
```

This example stores a note and returns its ID. It has no public endpoint and does not use Shopify. The manifest validates the message before the handler runs.

## Describe the work

### Manifest

```json
{
  "name": "notes.save",
  "title": "Save a note",
  "feature": "notes",
  "environment": "preview",
  "trigger": {
    "type": "callable"
  },
  "permissions": {
    "shopify": false
  },
  "inputSchema": {
    "type": "object",
    "properties": {
      "message": {
        "type": "string",
        "minLength": 1,
        "maxLength": 1000
      }
    },
    "required": [
      "message"
    ],
    "additionalProperties": false
  }
}
```

| Field | Purpose |
| --- | --- |
| `name` | Stable work identifier, such as notes.save. Lowercase letter first, then lowercase letters, digits, dots, underscores, or hyphens; up to 80 characters. |
| `title` | Merchant-facing name, up to 100 characters. Optional description is limited to 400 characters. |
| `feature` | Data namespace shared by related works. Keep it unchanged when redeploying an existing work. |
| `environment` | `preview` or `production`. Shopify admin calls production Live. |
| `trigger` | Private `callable`, or an explicit HTTP GET or POST entry point. |
| `permissions.shopify` | Whether this work can use the Shopify GraphQL client. |
| `inputSchema` | JSON Schema checked before execution. Declare required properties and reject unexpected fields where appropriate. |
| `runtime` | Optional. The current supported profile is `portable-js-v1`. |

## Deploy and test

Call `deploy_function` with `{ manifest, source }`. Pass the full manifest object and handler source string. The deployment saves an immutable version and activates it in that environment.

### invoke_function arguments

```json
{
  "name": "notes.save",
  "environment": "preview",
  "input": {
    "message": "A test note"
  },
  "idempotencyKey": "notes-preview-test-001"
}
```

Choose a new idempotency key for each intentional invocation. Reusing a key prevents replay with a conflict response; it does not return the original result. Use `read_collection` to inspect the note and `list_runs` to find the execution.

## Move to Live

Deploy the tested source with `environment: "production"` when the merchant’s instructions authorize it. Preview records are not copied into Live. Redeploying a paused work leaves it paused; use `set_function_state` to resume it.

> **Preview still uses the real Shopify store**
> Only Littleworks data is separated by environment. Shopify requests in Preview use the same installed store connection and can have real effects if the approved scopes allow them.

## Keep the source self-contained

MCP accepts JavaScript ES modules without imports or TypeScript syntax. It does not install dependencies or run a hosted build. The local development CLI can bundle compatible source before deployment. Packages cannot bypass the runtime’s network or capability restrictions.

The context contains `storeId`, `shop`, `environment`, `runId`, and `mode`. Credentials and provider database bindings are never passed to your code. See [Limits and security](https://littleworks.app/docs/limits).
