Advanced Portal Designer Documentation
Index

Data Action

Data actions provide RESTful API access to Business Central tables, enabling AJAX operations and external integrations without writing code.

Overview

A Data action:

  • Exposes table data via HTTP methods (GET, POST, PATCH, DELETE)
  • Returns JSON-formatted data
  • Supports filtering and field selection
  • Works with DataItems for multiple tables

When to Use Data Actions

Use Data actions for:

  • AJAX calls from JavaScript
  • Mobile app backends
  • Third-party integrations
  • Single-page applications (SPA)
  • Real-time data updates

Creating a Data Action

Step-by-Step

  1. Open the Advanced Portal Designer menu page (search for it in Tell Me) and click Actions
  2. Click New
  3. Enter the Web Action code
  4. Set Type to "Data"
  5. Configure HTTP methods allowed
  6. Add DataItems for tables to expose
  7. Save the action

HTTP Method Configuration

Configure which operations are allowed:

Field HTTP Method Operation
Allow Read (GET) GET Read records
Allow Insert (POST) POST Create records
Allow Modify (PATCH) PATCH Update records
Allow Delete (DELETE) DELETE Remove records

Security Note

Only enable the methods you need. Disabling unused methods reduces attack surface.

DataItems

DataItems define which tables are exposed through the action.

Adding a DataItem

  1. In the action card, navigate to the Data section
  2. Click in the DataItems list
  3. Add a new DataItem
  4. Select the table number
  5. Configure columns to expose
  6. Set filters if needed

DataItem Properties

Property Description
Table No. The BC table to expose
View Filter expression (optional)
Columns Which fields to include

Configuring Columns

For each DataItem, specify which columns to return:

  1. Navigate to Columns for the DataItem
  2. Add fields to include
  3. Set field order
  4. Optionally rename fields

Enum and Option Display

Show Enum and Option Fields as Text

When enabled:

  • Option fields return text values (e.g., "Order" instead of 1)
  • Enum fields return member names
  • Easier for frontend consumption

When disabled:

  • Returns integer values
  • More compact response
  • Requires frontend mapping

Making Requests

Important — APD Data actions are not OData. APD has its own simple JSON envelope and its own filter model. There is no $filter URL parameter. Pagination uses $top and $skip URL parameters. Filtering is configured per DataItem inside BC (see Filtering below).

GET Request (Read)

// Fetch records (all filters configured on the DataItem in BC)
fetch('/wsfn/?action=API-CUSTOMERS')
    .then(response => response.json())
    .then(data => console.log(data));

// Pagination: top N, skip M
fetch('/wsfn/?action=API-CUSTOMERS&$top=50&$skip=100')
    .then(response => response.json())
    .then(data => console.log(data));

// Pass a value to a DataItem filter that is configured with FilterType = Parameter
// (here the DataItem's filter references the URL parameter named "customerno")
fetch('/wsfn/?action=API-ORDERS&customerno=10000')
    .then(response => response.json())
    .then(data => console.log(data));

POST / PATCH / DELETE Requests

POST (Insert), PATCH (Modify) and DELETE bodies are JSON objects keyed by the DataItem's Element Name (the name you set on the DataItem in BC), with an array of records. Child DataItems are nested under their own Element Name.

// Insert one or more customer records (Element Name = "customers")
fetch('/wsfn/?action=API-CUSTOMERS', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
        customers: [
            { Name: 'New Customer', City: 'New York' }
        ]
    })
});

// PATCH must include the primary-key field(s) so APD can locate the record
fetch('/wsfn/?action=API-CUSTOMERS', {
    method: 'PATCH',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
        customers: [
            { "No.": "10000", Name: 'Updated Name' }
        ]
    })
});

// DELETE also takes a body with the primary key
fetch('/wsfn/?action=API-CUSTOMERS', {
    method: 'DELETE',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
        customers: [ { "No.": "10000" } ]
    })
});

POST/PATCH/DELETE currently do not return a JSON document — they return an empty body on success and an AL error on failure. Re-read with GET if you need the resulting state.

Response Format

Successful Read

The response envelope uses the DataItem's Element Name as the property key (the example below assumes Element Name = customers). $more indicates whether more rows exist beyond $top.

{
    "customers": [
        { "No.": "10000", "Name": "Customer A", "City": "New York" },
        { "No.": "10001", "Name": "Customer B", "City": "Los Angeles" }
    ],
    "$more": false
}

When the DataItem has child DataItems, child rows appear nested inside each parent row under the child's Element Name:

{
    "orders": [
        {
            "No.": "PS-1001",
            "Sell-to Customer No.": "10000",
            "lines": [
                { "Line No.": 10000, "No.": "ITEM-A", "Quantity": 2 },
                { "Line No.": 20000, "No.": "ITEM-B", "Quantity": 1 }
            ]
        }
    ],
    "$more": false
}

Error Response

Errors come back as the standard Business Central API error envelope (e.g. HTTP error from the underlying AL error). Inspect the response status and body on the client.

Filtering

APD Data actions do not parse OData $filter expressions. All filters live on the DataItem inside Business Central. The supported filter types are:

Filter Type Behaves like Filter Value
Link A flowfield link from a parent DataItem The field number on the parent that supplies the value
Const SetRange(<field>, <value>) A literal value validated against the field type
Filter SetFilter(<field>, <expression>) A BC filter expression (e.g. 10000..20000, <>BLOCKED)
Parameter SetFilter(<field>, urlParam) The name of a URL parameter — APD reads that parameter at runtime and applies it as a filter

To accept a value from the URL, add a filter of type Parameter on the DataItem field and set Filter Value to the parameter name. Then the URL ?action=API-ORDERS&customerno=10000 will filter the linked field to 10000.

Security filters (configured under Security Settings) are always applied on top of DataItem filters.

Security

Security Profiles

Data actions respect security profiles:

  • Configure in Security Settings
  • Control table-level access
  • Limit operations per profile

Record-Level Security

For per-user record filtering, use Security Filters (configured in Security Settings under the Advanced Portal Designer menu) rather than DataItem filters. Security Filters automatically apply placeholders like %1 (User ID), %4 (BC Number / NAV Identification), etc. to the table at filter group 6, so users only ever see their own records. See Security Filters for the full placeholder reference.

Required Fields

For POST/PATCH operations, validate required fields in security settings or through triggers.

Using with JavaScript

With APD JavaScript Library

// Using APD_GetAction for simple requests
var result = APD_GetAction('API-CUSTOMERS', 'city=New York');

// Parse the result
var data = JSON.parse(result);

With Fetch API

async function getCustomers() {
    const response = await fetch('/wsfn/?action=API-CUSTOMERS');
    if (!response.ok) {
        throw new Error('API request failed');
    }
    return await response.json();
}

With AJAX

var xhr = new XMLHttpRequest();
xhr.open('GET', '/wsfn/?action=API-CUSTOMERS', true);
xhr.onload = function() {
    if (xhr.status === 200) {
        var data = JSON.parse(xhr.responseText);
        // Process data
    }
};
xhr.send();

Override MIME Type

The default MIME type is appropriate for JSON. Override only if needed:

Override Use Case
(default) Standard JSON API
application/json Explicit JSON
text/plain Plain text responses

Multiple DataItems

You can include multiple tables in one action:

DataItem 1: Customer
DataItem 2: Sales Header (linked to Customer)
DataItem 3: Sales Line (linked to Sales Header)

This creates hierarchical data suitable for master-detail scenarios.

Best Practices

API Naming

  • Use clear, RESTful names: API-CUSTOMERS, API-ORDERS
  • Include version if needed: API-V2-CUSTOMERS
  • Be consistent across your API

Field Selection

  • Only expose necessary fields
  • Avoid sensitive data in responses
  • Consider data volume

Security

  • Always require authentication for write operations
  • Implement proper security filters
  • Validate all input data

Performance

  • Use pagination for large datasets
  • Apply appropriate filters
  • Consider caching for read-only data

Troubleshooting

Empty Response

  • Check DataItem configuration
  • Verify filters aren't too restrictive
  • Ensure columns are configured

Access Denied

  • Verify authentication
  • Check security profile permissions
  • Review table-level security

Invalid JSON

  • Check MIME type setting
  • Verify no HTML errors in response
  • Test with simple request first