Development 19 April 2026 12 min read

Liquid FetchXML vs. Web API in Power Pages: When to Use Which?

Liquid FetchXML or Web API in Power Pages? Decision tree, performance comparison, security, and hybrid pattern from real-world projects.

Both exist, both work — and yet the same question comes up in almost every Power Pages project at some point: Liquid FetchXML or Web API? Answering with "it depends" is technically correct, but helpful to no one. This article gives you a concrete decision tree, explains the technical differences, and shows when the hybrid approach is the cleanest solution.

Liquid FetchXML: Server-Side Rendering

Liquid FetchXML is the older of the two approaches — and often the better one. The mechanism is straightforward: when the page renders, Power Pages executes the FetchXML query server-side and writes the result directly into the HTML output. By the time the browser receives the page, the data is already in the DOM.

Liquid FetchXML — Basic Pattern
{% fetchxml accounts_query %}
<fetch top="50">
  <entity name="account">
    <attribute name="name" />
    <attribute name="accountid" />
    <filter>
      <condition attribute="statecode" operator="eq" value="0" />
    </filter>
    <link-entity name="contact" from="accountid"
                 to="accountid" link-type="inner">
      <filter>
        <condition attribute="contactid" operator="eq"
                   value="{{ user.id }}" />
      </filter>
    </link-entity>
  </entity>
</fetch>
{% endfetchxml %}

{% for account in accounts_query.results.entities %}
  <div>{{ account.name }}</div>
{% endfor %}

Advantages

No CORS, no CSRF

Since everything happens server-side, there are no cross-origin issues and no anti-forgery token is required. The request runs from the portal server itself, not from the browser.

N:M Relationships Work Cleanly

Both approaches use the same Table Permissions model. The difference: FetchXML traverses N:M relationships directly in the query via link-entity — the permission scope doesn't interfere. The Web API translates scope conditions into OData filters, which causes CDS errors for N:M traversal with Parental, Contact, or Account scope (workaround: FetchXML parameter in OData query).

Faster Initial Load

Data arrives with the first HTML response. No extra requests, no flickering, no spinners. Time to Interactive is measurably lower — especially with larger datasets.

SEO-Friendly

Search engine crawlers see the data directly in the HTML. Content loaded via JavaScript can be ignored by crawlers — especially critical for product pages or public directories.

Disadvantages

No Dynamic Loading

What isn't there at page load stays absent — without a full page reload.

Read-Only — No Create, Update, Delete

With Liquid you can read data (including across complex relationships), but not write, update, or delete. For mutations you always need the Web API.

Template Complexity

Nested relationships, conditional logic, and transformations in Liquid quickly become hard to maintain.

Web API: Client-Side Data Access

The Power Pages Web API is an OData-compatible REST endpoint at /_api/. Requests run from the browser — after page load, triggered by user interactions or timers. That makes it the right choice for everything dynamic.

Web API — GET with safeAjax
webapi.safeAjax({
    type: "GET",
    url: "/_api/accounts?$select=name,accountid&$filter=statecode eq 0",
    contentType: "application/json"
}).done(function(data) {
    data.value.forEach(function(account) {
        console.log(account.name);
    });
}).fail(function(jqXHR) {
    console.error("Error:", jqXHR.status);
});

Important: webapi.safeAjax() is Power Pages' own wrapper function. It automatically injects the anti-forgery token (__RequestVerificationToken), which is mandatory for all write operations (POST, PATCH, DELETE). Direct $.ajax() calls without this token will fail with a 403.

Advantages

Full CRUD Support

GET, POST, PATCH, DELETE — all operations are available. Plus Associate and Disassociate for N:M relationships via the $ref endpoint.

Dynamic Without Reload

Content updates in place, filters apply instantly, forms save without page navigation. Modern UX patterns like infinite scroll or live updates are only possible this way.

Familiar Technology

OData is an established standard. Developers familiar with Dataverse know the query syntax. Debugging in browser DevTools is easier than tracking down Liquid issues.

Selective Data Loading

You can load only the data that's actually needed at any given moment — rather than rendering everything at page load.

Disadvantages

Table Permissions + Known Issue with N:M

Table Permissions and the site setting (Webapi/<entity>/enabled = true) are mandatory. For N:M read access with Parental, Contact, or Account scope there is a known CDS error issue — workaround: FetchXML parameter in OData query or disableodatafilter = true.

N+1 Request Problem

Every API call is a separate HTTP request. When many data points are needed at page load, this adds up to noticeable latency — and users see spinners instead of content.

The Decision Tree

In practice, four questions are enough to choose the right approach. Work through them in order:

flowchart TD Start([Dataverse data in Power Pages]) --> Q1{Needed at\npage load?} Q1 -->|Yes| Q2{Reading an\nN:M relationship?} Q1 -->|No – only after\nuser interaction| Q3{CRUD operation?} Q2 -->|Yes| LIQUID1["Liquid FetchXML recommended\n(Web API possible, but\nFetchXML parameter required)"] Q2 -->|No| Q4{SEO-relevant?} Q4 -->|Yes| LIQUID2[Liquid FetchXML] Q4 -->|No| BOTH["Either works —\nuse Liquid for\nsimpler code"] Q3 -->|Create / Update /\nDelete| WEBAPI1[Web API] Q3 -->|N:M Associate /\nDisassociate| WEBAPI2["Web API\n$ref endpoint"] Q3 -->|Read only| LIQUID3[Liquid FetchXML] style LIQUID1 fill:#dcfce7,stroke:#16a34a,color:#15803d style LIQUID2 fill:#dcfce7,stroke:#16a34a,color:#15803d style LIQUID3 fill:#dcfce7,stroke:#16a34a,color:#15803d style BOTH fill:#fef9c3,stroke:#ca8a04,color:#92400e style WEBAPI1 fill:#dbeafe,stroke:#2563eb,color:#1d4ed8 style WEBAPI2 fill:#dbeafe,stroke:#2563eb,color:#1d4ed8

Special case — N:M reads: According to the official Microsoft documentation, the Web API has a known issue: GET requests on tables with N:M table permissions fail when the scope is Parental, Contact, or Account — the portal server throws a CDS error. Microsoft's recommended solution is to use FetchXML as a parameter in the OData query (/_api/entity?fetchXml=...) instead of OData filtering. An alternative is the site setting Webapi/<table>/disableodatafilter = true, which comes with a performance penalty. Liquid FetchXML avoids this problem entirely — no workaround needed.

The Hybrid Pattern: Best of Both Worlds

In practice, most sophisticated Power Pages pages are hybrids. The pattern is simple and proven: Liquid loads data at page load into a JavaScript variable — the Web API handles mutations only.

A concrete example: a page displays all products associated with an incident (N:M). The user can add and remove products. Without the hybrid approach, every click would trigger a full reload, or you'd fight with Global permissions. With the hybrid pattern it's clean:

Step 1 — Liquid preloads all products (server-side)
{% fetchxml products_query %}
<fetch>
  <entity name="product">
    <attribute name="name" />
    <attribute name="productid" />
    <attribute name="productnumber" />
    <filter>
      <condition attribute="statecode" operator="eq" value="0" />
    </filter>
  </entity>
</fetch>
{% endfetchxml %}

<script>
// All available products – one request, no spinner
var AVAILABLE_PRODUCTS = [
{% for p in products_query.results.entities %}
  { id: "{{ p.productid }}", name: "{{ p.name | escape }}", number: "{{ p.productnumber | escape }}" }{% unless forloop.last %},{% endunless %}
{% endfor %}
];
</script>
Step 2 — Web API only for Associate / Disassociate (client-side)
// Associate product with an Incident (N:M Associate)
function associateProduct(incidentId, productId) {
    return webapi.safeAjax({
        type: "POST",
        url: "/_api/incidents(" + incidentId + ")/product_incidents/$ref",
        contentType: "application/json",
        data: JSON.stringify({
            "@odata.id": "/_api/products(" + productId + ")"
        })
    });
}

// Remove association (N:M Disassociate)
function disassociateProduct(incidentId, productId) {
    return webapi.safeAjax({
        type: "DELETE",
        url: "/_api/incidents(" + incidentId + ")/product_incidents(" + productId + ")/$ref"
    });
}

// JavaScript filters preloaded data — no extra request
function renderFilteredProducts(searchTerm) {
    return AVAILABLE_PRODUCTS.filter(function(p) {
        return p.name.toLowerCase().includes(searchTerm.toLowerCase());
    });
}

The result: the initial page load is fast (a single server request via Liquid), client-side filtering and search require no network calls, and only the actual changes (Associate/Disassociate) go through the Web API. Table Permissions only need to be configured for the mutation — not for reading the N:M data.

Performance Comparison

The performance implications are more direct than most developers expect. The key metric is Time to Interactive — when can the user actually work with the page?

Scenario Liquid FetchXML Web API
100 records at page load 1 request (HTML) 2 requests (HTML + API)
Data visible from Immediately (in HTML) After API response
Client-side filtering / search Instant (no network) Extra request per search
Update data after user action Page reload required No reload needed
Large dataset (1,000+ rows) Slower page load Pagination possible

Rule of thumb: Up to around 200 records, Liquid has a clear advantage for the initial load. Beyond that, or when pagination is required, the Web API is worth considering — ideally combined with a Liquid preload of the first page.

Security Comparison

Both approaches — Liquid FetchXML and Web API — use the same Table Permissions model. There is no separate permission system per method. The difference lies in how the permission check is technically implemented and what consequences that has for certain scenarios.

Liquid FetchXML

Table Permissions — server-side query filtering

  • + Permission check: is the user allowed to read the target table?
  • + Relationship filtering is handled by FetchXML itself — scope boundaries do not interfere with N:M traversal
  • + All scopes (Global, Account, Contact, Self, Parental) work without workarounds
  • ~ Configured in Power Pages Studio under "Security"

Web API

Table Permissions — translated as OData filters

  • + Automatic CSRF token protection via safeAjax
  • + Simple to configure for standard CRUD
  • - Scope conditions are injected as OData filters — N:M with Parental/Contact/Account scope causes a known CDS error
  • - Site settings required per table (Webapi/entity/enabled)

Security warning: Global Table Permissions are often the "quick fix" for the N:M problem with the Web API — and a serious security risk. They grant authenticated users read access to all records in the table, regardless of relationships. Microsoft's recommended approach is to use FetchXML as a parameter in the OData query, or Liquid FetchXML as the simplest option requiring no workaround at all. More on the security layers in the Power Pages security architecture article.

Quick Reference: When to Use What

Use Case Recommendation Reason
Initial data load Liquid 1 request, immediately in DOM, SEO-compatible
Read N:M relationship Liquid recommended Web API possible, but known issue with narrow scopes — FetchXML parameter required as workaround
Write N:M relationship Web API $ref endpoint for Associate / Disassociate
CRUD after user interaction Web API No page reload, modern UX pattern
Populate dropdown / lookup Liquid Reference data rarely changes, preload is more efficient
Infinite scroll / pagination Web API $top / $skip for page-by-page loading
SEO-relevant data Liquid Crawlers see server-rendered HTML content
Live updates (polling) Web API Interval-based API calls without reload
Client-side filtering / search Hybrid Liquid preloads all data, JS filters without network

The cheat sheets for Power Pages Web API and Liquid Templates are available in the downloads section.

Power Pages Architecture Questions?

Liquid vs. Web API is one of many architectural decisions that determine success or frustration in Power Pages projects. In a free consultation, I'll look at your specific situation.

Book Free Consultation

Related Articles

Tino Rabe

Tino Rabe

Microsoft Power Pages MVP

Microsoft MVP for Power Pages. I help mid-sized companies build secure customer portals.

Questions about this topic?

Let's talk about it.

Book a call