docs/en/low-code/custom-endpoints.md
//[doc-seo]
{
"Description": "Define custom REST API endpoints with JavaScript handlers in the ABP Low-Code System. Create dynamic APIs without writing C# controllers."
}
Custom Endpoints allow you to define REST API routes with server-side JavaScript handlers directly in model.json. Each endpoint is registered as an ASP.NET Core endpoint at startup and supports hot-reload when the model changes.
Add endpoints to the endpoints array in model.json:
{
"endpoints": [
{
"name": "GetProductStats",
"route": "/api/custom/products/stats",
"method": "GET",
"description": "Get product statistics",
"requireAuthentication": false,
"javascript": "var count = await db.count('LowCodeDemo.Products.Product');\nreturn ok({ totalProducts: count });"
}
]
}
| Field | Type | Default | Description |
|---|---|---|---|
name | string | Required | Unique endpoint name |
route | string | Required | URL route pattern (supports {parameters}) |
method | string | "GET" | HTTP method: GET, POST, PUT, DELETE |
javascript | string | Required | JavaScript handler code |
description | string | null | Description for documentation |
requireAuthentication | bool | true | Require authenticated user |
requiredPermissions | string[] | null | Required permission names |
Use {paramName} syntax in the route. Access values via the route object:
{
"name": "GetProductById",
"route": "/api/custom/products/{id}",
"method": "GET",
"javascript": "var product = await db.get('LowCodeDemo.Products.Product', route.id);\nif (!product) { return notFound('Product not found'); }\nreturn ok({ id: product.Id, name: product.Name, price: product.Price });"
}
Inside custom endpoint scripts, you have access to:
| Variable | Description |
|---|---|
request | Full request object |
route | Route parameter values (e.g., route.id) |
params | Alias for route parameters |
query | Query string parameters (e.g., query.q, query.page) |
body | Request body (for POST/PUT) |
headers | Request headers |
user | Current user (same as context.currentUser in Interceptors) |
email | Email sender (same as context.emailSender in Interceptors) |
| Function | HTTP Status | Description |
|---|---|---|
ok(data) | 200 | Success response with data |
created(data) | 201 | Created response with data |
noContent() | 204 | No content response |
badRequest(message) | 400 | Bad request response |
unauthorized(message) | 401 | Unauthorized response |
forbidden(message) | 403 | Forbidden response |
notFound(message) | 404 | Not found response |
error(message) | 500 | Internal server error response |
response(statusCode, data, error) | Custom | Custom status code response |
| Function | Description |
|---|---|
log(message) | Log an informational message |
logWarning(message) | Log a warning message |
logError(message) | Log an error message |
The full Scripting API (db object) is available for querying and mutating data.
{
"name": "GetProductStats",
"route": "/api/custom/products/stats",
"method": "GET",
"requireAuthentication": false,
"javascript": "var totalCount = await db.count('LowCodeDemo.Products.Product');\nvar avgPrice = totalCount > 0 ? await db.query('LowCodeDemo.Products.Product').average(p => p.Price) : 0;\nreturn ok({ totalProducts: totalCount, averagePrice: avgPrice });"
}
{
"name": "SearchCustomers",
"route": "/api/custom/customers/search",
"method": "GET",
"requireAuthentication": true,
"javascript": "var searchTerm = query.q || '';\nvar customers = await db.query('LowCodeDemo.Customers.Customer')\n .where(c => c.Name.toLowerCase().includes(searchTerm.toLowerCase()))\n .take(10)\n .toList();\nreturn ok(customers.map(c => ({ id: c.Id, name: c.Name, email: c.EmailAddress })));"
}
{
"name": "GetDashboardSummary",
"route": "/api/custom/dashboard",
"method": "GET",
"requireAuthentication": true,
"javascript": "var productCount = await db.count('LowCodeDemo.Products.Product');\nvar customerCount = await db.count('LowCodeDemo.Customers.Customer');\nvar orderCount = await db.count('LowCodeDemo.Orders.Order');\nreturn ok({ products: productCount, customers: customerCount, orders: orderCount, user: user.isAuthenticated ? user.userName : 'Anonymous' });"
}
| Setting | Behavior |
|---|---|
requireAuthentication: false | Endpoint is publicly accessible |
requireAuthentication: true | User must be authenticated |
requiredPermissions: ["MyApp.Products"] | User must have the specified permissions |