@forestadmin/agent (Node.js/TypeScript).
Agent Class
createAgent(options)
Create and configure a Forest agent.Required
Connection and routing
Changing
prefix changes the URL Forest calls, so update the environment’s URL in Forest to match. Two agents on one host need different prefixes — see Running two agents at once.Schema and typings
Logging and errors
Permissions and caching
HTTP and payloads
Integrations
Example:
agent.addDataSource(factory, options?)
Add a datasource to the agent.
Example:
agent.customizeCollection(name, callback)
Customize a specific collection with the provided callback.agent.addChart(name, definition)
Create a datasource-level API chart.agent.removeCollection(…names)
Remove collections from the exported schema (they remain usable within the agent).agent.use(plugin, options?)
Load a plugin across all collections.agent.start()
Start the agent and connect to Forest servers.agent.stop()
Stop the agent and close all connections.agent.restart()
Reconstruct routing and remount routes at runtime. Called when customizations are refreshed externally (e.g. in cloud environments).agent.addGateway(options?)
Serve the Forest Gateway inside the agent process: one mount exposing the MCP service (mcp), the API service (api, the “Gateway API”), or both. Available from @forestadmin/agent 1.109.0. The API service also requires the @forestadmin/agent-bff package, installed at the exact version the agent declares; MCP alone installs nothing extra.
api options:
The secrets, the Forest URLs and the logger are inherited from the agent and cannot be overridden. These options are the only configuration channel: embedded, the API never reads the environment, so
FOREST_GATEWAY_API_RATE_LIMIT_MAX_REQUESTS, FOREST_GATEWAY_API_RATE_LIMIT_WINDOW_MS, FOREST_GATEWAY_API_PUBLIC_URL and PORT are ignored silently and their defaults stand. The feature switches default to off, so what you leave out stays off, and GET {basePath}/api/health reports which surfaces are on under configured.
Routes: with an empty basePath, POST /mcp, /mcp/uploads, /oauth/authorize · /oauth/token (?service=api dispatches to the API service), /api/agent/v1/*, GET /api/health, and GET /api/docs when api.openapiEnabled is set; the two /.well-known discovery documents sit at the origin root. The full route table and the ingress rules live on the Gateway page.
addGateway() can be called once, before start(): a second call throws addGateway can only be called once, so merge the options you need into a single call rather than stacking calls. It must enable at least one service (addGateway({ mcp: true }), addGateway({ api: {} }), or both), checked when the agent starts; an argument-less addGateway() is a TypeError on the spot. It cannot be combined with the deprecated mountAiMcpServer() or addBff() aliases: whichever is called second throws, and the agent logs the reason at Error level first. Nesting a top-level option under a service (addGateway({ mcp: { basePath } })) throws too, with the correct placement named.agent.mountAiMcpServer(options?)
Deprecated. Use
addGateway({ mcp }) instead. mountAiMcpServer() keeps its current paths and semantics (the host root, or its own basePath), and the agent logs a deprecation warning at startup. It cannot be combined with addGateway(). See The deprecated MCP alias.
Available tool names:
'describeCollection', 'list', 'listRelated', 'create', 'update', 'delete', 'associate', 'dissociate', 'getActionForm', 'executeAction', 'requestActionFileUpload', 'listWorkflows', 'triggerWorkflow', 'getWorkflowRun'
Example:
Scoping under a prefix:
basePath: '/ai', the OAuth and protocol routes move under the prefix. The .well-known discovery documents stay at the host root (as required by OAuth discovery, RFC 8414/9728) but are served at prefix-suffixed paths, so they no longer collide with your application’s own root .well-known routes:
MCP clients discover these endpoints automatically from the metadata, so no client-side configuration is needed.
The prefix applies to all routes, including the protocol endpoint — so
basePath: '/mcp' yields /mcp/mcp. Use a distinct prefix such as /ai to avoid the repetition.Because the
.well-known discovery documents must stay at the origin root, basePath requires the agent to be served at the domain root. If your agent’s URL already includes a path (e.g. https://host/api), setting basePath throws at startup. And root /.well-known/* requests must still reach the agent — a reverse proxy that forwards only /<prefix>/* will break discovery.FOREST_MCP_ENABLED_TOOLS (comma-separated tool names), FOREST_GATEWAY_ALLOWED_OAUTH_CLIENTS (comma-separated domains), FOREST_MCP_ACCESS_TOKEN_TTL_SECONDS and FOREST_MCP_REFRESH_TOKEN_TTL_SECONDS (seconds), and PORT (default: 3931). A mounted server does not read these variables — pass the options to mountAiMcpServer instead.
Standalone Gateway environment variables
When the agent cannot host the Gateway, the standalone Gateway serves the same routes from its own process, configured through environment variables. Their reference lives on its page.agent.updateTypesOnFileSystem(typingsPath, typingsMaxDepth)
Update the TypeScript typings file generated from your datasources.agent.generateSchemaOnly()
Build the schema (.forestadmin-schema.json) and the TypeScript typings and write them to disk without starting the agent or sending the schema to Forest. Useful for generating the schema at build time in a CI/CD pipeline, see Generate the schema at build time. Available since @forestadmin/agent 1.83.0.
schemaPath, typingsPath and typingsMaxDepth from the options you passed to createAgent. It always writes the schema to schemaPath, and also writes the TypeScript typings to typingsPath in the same pass whenever that option is set. Unlike agent.start(), it always rebuilds the schema, even when isProduction is true, and it never contacts Forest, with one exception: if you enable experimental no-code customizations, it still fetches their configuration from the Forest API, so connectivity is required in that case.
Despite the name,
Only means it only generates the files without starting the agent or sending the schema to Forest, not “the schema only”. If you want to regenerate only the typings, use agent.updateTypesOnFileSystem() instead.This does not close your data source connections. In a one-shot script, close your data source (the SQL/Sequelize/Mongo client you passed in) or call
process.exit() once it resolves, otherwise an open connection pool can keep the process alive.Collection Customizer
Methods available when customizing a collection throughagent.customizeCollection().
Actions
collection.addAction(name, definition)
Add an action to the collection.
Execute Function:
context.collection- Collection instancecontext.filter- Filter for selected recordscontext.caller- User who triggered the actioncontext.formValues- Form values submitted
context.getRecords(fields)- Get multiple records (Bulk/Single scope)context.getRecordIds()- Get IDs of selected recordscontext.getCompositeRecordIds()- Get composite IDs of selected recordscontext.hasFieldChanged(fieldName)- Check if form field changedcontext.getRecord(fields)- Get single record (Single scope only)context.getRecordId()- Get single record ID (Single scope only)context.getCompositeRecordId()- Get composite ID (Single scope only)context.getField(fieldName)- Get single field value (Single scope only)
resultBuilder.success(message?, options?)- Success responseoptions.html- Custom HTML to displayoptions.invalidated- Array of collection names to refresh
resultBuilder.error(message?, options?)- Error responseoptions.html- Custom HTML to display
resultBuilder.webhook(url, method, headers, body)- Trigger webhookresultBuilder.file(stream, filename, mimeType)- Return file downloadresultBuilder.redirectTo(path)- Redirect to URLresultBuilder.setHeader(name, value)- Add HTTP header to response
Fields
collection.addField(name, definition)
Add a computed field to the collection.
Column Types:
'String'- Text'Number'- Numeric value'Boolean'- True/false'Date'- Date with time'Dateonly'- Date without time'Time'- Time only'Enum'- Enumeration'Json'- JSON object'Uuid'- UUID'Point'- Geographic point'File'- File reference
collection.importField(name, options)
Import a field from a related collection.collection.renameField(currentName, newName)
Rename a field in the exported schema.collection.removeField(…names)
Remove fields from the exported schema (they remain usable within the agent).collection.addFieldValidation(name, operator, value?)
Add a validation rule to a field.'Present', 'LongerThan', 'ShorterThan', 'Contains', 'Like', 'Match', 'GreaterThan', 'LessThan', 'Before', 'After'
Example:
collection.setFieldNullable(name)
Mark a field as optional (nullable).collection.replaceFieldWriting(name, definition)
Replace the write behavior of a field.collection.replaceFieldBinaryMode(name, mode)
Choose how binary data should be transported to the GUI.'datauri'- Best for file uploads, uses FilePicker widget'hex'- Best for short binary data like UUIDs
Relationships
collection.addManyToOneRelation(name, foreignCollection, options)
Add a many-to-one relationship.collection.addOneToManyRelation(name, foreignCollection, options)
Add a one-to-many relationship.collection.addOneToOneRelation(name, foreignCollection, options)
Add a one-to-one relationship.collection.addManyToManyRelation(name, foreignCollection, throughCollection, options)
Add a many-to-many relationship.collection.addExternalRelation(name, definition)
Add a virtual collection into the related data of a record.Segments
collection.addSegment(name, definition)
Add a segment (saved filter) to the collection.Hooks
collection.addHook(position, type, handler)
Add a hook to execute code before or after operations.'List', 'Create', 'Update', 'Delete', 'Aggregate'
Example - Before Hook:
Search
collection.replaceSearch(definition)
Replace the default search behavior, with a field selection or a handler.read permission and extended search keeps working. Requires @forestadmin/datasource-customizer 1.71.3.
On a collection whose datasource searches natively — one calling enableSearch(), which no Forest-maintained datasource does — a field selection does not narrow that native search: it replaces it with the agent’s own per-column search restricted to the selection, so matching semantics change with it.
Paths cross relations with a colon, at any depth.
ManyToOne, OneToOne and OneToMany segments resolve; a ManyToMany segment does not, and a path crossing one is dropped from the selection without an error. extended is absent on purpose: the caller owns that flag, and the agent forwards it from the request.
(searchString, extended, context) and returns a condition tree. Use it when the filter depends on the search string, an external service, or anything a field list cannot express.
collection.disableSearch()
Disable search functionality on the collection.Sorting
collection.emulateFieldSorting(name)
Enable in-memory sorting on a field.collection.replaceFieldSorting(name, equivalentSort)
Replace sorting implementation for a field.collection.disableFieldSorting(name)
Disable sorting on a specific field.Filtering
collection.disableFieldFiltering(name)
Disable filtering for end users on a column, or on a many-to-one or one-to-one relationship. Needs@forestadmin/agent 1.107.0 or later. See Disabling filtering.
collection.emulateFieldFiltering(name)
Enable in-memory filtering on all operators for a field.collection.emulateFieldOperator(name, operator)
Enable in-memory filtering for a specific operator on a field.collection.replaceFieldOperator(name, operator, replacer)
Replace the implementation of a filter operator.Charts
collection.addChart(name, definition)
Add a chart to the collection.Collection Overrides
collection.overrideCreate(handler)
Replace the default create operation.collection.overrideUpdate(handler)
Replace the default update operation.collection.overrideDelete(handler)
Replace the default delete operation.Other Methods
collection.disableCount()
Disable count in list view pagination for improved performance.collection.use(plugin, options?)
Load a plugin on a specific collection.Chart Result Builders
When creating charts withcollection.addChart() or agent.addChart(), the result builder provides methods to format chart data.
resultBuilder.value(value, previousValue?)
Create a Value chart (single number).resultBuilder.distribution(obj)
Create a Distribution/Pie chart.resultBuilder.timeBased(timeRange, values)
Create a Time-based/Line chart.'Day', 'Week', 'Month', 'Quarter', 'Year'
resultBuilder.multipleTimeBased(timeRange, dates, lines)
Create a Multi-line Time-based chart.resultBuilder.percentage(value)
Create a Percentage chart.resultBuilder.objective(value, objective)
Create an Objective chart (progress toward goal).resultBuilder.leaderboard(obj)
Create a Leaderboard chart (sorted distribution).resultBuilder.smart(data)
Create a Smart chart (custom format).Form Field Types
Action forms support various field types with different widgets.Basic Field Types
'String'- Text input'Number'- Numeric input'Boolean'- Checkbox'Date'- Date picker'Dateonly'- Date without time'Time'- Time picker'Enum'- Single selection'EnumList'- Multiple selection'File'- File upload'FileList'- Multiple file upload'Json'- JSON editor'Collection'- Record picker'NumberList'- Array of numbers'StringList'- Array of strings
Enum Fields
Collection Fields
Pick a record from another collection:Dropdown Widget
Dynamic Dropdown
Conditional Fields
Show/hide fields based on other field values:Dynamic Field Values
Read-Only Fields
ConditionTree Utilities
Build complex filter conditions programmatically.ConditionTreeLeaf
Simple condition on a single field:ConditionTreeBranch
Combine multiple conditions with AND/OR:ConditionTree Factory
Available Operators
Comparison:'Equal','NotEqual''GreaterThan','LessThan''In','NotIn''Present','Blank'
'Contains','NotContains''StartsWith','EndsWith''Like','ILike'(case-insensitive)
'Before','After''Today','Yesterday','PreviousWeek','PreviousMonth','PreviousQuarter','PreviousYear''Past','Future'
'IncludesAll','IncludesNone'
Related Types
Caller
User information available in all contexts:Filter
Projection
Specify which fields to retrieve:Plugin: Flattener
@forestadmin/plugin-flattener flattens nested data structures (composite columns, relations, JSON columns) into individual top-level fields.
flattenColumn(dataSource, collection, options)
Decompose a column with a composite type into individual fields.flattenRelation(dataSource, collection, options)
Import fields from a relation directly into the collection.flattenJsonColumn(dataSource, collection, options)
Expand a JSON column into individual typed fields.Package: Forest Cloud
@forestadmin/forest-cloud is a dev-only package that provides CLI tooling for cloud-hosted customization projects.
Installation
CLI Commands
bootstrap
Initialize a cloud customization project. Authenticates with Forest, creates acloud-customizer directory, configures credentials, and generates type definitions.
update-typings
Regenerate TypeScript type definitions based on your current database structure and customization code.login
Refresh your Forest authentication token.Exported Types
Package: Agent Testing
@forestadmin/agent-testing provides utilities to test agent customizations locally without connecting to Forest servers.