A collection of customizable hooks for Directus. This is not an extension, but a library of scripts that could be used inside a Directus hook extension.
First create a Directus Extension and during setup choose the extension type hook.
Inside the extension folder install directus-hook-library:
npm install directus-hook-libraryImport it in src/index.ts, like:
import { setProjectSettingsFromEnvVars } from "directus-hook-library";Have a look at the examples below.
Tip: You can use multiple of these hook scripts inside the same Directus hook.
Used to delete related M2O items that loose their relation and should not be kept, which is not possible via directus itself. This makes sense for a M2O relation that is used like a O2O relation.
Delete all oneCollection items that loose their relationship to a manyCollections item.
(!) Important: You have to specify a (hidden) reverse relationship O2M in your oneCollection inside Directus to make this work.
Example
// src/index.ts
import { defineHook } from "@directus/extensions-sdk";
import { deleteUnusedM2OItems } from "directus-hook-library";
export default defineHook((register, context) => {
deleteUnusedM2OItems(register, context, {
oneCollection: "meta_infos",
manyCollections: {
pages: "pages",
posts: "posts",
},
});
});This replaces the reference to a deleted user with a reference to the current user in the directus_files collection.
Example
// src/index.ts
import { defineHook } from "@directus/extensions-sdk";
import { replaceDeletedUserReferences } from "directus-hook-library";
export default defineHook((register, context) => {
replaceDeletedUserReferences(register, context);
});resetFieldsHiddenByOption
Set fields to null that have a value but are hidden by a condition.
Example
// src/index.ts
import { defineHook } from "@directus/extensions-sdk";
import { resetFieldsHiddenByOption } from "directus-hook-library";
export default defineHook((register, context) => {
resetFieldsHiddenByOption(register, context, {
collection: "conditional",
optionsField: "detail",
resetGroups: [
{
not: ["yes"],
nullify: ["title", "description"],
},
{
not: ["no"],
nullify: ["external_link"],
},
],
});
});Used for setting project settings from ENV vars like, PROJECT_URL.
This overwrites the values for settings in the Project Settings when starting Directus.
Example
// src/index.ts
import { defineHook } from "@directus/extensions-sdk";
import { setProjectSettingsFromEnvVars } from "directus-hook-library";
export default defineHook((register, context) => {
setProjectSettingsFromEnvVars(register, context, [
"project_name",
"project_descriptor",
"project_url",
]);
});For ENV variables like:
PROJECT_NAME=Directus
PROJECT_DESCRIPTOR=Hook
PROJECT_URL=http://localhost:3000This library provides hooks implementing cascading functionality for M2A relations, which is not possible via directus itself.
For more information and configuration details, read the following sections referring to each function.
The configuration object for all M2A type hooks is the same.
Configuration objects are described by:
-
export type JunctionCollection = { collectionName: string; foreignKeyFieldName: string; itemDiscriminatorFieldName: string; };
Described by:
- collectionName: The name of the junction collection (
article_blockin examples) - foreignKeyField: The name of the field containing the foreign key for related any items. (
itemif left as Directus default, left as default in examples) - itemDiscriminatorFieldName: The name of the field containing the table containing the related item. (
collectionif left as Directus default, left as default in examples)
- collectionName: The name of the junction collection (
-
export type AnyCollection = { collectionName: string; primaryKeyFieldName: string; };
Described by:
- collectionName: The name of the junction collection (
text_block,image_blockorvideo_blockin examples) - primaryKeyFieldName: The name of the field containing the primary key, which is used to create a relation of items inside of
junctionCollectioncollections. (idif left as Directus default, left as default in examples)
- collectionName: The name of the junction collection (
Grouped together as an object describing the relation called M2AConfig:
export type M2AConfig = {
anyCollections: AnyCollection[];
junctionCollection: JunctionCollection;
};Two helper functions are provided, reducing configuration repetition if default fields are left as default.
The specified helper functions are:
-
toJunctionCollectionM2A(optional):This functions takes the
collectionNameas string and sets the other properties to Directus defaults.
It is used in the following examples, but customizedJunctionCollectionitems can also be passed.
For details regarding default values see M2A configuration or Directus documentation.export const toJunctionCollectionM2A = (collectionName: string): JunctionCollection => ({ collectionName, foreignKeyFieldName: "item", itemDiscriminatorFieldName: "collection", });
-
toAnyCollectionM2A(optional):This functions takes the
collectionNameas string and sets the other properties to Directus defaults.
It is used in the following examples, but customizedAnyCollectionitems can also be passed.
For details regarding default values see M2A configuration or Directus documentation.export const toAnyCollectionM2A = (collectionName: string): AnyCollection => ({ collectionName, primaryKeyFieldName: "id", });
Used to cascade deletes performed on junctionCollection items to all configured anyCollections (on junctionCollection delete, the related anyCollection item is also deleted).
Each Filter instance is configured with:
- a
Directus register function(see register function) - a
Directus extension context(see context object) - a
M2AConfigobject, containing configuration as specified in M2A configuration On delete of an item inside a configuredjunctionCollectionitem, theanyCollectionitem which the junction collection item relates to is also deleted.
Works likeM2O,O2MorM2Mselection ofDELETE CASCADE.
Examples
Basic in code usage/configuration (configure a single M2A relation):
// src/index.ts
import { defineHook } from "@directus/extensions-sdk";
import {
toJunctionCollectionM2A,
toAnyCollectionM2A,
deleteUnusedItemFilterM2A,
} from "directus-hook-library";
export default defineHook((register, context) => {
deleteUnusedItemFilterM2A(register, context, {
anyCollections: [
"text_block",
"image_block",
"video_block"
].map(toAnyCollectionM2A),
junctionCollection: toJunctionCollectionM2A("article_block")
});
});Advanced in code usage/configuration (configure multiple M2A relations with multiple junction tables referencing identical item pools):
// src/index.ts
import { defineHook } from "@directus/extensions-sdk";
import {
toJunctionCollectionM2A,
toAnyCollectionM2A,
deleteUnusedItemFilterM2A,
} from "directus-hook-library";
const advancedExampleConfig = [
{
configCollections: [
"image_block",
"video_block",
"text_block"
],
configJunctions: [
"article_block",
"blogpost_block"
]
},
{
configCollections: [
"pizza_item",
"pasta_item",
"dessert_item"
],
configJunctions: [
"order_item"
]
},
]
export default defineHook((register, context) => {
for (const relationConfig of advancedExampleConfig) {
for (const junctionCollectionName of relationConfig.configJunctions) {
deleteUnusedItemFilterM2A(register, context, {
anyCollections: relationConfig.configCollections.map(toAnyCollectionM2A),
junctionCollection: toJunctionCollectionM2A(junctionCollectionName)
});
}
}
});Used to cascade deletes performed on anyCollection items to a configured junctionCollection (on anyCollection item delete, relating items in junctionCollection are also deleted).
Each Action instance is configured with:
- a
Directus register function(see register function) - a
Directus extension context(see context object) - a
M2AConfigobject, containing configuration as specified in M2A configuration On delete of an item inside a configuredanycollection,junctionentries pointing to the deletedanyitem are deleted.
Works likeM2O,O2MorM2Mselection ofDELETE CASCADE.
Examples
Basic in code usage/configuration (configure a single M2A relation):
// src/index.ts
import { defineHook } from "@directus/extensions-sdk";
import {
toJunctionCollectionM2A,
toAnyCollectionM2A,
deleteUnusedRelationActionM2A,
} from "directus-hook-library";
export default defineHook((register, context) => {
deleteUnusedRelationActionM2A(register, context, {
anyCollections: [
"text_block",
"image_block",
"video_block"
].map(toAnyCollectionM2A),
junctionCollection: toJunctionCollectionM2A("article_block")
});
});Advanced in code usage/configuration (configure multiple M2A relations with multiple junction tables referencing identical item pools):
// src/index.ts
import { defineHook } from "@directus/extensions-sdk";
import {
toJunctionCollectionM2A,
toAnyCollectionM2A,
deleteUnusedRelationActionM2A,
} from "directus-hook-library";
const advancedExampleConfig = [
{
configCollections: [
"image_block",
"video_block",
"text_block"
],
configJunctions: [
"article_block",
"blogpost_block"
]
},
{
configCollections: [
"pizza_item",
"pasta_item",
"dessert_item"
],
configJunctions: [
"order_item"
]
},
]
export default defineHook((register, context) => {
for (const relationConfig of advancedExampleConfig) {
for (const junctionCollectionName of relationConfig.configJunctions) {
deleteUnusedRelationActionM2A(register, context, {
anyCollections: relationConfig.configCollections.map(toAnyCollectionM2A),
junctionCollection: toJunctionCollectionM2A(junctionCollectionName)
});
}
}
});Used to prevent deletes performed on anyCollection if the related junctionCollection contains a relation to deleted anyCollection item (delete request for junctionCollection item should be prevented).
Each Filter instance is configured with:
- a
Directus register function(see register function) - a
Directus extension context(see context object) - a
M2AConfigobject, containing configuration as specified in M2A configuration On delete request of ananyCollectionitem, configuredjunctionCollectionis checked.
If an item containing a relation still exists, the delete request is denied and the operation is prohibited.
Works likeM2O,O2MorM2Mselection ofDELETE RESTRICT.
Examples
Basic in code usage/configuration (configure a single M2A relation):
// src/index.ts
import { defineHook } from "@directus/extensions-sdk";
import {
toJunctionCollectionM2A,
toAnyCollectionM2A,
preventItemDeleteFilterM2A,
} from "directus-hook-library";
export default defineHook((register, context) => {
preventItemDeleteFilterM2A(register, context, {
anyCollections: [
"text_block",
"image_block",
"video_block"
].map(toAnyCollectionM2A),
junctionCollection: toJunctionCollectionM2A("article_block")
});
});Advanced in code usage/configuration (configure multiple M2A relations with multiple junction tables referencing identical item pools):
// src/index.ts
import { defineHook } from "@directus/extensions-sdk";
import {
toJunctionCollectionM2A,
toAnyCollectionM2A,
preventItemDeleteFilterM2A,
} from "directus-hook-library";
const advancedExampleConfig = [
{
configCollections: [
"image_block",
"video_block",
"text_block"
],
configJunctions: [
"article_block",
"blogpost_block"
]
},
{
configCollections: [
"pizza_item",
"pasta_item",
"dessert_item"
],
configJunctions: [
"order_item"
]
},
]
export default defineHook((register, context) => {
for (const relationConfig of advancedExampleConfig) {
for (const junctionCollectionName of relationConfig.configJunctions) {
preventItemDeleteFilterM2A(register, context, {
anyCollections: relationConfig.configCollections.map(toAnyCollectionM2A),
junctionCollection: toJunctionCollectionM2A(junctionCollectionName)
});
}
}
});Used to prevent deletes performed on junctionCollection if the related anyCollection item still exists (delete request for anyCollection item should be prevented).
Each Filter instance is configured with:
- a
Directus register function(see register function) - a
Directus extension context(see context object) - a
M2AConfigobject, containing configuration as specified in M2A configuration On delete request of ajunctionCollectionitem, configuredanyCollectionsare checked.
If related item still exists, the delete request is denied and the operation is prohibited.
Works likeM2O,O2MorM2Mselection ofDELETE RESTRICT.
Examples
Basic in code usage/configuration (configure a single M2A relation):
// src/index.ts
import { defineHook } from "@directus/extensions-sdk";
import {
toJunctionCollectionM2A,
toAnyCollectionM2A,
preventRelationDeleteFilterM2A,
} from "directus-hook-library";
export default defineHook((register, context) => {
preventRelationDeleteFilterM2A(register, context, {
anyCollections: [
"text_block",
"image_block",
"video_block"
].map(toAnyCollectionM2A),
junctionCollection: toJunctionCollectionM2A("article_block")
});
});Advanced in code usage/configuration (configure multiple M2A relations with multiple junction tables referencing identical item pools):
// src/index.ts
import { defineHook } from "@directus/extensions-sdk";
import {
toJunctionCollectionM2A,
toAnyCollectionM2A,
preventRelationDeleteFilterM2A,
} from "directus-hook-library";
const advancedExampleConfig = [
{
configCollections: [
"image_block",
"video_block",
"text_block"
],
configJunctions: [
"article_block",
"blogpost_block"
]
},
{
configCollections: [
"pizza_item",
"pasta_item",
"dessert_item"
],
configJunctions: [
"order_item"
]
},
]
export default defineHook((register, context) => {
for (const relationConfig of advancedExampleConfig) {
for (const junctionCollectionName of relationConfig.configJunctions) {
preventRelationDeleteFilterM2A(register, context, {
anyCollections: relationConfig.configCollections.map(toAnyCollectionM2A),
junctionCollection: toJunctionCollectionM2A(junctionCollectionName)
});
}
}
});