# Pre Hooks

> Run your code (TypeScript, Python, PHP, C#, Go, Rust, Java or Kotlin) before any API of API Maker - at the level of an instance, a database, a table, one API, a custom or system API, or a group - to check, change, narrow or answer a request, in the order API Maker runs them.

Source: https://docs.apimaker.dev/v4/docs/apis-all/hooks/preHook-api.html
Code: Python only. The same page with the code of another language: [TypeScript](https://docs.apimaker.dev/v4/docs/apis-all/hooks/preHook-api.typescript.md) · [PHP](https://docs.apimaker.dev/v4/docs/apis-all/hooks/preHook-api.php.md) · [C#](https://docs.apimaker.dev/v4/docs/apis-all/hooks/preHook-api.csharp.md) · [Go](https://docs.apimaker.dev/v4/docs/apis-all/hooks/preHook-api.go.md) · [Rust](https://docs.apimaker.dev/v4/docs/apis-all/hooks/preHook-api.rust.md) · [Java](https://docs.apimaker.dev/v4/docs/apis-all/hooks/preHook-api.java.md) · [Kotlin](https://docs.apimaker.dev/v4/docs/apis-all/hooks/preHook-api.kotlin.md)

A pre hook is your function that runs **before** an API, written in any of the [languages](https://docs.apimaker.dev/v4/docs/languages/index.python.md) of API Maker. It sees the request. It can change it, answer in place of the API, or refuse it. Attach it to an instance, a database, a table, one API, a custom API or a system API. Change it any time : nothing restarts.

> **In short**
>
> - A pre hook is your code that runs **before** an API.
> - It can change the request (add a filter, a limit), answer instead of the API, or refuse the call.
> - Attach it to an instance, a database, a table or one API : it runs for every API below.
> - Its most common job : keep each person to their own rows.

[![A pre hook of the database sales, in its editor](https://docs.apimaker.dev/assets/images/screens/ref-hook.webp)](https://docs.apimaker.dev/assets/images/screens/ref-hook.webp)

1. The hook changes the request before the API runs : here, at most 50 rows.

> Diagram : Hooks around an API : pre hooks from the instance down to the API, the API, then post hooks from the API up to the instance
>
> Order of the hooks around a database API : the pre hooks of the instance, then of the database, then of the table, then of the API run before it ; the API runs ; the post hooks run in the opposite order, API, table, database, instance. A pre hook which returns a value answers the caller and stops everything after it.

- Video: [API Hooks: Run Your Code Before and After Every API](https://apimaker.dev/videos/api-hooks) (7 min)

## Where hooks live

| Level | Runs for | Where |
|---|---|---|
| Instance | every API of every table of the instance | Instances page › the instance › hooks |
| Database | every API of every table of the database | the database › hooks |
| Table (collection) | every API of the table | the table › hooks |
| API | one API of one table, `SCHEMA_GET_ALL` for example | the API › hooks |
| Custom or system API | that API | its hooks tab |
| Group | the hooks above, only for API users of the groups named on the hook | `groupNames` on the hook |

- Pre hooks run from the widest level to the narrowest : instance, database, table, API, each level in the order of its list. Post hooks run the other way round. See [Post hooks](https://docs.apimaker.dev/v4/docs/apis-all/hooks/postHook-api.python.md).
- In API Testing, the **Pre Hooks** tab of an API of a table lists every pre hook which runs before it, in that order : the hooks of its instance, its database and its table (marked **Instance**, **Database**, **Table**), then its own. The ones of the other levels are changed in their categories of API Testing (**Instance Hooks**, **Database Hooks**, **Collection Hooks**).
- In Git, every hook is a [folder of its own](https://docs.apimaker.dev/v4/docs/Git/git.md#hooks), named by the hook.
- A hook with `groupNames` runs only when the API user of the call belongs to one of those groups. That is how one table gets a row scoping hook for the application users and none for the back office.
- **A hook is written in the language of its API.** The code of an API and all its hooks are [one language on one runtime](https://docs.apimaker.dev/v4/docs/languages/index.python.md#one-language) : the request is handed to one sandbox, which runs the hooks and the API one after the other. The form of a hook shows that language with a lock. For an API of a table, the hooks of its instance, its database, its table and the API itself are one language.

## The code

```python
from am import G


async def main(g: G):
    if not g.req.is_api_request_from_user:
        return  # calls made by your own code : leave them alone

    # g.req.query, g.req.body, g.req.params, g.req.headers : the request, changeable
    # g.req.auth.authAMUser, g.req.auth.authAMDB… : who calls
    # g.res.shared : values for the API and the post hooks of the same request
```

What the return value does :

| The hook | Effect |
|---|---|
| returns nothing | The next hook, then the API, run. |
| returns a value | That value is the answer of the request, in `data` : the remaining hooks and the API **do not run**. |
| sets `g.res.output` and returns nothing | The API runs ; its output replaces `g.res.output` unless a post hook changes it again. |
| throws | The request stops with the error. An exception gives `500` and its message. An error of API Maker gives its own `code` as the status when it is `400`, `401`, `403`, `404` or `500`, else `500` : `throw [{ code: 400, message: '…' }]` in TypeScript, `raise Errors('…', code=400)` in Python, `AmErrors` in PHP, C#, Java and Kotlin, `am.Fail(400, '…')` in Go, `am::fail(400, '…')` in Rust. |

- A hook in any language other than TypeScript runs in the sandbox of its language. A hook in TypeScript runs [inside API Maker](https://docs.apimaker.dev/v4/docs/languages/index.python.md#in-the-server) when it needs nothing of a sandbox, else in its sandbox of Node.js or Bun ; the answer is the same. (`runOnNativeProcess` runs a hook on the machine of the server.) Every hook runs within the sandbox timeout of the call.
- When the answer of an API comes from the [cache](https://docs.apimaker.dev/v4/docs/features/automatic-caching.md), neither pre nor post hooks run.
- Calls from your own code (`g.sys.db.getAll` in a custom API) skip the hooks by default. Pass `skipHookRunning: false` to run them.

## Narrow a request to the rows of the caller

A person must reach only their own rows. This is the hook the [APIs Security Report](https://docs.apimaker.dev/v4/docs/apis-security/api-security-report.md) installs with **Add a row scoping pre-hook**, here on `orders`, whose owner is in `customer_id` :

- every read, update and delete gets the owner added to its filter, with `$and`, so a caller can not widen it ;
- every write gets the owner stamped into its rows, so a caller can not hand a row to someone else ;
- aggregate and distinct can not be scoped this way, and any API the hook does not know is refused : it fails closed.

> Diagram : The row gate : the same call, without and with the row scoping pre hook
>
> Without a row scoping pre hook, Diya calling get all on orders gets every order of the table, hers and Kabir's. With the hook, API Maker adds her customer id to the filter with $and before the query runs, and she gets only her two orders.

**Collection level pre hook on orders**

```python
from am import Errors, G, Obj

# Row scoping pre-hook for orders, as the API Security Report writes it.
# Every request is limited to the rows whose "customer_id" equals the id of the person behind it
# (g.req.auth.authAMDB.customer_id). Edit freely : the report reads the code again on every scan.
OWNER_COLUMN = 'customer_id'
IDENTITY_FIELD = 'customer_id'

# Where each generated API keeps its filter. API Maker ANDs query.find onto the primary key for the *-by-id APIs.
QUERY_FIND_APIS = {
    'SCHEMA_GET_ALL', 'SCHEMA_GET_ALL_STREAM', 'SCHEMA_GET_BY_ID', 'SCHEMA_PUT_UPDATE_BY_ID',
    'SCHEMA_PUT_REPLACE_BY_ID', 'SCHEMA_DEL_DELETE_BY_ID', 'GEN_GET_ALL', 'GEN_GET_ALL_STREAM', 'GEN_GET_BY_ID',
    'GEN_PUT_UPDATE_BY_ID', 'GEN_PUT_REPLACE_BY_ID', 'GEN_DEL_DELETE_BY_ID',
}
BODY_FIND_APIS = {
    'SCHEMA_POST_QUERY', 'SCHEMA_POST_QUERY_STREAM', 'SCHEMA_POST_QUERY_DELETE', 'SCHEMA_UPDATE_MANY',
    'SCHEMA_POST_DISTINCT_QUERY', 'SCHEMA_ARRAY_OPERATIONS', 'SCHEMA_POST_COUNT', 'GEN_POST_QUERY',
    'GEN_POST_QUERY_STREAM', 'GEN_POST_QUERY_DELETE', 'GEN_UPDATE_MANY', 'GEN_POST_DISTINCT_QUERY',
    'GEN_ARRAY_OPERATIONS', 'GEN_POST_COUNT',
}
STAMP_OWNER_APIS = {
    'SCHEMA_POST_BULK_INSERT', 'SCHEMA_MASTER_SAVE', 'GEN_POST_BULK_INSERT', 'GEN_MASTER_SAVE',
}


async def main(g: G):
    # Custom APIs, schedulers and events calling g.sys.db run under their own rules, only real callers are scoped.
    if not g.req.is_api_request_from_user:
        return

    person = g.req.auth.authAMDB
    me = person.get(IDENTITY_FIELD) if isinstance(person, dict) else None
    if me is None or me == '':
        raise Errors('A person token is required for this API.', code=401)

    scope = {OWNER_COLUMN: me}
    # MERGE, never replace : $and keeps what the caller asked for and adds the owner on top, so a caller
    # can never widen the rows by sending its own owner filter.
    def merge(existing):
        return {'$and': [existing, scope]} if isinstance(existing, dict) and existing else dict(scope)

    api_id = g.req.req_info.apiInfo.id
    if api_id in QUERY_FIND_APIS:
        g.req.query['find'] = merge(g.req.query.get('find'))
        if isinstance(g.req.body, dict):
            g.req.body[OWNER_COLUMN] = me  # update-by-id and replace-by-id : the owner of a row can not be handed over
        return
    if api_id in BODY_FIND_APIS:
        if not isinstance(g.req.body, dict):
            g.req.body = Obj()
        g.req.body['find'] = merge(g.req.body.get('find'))
        if isinstance(g.req.body.get('updateData'), dict):
            g.req.body['updateData'][OWNER_COLUMN] = me  # update-many
        return
    if api_id in STAMP_OWNER_APIS:
        rows = g.req.body if isinstance(g.req.body, list) else [g.req.body]
        for row in rows:
            if isinstance(row, dict):
                row[OWNER_COLUMN] = me  # never trust the owner sent by the caller
        return

    # Fail closed : aggregate and distinct can not be scoped generically, so they are refused for row scoped callers.
    raise Errors(f'API {api_id} is not available for row scoped users.', code=403)
```

The report writes this hook for you, for the tables which need it, and reads it again on every scan. [Handle role based permissions](https://docs.apimaker.dev/v4/docs/authorization/handle-role-based-permissions.python.md) builds the same pattern for several tables at once, with a group which reads every row.

## Answer without the API

**A pre hook which answers from the cache**

```python
import json

from am import G


async def main(g: G):
    cached = await g.sys.cache.get_key('countries')
    if cached:
        return json.loads(cached)  # the API does not run
```

## Validate and change

```python
import time

from am import Errors, G


async def main(g: G):
    if not g.req.is_api_request_from_user:
        return
    if (g.req.body.price_cents or 0) < 0:
        raise Errors('A price can not be negative.', code=400)
    g.req.body.updated_by = (g.req.auth.authAMDB or {}).get('email')
    g.req.headers['x-am-response-case'] = 'camelCase'   # headers can change too
    g.res.shared.startedAt = int(time.time() * 1000)    # for the post hook of this request
```

## Utility classes in a hook

A hook calls your [utility classes](https://docs.apimaker.dev/v4/docs/utility-class/utility-class.python.md) like any other code of its language : a check shared by many hooks lives in one.

**The utility class Guards**

```python
from am import Errors


# The person of the request has the role, or the request is refused.
def require_role(g, role):
    roles = (g.req.auth.authAMDB or {}).get('roles') or []
    if role not in roles:
        raise Errors(f'The role {role} is needed.', code=403)
```

**A pre hook which uses it**

```python
from Guards import require_role


async def main(g):
    require_role(g, 'PURCHASE_INVOICE_VIEW')
```

- Press Ctrl+Space in the editor for the list of your [utility classes](https://docs.apimaker.dev/v4/docs/utility-class/utility-class.python.md).

## Testing a hook

- The API testing page runs an API with its hooks. While you edit a hook there, the **Test** button of the hook runs only that hook, so you see its effect alone.
- A level holds a list of hooks, each one active or not. Keep the old hook active until the new one is ready, then switch.
- The log profile records each hook run with the request ; the log explorer shows them next to the API call.

## Good to know

- Streams : pre hooks run before the stream starts. Post hooks do not run on a stream.
- A hook which calls the same API it is attached to creates a cycle : API Maker detects it and stops the request with an error.
- Hooks are files in Git, under the folder of their instance, database, table or API, and deploy with a Git pull.

- [Post hooks →](https://docs.apimaker.dev/v4/docs/apis-all/hooks/postHook-api.python.md)
- [The global object g](https://docs.apimaker.dev/v4/docs/pre-defined-terms/global-object-g.python.md)
