App Data Management
Shared records that belong to the application rather than to any one user — announcements, catalogues, feature flags, pricing tables. Seven methods, all scoped by the configured appId.
1// App data belongs to the app, not to any user.
2final created = await authix.addAppData(
3 const AddAppDataParams(
4 dataCategory: 'announcements',
5 data: {'title': 'We are live', 'pinned': true},
6 ),
7);
8
9created.id; // 'ck…'
10created.version; // 0
11created.data!['title']; // 'We are live'Secret key only
App-wide data is readable and writable by every user of your app, so these routes are account-level: a secret key or an admin dashboard session. Called with a publishable key they return InsufficientScopeError. Put them behind your own backend and expose only what your app needs.
The DataItem type
App-wide records and user records share one shape. Pass DataItem.fromJson as itemParser to get a typed id, version and dataCategory plus an indexer for the payload.
| Member | Type | Meaning |
|---|---|---|
| id | String | Record ID. |
| dataCategory | String | The category it was filed under. |
| version | int | Bumped on every write. Pass it back on update for optimistic concurrency. |
| createdAt / updatedAt | DateTime? | Parsed timestamps, when the API sent them. |
| parentId | String? | Parent record, for nested shapes. |
| operator [] | dynamic | Read any payload field: item['title']. |
| payload | Map<String, dynamic> | The payload with the identity fields stripped out. |
| fields | Map<String, dynamic> | The full decoded record, identity fields included. |
1final page = await authix.getAppData<DataItem>(
2 const GetAppDataParams(category: 'announcements'),
3 itemParser: DataItem.fromJson,
4);
5
6for (final item in page) {
7 item.id; // typed String
8 item.version; // typed int — keep it to update safely
9 item.dataCategory; // 'announcements'
10 item['title']; // any payload field, via the [] operator
11 item.payload; // the payload without the identity fields
12 item.fields; // the full decoded record as a Map
13}Methods
addAppData
POST /app/:appId/data/:dataCategory
Future<DataItemResponse> addAppData(AddAppDataParams params)
| Param | Type | Required | Description |
|---|---|---|---|
| dataCategory | String | Yes | Category namespace — travels in the URL, not the body. |
| data | Map<String, dynamic> | Yes | The record body. |
Returns: DataItemResponse
1final created = await authix.addAppData(
2 const AddAppDataParams(
3 dataCategory: 'announcements',
4 data: {
5 'title': 'Version 2 is out',
6 'body': 'Dark mode, faster sync.',
7 'publishedAt': '2026-08-08',
8 },
9 ),
10);Throws: ConfigurationError when dataCategory is empty. PermissionDeniedError when the plan's app-data storage limit is reached.
Your map is sent as the body verbatim — no envelope, and no appId is merged in, because the app id is already in the path. In v1 both were, and ended up stored inside the record.
getAppData
GET /app/:appId/app-data[/:category]
Future<Page<T>> getAppData<T>(GetAppDataParams params, {ItemParser<T>? itemParser})| Param | Type | Required | Description |
|---|---|---|---|
| category | String? | No | Restrict to one category. Omit it to list every category. |
| limit | int? | No | Page size. Defaults to 20, capped at 100. |
| cursor | String? | No | nextCursor from the previous page. |
| itemParser | ItemParser<T>? | No | Maps each record into your own model. |
Returns: Page<T>
1// One category
2final news = await authix.getAppData(
3 const GetAppDataParams(category: 'announcements', limit: 50),
4);
5
6// Everything
7final all = await authix.getAppData(const GetAppDataParams());Cursor-paginated, newest first. In v1 this returned the whole list — an app with a large catalogue would load all of it into memory.
iterateAppData
GET /app/:appId/app-data (repeated)
Stream<T> iterateAppData<T>(GetAppDataParams params, {ItemParser<T>? itemParser, int? maxPages})| Param | Type | Required | Description |
|---|---|---|---|
| params | GetAppDataParams | Yes | Same filters as getAppData. Any cursor you set is ignored. |
| itemParser | ItemParser<T>? | No | Maps each record into your own model. |
| maxPages | int? | No | Stop after this many pages. |
Returns: Stream<T>
1await for (final item in authix.iterateAppData(
2 const GetAppDataParams(category: 'catalogue'),
3)) {
4 print(item['sku']);
5}Follows the cursor for you and yields records as they arrive.
getSingleAppData
GET /app/:appId/data/:dataId
Future<DataItemResponse> getSingleAppData({required String dataId})| Param | Type | Required | Description |
|---|---|---|---|
| dataId | String | Yes | Record ID. |
Returns: DataItemResponse
1final item = await authix.getSingleAppData(dataId: 'app_data_123');
2print(item.data!['title']);Throws: ConfigurationError when dataId is empty. NotFoundError when the record does not exist.
searchInAppData
POST /app/:appId/app-data/search
Future<Page<T>> searchInAppData<T>(SearchAppDataParams params, {ItemParser<T>? itemParser})| Param | Type | Required | Description |
|---|---|---|---|
| q | String? | No | Substring match across the whole record. |
| category | String? | No | Restrict to one category. |
| keys | Map<String, dynamic>? | No | Exact field match, e.g. {'pinned': true}. |
| limit | int? | No | Page size. Defaults to 20, capped at 100. |
| cursor | String? | No | nextCursor from the previous page. |
Returns: Page<T>
1final results = await authix.searchInAppData(
2 const SearchAppDataParams(
3 category: 'announcements',
4 q: 'dark mode',
5 keys: {'pinned': true},
6 ),
7);Every argument is optional — calling it with an empty params object searches all app data. Both filters are index-backed in Postgres.
updateAppData
PATCH /app/:appId/data/:dataId
Future<DataItemResponse> updateAppData(UpdateAppDataParams params)
| Param | Type | Required | Description |
|---|---|---|---|
| dataId | String | Yes | Record to update. |
| data | Map<String, dynamic> | Yes | Fields to write. Fields you omit are left alone. |
| version | int? | No | The version you last read. Omit it and the write always wins. |
Returns: DataItemResponse
1await authix.updateAppData(
2 const UpdateAppDataParams(
3 dataId: 'app_data_123',
4 data: {'title': 'Version 2.1 is out'},
5 version: 4,
6 ),
7);Throws: ConfigurationError when dataId is empty. VersionConflictError when version no longer matches.
Uses PATCH — the only method in the SDK that does. It is a partial update.
deleteAppData
DELETE /app/:appId/data/:dataId
Future<Map<String, dynamic>> deleteAppData(DeleteAppDataParams params)
| Param | Type | Required | Description |
|---|---|---|---|
| dataId | String | Yes | Record to delete. |
Returns: Map<String, dynamic>
1await authix.deleteAppData(
2 const DeleteAppDataParams(dataId: 'app_data_123'),
3);Permanent.
App data vs user data
| User data | App data | |
|---|---|---|
| Scoped by | userId (pinned to the session for pk_ keys) | appId from your config |
| Reachable with | Publishable or secret key | Secret key only |
| Listing | Cursor-paginated Page | Cursor-paginated Page |
| Update verb | PUT | PATCH |
| Category lives in | Request body | URL path |
| Atomic batches | batch() | Not available |
Related