# Running a function

> Runs one of your app's own functions from the command line — to put the app in a known state, backfill a column, or kick off a job. It runs as a real user (or as nobody), so your security rules apply exactly as they do in the browser.

<!-- id: local-running-a-function · area: local · stability: stable · html: https://osysharp.com/reference/local/running-a-function/ -->

## Summary        {#summary}

Runs one function your app declares, with arguments, against the local platform
([Running a local platform](https://osysharp.com/reference/local/running-a-local-platform/)). It is the third thing you can do to an app from outside it: `osy compile` ships
its code, [`osy import`](https://osysharp.com/reference/local/importing-data/) loads its rows, and `osy run` makes it **do** something.

The run happens **as somebody**. With no `--as` it runs anonymous — the same thing a visitor with no session gets.
Name a user and it runs as that person, with their roles and row filters live.

## Signature      {#signature}

```console
osy run <function> [path] [--arg NAME=VALUE] [--args-json <json>] [--as <login>] [--password <pw>] [--json]
```

## Description    {#description}

### What you can run   {#what}

Any top-level function your app declares. Methods on a class need an instance, constructors are not verbs, tests
belong to `osy test`, and a workflow's entry points are driven by the workflow engine — each of those is refused by
name, telling you which it is. Misspell a function and the answer lists the ones you can run.

```console
osy run RecalculateTotals
osy run SendDigest --arg since=2026-08-01 --arg dryRun=true
```

### How do I pass arguments?      {#arguments}

`--arg NAME=VALUE` binds one parameter and repeats. `true`, `false` and numbers are read as such; everything else is
text, including identifiers and dates, which are converted to the parameter's declared type on arrival. Only the first
`=` splits, so `--arg filter=status=open` passes `status=open`.

When a parameter takes a class, pass the whole argument object as JSON:

```console
osy run PlaceOrder --args-json '{"order":{"reference":"A-1","total":42}}'
```

### Passing an entity      {#entity-arguments}

A parameter typed as one of your entities takes **a row**, and what you pass decides which row:

| you pass | it binds |
|---|---|
| an existing row's id — `--arg customer=8050d9b7-…`, or `{"customer": "8050d9b7-…"}`, or `{"customer": {"id": "8050d9b7-…"}}` | **that row**, loaded as the principal the run happens as. An id that names no row — or a row that principal may not read — is refused as *not found*, naming the entity and the id. Nothing is constructed. |
| an object of fields with **no** id — `{"customer": {"name": "Acme", "code": "acme"}}` | **a new row** in the run's unit of work, exactly as `new Customer { … }` in the body would be; it is written when the function commits. |
| an id **and** fields | refused. An id binds a row as it is; to change its fields, do so in the function. |

```osy title="a function that returns a row, and one that takes one" test app=local-running-a-function
entity Customer {
  [MaxLength(200)] string Name;
  [Unique, MaxLength(100)] string Code;
  security {
    allow read, create when IsAuthenticated || IsAnonymous;
  }
}

entity Invoice {
  [Required] Customer Customer;
  decimal Amount;
  security {
    allow read, create when IsAuthenticated || IsAnonymous;
  }
}

Customer AddCustomer(string name, string code) {
  var c = new Customer { Name = name, Code = code };
  UnitOfWork.Commit();
  return c;
}

Invoice RaiseInvoice(Customer customer, decimal amount) {
  var i = new Invoice { Customer = customer, Amount = amount };
  UnitOfWork.Commit();
  return i;
}
```

```console
osy run AddCustomer --arg name=Acme --arg code=acme --json         # answers the row, `id` included
osy run RaiseInvoice --arg customer=8050d9b7-… --arg amount=42      # binds THAT Customer
osy run RaiseInvoice --args-json '{"customer": {"id": "8050d9b7-…"}, "amount": 42}'   # the same
osy run RaiseInvoice --args-json '{"customer": {"name": "New Co", "code": "new"}, "amount": 42}'  # a new Customer, written with the Invoice
```

The id is the one `--json` gave you when the row was returned (below), or what [`osy query`](https://osysharp.com/reference/local/reading-your-data/)
shows.

### Who it runs as   {#principal}

This is the part worth reading twice, because it decides what the run is allowed to do.

- **No `--as`** — the function runs **anonymous**. If your app denies anonymous writes, the run is refused, and that
  refusal is correct: it is what a stranger hitting the same code would get.
- **`--as <login> --password <pw>`** — the function runs as that user. `<login>` is whatever your `app.Auth` binds as
  its login field, usually an email. Their roles apply and row filters bind to them, exactly as under
  [`runas`](https://osysharp.com/reference/testing/runas/) in a test. It is a real **login**: `--as` takes that user's own password, because naming a
  principal must never be enough to become one. Omit `--password` and you are prompted.

There is no switch that turns security off. A run no user could perform tells you nothing about whether your app
works, and a privileged job is served by naming a user who genuinely holds that authority — add one with
[Adding an account](https://osysharp.com/reference/local/adding-an-account/), roles and all, if the app has none yet.

```console
osy run ArchiveOldOrders --as ops@example.com --password 's3cret'
```

A login or password that does not check out refuses the run — as one answer, "invalid login or password", the same
thing your app's own login page says. It never quietly falls back to anonymous, because a run with less authority than
you asked for looks exactly like a successful one until it doesn't.

### What comes back   {#output}

A function that returns a value prints it. `--json` gives you the whole result — whether it succeeded, what it
returned, and which principal it ran as — for a script to read.

A returned **entity** is its row: `id` first, then every field you declared, then `createdAt` and `modifiedAt`. A
reference member (`Invoice.Customer`) is the referenced row's id. A returned list is one such object per row. The `id`
is the one value every row is guaranteed to have, and it is what the next call takes — you never need a function of
your own to learn it.

```json
{
  "success": true,
  "output": {
    "id": "8050d9b7-ab3b-46b1-9037-ee9631e52064",
    "name": "Acme",
    "code": "acme",
    "createdAt": "2026-09-05T21:45:26.8817050Z",
    "modifiedAt": "2026-09-05T21:45:26.8817050Z"
  },
  "error": null,
  "ranAs": "anonymous"
}
```

A function that **refused** (a validation error, a denial, a `throw` of your own) is reported with its own message and
a non-zero exit code. That is an answer about your app, not a failure to reach it.

### Against a deployed app   {#remote}

`osyrin app run <function>` is the same command against a platform you are logged in to, with the same `--as` rule —
which matters more there, not less.

## Examples       {#examples}

```console
osy run SeedCatalogue                                  # anonymous — fine if the app allows it
osy run SeedCatalogue --as ops@example.com --password 's3cret'   # as a real user, with their rules
osy run Backfill --arg batch=500 --arg dryRun=true --json
```

## See also       {#see-also}

[Importing data](https://osysharp.com/reference/local/importing-data/) — load the app's rows, the other half of putting it in a known state.

[Compiling your app](https://osysharp.com/reference/local/compiling-your-app/) — ship the code the function comes from.

[runas](https://osysharp.com/reference/testing/runas/) — the same principal idea inside a test.

[The inner loop](https://osysharp.com/reference/local/the-inner-loop/) — where this sits in the build-run-look loop.
