{"id":32293724,"url":"https://github.com/maxbritto/directus_api_manager","last_synced_at":"2026-02-23T19:05:39.193Z","repository":{"id":42432880,"uuid":"449376684","full_name":"maxbritto/directus_api_manager","owner":"maxbritto","description":"Dart \u0026 Flutter library to communicate with a Directus REST API","archived":false,"fork":false,"pushed_at":"2026-02-13T16:47:33.000Z","size":320,"stargazers_count":30,"open_issues_count":3,"forks_count":7,"subscribers_count":5,"default_branch":"main","last_synced_at":"2026-02-13T23:47:44.153Z","etag":null,"topics":["dart","directus","flutter","rest","rest-api"],"latest_commit_sha":null,"homepage":"","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/maxbritto.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-01-18T17:12:50.000Z","updated_at":"2026-02-13T16:47:37.000Z","dependencies_parsed_at":"2024-12-31T19:28:27.861Z","dependency_job_id":"2c08dacd-25ea-48b1-81ab-2f670d564e17","html_url":"https://github.com/maxbritto/directus_api_manager","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/maxbritto/directus_api_manager","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxbritto%2Fdirectus_api_manager","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxbritto%2Fdirectus_api_manager/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxbritto%2Fdirectus_api_manager/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxbritto%2Fdirectus_api_manager/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maxbritto","download_url":"https://codeload.github.com/maxbritto/directus_api_manager/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxbritto%2Fdirectus_api_manager/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29751838,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-23T07:44:07.782Z","status":"ssl_error","status_checked_at":"2026-02-23T07:44:07.432Z","response_time":90,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dart","directus","flutter","rest","rest-api"],"created_at":"2025-10-23T03:35:44.253Z","updated_at":"2026-02-23T19:05:39.187Z","avatar_url":"https://github.com/maxbritto.png","language":"Dart","readme":"\u003c!--\nThis README describes the package. If you publish this package to pub.dev,\nthis README's contents appear on the landing page for your package.\n\nFor information about how to write a good package README, see the guide for\n[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).\n\nFor general information about developing packages, see the Dart guide for\n[creating packages](https://dart.dev/guides/libraries/create-library-packages)\nand the Flutter guide for\n[developing packages and plugins](https://flutter.dev/developing-packages).\n--\u003e\n\nCommunicate with a Directus server using its REST API.\n\n## Features\n\nThis packages can generate model classes for your each of your Directus collections.\n\n## Install\n\nYou need to add 1 dependencies :  (will be added to your app)\n- directus_api_manager \n\nAnd 2 dev dependencies : (will be only be used at build time to generate the model classes)\n- reflectable_builder \n- build_runner \n\nAdd the packages as a dependencies in your pubspec.yaml file :\n\n```yaml\ndependencies:\n     flutter:\n          sdk: flutter\n\n     directus_api_manager: ^1.11.0 #replace by latest version\n\ndev_dependencies:\n     build_runner: any\n     reflectable_builder: any\n```\n\n## Getting started\n\n### Create your models\n\nFor each directus model, create a new class that inherits `DirectusItem` and use annotations to specify the èndpointName`:\n\n```dart\n@DirectusCollection()\n@CollectionMetadata(endpointName: \"player\")\nclass PlayerDirectusModel extends DirectusItem {\n}\n```\n\nThis `endpointName` is the name of the collection in Directus : use the exact same name you used when creating your directus collection, including capitalized letters.\n\n**Important :** You must include the init method that calls the one from `super` and passes the raw received data, without adding any other parameter.\n\n```dart\nPlayerDirectusModel(super.rawReceivedData);\n```\n\nYou can create other named constructors if you want.\nIf you intend to create new items and send them to your server, you should override the secondary init method named `newItem()` :\n\n```dart\nPlayerDirectusModel.newItem() : super.newItem();\n```\n\nAdd any property you need as computed property using inner functions to access your data :\n\n```dart\nString get nickname =\u003e getValue(forKey: \"nickname\");\n\nint get bestScore =\u003e getValue(forKey: \"best_score\");\nset bestScore(int newBestScore) =\u003e\n      setValue(newBestScore, forKey: \"best_score\");\n```\n\nThe _key_ is the name of the property in your directus collection, you must use the same types in your directus collection as in your Dart computed properties.\n\n## Generate the code for your models\n\nEvery time you add a new collection, you can trigger the generator for your project:\nIn your project folder, execute this line :\n\n```bash\ndart run build_runner build lib\n```\n\nIt will add new `.reflectable.dart` files in your projects : do not include those files in your git repository.\n**Tip :** Add this line at the end of your `.gitignore` file :\n\n```\n*.reflectable.dart\n```\n\n## Inititalize the library to use the generated models\n\n```dart\nvoid main() {\n  initializeReflectable();\n  //...\n  // rest of your app\n}\n```\n\n### Create your DirectusApiManager\n\nThis object is the one that will handle everything for you :\n\n-    authentication and token management\n-    sending request\n-    parsing responses\n-    etc.\n\nYou should only create one object of this type and it only requires the url of your Directus instance :\n\n```dart\nDirectusApiManager _directusApiManager = DirectusApiManager(baseURL: \"http://0.0.0.0:8055/\");\n```\n\n### Manage users and Authentication\n\nTo authenticate use the `loginDirectusUser` method before making request that needs to be authorized:\n\n```dart\nfinal apiManager = DirectusApiManager(baseURL: \"http://0.0.0.0:8055/\");\nfinal result = await apiManager.loginDirectusUser(\"will@acn.com\", \"will-password\");\nif (result.type == DirectusLoginResultType.success) {\n  print(\"User logged in\");\n} else if (result.type == DirectusLoginResultType.invalidCredentials) {\n  print(\"Please verify entered credentials\");\n} else if (result.type == DirectusLoginResultType.invalidOTP) {\n  print(\"Please provide OTP\");\n  // Keep email and password for the next screen or extra field and resubmit using\n  // await apiManager.loginDirectusUser(\"will@acn.com\", \"will-password\", oneTimePassword: \"123456\");\n} else if (result.type == DirectusLoginResultType.error) {\n  print(\"An unknown error occured\");\n  final additionalMessage = result.message;\n  if (additionalMessage != null) {\n    print(\"More information : $additionalMessage\");\n  }\n}\n```\n\nIf the user's login requires MFA/OTP, you should present an extra field or page to the user to complete, and resend the authentication with the `oneTimePassword` parameter:\n\n```dart\nfinal result = await apiManager.loginDirectusUser(\"will@acn.com\", \"will-password\", oneTimePassword: \"123456\");\n```\n\nAll future request of this `apiManager` instance will include this user token.\n\n### OTP Email Authentication\n\nDirectus does not yet support OTP email authentication natively. However, you can use our OTP email authentication extension to add this functionality to your Directus server : https://github.com/maxbritto/directus-extension-otp-auth\n\nThen, with the OTP email authentication extension installed, you can use passwordless login via a one-time code sent by email.\n\nFirst, request a code to be sent to the user's email:\n\n```dart\nfinal codeSent = await apiManager.requestOtpCode(email: \"will@acn.com\");\nif (codeSent) {\n  print(\"A one-time code has been sent to your email\");\n}\n```\n\nThen, once the user has received and entered the code, verify it to log in:\n\n```dart\nfinal result = await apiManager.loginDirectusUserWithOtp(\n    email: \"will@acn.com\", otpCode: \"123456\");\nif (result.type == DirectusLoginResultType.success) {\n  print(\"User logged in\");\n} else if (result.type == DirectusLoginResultType.invalidOTP) {\n  print(\"Invalid code, please try again\");\n} else if (result.type == DirectusLoginResultType.requestsExceeded) {\n  print(\"Too many attempts, please wait before trying again\");\n} else if (result.type == DirectusLoginResultType.error) {\n  print(\"An error occurred: ${result.message}\");\n}\n```\n\nOn success, the user is fully authenticated and all future requests will include their token, just like with `loginDirectusUser`.\n\n### CRUD for your collections\n\nFor each collection you can either :\n\n-    fetch one or multiple items\n-    update items\n-    create items\n-    delete items\n\n## Creating new items\n\n```dart\nfinal newPlayer = PlayerDirectusModel.newItem(nickname: \"Sheldon\");\nfinal creationResult =\n    await apiManager.createNewItem(objectToCreate: newPlayer);\nif (creationResult.isSuccess) {\n  print(\"Created player!\");\n  final createdPlayer = creationResult.createdItem;\n  // depending on your directus server authorization you might not have access to the created item\n  if (createdPlayer != null) {\n    print(\"The id of this new player is ${createdPlayer.id}\");\n  }\n} else {\n  final error = creationResult.error;\n  if (error != null) {\n    print(\"Error while creating player : $error\");\n  }\n}\n```\n\n## Fetching existing items\n\n```dart\n//Multiple items\nfinal list = await apiManager.findListOfItems\u003cPlayerDirectusModel\u003e();\nfor (final player in list) {\n  print(\"${player.nickname} - ${player.bestScore}\");\n}\n\n//One specific item from an ID\nfinal PlayerDirectusModel onePlayer = await apiManager.getSpecificItem(id: \"1\");\nprint(onePlayer.nickname);\n```\n\n## Update existing items\n\n```dart\nfinal PlayerDirectusModel onePlayer = await apiManager.getSpecificItem(id: \"1\");\nonePlayer.bestScore = 123;\nfinal updatedPlayer = await apiManager.updateItem(objectToUpdate: onePlayer);\n```\n\n## Web Socket support\n\n### DirectusWebSocket\n\nWeb sockets allow your directus server to send you events as they occur and your app can react to those changes. This lib helps you set up `DirectusWebSocketSubscription` objects to register for those events. The lib will handle the authentication, the refresh token process and keep the connection alive automatically. You just have to start a subscription and stop a subscription when needed.\n\n### DirectusWebSocketSubscription\n\n`DirectusWebSocketSubscription` represent a subscription to your Directus server. Here are the mandatory properties to use it :\n\n-    `uid` must be specified, the format can be any text that is unique between 2 subscriptions. When the server will send a message, this uid will be provided. This allow us to know from which subscription this message came from.\n-    `onCreate`, `onUpdate`, `onDelete` callbacks are trigger when a subscription receive a subscription message. They are all optional but the `DirectusWebSocketSubscription` must have at least one of them.\n\n```dart\nDirectusWebSocketSubscription\u003cDirectusDataExtension\u003e(\n        uid: \"directus_data_extension_uid\",\n        onCreate: onCreate,\n        onUpdate: onUpdate,\n        onDelete: onDelete,\n        sort: const [SortProperty(\"id\")],\n        limit: 10,\n        offset: 10\n        filter: const PropertyFilter(\n            field: \"folder\",\n            operator: FilterOperator.equals,\n            value: \"folder_id\"));\n```\n\n### start and stop a subscription\n\nFirst create your subscription and then call the `startWebsocketSubscription` method on your `DirectusApiManager` instance to start it.\n```dart\nawait apiManager.startWebsocketSubscription(subscription);\n```\n\nWhen you no longer need the subscription, you can stop it by calling the `stopWebsocketSubscription` method on your `DirectusApiManager` instance and providing the uid of the subscription.\n```dart\nawait apiManager.stopWebsocketSubscription(subscription.uid);\n```\n\n## Cache system\n\n### Enabling and configuring the Cache system\n\nThis api comes with a caching system that can be enabled by providing an instance of `ILocalDirectusCacheInterface` when creating your `DirectusApiManager` instance.\n\nCurrently 2 ready to use implementations are provided :\n\n- The `JsonCacheEngine` class will store the data in a folder of your choosing using json files.\n- The `MemoryCacheEngine` class will store the data in memory only. If you use it inside a Flutter app, the cache will emptied on each app restart\n\nExample : \n\n```dart\nimport 'package:path_provider/path_provider.dart';\n\nvoid main() async {\n  final directory = await getApplicationCacheDirectory();\n  final apiManager = DirectusApiManager(baseURL: \"http://0.0.0.0:8055/\", cacheEngine: JsonCacheEngine(cacheFolderPath: \"${directory.path}/directus_api_cache\"));\n  // ...\n}\n```\nYou can decide to replace the json file implementation by creating and supplying your own implementation which implements the `ILocalDirectusCacheInterface` class.\n\n### Using the Cache system for your requests\n\nAll read requests (get, find, currentUser, etc.) now have optional parameters to configure the cache. By default, most of those will save responses but will only use those if the next request fails.\nIf you want to also replace future responses by a local cache read, you can set the `canUseCacheForResponse` parameter to `true` and tweak the `maxCacheAge` parameter to set the maximum age of the cache (defaults to 1 day). This will prevent calling the directus server if a valid cache exists for the same request.\n\n```dart\nawait apiManager.getSpecificItem\u003cDirectusItemTest\u003e(\n    id: \"element1\",\n    canUseCacheForResponse: true,\n    maxCacheAge: const Duration(days: 1));\n```\n\nIf you want to disable the cache completely for a request, you can set the `canSaveResponseToCache` parameter to `false`.\n\n```dart\nawait apiManager.getSpecificItem\u003cDirectusItemTest\u003e(\n    id: \"element1\",\n    canSaveResponseToCache: false);\n```\n\nBy default, an expired cache can still be used if the real network request fails. If you want to disable this behavior, you can set the `canUseOldCachedResponseAsFallback` parameter to `false`.\n\n```dart\nawait apiManager.getSpecificItem\u003cDirectusItemTest\u003e(\n    id: \"element1\",\n    canUseOldCachedResponseAsFallback: false);\n```\n\nThose parameters are available for all read based requests.\n\n### Clearing the cache before it expires\n\nThe engine tries to be smart and will regularly invalidate caches when it performs modifications on the same type of data. For example :\n- if you create a new item, the cache for the list of items will be invalidated.\n- If you update an item, the cache for this specific item will be invalidated, as long as any list for that type of object. \n- If you delete an item, the cache for this specific item will be invalidated, as long as any list for that type of object.\n\nWe suggest you rely mostly on automatic cache invalidation, but you can also manually clear the cache for specific requests.\n\n#### Clearing cache for a specific object\n\nYou can use the [clearCacheForObject] function to clear the cache for a specific object. The object must be of type extending `DirectusData`.\nIf you only have the id of the object, you can use the [clearCacheForObjectWithId] function with the type hinted :\n\n```dart\nawait apiManager.clearCacheForObjectWithId\u003cDirectusItemTest\u003e(\"element1\");\n```\n\n#### Clearing the current user cache\n\nThe current user is a specific cache and for it, you can use the [discardCurrentUserCache] function.\n\n#### Clearing all caches\n\nLogging out the current user will automatically clear all the cached data. \n\n#### Clearing specific caches with the cache key\n\nEach cached object has a key that is used to store and retrieve it. This key is usually generated automatically based on the request, but you can provide your own cache key with the `requestIdentifier` parameter available on every `read` based method. \n\n```dart\nawait apiManager.getSpecificItem\u003cDirectusItemTest\u003e(\n    id: \"element1\",\n    requestIdentifier: \"my_custom_key\");\n```\n\nThen you can use the [clearCacheWithKey] function to clear the cache associated with this key.\n\n```dart\nawait apiManager.clearCacheWithKey(\"my_custom_key\");\n```\n\n#### Clearing specific caches with tags\n\nYou can associate a set of tags with every read you perform. Those tags will be associated with the cached data, and can be use to invalidate those before the cache expiration time. \n\n```dart\nawait sut.getSpecificItem\u003cDirectusItemTest\u003e(\n    id: \"element1\",\n    extraTags: [\"tag1\", \"tag2\"]);\nawait sut.getSpecificItem\u003cDirectusItemTest\u003e(\n    id: \"element2\",\n    extraTags: [\"tag3\"]);\nawait sut.getSpecificItem\u003cDirectusItemTest\u003e(\n    id: \"element3\",\n    extraTags: [\"tag2\", \"tag4\"]);\n\n```\nThose parameters are available for all read based requests.\n\nThen you can use the [removeCacheEntriesWithTags] function to clear the caches entries associated with those tags.\n\n```dart\nawait apiManager.removeCacheEntriesWithTags([\"tag1\", \"tag3\"]); //this will invalidate element1 and element2 from the example above\n```\n\nYou can also use the `List\u003cString\u003e extraTagsToClear` parameter present in each modification based method (create, update, delete) to clear the cache associated with those tags if the call succeeds.\n\n## Additional information\n\n### Install\n\nIf you want to use a specific version, it can be done in addition to the git ur:\n\n```yaml\ndirectus_api_manager:\n     git: https://github.com/maxbritto/directus_api_manager.git\n     version: ^1.2.0\n```\n\n### Advanced properties :\n\nIf your collections have advanced properties that needs specific parsing you can do it in the computed properties.\nHere is an example of a properties of type _Tag list_ in Directus, inside we can enter some courses ids as number but Directus consider all tags as Strings. So we convert them in the dart code like this :\n\n```dart\nList\u003cint\u003e get requiredCourseIdList {\n  final courseListJson = getValue(forKey: \"required_course_id_list\");\n  if (courseListJson is List\u003cdynamic\u003e) {\n    return courseListJson\n        .map\u003cint\u003e((courseIdString) =\u003e int.parse(courseIdString))\n        .toList();\n  } else {\n    return [];\n  }\n}\n```\n\n### Filtering\n\nYou can filter your items using the `filter` parameter. The filter can be a single filter or a combination of filters using logical operators.\n\n#### Property Filter\n\nA property filter is used to filter items based on a property value. Here are some examples:\n\n```dart\n// Filter by exact match\nfinal filter = PropertyFilter(\n    field: \"title\",\n    operator: FilterOperator.equals,\n    value: \"Hello World!\");\n\n// Filter by contains\nfinal filter = PropertyFilter(\n    field: \"title\",\n    operator: FilterOperator.contains,\n    value: \"Hello\");\n\n// Filter by greater than\nfinal filter = PropertyFilter(\n    field: \"score\",\n    operator: FilterOperator.greaterThan,\n    value: 10);\n\n// Filter by between\nfinal filter = PropertyFilter(\n    field: \"score\",\n    operator: FilterOperator.between,\n    value: [10, 100]);\n\n// Filter by is null\nfinal filter = PropertyFilter(\n    field: \"description\",\n    operator: FilterOperator.isNull,\n    value: null);\n```\n\n#### Logical Filter\n\nA logical filter is used to combine multiple filters using logical operators. Here are some examples:\n\n```dart\n// AND operator\nfinal filter = LogicalOperatorFilter(\n    operator: LogicalOperator.and,\n    children: [\n        PropertyFilter(\n            field: \"title\",\n            operator: FilterOperator.contains,\n            value: \"Hello\"),\n        PropertyFilter(\n            field: \"description\",\n            operator: FilterOperator.equals,\n            value: \"world\"),\n    ]);\n\n// OR operator\nfinal filter = LogicalOperatorFilter(\n    operator: LogicalOperator.or,\n    children: [\n        PropertyFilter(\n            field: \"title\",\n            operator: FilterOperator.contains,\n            value: \"Hello\"),\n        PropertyFilter(\n            field: \"description\",\n            operator: FilterOperator.equals,\n            value: \"world\"),\n    ]);\n```\n\n#### Relation Filter\n\nA relation filter is used to filter items based on related items. Here are some examples:\n\n```dart\n// Filter by related item\nfinal filter = RelationFilter(\n    propertyName: \"users\",\n    linkedObjectFilter: PropertyFilter(\n        field: \"id\",\n        operator: FilterOperator.equals,\n        value: \"23\"));\n\n// Filter by M2M relation\nfinal filter = RelationFilter(\n    propertyName: \"words\",\n    linkedObjectFilter: RelationFilter(\n        propertyName: \"idWord\",\n        linkedObjectFilter: PropertyFilter(\n            field: \"word\",\n            operator: FilterOperator.equals,\n            value: \"zelda\")));\n```\n\n#### Geo Filter\n\nA geo filter is used to filter items based on their geographical location. This is particularly useful for finding items within a specific geographical area. Here are some examples:\n\n```dart\n// Filter by rectangle area\nfinal rectangle = GeoJsonPolygon.rectangle(\n    topLeft: [longitude1, latitude1],\n    bottomRight: [longitude2, latitude2],\n);\n\nfinal filter = GeoFilter(\n    field: \"location\",\n    operator: GeoFilterOperator.intersectsBbox,\n    feature: rectangle,\n);\n\n// Filter by custom polygon area\nfinal polygon = GeoJsonPolygon.polygon(\n    points: [\n        [longitude1, latitude1],\n        [longitude2, latitude1],\n        [longitude2, latitude2],\n        [longitude1, latitude2],\n    ],\n);\n\nfinal filter = GeoFilter(\n    field: \"location\",\n    operator: GeoFilterOperator.intersectsBbox,\n    feature: polygon,\n);\n\n// Filter by square area from center point\nfinal square = GeoJsonPolygon.squareFromCenter(\n    center: [longitude, latitude],\n    distanceInMeters: 400, // Distance in meters\n);\n\nfinal filter = GeoFilter(\n    field: \"location\",\n    operator: GeoFilterOperator.intersectsBbox,\n    feature: square,\n);\n\n// Combine geo filter with other filters\nfinal combinedFilter = LogicalOperatorFilter(\n    operator: LogicalOperator.and,\n    children: [\n        geoFilter,\n        PropertyFilter(\n            field: \"type\",\n            operator: FilterOperator.equals,\n            value: \"restaurant\",\n        ),\n    ],\n);\n```\n\nThe `GeoJsonPolygon` class provides three constructors for creating different types of geographical areas:\n- `rectangle`: Creates a rectangular area defined by top-left and bottom-right coordinates\n- `polygon`: Creates a custom polygon area defined by a list of coordinates\n- `squareFromCenter`: Creates a square area centered at a specific point with a given distance in meters\n\nAll polygons are automatically closed (the last point connects back to the first point) to ensure valid GeoJSON format.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxbritto%2Fdirectus_api_manager","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaxbritto%2Fdirectus_api_manager","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxbritto%2Fdirectus_api_manager/lists"}