Back to Abp

Custom Endpoints

docs/en/low-code/custom-endpoints.md

10.5.08.6 KB
Original Source
json
//[doc-seo]
{
    "Description": "Define JavaScript-backed custom REST endpoints in the ABP Low-Code System without writing custom .NET controllers."
}

Custom Endpoints

Preview: Custom endpoint descriptors and scripting helpers are part of the preview Low-Code System. Names, validation rules, and runtime behavior may change before general availability.

Use generated page CRUD APIs for normal dynamic entity pages. Custom endpoints are an advanced option for exposing small model-owned REST APIs that do not map to standard list, get, create, update, delete, export, file, or attachment operations.

Custom endpoints are defined in JSON descriptor files or through the Low-Code Designer. Each endpoint executes server-side JavaScript and is registered as an ASP.NET Core route.

Defining Endpoints

json
{
  "endpoints": [
    {
      "name": "GetCampaignStats",
      "route": "/api/custom/campaigns/stats",
      "method": "GET",
      "description": "Get campaign statistics",
      "requireAuthentication": true,
      "requiredPermissions": ["Acme.Campaigns"],
      "javascript": "var count = await db.count('Acme.Campaigns.Campaign');\nreturn ok({ totalCampaigns: count });"
    }
  ]
}

Endpoint Descriptor

FieldTypeDefaultDescription
namestringRequiredUnique endpoint identifier
routestringRequiredURL route pattern; must start with / and can contain {parameters}
methodstringGETGET, POST, PUT, DELETE, or PATCH
javascriptstringRequiredJavaScript handler code
descriptionstringnullOptional designer/documentation text
requireAuthenticationbooltrueWhether the caller must be authenticated
requiredPermissionsstring[]nullPermission names required to call the endpoint

Permission checks require an authorized user even when requireAuthentication is set to false. Keep endpoints authenticated by default and use requireAuthentication: false only for intentionally public APIs without requiredPermissions.

Route and Request Data

Use {paramName} syntax for route parameters. Endpoint scripts can read request data through globals:

VariableDescription
requestFull request object
routeRoute values, for example route.id
paramsAlias for route
queryQuery string values, for example query.q
bodyParsed request body
headersSelected safe request headers
json
{
  "name": "GetCampaignById",
  "route": "/api/custom/campaigns/{id}",
  "method": "GET",
  "javascript": "var campaign = await db.get('Acme.Campaigns.Campaign', route.id);\nif (!campaign) { return notFound('Campaign not found'); }\nreturn ok({ id: campaign.Id, name: campaign.Name });"
}

For non-GET requests, body is parsed when a body is present and remains subject to the configured request size limit. The headers object intentionally contains only selected request headers such as Content-Type, Accept, Accept-Language, and X-Requested-With.

Response Helpers

Endpoint scripts can return plain data, an endpoint response object, or one of the response helpers.

FunctionHTTP statusResponse kind
ok(data, headers?)200JSON
okText(text, contentType?)200Text
okBinary(base64Data, contentType?)200Binary from base64
created(data, headers?)201JSON
noContent()204Empty
badRequest(message)400JSON error
unauthorized(message)401JSON error
forbidden(message)403JSON error
notFound(message)404JSON error
error(message)500JSON error

For custom status codes or response metadata, return an object with response fields:

javascript
return {
    statusCode: 202,
    kind: 'json',
    data: { accepted: true },
    headers: { 'x-trace-id': guid() }
};

Response kinds are:

KindDescription
jsonJSON serialization, default
textPlain text
binaryBase64Base64-encoded binary payload

Script Services

Custom endpoint scripts use the same common Scripting API services as other low-code scripts:

ServiceExample
dbQuery or mutate dynamic entities
user / currentUserRead current user information
tenant / currentTenantRead tenant information
auth / authorizationCheck permissions
settings, features, configRead allowed settings, features, and app configuration
httpCall allowed outbound HTTP services
emailSend or queue email
events, jobsPublish distributed events or enqueue background jobs
files, images, attachmentsWork with low-code files and record attachments
log, logWarning, logErrorWrite logs

Examples

Statistics

json
{
  "name": "GetCampaignStats",
  "route": "/api/custom/campaigns/stats",
  "method": "GET",
  "requireAuthentication": true,
  "javascript": "var campaignQuery = await db.query('Acme.Campaigns.Campaign');\nvar total = await campaignQuery.count();\nvar active = await campaignQuery.where(c => c.Status === 1).count();\nreturn ok({ total: total, active: active });"
}

Search with Query Parameters

json
{
  "name": "SearchCampaigns",
  "route": "/api/custom/campaigns/search",
  "method": "GET",
  "requireAuthentication": true,
  "javascript": "var q = query.q || '';\nvar campaignQuery = await db.query('Acme.Campaigns.Campaign');\nvar rows = await campaignQuery\n  .where(c => c.Name.toLowerCase().includes(q.toLowerCase()))\n  .take(10)\n  .toList();\nreturn ok(rows.map(c => ({ id: c.Id, name: c.Name })));"
}

Create with Validation

json
{
  "name": "CreateCampaignDraft",
  "route": "/api/custom/campaigns/draft",
  "method": "POST",
  "requireAuthentication": true,
  "requiredPermissions": ["Acme.Campaigns.Create"],
  "javascript": "if (!body.name) { return badRequest('Name is required.'); }\nvar record = await db.insert('Acme.Campaigns.Campaign', { Name: body.name, Status: 0 });\nreturn created({ id: record.Id, name: record.Name });"
}

Testing Endpoint Scripts

The Low-Code Designer endpoint editor includes Test JavaScript. Use it to run the current editor content without saving it.

The dry-run request editor lets you provide:

  • HTTP method
  • Request path
  • Route values
  • Query values
  • Headers
  • Body JSON
  • Outbound HTTP mocks

Dry-run execution evaluates the endpoint descriptor, request context, script, authentication metadata, and required permissions against the current user. It returns the same response shape that a real endpoint execution would return.

Side effects are captured instead of being sent to external systems:

OperationDry-run behavior
Database writesRolled back
Email send or queueCaptured under Captured Side Effects
Event publishCaptured under Captured Side Effects
Background job enqueueCaptured under Captured Side Effects
Outbound HTTPMatched against HTTP mocks
File, image, and attachment operationsCaptured without persisting files

If a script calls the http helper and no mock matches the method and URL, the result contains a mock miss instead of sending a real HTTP request.

Response Policy

Dynamic endpoint responses are validated by LowCode:Scripting:EndpointResponse.

json
{
  "LowCode": {
    "Scripting": {
      "EndpointResponse": {
        "MaxBodyBytes": 1048576,
        "AllowedContentTypes": [
          "application/json",
          "text/plain",
          "application/octet-stream"
        ],
        "BlockedHeaders": [
          "Set-Cookie",
          "Content-Length",
          "Content-Type"
        ]
      }
    }
  }
}

Default blocked headers also include hop-by-hop headers such as Connection, Transfer-Encoding, and Upgrade.

Content-Type is blocked as a custom response header. Choose the response kind or contentType field instead of setting a raw Content-Type header from script.

Security Notes

  • Prefer authenticated endpoints with explicit requiredPermissions.
  • Treat endpoints with requireAuthentication: false and no requiredPermissions as public API surface.
  • Keep endpoint scripts small and focused.
  • Validate route, query, and body input before using it.
  • Use take() for list queries.
  • Use the configured HTTP, email, file, and response limits for untrusted integrations.

See Also