content/releases/3.breaking-changes/2.version-11.md
Existing versions with the key "draft" and a custom name other than "Draft" will have its display name standardized to “Draft”.
Field permissions and version access checks require @directus/visual-editing v2.0.0+.
The UsersService.requestPasswordReset now errors for external auth provider users to prevent erroneuos emails.
Disabled interfaces are no longer interactive, this includes opening drawers for read-only relational fields.
AssetsService.getAsset and GET /assets/:id now respect the users directus_files permissions when returning file based fields.
The SSO callback URL generation and redirect validation now includes port matching to ensure redirects target the correct server.
The navbarOpen, sidebarOpen, and fullScreen states have been removed from the appStore store.
presentation and group are have been removed from RELATIONAL_TYPES.
<scope>.delete hookThe <scope>.delete hook was moved before permissions checks, and as such will now trigger regardless of the user permissions. Ensure any necessary permission checks are performed prior to any data processing.
The hook will also use any keys returned in place of the original keys for any subsequent processing.
Content Versioning has been further enhanced to ensure USER_CREATED, USER_UPDATED, DATE_CREATED, and DATE_UPDATED fields now represent the last actual changes, not the promotion metadata.
Additionally, if a non-existent version is requested, the API will now return a Forbidden error instead of defaulting to the main version.
Content Versioning has been updated to correctly return deeply nested relational data and support all query parameters when requesting a version of an item.
Due to these changes the following breaking changes and caveats should be noted:
Breaking Changes
Caveat
id of null until promoted to main.The services exposed to API extensions using TypeScript are now fully typed instead of any, which may cause new type errors when building extensions.
Arguments of service methods are now strictly typed, which can result in type errors for broader types that would not error before:
string and will error on string | undefined (or other unions).service.readOne()/service.readMany() now expect string | number for their primary keys and will error for nullable types::callout{icon="material-symbols:info-outline"}
Workaround
:::collapsible{name="type workarounds"}
If you encounter type errors, you can cast the services to any to restore the previous behavior:
```ts
export default defineHook(({ filter, action }, context) => {
const { services, database: knex, getSchema, logger } = context;
const { ItemsService, FieldsService } = services as any;
...
});
```
::: ::
Snapshots now exclude tables not tracked in directus_collections (database-only tables).
| Source Version | Target Version | Behavior | Impact |
|---|---|---|---|
| < 11.10.0 | ≥ 11.10.0 | Database-only tables from source will be created on target | ⚠️ Tables added |
| ≥ 11.10.0 | < 11.10.0 | Database-only tables will be dropped from target | 🚨 Data loss risk |
| ≥ 11.10.0 | ≥ 11.10.0 | Database-only tables are ignored in snapshots | ✅ No changes |
| < 11.10.0 | < 11.10.0 | Database-only tables may be created or dropped | ⚠️ Depends on the diff between source/target |
Please review your snapshot workflows to ensure these changes will not result in unexpected behaviour.
NODE_ENV is no longer replaced for API extensionsThe NODE_ENV value for API extensions now respects its defined value, rather than being hardcoded to production.
The SDK's login functions now takes a payload object that accepts email/password or identifier/password
property combos to support both local and LDAP provider login.
The new usage is sdk.login({ email, password }) or sdk.login({ identifier, password }) for LDAP instead of sdk.login(email, password).
The SDK's refresh and logout functions now take an options object that accepts the optional mode and
refresh_token values
The new usage for refresh is sdk.request(refresh({ mode: "json", refresh_token })) instead of sdk.request(refresh('json', refresh_token)).
User authentication is required to trigger flows with a manual trigger. For publicly accessible flows, please use a webhook trigger instead.
MySQL 5.7 reached end of life on 31 October 2023, and is no longer supported.
String to IDGraphQL primary key field types have changed from String to ID. If you're using GraphQL queries or mutations that pass primary keys as variables, you’ll need to update those variable types from String to ID to maintain compatibility.
items.create action hook receives final payloadWe now pass the final payload to the items.create action hook, after the filter hooks and preset changes have been applied, instead of the original payload.
The error thrown when a Flow condition operation fails has been changed to FailedValidationError.
Flows that check errors from Flow condition operation failures will need to be updated accordingly.
We've introduced a dedicated directus_comments collection, replacing the previous system that used directus_activity
for comments. While new comment endpoints have been added, existing endpoints remain functional.
Comment primary keys are now UUIDs instead of numeric values, which may impact custom type checking implementations.
The internal comment endpoints in the Directus SDK have been updated to reflect this change. To avoid errors, ensure your Directus version is compatible with the latest SDK when using comment functions.
CommentsService in ExtensionsExtensions using the ActivityService to manage comments should migrate to the new CommentsService.
The SendGrid abstraction for nodemailer is no longer supported, so we have dropped it's usage from Directus. Users of
SendGrid should update their configuration to use their SendGrid account's SMTP Relay configuration instead.
Directus 11 introduces policies, a new concept within access control configuration. Permissions are no longer held in roles, but instead in policies. Policies can be attached to roles and also directly to users.
While users can still only have one direct role, roles can now also be nested within roles. A user's permissions are now an aggregate of all policies attached directly to them, to their role, and any nested roles.
Object properties have changed and moved. This should only impact users who use and rely on the users, roles, and permissions endpoints.
Users now have one additional property - policies, which is a many-to-many relationship to policies.
Roles no longer hold admin_access, app_access, enforce_tfa, or ip_access. These have been moved to policies.
Roles now have one additional property - children, which is a one-to-many relationship to roles.
Permissions are no longer attached to a role. This has been changed to a policy.
If you are requesting fields that do not exist anymore, your requests will throw an error. To fix this, either put fields back into your data model or remove them from the request.
If you are requesting Many-to-Any (M2A) fields without collection name, they will throw an error. To fix this, you need to put the collection name you are targeting. This is true regardless of level or if using REST/GraphQL.
::callout{icon="material-symbols:info-outline"}
Migration/Mitigation
:::collapsible{name="migration steps"}
You could previously request fields in a M2A builder without specifying the collection they came from, for example:
```
GET https://example.directus.app/items/example?fields=items.item.m2a_field
```
This no longer works and you must specify which collection the field is located in:
```
GET https://example.directus.app/items/example?fields=items.item:m2a_collection.m2a_field
```
[Understand the M2A field syntax in our global query parameter page](/guides/connect/query-parameters).
::: ::
usersStoreThe usersStore has a role object that previously contained the admin_access, app_access, and enforce_tfa
properties. These are now returned directly in the user object.
preRegisterCheck Data StructureIf you use the preRegisterCheck guard function in your module extension to determine whether it is shown, it now
receives a different data structure. It previously received a list of permission objects. Now, it receives the same data
returned from the new Get Current User Permissions
endpoint.
mysql with mysql2The database client library mysql has been replaced with
mysql2, which is a continuation of the former. The client is used to connect
to MySQL/MariaDB databases.
If you're using MySQL/MariaDB, please note that:
mysql2 leads to cross-collection queries (filtering on relations) with stricter charset comparison. Therefore,
ensure again that the value of the config option
DB_CHARSET/DB_CHARSET_NUMBER matches the charset of your tables.string instead of a number, which ensures that the precision is
preserved.