OData Data Sharing Between Mendix Apps
SkillDev toolsLets your agent share data between Mendix apps by publishing or consuming OData services.
Available today. Use it from your connected AI after setup.
No other account needed.
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:
| Format | Example | Stored In Model |
|---|---|---|
| HTTP(S) URL | https://api.example.com/odata/v4/$metadata | Unchanged |
| Absolute file:// URI | file:///Users/team/contracts/service.xml | Unchanged |
| Relative path | ./metadata/service.xml or metadata/service.xml | Normalized to absolute file:// |
Path Normalization:
- Relative paths (with or without
./) are automatically converted to absolutefile://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 (
-pflag or REPL): relative paths are resolved against the.mprfile'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:
| Output | Meaning |
|---|---|
Modified OData client: … + Refreshed $metadata: 5 entity types, 0 actions | The 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:
- Stable API contract -- the view's shape stays the same even when the underlying tables change
- Flattened data -- joins across multiple tables into a single flat resource (e.g., Customer + BillingAddress + DeliveryAddress into one
CustomerAddressVE) - Computed fields -- add calculated columns like
FullAddressorActivePriceusing OQL expressions - Filtered datasets -- restrict what's visible (e.g., only active products, cheap products)
- 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:
- HTTP(S) URL — fetches from remote service (production)
- file:///absolute/path — reads from local absolute path
- ./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:
| Module | Purpose | Contains |
|---|---|---|
Shop | Core domain | Persistent entities, business logic |
ShopApi or ShopViews | API layer (producer) | View entities, OData service, CUD microflows |
ShopClient or ShopViewsClient | API consumer | OData 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
keyfield 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
MetadataUrlpoints 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.mprdirectory)
- HTTP(S) URL:
- 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