---
title: "handler.js reference"
description: "An example of what the handler.js file should look like."
---

**This page is intended for developers who want to create custom agent skills for AnythingLLM.**

## Rules & Guidelines

<ul className="nx-list-disc nx-list-inside nx-flex nx-flex-col nx-gap-2 nx-mt-2">
  <li>
    The `handler.js` file must export a `runtime` object with a `handler`
    function.
  </li>
  <li>
    The `handler` function must accept a single argument which is an object
    containing the parameters defined in the `plugin.json` `entrypoint`
    property, if any.
  </li>
  <li>
    The `handler` function must return a string value, anything else may break
    the agent invocation or loop indefinitely.
  </li>
  <li>
    You must use `require` to import any modules you need from the NodeJS
    standard library or any modules you have bundled with your custom agent
    skill.
  </li>
  <li>
    You must use `await` when making any calls to external APIs or services.
  </li>
  <li>
    You must wrap your entire custom agent skill in a `try`/`catch` block and
    return any error messages to the agent at invocation time.
  </li>
</ul>

## Available runtime properties and methods

### `this.runtimeArgs`

The `this.runtimeArgs` object contains the arguments that were passed to the `setup_args` from the `plugin.json` file.

You can access the value of a specific argument by using the `propertyName` as the key.

```js
// plugin.json excerpt
// "setup_args": {
//     "OPEN_METEO_API_KEY": {
//       "type": "string",
//       "required": false,
//       "input": {
//         "type": "text",
//         "default": "YOUR_OPEN_METEO_API_KEY",
//         "placeholder": "sk-1234567890",
//         "hint": "The API key for the open-meteo API"
//       },
//       "value": "sk-key-for-service",
//     }
//   },

this.runtimeArgs["OPEN_METEO_API_KEY"]; // 'sk-key-for-service'
```

### `this.introspect`

The `this.introspect` function is used to log "thoughts" or "observations" to the user interface while the agent is running.

```js
this.introspect("Hello, world!"); // must be a string - will be shown to user
```

### `this.logger`

The `this.logger` function is used to log messages to the console. This is useful for debugging your custom agent skill via logs.

```js
this.logger("Hello, world!"); // must be a string - will be printed to console while the agent is running
```

### `this.config`

The `this.config` object contains the configuration for your custom agent skill. Useful for when you need to know the name of your custom agent skill or the version or for logs.

```js
this.config.name; // 'Get Weather'
this.config.hubId; // 'open-meteo-weather-api'
this.config.version; // '1.0.0'
```

### `this.requestToolApproval`

The `this.requestToolApproval` method pauses the agent and asks the user to approve a potentially destructive action **before** your skill performs it. It shows the same Approve/Reject card that AnythingLLM's built-in tools use (for example, the Gmail send/reply tools), and resolves once the user responds.

Use this whenever your skill is about to do something irreversible or high-impact — deleting records, sending messages, making purchases, writing to external systems, etc.

```js
const approval = await this.requestToolApproval({
  payload: { recordId }, // optional: arbitrary data shown/recorded alongside the request
  description: `Permanently delete record ${recordId}? This cannot be undone.`,
});

if (!approval.approved) return approval.message; // user rejected - stop and report back
// ...proceed with the destructive action
```

It returns a `{ approved, message }` object:

<ul className="nx-list-disc nx-list-inside nx-flex nx-flex-col nx-gap-2 nx-mt-2">
  <li>
    `approved` (`boolean`) — `true` if the user approved (or if approval is not
    required in the current context), `false` if they rejected.
  </li>
  <li>
    `message` (`string`) — a human-readable result message. On rejection, return
    this string from your handler so the agent and user know the action was
    declined.
  </li>
</ul>

Both arguments are optional — `payload` defaults to `{}` and `description` defaults to `null` — but you should always pass a clear `description` so the user understands exactly what they are approving.

A few behaviors worth knowing:

<ul className="nx-list-disc nx-list-inside nx-flex nx-flex-col nx-gap-2 nx-mt-2">
  <li>
    The approval is keyed to **your** skill automatically (by its `hubId`). You
    cannot pass a `skillName` to impersonate another tool, and the "Always
    allow" whitelist a user grants applies only to your skill.
  </li>
  <li>
    In non-interactive contexts where there is no user to approve (for example,
    a scheduled agent run), the method resolves as approved
    (`{ approved: true, message: "Approval not required in this context." }`) so
    your skill still runs.
  </li>
  <li>
    The user has 120 seconds to respond. If they do not respond in time, the
    request is treated as rejected.
  </li>
</ul>

# Example `handler.js`

Objective: Get the weather for a given location latitude and longitude using the open-meteo API.

```js
// handler.js
// NOT RECOMMENDED: We're using an external module here for demonstration purposes
// this would be a module we bundled with our custom agent skill and would be located in the same folder as our handler.js file
// Do not require modules outside of the plugin folder. It is recommended to use require within a function scope instead of the global scope.
// const _ExternalApiCaller = require('./external-api-caller.js');

module.exports.runtime = {
  handler: async function ({ latitude, longitude }) {
    const callerId = `${this.config.name}-v${this.config.version}`;
    try {
      this.introspect(
        `${callerId} called with lat:${latitude} long:${longitude}...`
      );
      const response = await fetch(
        `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current_weather=true&hourly=temperature_2m,relativehumidity_2m,windspeed_10m`
      );
      const data = await response.json();
      const averageTemperature = this._getAverage(data, "temperature_2m");
      const averageHumidity = this._getAverage(data, "relativehumidity_2m");
      const averageWindSpeed = this._getAverage(data, "windspeed_10m");
      return JSON.stringify({
        averageTemperature,
        averageHumidity,
        averageWindSpeed,
      });
    } catch (e) {
      this.introspect(
        `${callerId} failed to invoke with lat:${latitude} long:${longitude}. Reason: ${e.message}`
      );
      this.logger(
        `${callerId} failed to invoke with lat:${latitude} long:${longitude}`,
        e.message
      );
      return `The tool failed to run for some reason. Here is all we know ${e.message}`;
    }
  },
  // Helper function to get the average of an array of numbers!
  _getAverage(data, property) {
    return (
      data.hourly[property].reduce((a, b) => a + b, 0) /
      data.hourly[property].length
    );
  },

  // Recommended: Use this method to call external APIs or services
  // by requiring the module in the function scope and only if the code execution reaches that line
  // this is to prevent any unforseen issues with the global scope and module loading/unloading.
  // This file should be placed in the same folder as your handler.js file.
  _doExternalApiCall(myProp) {
    const _ScopedExternalCaller = require("./external-api-caller.js");
    return _ScopedExternalCaller.doSomething(myProp);
  },
};
```
