OData Data Sharing Between Mendix Apps

SkillDev tools

Lets your agent share data between Mendix apps by publishing or consuming OData services.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the OData Data Sharing Between Mendix Apps skill

About this capability

Share data between Mendix apps over OData, published services, view entities as an abstraction layer, consumed clients and external entities. Use when exposing data from one app to another, consuming another app's OData service, or refreshing a cached contract.

What this skill tells your AI

The instructions your AI receives, as published by mendixlabs/mxcli in .claude/skills/mendix/odata-data-sharing/SKILL.md and read by ahel’s review.

This skill covers how to use OData services to share data between Mendix applications, with emphasis on using view entities as an abstraction layer to decouple the API contract from the internal domain model.

Reference files

SKILL.md covers the architecture, the contract rules, and the conventions. The long build-outs are next door:

  • reference/walkthroughs.md — four complete builds, start to finish: a read-only API behind view entities, a non-persistable published entity, a read-write API with microflow handlers, publishing a microflow as an OData action, and the GraphQL variant.
  • reference/errors-and-auth.md — which HTTP status each capability can actually return, the authentication methods and what Basic auth costs, and the configuration microflow for custom headers.

When to Use This Skill

  • User asks to expose data from one Mendix app to another
  • User wants to set up inter-app communication via OData
  • User needs to create an API layer that abstracts internal entities
  • User asks about external entities, consumed/published OData services
  • User wants to decouple modules or apps for independent deployment
  • User asks about the view entity pattern for OData services
  • User asks about local metadata files or offline OData development

MetadataUrl Formats

CREATE ODATA CLIENT supports three formats for the MetadataUrl parameter:

FormatExampleStored In Model
HTTP(S) URLhttps://api.example.com/odata/v4/$metadataUnchanged
Absolute file:// URIfile:///Users/team/contracts/service.xmlUnchanged
Relative path./metadata/service.xml or metadata/service.xmlNormalized to absolute file://

Path Normalization:

  • Relative paths (with or without ./) are automatically converted to absolute file:// URLs in the Mendix model
  • This ensures Studio Pro can properly detect local file vs HTTP metadata sources (radio button in UI)
  • Example: ./metadata/service.xml → file:///absolute/path/to/project/metadata/service.xml

Path Resolution (before normalization):

  • With project loaded (-p flag or REPL): relative paths are resolved against the .mpr file's directory
  • Without project: relative paths are resolved against the current working directory

Refreshing the Cached Contract

mxcli caches the $metadata document in the client so the model rebuilds without the service running. That cache is a snapshot, and a consumed service that gains entity sets makes it stale — the file on disk has five, the client still answers three.

CREATE OR MODIFY ODATA CLIENT re-reads the contract every time it runs, so refreshing is a re-run of the statement you already have:

-- after refreshing ./contracts/live-now-metadata.xml from the running backend
CREATE OR MODIFY ODATA CLIENT F1Now.NowApi (
  ODataVersion: OData4,
  MetadataUrl: './contracts/live-now-metadata.xml',
  Timeout: 300,
  ServiceUrl: '@F1Now.ApiLocation'
);

Read the verb it prints — it tells you which happened:

OutputMeaning
Modified OData client: … + Refreshed $metadata: 5 entity types, 0 actionsThe contract changed and the client now carries the new one
Unchanged OData client: …The contract is identical, so nothing was written
Warning: could not refresh $metadata: …The contract could not be read; the previously cached one is kept, so re-run once it is reachable

Then re-import: CREATE OR MODIFY EXTERNAL ENTITIES FROM F1Now.NowApi maps the new entity sets. Do not DROP ODATA CLIENT and recreate it to force a refresh — that invalidates the client ID the existing external entities point at.

Note that ALTER ODATA CLIENT SET MetadataUrl = … does not re-fetch. Use CREATE OR MODIFY when the contract is what changed.

Use Cases for Local Metadata:

  • Offline development — no network access required
  • Testing and CI/CD — reproducible builds with metadata snapshots
  • Version control — commit metadata files alongside code
  • Pre-production — test against upcoming API changes before deployment
  • Firewall-friendly — works in locked-down corporate environments

ServiceUrl Must Be a Constant

IMPORTANT: The ServiceUrl parameter must always be a constant reference (prefixed with @). Direct URLs are not allowed.

Correct:

CREATE CONSTANT ProductClient.ProductDataApiLocation
  TYPE String
  DEFAULT 'http://localhost:8080/odata/productdataapi/v1/';

CREATE ODATA CLIENT ProductClient.ProductDataApiClient (
  ODataVersion: OData4,
  MetadataUrl: 'https://api.example.com/$metadata',
  ServiceUrl: '@ProductClient.ProductDataApiLocation'  -- ✅ Constant reference
);

Incorrect:

CREATE ODATA CLIENT ProductClient.ProductDataApiClient (
  ODataVersion: OData4,
  MetadataUrl: 'https://api.example.com/$metadata',
  ServiceUrl: 'https://api.example.com/odata'  -- ❌ Direct URL not allowed
);

This enforces Mendix best practice of externalizing configuration values for different environments.

Architecture Overview

OData data sharing follows a producer/consumer pattern with three layers:

┌─────────────────────────────────────────────┐
│  PRODUCER APP                               │
│                                             │
│  persistent entities  ──▶  view entities    │
│  (Shop.Customer,          (Api.CustomerVE)  │
│   Shop.Address)                             │
│                          ▼                  │
│                    odata service             │
│                   (Api.CustomerApi)          │
└──────────────────────┬──────────────────────┘
                       │ HTTP/OData4
┌──────────────────────▼──────────────────────┐
│  CONSUMER APP                               │
│                                             │
│                    odata client             │
│                  (Client.CustomerApiClient)  │
│                          ▼                  │
│                  external entities           │
│                 (Client.CustomersEE)         │
│                          ▼                  │
│                  pages & microflows          │
└─────────────────────────────────────────────┘

Why View Entities?

Publishing persistent entities directly exposes your internal schema. When you change a column name, add a table, or restructure associations, every consumer app breaks. View entities solve this:

  1. Stable API contract -- the view's shape stays the same even when the underlying tables change
  2. Flattened data -- joins across multiple tables into a single flat resource (e.g., Customer + BillingAddress + DeliveryAddress into one CustomerAddressVE)
  3. Computed fields -- add calculated columns like FullAddress or ActivePrice using OQL expressions
  4. Filtered datasets -- restrict what's visible (e.g., only active products, cheap products)
  5. Aggregations -- expose pre-aggregated metrics (e.g., orders per day, sum of line items)

API Versioning

When your API contract changes, create a new version rather than breaking existing consumers:

-- v1: Original API (keep running for existing consumers)
create odata service ProductApi.ProductDataApi (
  path: 'odata/productdataapi/v1/',
  version: '1.0.0',
  ...
);

-- v2: New version with additional fields
create odata service ProductApi.ProductDataApi_v2 (
  path: 'odata/productdataapi/v2/',
  version: '2.0.0',
  ODataVersion: OData4,
  ServiceName: 'ProductDataApi',
  Summary: 'Product API v2 - includes weight and tags',
  ...
)
authentication basic
{
  publish entity ProductApi.ProductWithPriceAndTagsVE as 'Product' (
    ReadMode: ReadFromDatabase,
    InsertMode: microflow ProductApi.InsertProductV2,
    UpdateMode: microflow ProductApi.UpdateProductV2,
    DeleteMode: microflow ProductApi.DeleteProductV2
  )
  expose (...);
};

Folder Organization

Use the Folder property to organize OData documents within modules.

MetadataUrl accepts three formats:

  1. HTTP(S) URL — fetches from remote service (production)
  2. file:///absolute/path — reads from local absolute path
  3. ./path or path/file.xml — reads from local relative path (resolved against .mpr directory)
-- Format 1: HTTP(S) URL
create odata client ProductClient.ProductDataApiClient (
  ODataVersion: OData4,
  MetadataUrl: 'https://api.example.com/odata/v4/$metadata',
  Folder: 'Integration/ProductAPI'
);

-- Format 2: Absolute file:// URI
create odata client ProductClient.ProductDataApiClient (
  ODataVersion: OData4,
  MetadataUrl: 'file:///Users/team/contracts/productdataapi.xml',
  Folder: 'Integration/ProductAPI'
);

-- Format 3a: Relative path with ./
create odata client ProductClient.ProductDataApiClient (
  ODataVersion: OData4,
  MetadataUrl: './metadata/productdataapi.xml',
  Folder: 'Integration/ProductAPI'
);

-- Format 3b: Relative path without ./
create odata client ProductClient.ProductDataApiClient (
  ODataVersion: OData4,
  MetadataUrl: 'metadata/productdataapi.xml',
  Folder: 'Integration/ProductAPI'
);

create odata service ProductApi.ProductDataApi (
  path: 'odata/productdataapi/v1/',
  version: '1.0.0',
  ODataVersion: OData4,
  folder: 'Integration/APIs'
)
authentication basic
{ ... };

Folders are created automatically if they don't exist. Use / for nested folders.

Module Organization Conventions

Follow this naming convention for clean separation:

ModulePurposeContains
ShopCore domainPersistent entities, business logic
ShopApi or ShopViewsAPI layer (producer)View entities, OData service, CUD microflows
ShopClient or ShopViewsClientAPI consumerOData client, external entities, client constants

This keeps the API contract separate from the domain logic, and the consumer separate from the producer.

Checklist

Before publishing:

  • View entities expose only the fields consumers need (no internal IDs unless needed for writes)
  • View entity has at least one key field for OData identity
  • Module role created and granted on view entities (READ, optionally WRITE)
  • OData service has AUTHENTICATION set (Basic, Session, or Microflow)
  • GRANT ACCESS ON ODATA SERVICE to the API module role
  • CUD microflows (if writable) accept ($ViewEntity, $HttpRequest) parameters
  • CUD microflows granted EXECUTE to the API module role

Before consuming:

  • Location constant created for environment-specific URLs
  • OData client MetadataUrl points to either:
    • HTTP(S) URL: https://api.example.com/$metadata
    • Local file (absolute): file:///path/to/metadata.xml
    • Local file (relative): ./metadata/service.xml (resolved against .mpr directory)
  • OData client uses ServiceUrl: '@Module.Constant' for runtime endpoint
  • External entities match the published exposed names and types
  • Module role created and granted on external entities (READ, optionally CREATE/WRITE/DELETE)

Exploration Commands

Use these commands to inspect existing OData setup in a project:

-- List all published and consumed services
show odata services;
show odata clients;

-- Inspect a specific service
describe odata service ShopViews.ShopViewsApi;
describe odata client ShopViewsClient.ShopViewsApiClient;

-- See external entities and view entities
show entities in ShopViewsClient;
show external entities;
show external actions;

-- Browse available assets from cached $metadata contract
show contract entities from ShopViewsClient.ShopViewsApiClient;
show contract actions from ShopViewsClient.ShopViewsApiClient;
describe contract entity ShopViewsClient.ShopViewsApiClient.Product;
describe contract entity ShopViewsClient.ShopViewsApiClient.Product format mdl;

-- Check security setup
show access on odata service ShopViews.ShopViewsApi;
show module roles in ShopViews;

Signals

GitHub stars
122
Forks
49
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
odata-data-sharing
Source
github.com/mendixlabs/mxcli