Dynamic365 API Client for Dart & Flutter
A Dart and Flutter helper package for integrating with Microsoft Dynamics 365 Finance and Operations OData APIs.
Version 3.0.0 introduces a cleaner OData client, token provider abstraction, paging support,
composite key support, structured error handling, and better support for D365FO /data entities.
This package is designed mainly for Dynamics 365 Finance and Operations OData endpoints. For production Flutter mobile apps, avoid storing
clientSecretdirectly inside the app. Use a secure backend or Azure Function to issue tokens or proxy D365FO requests.
Features
-
Dynamics 365 Finance and Operations OData support through
/dataendpoints. -
OAuth 2.0 client credentials token provider.
-
Static token provider for backend-generated access tokens.
-
Token caching with expiry handling.
-
Get entity sets.
-
Get entity by key.
-
Create entity records.
-
Update entity records.
-
Delete entity records.
-
Composite OData key support.
-
OData query builder support:
$select$filter$top$skip$orderby$countcross-companydataAreaIdcompany filtering
-
OData paging support using
@odata.nextLink. -
getAllPageshelper method. -
Structured response wrapper with status code, raw body, and next link.
-
Structured exception handling using
D365Exception. -
Flutter and Dart ready.
Installation
Add the package to your pubspec.yaml:
dependencies:
api_dynamic365_library: ^3.0.0
Then run:
flutter pub get
or for pure Dart projects:
dart pub get
Import
import 'package:api_dynamic365_library/api_dynamic365_library.dart';
Quick Start
1. Create a token provider
For internal tools or server-to-server scenarios:
final tokenProvider = ClientCredentialsTokenProvider(
tenantId: 'YOUR_TENANT_ID',
clientId: 'YOUR_CLIENT_ID',
clientSecret: 'YOUR_CLIENT_SECRET',
resource: 'https://your-environment.operations.dynamics.com',
);
Warning: Do not use
clientSecretdirectly in public Flutter mobile apps. For production mobile apps, use a backend API or Azure Function.
2. Create the D365 OData client
final client = D365ODataClient(
baseUrl: 'https://your-environment.operations.dynamics.com',
tokenProvider: tokenProvider,
);
Read Entity Data
Example: Read customers from CustomersV3.
final response = await
client.getEntitySet
(
entityName: 'CustomersV3',
query: D365ODataQuery()
.company('usmf')
.select([
'CustomerAccount',
'OrganizationName',
'dataAreaId',
])
.top(10),
);
for (final row in response.data) {
print(row);
}
Read Entity By Key
final response = await
client.getEntityByKey
(
entityName: 'CustomersV3',
key: const D365ODataKey({
'dataAreaId': 'usmf',
'CustomerAccount': 'US-001',
}),
);
print(response.data);
Create Entity Record
final response = await
client.createEntity
(
entityName: 'CustomersV3',
payload: {
'dataAreaId': 'usmf',
'CustomerAccount': 'US-999',
'OrganizationName': 'Test Customer',
'CustomerGroupId': '10',
'CurrencyCode': 'USD',
},
);
print(response.data);
Update Entity Record
await
client.updateEntity
(
entityName: 'CustomersV3',
key: const D365ODataKey({
'dataAreaId': 'usmf',
'CustomerAccount': 'US-999',
}),
payload: {
'OrganizationName': 'Updated Customer Name',
},
);
Delete Entity Record
await
client.deleteEntity
(
entityName: 'CustomersV3',
key: const D365ODataKey({
'dataAreaId': 'usmf',
'CustomerAccount': 'US-999',
})
,
);
OData Query Examples
Select fields
final query = D365ODataQuery().select([
'CustomerAccount',
'OrganizationName',
]);
Filter records
final query = D365ODataQuery().filter(
"CustomerGroupId eq '10'",
);
Filter by company
final query = D365ODataQuery().company('usmf');
This adds:
dataAreaId eq 'usmf'
to the OData filter.
Cross company
final query = D365ODataQuery().crossCompany();
Top and skip
final query = D365ODataQuery()
.top(50)
.skip(100);
Order by
final query = D365ODataQuery().orderBy([
'CustomerAccount asc',
]);
Count
final query = D365ODataQuery().count();
Paging
D365FO OData may return @odata.nextLink for large result sets.
Manual paging
final firstPage = await
client.getEntitySet
(
entityName: 'CustomersV3',
query: D365ODataQuery().top(100),
);
if (firstPage.nextLink != null) {
final secondPage = await client.getNextLink(
nextLink: firstPage.nextLink!,
);
print(secondPage.data);
}
Get all pages
final rows = await
client.getAllPages
(
entityName: 'CustomersV3',
query: D365ODataQuery().top(100),
maxPages: 20,
);
print
(
rows
.
length
);
Composite OData Keys
Many D365FO entities use composite keys.
const key = D365ODataKey({
'dataAreaId': 'usmf',
'ItemNumber': '1000',
});
The generated OData key will be:
dataAreaId='usmf',ItemNumber='1000'
Using a Backend Token
For production Flutter apps, the recommended approach is:
Flutter App
↓
Backend API / Azure Function
↓
Dynamics 365 Finance and Operations
Your backend should handle the client credentials flow securely and return a short-lived access token to the app.
Example using StaticTokenProvider:
final tokenProvider = StaticTokenProvider(
accessToken: 'ACCESS_TOKEN_FROM_BACKEND',
expiresAt: DateTime.now().add(
const Duration(minutes: 50),
),
);
final client = D365ODataClient(
baseUrl: 'https://your-environment.operations.dynamics.com',
tokenProvider: tokenProvider,
);
When your backend returns a new token:
tokenProvider.updateToken
(
accessToken: 'NEW_ACCESS_TOKEN',
expiresAt: DateTime.now().add(
const Duration(minutes: 50
)
,
)
,
);
Error Handling
The package throws D365Exception for token errors, HTTP errors, timeout errors, and invalid
responses.
try {
final response = await client.getEntitySet(
entityName: 'CustomersV3',
query: D365ODataQuery().top(10),
);
print(response.data);
} on D365Exception catch (e) {
print('D365 error: ${e.message}');
print('Status code: ${e.statusCode}');
print('Response body: ${e.responseBody}');
} catch (e) {
print('Unexpected error: $e');
}
Main Classes
| Class | Description |
|---|---|
D365ODataClient |
Main client for D365FO OData operations. |
D365ODataQuery |
Builds OData query parameters. |
D365ODataKey |
Builds single or composite OData keys. |
D365TokenProvider |
Base abstraction for token providers. |
ClientCredentialsTokenProvider |
Gets OAuth token using client credentials. |
StaticTokenProvider |
Uses an access token provided by another source. |
D365Response<T> |
Wraps response data, status code, raw body, and next link. |
D365Exception |
Structured exception for D365 errors. |
Security Notes
Do not store Azure AD clientSecret inside a public Flutter mobile or web app.
Recommended production architecture:
Flutter App
↓
Your Backend API
↓
D365FO OData / Custom Service
Use direct ClientCredentialsTokenProvider only for:
- Internal apps.
- Admin tools.
- Local testing.
- Backend/server-side Dart apps.
Supported D365FO Endpoint Style
This package expects D365FO OData URLs like:
https://your-environment.operations.dynamics.com/data/CustomersV3
It is not intended for Dataverse CRM Web API URLs like:
https://your-org.crm.dynamics.com/api/data/v9.2/accounts
License
This project is licensed under the MIT License.
See the LICENSE file for details.