Purge legacy docs (#25594)

This commit is contained in:
ian
2025-08-05 01:44:13 +08:00
committed by GitHub
parent 12cad6ffd6
commit f855178ec9
303 changed files with 8 additions and 73930 deletions

View File

@@ -1,30 +0,0 @@
name: Docs
on:
pull_request:
branches:
- main
- v11-rc
paths:
- docs/**
- .github/workflows/docs.yml
concurrency:
group: docs-${{ github.ref }}
cancel-in-progress: true
jobs:
spellcheck:
name: Check Spelling
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Prepare
uses: ./.github/actions/prepare
with:
build: false
- name: Lint
run: pnpm --filter docs spellcheck

1
.gitignore vendored
View File

@@ -51,4 +51,3 @@ TODO
/tests/blackbox/server-log-*
/tests/blackbox/sequencer-data.json
/packages/extensions-sdk/temp-extension-*/
/docs/packages/

View File

@@ -7,6 +7,5 @@ pnpm-lock.yaml
/.changeset/*.md
/app/src/lang/translations/*.yaml
!/app/src/lang/translations/en-US.yaml
/docs/packages/
/api/uploads/
/api/extensions/

View File

@@ -3,13 +3,5 @@
"printWidth": 120,
"singleQuote": true,
"proseWrap": "always",
"htmlWhitespaceSensitivity": "ignore",
"overrides": [
{
"files": "docs/**/*.md",
"options": {
"embeddedLanguageFormatting": "off"
}
}
]
"htmlWhitespaceSensitivity": "ignore"
}

View File

@@ -14,7 +14,7 @@
}
],
"ignoreFiles": ["app/src/__histoire__/**", "docs/**"],
"ignoreFiles": ["app/src/__histoire__/**"],
"rules": {
"alpha-value-notation": "number",

View File

@@ -1,6 +0,0 @@
files:
- '**/*.md'
- '!node_modules'
- '!packages'
dictionaries:
- dictionary.txt

View File

@@ -1,24 +0,0 @@
# Help
## Add a Package
To add packages to the Typedocs in the Docs, just follow these steps.
1. In the package itself, add a TypeDoc config to the TSConfig file.
```diff
{
"extends": "@directus/tsconfig/node22",
"compilerOptions": {
"outDir": "dist"
},
"include": ["src"],
+ "typedocOptions": {
+ "entryPoints": ["./src"]
+ }
}
```
2. Add the entry point to the `options.json` file in this folder.
3. Update the link to the package in the `docs/contributing/codebase-overview.md` file.

View File

@@ -1,11 +0,0 @@
# Packages TypeDocs
Welcome to the Directus Packages TypeDocs!
Here you will find comprehensive documentation for each package in the Directus ecosystem.
TypeDocs provide detailed information about the TypeScript types, interfaces, functions, and more available in each
package.
Whether you are developing custom extensions, building integrations, or extending Directus functionality, the TypeDocs
will guide you through the available APIs and help you write type-safe code.

View File

@@ -1,28 +0,0 @@
{
"entryPoints": [
"../../packages/composables",
"../../packages/constants",
"../../packages/errors",
"../../packages/extensions-sdk",
"../../packages/format-title",
"../../packages/memory",
"../../packages/pressure",
"../../packages/random",
"../../sdk",
"../../packages/stores",
"../../packages/system-data",
"../../packages/themes",
"../../packages/types",
"../../packages/update-check"
],
"name": "Directus",
"entryPointStrategy": "packages",
"plugin": ["typedoc-plugin-markdown", "typedoc-vitepress-theme", "typedoc-plugin-frontmatter", "typedoc-plugin-zod"],
"out": "../packages",
"docsRoot": "/",
"readme": "./home.md",
"hideGenerator": true,
"frontmatterGlobals": {
"editLink": false
}
}

View File

@@ -1,95 +0,0 @@
<script setup lang="ts">
defineProps<{
title: string;
tag?: string;
url: string;
img: string;
author?: string;
date?: string;
desc?: string;
}>();
</script>
<template>
<div class="article">
<a :href="url">
<div class="image">
<img :src="img" alt="" loading="lazy" />
</div>
<div class="content">
<h3 class="heading">{{ title }}</h3>
<p v-if="desc" class="description">{{ desc }}</p>
<div v-if="author" class="author">
<span>{{ author }}</span>
<span></span>
<span class="date">{{ date }}</span>
</div>
</div>
</a>
</div>
</template>
<style scoped>
.article {
display: flex;
flex-direction: column;
height: 100%;
}
.article a {
display: flex;
flex-direction: column;
height: 100%;
}
.content {
flex-grow: 1;
display: flex;
flex-direction: column;
}
.image {
width: 100%;
border-radius: 8px;
aspect-ratio: 16 / 9;
overflow: hidden;
}
.image img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center center;
transition: scale 150ms ease-out;
}
.dark .image img {
filter: brightness(0.9);
}
.heading {
font-weight: 600;
margin-top: 12px;
}
.description {
color: var(--vp-c-text-2);
padding-inline-end: 8px;
}
.author {
color: var(--vp-c-gray);
display: flex;
gap: 0.5em;
margin-top: 0.25em;
font-size: 0.8em;
}
.article:hover .heading {
text-decoration: underline;
}
.article:hover .image img {
scale: 1.05;
}
</style>

View File

@@ -1,39 +0,0 @@
<script setup lang="ts">
defineProps<{
image: string;
name: string;
title: string;
}>();
</script>
<template>
<div class="avatar">
<img :src="image" :alt="name" />
<div>
<div class="avatar_name">{{ name }}</div>
<div>{{ title }}</div>
</div>
</div>
</template>
<style scoped>
.avatar {
position: relative;
display: flex;
align-items: center;
column-gap: 1rem;
}
.avatar > img {
height: 2.5rem;
object-fit: cover;
object-position: top;
width: 2.5rem;
border-radius: 9999px;
background-color: var(--vp-c-gray);
}
.avatar_name {
font-weight: 700;
}
</style>

View File

@@ -1,28 +0,0 @@
<script setup lang="ts">
withDefaults(
defineProps<{
as?: string;
}>(),
{
as: 'p',
},
);
</script>
<template>
<component :is="as" class="badge">
<slot />
</component>
</template>
<style scoped>
.badge {
color: var(--vp-c-pink);
font-size: 14px;
font-weight: 700;
text-transform: uppercase;
}
.badge :deep(*) {
color: inherit !important;
}
</style>

View File

@@ -1,78 +0,0 @@
<script setup lang="ts">
import { computed, type Component } from 'vue';
const props = defineProps<{
secondary?: boolean;
href?: string;
external?: boolean;
icon?: Component;
}>();
const component = computed(() => {
if (props.href) return 'a';
return 'button';
});
const additionalProps = computed(() => {
if (component.value === 'a') {
return {
...(props.href && { href: props.href }),
...(props.external && {
target: '_blank',
rel: 'noopener noreferrer',
}),
};
}
return {};
});
</script>
<template>
<component :is="component" class="button" :class="{ primary: !secondary, secondary }" v-bind="additionalProps">
<slot />
<component :is="icon" v-if="icon" class="icon" />
</component>
</template>
<style scoped>
.button {
display: inline-flex;
align-items: center;
border: 1px solid;
border-color: var(--vp-c-divider);
border-radius: 12px;
color: var(--vp-c-text-1);
font-weight: 600;
font-size: 14px;
margin-top: 10px;
padding: 4px 12px;
}
.primary {
background: white;
color: #0e1c2f;
font-size: 16px;
padding: 12px 16px;
}
.primary:hover {
opacity: 1;
background-color: #f0f4f9;
}
.secondary {
border: none;
padding: 16px;
color: var(--white);
opacity: 0.8;
}
.secondary:hover {
opacity: 1;
}
.icon {
margin-left: 6px;
}
</style>

View File

@@ -1,112 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue';
const props = withDefaults(
defineProps<{
title: string;
h?: '1' | '2' | '3' | '4' | '5';
text: string;
icon?: string;
url?: string;
addMargin?: boolean;
}>(),
{
h: '2',
icon: 'link',
},
);
const tagType = computed(() => (props.url ? 'a' : 'div'));
const headerType = computed(() => 'h' + props.h);
const iconIsImage = computed(() => props.icon.startsWith('/'));
</script>
<template>
<component :is="tagType" :href="url" class="card" :class="{ margin: addMargin }">
<div v-if="icon" class="icon">
<img v-if="iconIsImage" :src="icon" alt="" />
<span v-else mi translate="no">{{ icon }}</span>
</div>
<div class="text">
<component :is="headerType">{{ title }}</component>
<p>{{ text }}</p>
</div>
</component>
</template>
<style scoped>
.card {
display: flex;
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 24px;
width: 100%;
transition: border-color 0.25s;
gap: 20px;
}
.card.margin {
margin: 1rem 0;
}
.card:hover {
border-color: var(--vp-c-brand);
text-decoration: none;
}
.icon {
width: 54px;
height: 54px;
background: var(--vp-c-purple-dimm-3);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.icon:has(img) {
background: var(--vp-c-bg-soft-up);
}
.icon span[mi] {
font-size: 24px;
font-variation-settings:
'opsz' 24,
'wght' 500;
color: var(--vp-c-purple);
}
.icon img {
width: 24px;
height: 24px;
object-fit: contain;
object-position: center center;
box-shadow: none;
border-radius: 0;
}
h1,
h2,
h3,
h4,
h5 {
font-size: 16px;
font-weight: 600;
line-height: 24px;
padding: 0;
margin: 0;
border: none;
color: var(--vp-c-text-1);
margin-bottom: 4px;
}
p {
font-size: 14px;
margin: 0;
padding: 0;
color: var(--vp-c-text-2);
line-height: 22px;
}
</style>

View File

@@ -1,48 +0,0 @@
<template>
<div id="cl">
<h2>Directus Cloud</h2>
<p>Everything you need to start building. Provisioned in 90 seconds. Starting at $15/month.</p>
<a href="https://directus.cloud">Get Started</a>
</div>
</template>
<style scoped>
#cl {
background-color: var(--vp-c-brand-darkest);
color: white;
border-radius: var(--rounded-lg);
padding: 1rem;
}
h2 {
font-size: 1.125rem;
text-wrap: balance;
font-weight: bold;
}
p {
font-size: 0.75rem;
line-height: 1.125rem;
margin: 0.75rem 0;
font-weight: 500;
}
a {
font-size: 0.8rem;
text-align: center;
background-color: var(--vp-c-brand);
font-weight: 600;
color: white;
border-radius: var(--rounded-full);
border: none;
display: block;
width: 100%;
padding: 0.5em;
margin-top: 1rem;
cursor: pointer;
}
a:hover {
background-color: var(--vp-c-brand-light);
}
</style>

View File

@@ -1,10 +0,0 @@
<template>
<div class="divider" />
</template>
<style scoped>
.divider {
height: 1px;
background: var(--vp-c-divider);
}
</style>

View File

@@ -1,209 +0,0 @@
<script setup lang="ts">
import { ref, reactive } from 'vue';
const props = defineProps<{ title: string; url: string }>();
const loading = ref(false);
const error = ref<unknown>(null);
const success = ref(false);
const feedback = reactive<{
id?: string;
rating?: number | undefined;
comments: string;
}>({ comments: '' });
const prompts = [
'Make it count',
'Leave some feedback',
'Help us improve',
`We're all ears 🐰`,
'Tell us what is missing',
'Your thoughts matter to us',
'Feedback is a gift',
'What do you think?',
];
function getPrompt() {
return prompts[Math.floor(Math.random() * prompts.length)];
}
const ratingOptions = [
{ label: 'Worst Doc Ever 🗑️', value: 1, message: 'Woof! 🤦‍♂️ Sorry about that. How do we fix it?' },
{ label: 'Not Helpful 😡', value: 2, message: '🧐 Help us do better. How can we improve this article?' },
{ label: 'Helpful 😃', value: 3, message: 'Nice! 👍 Anything we can improve upon?' },
{
label: 'Super Helpful 🤩',
value: 4,
message: `Awesome! The whole team is rejoicing in celebration! 🥳🎉🎊 Anything you'd like to say to them?`,
},
];
function getRatingOption(rating: number) {
return ratingOptions.find((option) => option.value === rating);
}
async function handleSubmission(rating?: number) {
loading.value = true;
if (rating) feedback.rating = rating;
const body = {
id: feedback.id,
rating: feedback.rating,
comments: feedback.comments,
title: props.title,
url: props.url,
};
try {
const response = await fetch('/api/feedback', {
method: 'POST',
body: JSON.stringify(body),
});
const data = await response.json();
feedback.id = data.id;
// If the reponse has comments, we can assume they've completed the second step.
if (data.comments) {
success.value = true;
}
} catch (err) {
error.value = err;
} finally {
loading.value = false;
}
}
</script>
<template>
<div class="wrapper">
<Transition name="fade" mode="out-in">
<div v-if="!feedback.rating" class="step">
<div>
<div>
<p class="desc">{{ getPrompt() }}</p>
<p class="heading">How helpful was this article?</p>
</div>
</div>
<div class="button-container">
<button v-for="item in ratingOptions" :key="item.value" class="btn" @click="handleSubmission(item.value)">
<span>{{ item.label }}</span>
</button>
</div>
</div>
<div v-else-if="feedback.rating && !success" class="step">
<div>
<p class="desc">This article is</p>
<div>
<span>{{ getRatingOption(feedback.rating)?.label }}</span>
<button style="margin-left: 0.5rem" class="btn" @click="feedback.rating = undefined">
<span mi icon translate="no">close</span>
</button>
</div>
</div>
<p class="heading">{{ getRatingOption(feedback.rating)?.message }}</p>
<textarea v-model="feedback.comments" autofocus class="input" />
<button class="btn btn-primary" :disabled="!feedback.comments" @click="handleSubmission()">
Send Us Your Feedback
</button>
</div>
<div v-else class="step">
<p class="heading">Thanks for your feedback!</p>
</div>
</Transition>
</div>
</template>
<style scoped>
.step > * + * {
margin-top: 1rem;
}
.btn {
border: 1px solid var(--vp-c-divider);
background-color: var(--vp-c-bg);
border-radius: 8px;
transition:
border-color 0.25s,
background-color 0.25s;
display: inline-block;
font-size: 14px;
font-weight: 500;
line-height: 1.5;
margin: 0;
padding: 0.375rem 0.75rem;
text-align: center;
vertical-align: middle;
white-space: nowrap;
}
.btn:disabled {
opacity: 0.5;
}
.btn:hover {
border-color: var(--vp-c-brand);
}
.btn-primary {
color: #fff;
background-color: var(--vp-c-brand);
border-color: var(--vp-c-brand);
}
.btn-primary:hover {
background-color: var(--vp-c-brand-darker);
border-color: var(--vp-c-brand-darker);
}
.heading {
font-size: 1.2rem;
font-weight: 700;
}
.button-container {
display: grid;
grid-gap: 0.5rem;
}
.wrapper {
margin: 2rem 0;
padding: 1.5rem;
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-alt);
}
.input {
width: 100%;
height: 100px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 0.375rem 0.75rem;
}
.desc {
display: block;
line-height: 20px;
font-size: 12px;
font-weight: 500;
color: var(--vp-c-text-2);
}
@media screen and (min-width: 768px) {
.button-container {
grid-template-columns: repeat(4, 1fr);
}
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.25s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>

View File

@@ -1,29 +0,0 @@
<script setup lang="ts">
import Meta from './Meta.vue';
import { useData } from 'vitepress';
import { marked } from 'marked';
const { page } = useData();
</script>
<template>
<blockquote>
<!-- eslint-disable-next-line vue/no-v-html -->
<p v-if="page.frontmatter['description']" v-html="marked(page.frontmatter['description'])" />
</blockquote>
<Meta title-left="Author" title-right="Directus Version">
<template #left>
<div class="data">{{ page.frontmatter['author'] }}</div>
</template>
<template #right>
<div class="data">{{ page.frontmatter['directus_version'] }}</div>
</template>
</Meta>
</template>
<style scoped>
.data {
font-weight: 700;
}
</style>

View File

@@ -1,79 +0,0 @@
<script setup lang="ts">
defineProps<{ titleLeft?: string; titleRight?: string; titleBottom?: string }>();
defineSlots<{
left(): any;
right(): any;
bottom(): any;
}>();
</script>
<template>
<div class="wrapper">
<div class="top">
<div v-if="$slots['left']">
<span class="sm-gray-text spacing">{{ titleLeft }}</span>
<slot name="left" />
</div>
<div v-if="$slots['right']" class="right">
<span class="sm-gray-text spacing">{{ titleRight }}</span>
<slot name="right" />
</div>
</div>
<div v-if="$slots['bottom']" class="bottom">
<span class="sm-gray-text">{{ titleBottom }}</span>
<slot name="bottom" />
</div>
</div>
</template>
<style scoped>
.wrapper {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 11px 16px 9px;
}
.sm-gray-text {
color: var(--vp-c-gray-light-1);
font-size: 0.75rem;
font-weight: 500;
}
.spacing + :deep(*) {
margin-top: 4px;
}
.bottom {
display: flex;
border-top: 1px solid var(--vp-c-divider);
flex-wrap: wrap;
margin-top: 0.75em;
padding-top: 0.5em;
align-items: baseline;
gap: 0.75em;
}
.bottom span {
font-weight: 500;
color: var(--vp-c-gray-light-1);
}
.top > * + * {
margin-top: 1em;
}
@media only screen and (min-width: 768px) {
.top {
display: flex;
justify-content: space-between;
}
.top > * + * {
margin-top: 0;
}
.right {
text-align: right;
}
}
</style>

View File

@@ -1,118 +0,0 @@
<script setup>
import { onMounted } from 'vue';
onMounted(() => {
const script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'https://js.hsforms.net/forms/embed/v2.js';
script.onload = () => {
// @ts-ignore - HubSpot is not typed
hbspt.forms.create({
region: 'na1',
portalId: '20534155',
formId: 'd57a69e4-6f43-4768-a600-5f7d30306260',
target: '#nl-form',
});
};
document.body.appendChild(script);
});
</script>
<template>
<div id="nl">
<h2>Newsletter</h2>
<p>Get insights, releases, and updates delivered directly to your inbox once a month.</p>
<div id="nl-form"></div>
</div>
</template>
<style scoped>
#nl {
background-color: var(--vp-c-purple-dimm-3);
border-radius: var(--rounded-lg);
padding: 1rem;
border: 1px solid var(--vp-c-purple-dimm-1);
}
h2 {
color: var(--vp-c-purple);
font-size: 1.125rem;
text-wrap: balance;
font-weight: bold;
}
p {
font-size: 0.75rem;
line-height: 1.125rem;
margin: 0.75rem 0;
font-weight: 500;
}
:deep(input) {
border-radius: var(--rounded-md);
background-color: var(--vp-c-bg);
display: block;
width: 100%;
padding: 0.5em 1em;
border: 1px solid var(--vp-c-divider);
}
:deep(input.invalid) {
border-color: var(--vp-c-red);
}
:deep(input::placeholder) {
color: var(--vp-c-gray-light-2);
}
:deep(input:focus) {
border-color: var(--vp-c-purple);
}
:deep(input[type='submit']) {
background-color: var(--vp-c-brand);
font-weight: 600;
color: white;
border-radius: var(--rounded-full);
border: none;
display: block;
width: 100%;
padding: 0.5em;
margin-top: 1em;
cursor: pointer;
}
:deep(input[type='submit']:hover) {
background-color: var(--vp-c-brand-light);
}
:deep(.hs_recaptcha .input .grecaptcha-badge) {
box-shadow: none !important;
height: 44px !important;
transform: scale(0.735);
transform-origin: top left;
margin-top: 0.5rem;
}
:deep(.hs-error-msgs) {
margin: 0;
padding: 0;
list-style: none;
color: var(--vp-c-red);
font-style: italic;
margin-block-start: var(--space-1);
}
:deep(.hs-error-msgs li) {
line-height: 1rem;
font-size: 0.75rem;
margin-top: 0.5rem;
}
/* Hide main error message if mail field already has one */
:deep(form:has(.hs-form-field .hs-error-msgs) .hs_error_rollup) {
display: none;
}
</style>

View File

@@ -1,184 +0,0 @@
<script setup lang="ts">
import { nextTick, onMounted, ref, watch, type Ref } from 'vue';
import { useLocalStorage } from '../composables/useLocalStorage';
const props = defineProps<{
choices: string[];
group?: string;
maintainHeight?: boolean;
}>();
const selected = ref<string>();
let storage: Ref<string | null> | undefined;
// Get local storage on client side (preventing SSR <-> Client mismatch & initial flash)
onMounted(() => {
const defaultValue = props.choices[0];
if (props.group) {
storage = useLocalStorage(`toggler-${props.group}`);
const initialValue = storage.value;
selected.value = initialValue && props.choices.includes(initialValue) ? initialValue : defaultValue;
watch(storage, (value) => {
if (!value || !props.choices.includes(value)) return;
selected.value = value;
});
} else {
selected.value = defaultValue;
}
});
const changeSelected = async (choice: string, el: HTMLElement) => {
const previousRelativeOffset = el.offsetTop - document.documentElement.scrollTop;
(storage ?? selected).value = choice;
if (props.group) {
await nextTick();
const newRelativeOffset = el.offsetTop - document.documentElement.scrollTop;
document.documentElement.scrollTop += newRelativeOffset - previousRelativeOffset;
}
};
</script>
<template>
<div class="snippet-toggler">
<div class="header">
<div class="buttons">
<button
v-for="choice in choices"
:key="choice"
class="button"
:class="{ active: choice === selected }"
@click="changeSelected(choice, $el)"
>
{{ choice }}
</button>
</div>
</div>
<div class="content-area">
<template v-for="choice in choices" :key="choice">
<div
v-if="maintainHeight || choice === selected"
:class="['content', { 'maintain-height': maintainHeight, active: maintainHeight && choice === selected }]"
>
<slot :name="choice.toLowerCase().split(' ').join('-')"></slot>
</div>
</template>
</div>
</div>
</template>
<style scoped lang="scss">
.snippet-toggler {
--snippet-toggler-border-color: var(--vp-c-gray-light-4);
--snippet-toggler-button-color: var(--vp-c-gray);
--snippet-toggler-button-active-color: var(--vp-c-black);
}
html.dark .snippet-toggler {
--snippet-toggler-border-color: transparent;
--snippet-toggler-button-color: var(--vp-c-gray-light-2);
--snippet-toggler-button-active-color: var(--vp-c-gray-light-4);
}
.snippet-toggler {
overflow: hidden;
background: linear-gradient(172.36deg, rgba(228, 234, 241, 0.1) -5.49%, rgba(228, 234, 241, 0) 123.05%);
border: 1px solid var(--snippet-toggler-border-color);
.header {
background: linear-gradient(172.36deg, rgba(228, 234, 241, 0.1) -5.49%, rgba(228, 234, 241, 0) 123.05%);
color: var(--vp-c-gray-light-2);
border-bottom: 1px solid var(--snippet-toggler-border-color);
height: 40px;
display: flex;
align-items: center;
padding: 24px;
.buttons {
display: flex;
gap: 0.5em;
.button {
padding: 0.25em 0.75em;
color: var(--snippet-toggler-button-color);
&.active {
color: var(--snippet-toggler-button-active-color);
background: var(--vp-c-mute);
border-radius: var(--rounded-lg);
}
}
}
}
.content-area {
scrollbar-width: thin;
overflow-y: auto;
tab-size: 2;
display: grid;
grid-template-columns: 100%;
:deep(.lang) {
display: none;
}
}
.content {
--padding-y: 24px;
--padding-x: 8px;
padding-top: var(--padding-x);
padding-bottom: var(--padding-x);
&:not(.maintain-height) {
padding-inline: var(--padding-y);
}
&.maintain-height {
overflow: hidden;
grid-row-start: 1;
grid-column-start: 1;
visibility: hidden;
width: 0;
mask-image: linear-gradient(
to right,
transparent,
black var(--padding-y),
black calc(100% - var(--padding-y)),
transparent
),
linear-gradient(
to top,
black,
black calc(2 * var(--padding-x)),
transparent calc(2 * var(--padding-x)),
transparent
);
&.active {
visibility: visible;
width: 100%;
overflow: auto;
}
:deep(.line) {
padding-inline: var(--padding-y);
}
}
}
}
@media (min-width: 640px) {
.snippet-toggler {
border-radius: 12px;
}
}
</style>

View File

@@ -1,90 +0,0 @@
<script setup lang="ts">
import { ref } from 'vue';
const props = defineProps<{
tabs: string[];
}>();
const activeTab = ref(props.tabs[0]);
</script>
<template>
<div class="tabs">
<div role="tablist" class="tab-buttons">
<template v-for="tab in tabs" :key="tab">
<button
type="button"
role="tab"
:aria-selected="activeTab === tab"
:class="{ active: activeTab === tab }"
@click="activeTab = tab"
>
{{ tab }}
</button>
</template>
</div>
<div>
<template v-for="tab in tabs" :key="tab">
<div v-if="activeTab === tab" role="tabpanel" class="tab-content">
<slot :name="tab.toLowerCase().replaceAll(' ', '-')" />
</div>
</template>
</div>
</div>
</template>
<style lang="scss" scoped>
.tab-buttons {
display: flex;
justify-content: center;
max-width: 560px;
margin-inline: auto;
padding: 12px;
box-shadow: 0 5px 10px 0 rgba(23, 41, 64, 0.1);
border-radius: 2em;
width: 100%;
button {
color: var(--vp-c-text-2);
cursor: pointer;
border: none;
font-size: 18px;
font-weight: bold;
width: 100%;
padding: 12px;
&:hover {
color: var(--vp-c-text-1);
}
&.active {
background: var(--vp-c-brand-darkest);
color: white;
border-radius: 10em;
width: 100%;
}
}
}
.tab-content {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
padding-top: 30px;
}
@media only screen and (max-width: 1200px) {
.tab-content {
grid-template-columns: 1fr 1fr;
}
}
@media only screen and (max-width: 768px) {
.tab-content {
grid-template-columns: 1fr;
}
.tab-buttons button {
font-size: 16px;
}
}
</style>

View File

@@ -1,33 +0,0 @@
<template>
<section class="hero">
<h1>Directus Developer Blog</h1>
<p>Project tutorials, tips & tricks, and best practices from the Directus team and community.</p>
<Button href="/blog/guest-author" primary>Become a Guest Author</Button>
</section>
</template>
<style scoped>
.hero {
padding: 32px 64px;
}
.hero h1 {
font-size: 60px;
font-weight: 700;
line-height: 1;
margin: 20px 0;
}
.hero p {
max-inline-size: 50ch;
margin: 20px 0;
}
@media screen and (max-width: 1200px) {
.hero {
padding-left: 32px;
padding-right: 32px;
}
.hero h1 {
font-size: 42px;
}
}
</style>

View File

@@ -1,54 +0,0 @@
<script setup lang="ts">
import { data } from '../../data/blog.data.js';
import { getFriendlyDate } from '../../lib/date.js';
import Badge from '../Badge.vue';
</script>
<template>
<section class="container">
<Badge>All Articles</Badge>
<div class="listing">
<Article
v-for="article in data.blog.articles"
:key="article.id"
:title="article.title"
:author="article.author.first_name + ' ' + article.author.last_name"
:date="getFriendlyDate(article.date_published)"
:url="`/blog/${article.id}`"
:img="`https://marketing.directus.app/assets/${article.image}?key=card`"
/>
</div>
</section>
</template>
<style scoped>
.container {
padding: 24px 64px;
}
.listing {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 32px;
margin-top: 8px;
}
@media only screen and (min-width: 768px) and (max-width: 1200px) {
.listing {
grid-template-columns: repeat(2, 1fr);
}
}
@media only screen and (max-width: 1200px) {
.container {
padding-left: 24px;
padding-right: 24px;
}
}
@media only screen and (max-width: 768px) {
.listing {
grid-template-columns: 1fr;
}
}
</style>

View File

@@ -1,88 +0,0 @@
<script setup lang="ts">
import { getFriendlyDate } from '../../lib/date.js';
import Avatar from '../Avatar.vue';
import Meta from '../Meta.vue';
import Tag from './Tag.vue';
interface PostMetaProps {
params: {
title: string;
slug: string;
date_published: string;
author: {
first_name?: string;
last_name?: string;
avatar?: string;
title?: string;
};
tags: {
title: string;
slug: string;
}[];
contributors?: Contributor[];
};
}
interface Contributor {
name: string;
url?: string;
}
defineProps<PostMetaProps>();
function getTag(person: Contributor) {
if (person.url) {
return {
tag: 'a',
props: {
href: person.url,
target: '_blank',
rel: 'noopener noreferrer',
style: { display: 'block' },
},
};
}
return {
tag: 'div',
props: {},
};
}
</script>
<template>
<p class="date">Published {{ getFriendlyDate(params.date_published) }}</p>
<Meta title-left="Written By" title-right="With Thanks To" title-bottom="Tags">
<template #left>
<Avatar
:image="`https://marketing.directus.app/assets/${params.author.avatar}?key=circle`"
:name="params.author.first_name + ' ' + params.author.last_name"
:title="params.author.title ?? 'Contributor'"
/>
</template>
<template v-if="params.contributors" #right>
<template v-for="(person, _personIdx) in params.contributors" :key="_personIdx">
<component :is="getTag(person).tag" v-bind="getTag(person).props">
{{ person.name }}
</component>
</template>
</template>
<template v-if="params.tags.length > 0" #bottom>
<Tag v-for="tag in params.tags" :key="tag.slug" class="tag" :href="`/blog/tags/${tag.slug}`" as="a">
{{ tag.title }}
</Tag>
</template>
</Meta>
</template>
<style scoped>
.date {
text-transform: capitalize;
color: var(--vp-c-gray);
}
.dark .date {
color: var(--vp-c-gray-light-1);
}
</style>

View File

@@ -1,34 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue';
const props = withDefaults(
defineProps<{
as: string;
href: string;
}>(),
{
as: 'div',
},
);
const tagType = computed(() => (props.href ? 'a' : 'div'));
const tagProps = computed(() => (props.href ? { href: props.href } : {}));
</script>
<template>
<component :is="tagType" class="badge" v-bind="tagProps">
<slot />
</component>
</template>
<style scoped>
.badge {
font-weight: 500;
color: var(--vp-c-gray);
display: flex;
font-size: 0.75em;
}
.dark .badge {
color: var(--vp-c-gray-light);
}
</style>

View File

@@ -1,56 +0,0 @@
<script setup lang="ts">
import { getFriendlyDate } from '../../lib/date.js';
import type { DocsTag, DeveloperArticle } from '../../types/schema.js';
import Badge from '../Badge.vue';
defineProps<{ tag: DocsTag & { articles: DeveloperArticle[] } }>();
</script>
<template>
<section class="container">
<Badge>Posts tagged {{ tag.title }}</Badge>
<div class="listing">
<Article
v-for="article in tag.articles"
:key="article.slug"
:title="article.title"
:author="article.author.first_name + ' ' + article.author.last_name"
:date="getFriendlyDate(article.date_published)"
:url="`/blog/${article.slug}`"
:img="`https://marketing.directus.app/assets/${article.image}?key=card`"
/>
</div>
</section>
</template>
<style scoped>
.container {
padding: 24px 64px;
}
.listing {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 32px;
margin-top: 8px;
}
@media only screen and (min-width: 768px) and (max-width: 1200px) {
.listing {
grid-template-columns: repeat(2, 1fr);
}
}
@media only screen and (max-width: 1200px) {
.container {
padding-left: 24px;
padding-right: 24px;
}
}
@media only screen and (max-width: 768px) {
.listing {
grid-template-columns: 1fr;
}
}
</style>

View File

@@ -1,26 +0,0 @@
<script setup lang="ts">
import { gettingStarted, sections } from '@/data/guides.js';
import GuidesSection from './GuidesSection.vue';
</script>
<template>
<section class="hero">
<div>
<h1>Directus Guides</h1>
<p>Our official guides to help you get started, integrate, and make the most of Directus.</p>
</div>
</section>
<div class="box">
<h2>Getting Started</h2>
<ul>
<li v-for="item in gettingStarted" :key="item.path">
<a :href="item.path">{{ item.display }}</a>
</li>
</ul>
</div>
<section v-for="[name, section] in Object.entries(sections)" :key="name">
<h2>{{ section.title }}</h2>
<GuidesSection :section="section" />
</section>
</template>

View File

@@ -1,19 +0,0 @@
<script setup lang="ts">
import { sections } from '@/data/guides.js';
const props = defineProps<{
type: string;
}>();
const extensionsGuides = sections.extensions;
const section = extensionsGuides.blocks.find((b) => b.title == props.type);
const guides = section?.items;
</script>
<template>
<ul>
<li v-for="item in guides" :key="item.path">
<a :href="item.path">{{ item.display }}</a>
</li>
</ul>
</template>

View File

@@ -1,59 +0,0 @@
<script setup lang="ts">
import { sections } from '@/data/guides';
type Keys = keyof typeof sections;
type Sections = (typeof sections)[Keys];
defineProps<{ section: Sections }>();
</script>
<template>
<div class="boxes" :style="`column-count: ${section.cols}`">
<div v-for="block of section.blocks" :key="block.title" class="box">
<h3>{{ block.title }}</h3>
<ul>
<li v-for="item in block.items" :key="item.display">
<a v-if="'path' in item" :href="item.path">{{ item.display }}</a>
<span v-else>
<span>{{ item.display }}:</span>
<a v-for="variant of item.paths" :key="variant.path" :href="variant.path">{{ variant.label }}</a>
</span>
</li>
</ul>
</div>
</div>
</template>
<style scoped>
.boxes {
column-gap: 20px;
}
.box {
break-inside: avoid;
margin-bottom: 20px;
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 24px;
}
.box h2,
.box h3 {
margin-top: 0;
padding-top: 0;
border: none;
}
.box ul {
margin-bottom: 0;
}
.box a {
font-weight: inherit;
}
.box li span a {
margin-left: 0.5em;
}
@media screen and (max-width: 1200px) {
.boxes {
column-count: 1 !important;
}
}
</style>

View File

@@ -1,134 +0,0 @@
<script setup lang="ts">
import Divider from '../Divider.vue';
import SocialIcon from './SocialIcon.vue';
import Logo from './icons/Logo.vue';
const currentYear = new Date().getFullYear();
</script>
<template>
<footer>
<div class="container">
<div class="content">
<Logo class="logo" />
<ul class="social">
<li>
<SocialIcon url="https://www.linkedin.com/company/directus-io" icon="linkedin" />
</li>
<li>
<SocialIcon url="https://hub.docker.com/r/directus/directus" icon="docker" />
</li>
<li>
<SocialIcon url="https://www.npmjs.com/package/directus" icon="npm" />
</li>
<li>
<SocialIcon url="https://www.youtube.com/c/DirectusVideos" icon="youtube" />
</li>
<li>
<SocialIcon url="https://directus.chat" icon="discord" />
</li>
<li>
<SocialIcon url="https://twitter.com/directus" icon="twitter" />
</li>
<li>
<SocialIcon url="https://github.com/directus" icon="github" />
</li>
</ul>
<ul class="links">
<li>
<a href="https://github.com/directus/directus/blob/main/license" target="_blank" rel="noreferrer noopener">
License
</a>
</li>
<li><a href="https://directus.io/terms/" target="_blank" rel="noreferrer noopener">Terms</a></li>
<li><a href="https://directus.io/privacy/" target="_blank" rel="noreferrer noopener">Privacy</a></li>
</ul>
</div>
<Divider />
<small>
<a href="https://monospace.io/" target="_blank" rel="noreferrer noopener">
&copy; {{ currentYear }} Monospace Inc
</a>
</small>
</div>
</footer>
</template>
<style scoped>
footer {
padding-bottom: 60px;
padding: 24px 32px;
}
.container {
max-width: 1000px;
margin-inline: auto;
}
a {
color: var(--footer-link-color);
}
.content ul {
display: flex;
list-style: none;
font-weight: 600;
justify-content: space-between;
gap: 12px;
max-width: 500px;
}
.content .logo,
.content ul {
margin-inline: auto;
margin-block-end: 24px;
}
.content:deep(.logo) {
margin: 0 auto 2em;
}
@media screen and (min-width: 1400px) {
.content {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
align-items: center;
}
.content ul {
margin: 0;
width: 100%;
gap: 24px;
}
.content .links {
order: 1;
}
.content:deep(.logo) {
order: 2;
margin: 0 auto;
}
.content .social {
order: 3;
}
.divider {
margin-top: 24px;
}
}
small {
text-align: center;
color: var(--footer-link-color);
font-weight: 500;
margin-block-start: 24px;
display: block;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -1,38 +0,0 @@
<script setup lang="ts">
import LinkedIn from './icons/LinkedIn.vue';
import Docker from './icons/Docker.vue';
import NPM from './icons/NPM.vue';
import YouTube from './icons/YouTube.vue';
import Discord from './icons/Discord.vue';
import Twitter from './icons/Twitter.vue';
import GitHub2 from './icons/GitHub2.vue';
const iconsMap = {
linkedin: LinkedIn,
docker: Docker,
npm: NPM,
youtube: YouTube,
discord: Discord,
twitter: Twitter,
github: GitHub2,
};
const props = defineProps<{
url: string;
icon: keyof typeof iconsMap;
}>();
const Comp = iconsMap[props.icon];
</script>
<template>
<a class="social-icons" :href="url" target="_blank" rel="noreferrer noopener">
<Comp />
</a>
</template>
<style scoped>
.social-icons {
font-size: 28px;
}
</style>

View File

@@ -1,8 +0,0 @@
<template>
<svg width="1em" height="1em" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M18.2477 6.17248C17.0651 5.61685 15.8166 5.22267 14.5342 5C14.3587 5.32194 14.1999 5.65318 14.0585 5.99232C12.6924 5.78107 11.3033 5.78107 9.93721 5.99232C9.79571 5.65321 9.63693 5.32198 9.46151 5C8.17825 5.22455 6.92895 5.61966 5.74514 6.17538C3.39498 9.74367 2.75789 13.2233 3.07644 16.6536C4.45275 17.6971 5.99324 18.4908 7.63094 19C7.9997 18.491 8.326 17.9511 8.60639 17.3858C8.07383 17.1817 7.55982 16.9299 7.07029 16.6333C7.19913 16.5374 7.32513 16.4386 7.44689 16.3427C8.87128 17.0301 10.4259 17.3865 12 17.3865C13.574 17.3865 15.1287 17.0301 16.5531 16.3427C16.6762 16.4458 16.8022 16.5446 16.9296 16.6333C16.4392 16.9304 15.9242 17.1827 15.3907 17.3873C15.6708 17.9523 15.9971 18.4918 16.3662 19C18.0053 18.4928 19.5469 17.6996 20.9235 16.655C21.2973 12.677 20.285 9.22935 18.2477 6.17248ZM9.00988 14.544C8.1222 14.544 7.38884 13.7173 7.38884 12.7003C7.38884 11.6833 8.09672 10.8493 9.00705 10.8493C9.91739 10.8493 10.6451 11.6833 10.6295 12.7003C10.6139 13.7173 9.91456 14.544 9.00988 14.544ZM14.9901 14.544C14.101 14.544 13.3704 13.7173 13.3704 12.7003C13.3704 11.6833 14.0783 10.8493 14.9901 10.8493C15.9018 10.8493 16.6238 11.6833 16.6083 12.7003C16.5927 13.7173 15.8947 14.544 14.9901 14.544Z"
fill="#8B9097"
/>
</svg>
</template>

View File

@@ -1,44 +0,0 @@
<template>
<svg width="1em" height="1em" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M20.8247 10.294C20.7746 10.2488 20.3238 9.87776 19.3554 9.87776C19.1049 9.87776 18.8461 9.90491 18.5957 9.95016C18.412 8.56555 17.3518 7.89587 17.3101 7.85967L17.0513 7.69678L16.8843 7.95922C16.6756 8.31216 16.517 8.71034 16.4251 9.11758C16.2498 9.90491 16.3584 10.647 16.7257 11.2805C16.2832 11.552 15.5653 11.6153 15.415 11.6244H3.56042C3.25154 11.6244 3.00109 11.8958 3.00109 12.2307C2.98439 13.3528 3.15971 14.475 3.51868 15.5338C3.92775 16.6922 4.53717 17.5519 5.32191 18.0768C6.20683 18.665 7.65109 18.9999 9.279 18.9999C10.0137 18.9999 10.7483 18.9275 11.4746 18.7827C12.4847 18.5836 13.4531 18.2035 14.3464 17.6515C15.0811 17.1899 15.7406 16.6017 16.2999 15.9139C17.2433 14.7646 17.8026 13.4795 18.2117 12.3393C18.2701 12.3393 18.3202 12.3393 18.3786 12.3393C19.4055 12.3393 20.0399 11.8958 20.3906 11.5158C20.6243 11.2805 20.7996 10.9909 20.9249 10.6651L21 10.4298L20.8247 10.294Z"
fill="#8B9097"
/>
<path
d="M4.66145 11.2534H6.24763C6.32276 11.2534 6.38955 11.19 6.38955 11.0995V9.56107C6.38955 9.47962 6.33111 9.40723 6.24763 9.40723H4.66145C4.58632 9.40723 4.51953 9.47057 4.51953 9.56107V11.0995C4.52788 11.19 4.58632 11.2534 4.66145 11.2534Z"
fill="#8B9097"
/>
<path
d="M6.84895 11.2534H8.43513C8.51026 11.2534 8.57705 11.19 8.57705 11.0995V9.56107C8.57705 9.47962 8.51861 9.40723 8.43513 9.40723H6.84895C6.77382 9.40723 6.70703 9.47057 6.70703 9.56107V11.0995C6.71538 11.19 6.77382 11.2534 6.84895 11.2534Z"
fill="#8B9097"
/>
<path
d="M9.07942 11.2534H10.6656C10.7407 11.2534 10.8075 11.19 10.8075 11.0995V9.56107C10.8075 9.47962 10.7491 9.40723 10.6656 9.40723H9.07942C9.00429 9.40723 8.9375 9.47057 8.9375 9.56107V11.0995C8.9375 11.19 8.99594 11.2534 9.07942 11.2534Z"
fill="#8B9097"
/>
<path
d="M11.2747 11.2534H12.8609C12.936 11.2534 13.0028 11.19 13.0028 11.0995V9.56107C13.0028 9.47962 12.9444 9.40723 12.8609 9.40723H11.2747C11.1996 9.40723 11.1328 9.47057 11.1328 9.56107V11.0995C11.1328 11.19 11.1996 11.2534 11.2747 11.2534Z"
fill="#8B9097"
/>
<path
d="M6.84895 9.0544H8.43513C8.51026 9.0544 8.57705 8.982 8.57705 8.90055V7.3621C8.57705 7.28065 8.51861 7.20825 8.43513 7.20825H6.84895C6.77382 7.20825 6.70703 7.2716 6.70703 7.3621V8.90055C6.71538 8.982 6.77382 9.0544 6.84895 9.0544Z"
fill="#8B9097"
/>
<path
d="M9.07942 9.0544H10.6656C10.7407 9.0544 10.8075 8.982 10.8075 8.90055V7.3621C10.8075 7.28065 10.7491 7.20825 10.6656 7.20825H9.07942C9.00429 7.20825 8.9375 7.2716 8.9375 7.3621V8.90055C8.9375 8.982 8.99594 9.0544 9.07942 9.0544Z"
fill="#8B9097"
/>
<path
d="M11.2747 9.0544H12.8609C12.936 9.0544 13.0028 8.982 13.0028 8.90055V7.3621C13.0028 7.28065 12.936 7.20825 12.8609 7.20825H11.2747C11.1996 7.20825 11.1328 7.2716 11.1328 7.3621V8.90055C11.1328 8.982 11.1996 9.0544 11.2747 9.0544Z"
fill="#8B9097"
/>
<path
d="M11.2747 6.84614H12.8609C12.936 6.84614 13.0028 6.78279 13.0028 6.6923V5.15385C13.0028 5.0724 12.936 5 12.8609 5H11.2747C11.1996 5 11.1328 5.06335 11.1328 5.15385V6.6923C11.1328 6.77375 11.1996 6.84614 11.2747 6.84614Z"
fill="#8B9097"
/>
<path
d="M13.4857 11.2534H15.0718C15.147 11.2534 15.2138 11.19 15.2138 11.0995V9.56107C15.2138 9.47962 15.1553 9.40723 15.0718 9.40723H13.4857C13.4105 9.40723 13.3438 9.47057 13.3438 9.56107V11.0995C13.3521 11.19 13.4105 11.2534 13.4857 11.2534Z"
fill="#8B9097"
/>
</svg>
</template>

View File

@@ -1,10 +0,0 @@
<template>
<svg width="1em" height="1em" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M12.0066 4C7.57917 4 4 7.66665 4 12.2028C4 15.8288 6.29329 18.8981 9.4747 19.9844C9.87246 20.0661 10.0182 19.8079 10.0182 19.5908C10.0182 19.4006 10.005 18.7488 10.005 18.0696C7.7778 18.5586 7.31399 17.0918 7.31399 17.0918C6.95606 16.1411 6.42572 15.8968 6.42572 15.8968C5.69674 15.3943 6.47882 15.3943 6.47882 15.3943C7.28744 15.4486 7.71175 16.2363 7.71175 16.2363C8.42745 17.4856 9.58074 17.1326 10.0447 16.9153C10.1109 16.3856 10.3232 16.0189 10.5485 15.8153C8.77211 15.6251 6.90313 14.919 6.90313 11.7681C6.90313 10.8718 7.22107 10.1385 7.72486 9.56814C7.64538 9.36448 7.36693 8.52231 7.80451 7.39515C7.80451 7.39515 8.48055 7.17782 10.0049 8.23715C10.6575 8.05759 11.3305 7.96625 12.0066 7.96548C12.6827 7.96548 13.3718 8.06065 14.0082 8.23715C15.5327 7.17782 16.2087 7.39515 16.2087 7.39515C16.6463 8.52231 16.3677 9.36448 16.2882 9.56814C16.8053 10.1385 17.1101 10.8718 17.1101 11.7681C17.1101 14.919 15.2411 15.6115 13.4515 15.8153C13.7432 16.0733 13.9949 16.5621 13.9949 17.3363C13.9949 18.4363 13.9818 19.3191 13.9818 19.5906C13.9818 19.8079 14.1277 20.0661 14.5253 19.9846C17.7067 18.8979 20 15.8288 20 12.2028C20.0131 7.66665 16.4208 4 12.0066 4Z"
fill="#8B9097"
/>
</svg>
</template>

View File

@@ -1,10 +0,0 @@
<template>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M12.0066 4C7.57917 4 4 7.66665 4 12.2028C4 15.8288 6.29329 18.8981 9.4747 19.9844C9.87246 20.0661 10.0182 19.8079 10.0182 19.5908C10.0182 19.4006 10.005 18.7488 10.005 18.0696C7.7778 18.5586 7.31399 17.0918 7.31399 17.0918C6.95606 16.1411 6.42572 15.8968 6.42572 15.8968C5.69674 15.3943 6.47882 15.3943 6.47882 15.3943C7.28744 15.4486 7.71175 16.2363 7.71175 16.2363C8.42745 17.4856 9.58074 17.1326 10.0447 16.9153C10.1109 16.3856 10.3232 16.0189 10.5485 15.8153C8.77211 15.6251 6.90313 14.919 6.90313 11.7681C6.90313 10.8718 7.22107 10.1385 7.72486 9.56814C7.64538 9.36448 7.36693 8.52231 7.80451 7.39515C7.80451 7.39515 8.48055 7.17782 10.0049 8.23715C10.6575 8.05759 11.3305 7.96625 12.0066 7.96548C12.6827 7.96548 13.3718 8.06065 14.0082 8.23715C15.5327 7.17782 16.2087 7.39515 16.2087 7.39515C16.6463 8.52231 16.3677 9.36448 16.2882 9.56814C16.8053 10.1385 17.1101 10.8718 17.1101 11.7681C17.1101 14.919 15.2411 15.6115 13.4515 15.8153C13.7432 16.0733 13.9949 16.5621 13.9949 17.3363C13.9949 18.4363 13.9818 19.3191 13.9818 19.5906C13.9818 19.8079 14.1277 20.0661 14.5253 19.9846C17.7067 18.8979 20 15.8288 20 12.2028C20.0131 7.66665 16.4208 4 12.0066 4Z"
fill="currentColor"
/>
</svg>
</template>

View File

@@ -1,8 +0,0 @@
<template>
<svg width="1em" height="1em" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M17.6311 17.6347H15.2622V13.9218C15.2622 13.0364 15.2444 11.8969 14.0267 11.8969C12.792 11.8969 12.6036 12.8604 12.6036 13.8569V17.6347H10.2338V10H12.5093V11.04H12.5404C12.8587 10.4409 13.632 9.80711 14.7876 9.80711C17.1876 9.80711 17.632 11.3876 17.632 13.4436V17.6347H17.6311ZM7.55822 8.95556C7.37738 8.95579 7.19828 8.92033 7.03117 8.85121C6.86406 8.78208 6.71224 8.68066 6.58441 8.55274C6.45657 8.42483 6.35525 8.27294 6.28623 8.10579C6.21722 7.93864 6.18187 7.75951 6.18222 7.57867C6.1824 7.30652 6.26327 7.04054 6.41461 6.81435C6.56596 6.58817 6.78097 6.41194 7.03247 6.30796C7.28397 6.20397 7.56065 6.1769 7.82754 6.23017C8.09442 6.28343 8.33952 6.41464 8.53183 6.60721C8.72414 6.79977 8.85503 7.04503 8.90796 7.31198C8.96088 7.57894 8.93345 7.85559 8.82914 8.10695C8.72483 8.35831 8.54833 8.5731 8.32195 8.72415C8.09557 8.8752 7.82948 8.95573 7.55733 8.95556H7.55822ZM8.74578 17.6347H6.36978V10H8.74667V17.6347H8.74578ZM18.8178 4H5.18133C4.52711 4 4 4.51556 4 5.15289V18.8471C4 19.4844 4.528 20 5.18044 20H18.8142C19.4667 20 20 19.4844 20 18.8471V5.15289C20 4.51556 19.4667 4 18.8142 4H18.8169H18.8178Z"
fill="#8B9097"
/>
</svg>
</template>

View File

@@ -1,108 +0,0 @@
<template>
<svg
class="logo logo-dark"
width="192"
height="40"
viewBox="0 0 192 40"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M52.484 25.2423C52.1794 25.1663 51.9256 25.0904 51.6971 24.9891C51.5282 24.9142 51.3871 24.8255 51.2635 24.723C51.1813 24.6548 51.146 24.5475 51.1563 24.4414C51.2787 23.1693 51.144 22.0473 51.2656 20.7858C51.7733 15.6708 54.9971 17.2914 57.891 16.4558C59.5527 15.9889 61.2145 15.0699 61.8217 13.2622C61.9211 12.9663 61.8336 12.6445 61.6269 12.4102C59.7313 10.2602 57.6327 8.36047 55.3525 6.7324C47.708 1.30326 37.7729 -0.951171 28.6491 0.367573C28.3035 0.417531 28.1223 0.799477 28.3112 1.09256C29.4667 2.88624 30.9912 4.35413 32.7394 5.43456C33.0572 5.6309 32.9295 6.05504 32.5648 5.97348C31.7074 5.78172 30.6058 5.40776 29.5697 4.67845C29.4698 4.60817 29.3412 4.59047 29.2277 4.63562C28.7646 4.81986 28.0957 5.08554 27.543 5.32355C27.2248 5.46056 27.1601 5.86986 27.4219 6.09629C32.0046 10.0587 38.6712 10.661 43.8914 7.49015C44.2094 7.29699 44.7185 7.69392 44.6161 8.05098C44.452 8.62295 44.2604 9.40919 44.0564 10.48C42.7618 17.0129 39.0302 16.5064 34.4102 14.8606C25.1786 11.523 19.9414 14.3731 15.2851 8.73124C14.9616 8.33922 14.3929 8.20331 14.008 8.53568C13.0468 9.3658 12.4779 10.5775 12.4779 11.8726C12.4779 13.4067 13.2718 14.7207 14.4484 15.5073C14.5956 15.6057 14.7912 15.5641 14.901 15.4254C15.1877 15.0634 15.4223 14.8235 15.7144 14.6717C16.034 14.5056 16.1899 14.9618 15.9201 15.2001C14.9311 16.0736 14.6473 17.114 14.001 19.1652C12.9856 22.381 13.4172 25.6728 8.67026 26.5337C6.15718 26.6603 6.20795 28.3568 5.29411 30.889C4.23354 33.9452 2.84475 35.2989 0.274456 37.9696C-0.0770783 38.3349 -0.107223 38.9189 0.279108 39.2474C1.30581 40.1202 2.36465 40.1682 3.44103 39.7261C6.10641 38.612 8.16256 35.1683 10.0918 32.94C12.2495 30.4585 17.4279 31.522 21.3372 29.0912C23.4455 27.8016 24.7122 26.1544 24.3071 23.6869C24.2419 23.2894 24.6978 23.0506 24.8631 23.418C25.177 24.1155 25.3828 24.8596 25.4683 25.6283C25.4907 25.8299 25.6713 25.979 25.8743 25.9676C30.1037 25.7304 35.5731 30.3838 40.6844 31.6428C40.9954 31.7194 41.2164 31.3613 41.0406 31.0942C40.7172 30.603 40.4423 30.0932 40.2233 29.5723C39.9972 29.0311 39.8259 28.506 39.7048 27.9992C39.6101 27.6028 40.1906 27.4963 40.3888 27.8527C41.6997 30.2097 44.3195 32.423 47.9656 32.6868C49.2094 32.7881 50.5802 32.6361 52.0017 32.2057C53.7025 31.6993 55.2764 31.0409 57.1548 31.3954C58.551 31.6486 59.8456 32.3576 60.6579 33.5477C61.7977 35.2059 64.2038 35.6452 65.4863 33.9122C65.6608 33.6764 65.6754 33.3611 65.5601 33.0915C62.7363 26.4892 55.566 26.0357 52.484 25.2423Z"
fill="#172940"
/>
<path
d="M91.5203 10.7853C91.6012 10.3409 91.9708 10.0057 92.422 9.96759L95.8522 9.67762C95.9412 9.6701 96.0301 9.69244 96.1049 9.74109C96.2397 9.82877 96.3088 9.98839 96.2803 10.1464L92.8335 29.3065C92.7478 29.7831 92.3321 30.1299 91.8467 30.1299H89.2523C89.0205 30.1299 88.8447 29.9215 88.8842 29.6937C88.9452 29.3423 88.5071 29.0965 88.2142 29.301C87.073 30.0977 85.8173 30.4961 84.4471 30.4961C82.5478 30.4961 80.9765 29.8174 79.7332 28.46C78.4801 27.1025 77.8535 25.4473 77.8535 23.4941C77.8535 20.9941 78.6269 18.9141 80.1737 17.2539C81.7206 15.5937 83.6883 14.7637 86.0771 14.7637C87.5158 14.7637 88.7315 15.1386 89.724 15.8884C90.0542 16.1378 90.5741 15.9823 90.6481 15.5759L91.5203 10.7853ZM85.71 26.585C86.8848 26.585 87.8491 26.165 88.6029 25.3252C89.3372 24.4854 89.7043 23.4551 89.7043 22.2344C89.7043 21.1699 89.4057 20.3105 88.8085 19.6562C88.2113 19.002 87.4281 18.6748 86.4589 18.6748C85.2743 18.6748 84.3149 19.0947 83.5807 19.9346C82.8366 20.7646 82.4646 21.7949 82.4646 23.0254C82.4646 24.0801 82.7681 24.9395 83.3751 25.6035C83.9722 26.2578 84.7506 26.585 85.71 26.585Z"
fill="#172940"
/>
<path
d="M101.342 12.7275C100.647 12.7275 100.045 12.4834 99.5362 11.9951C99.0369 11.5068 98.7873 10.9111 98.7873 10.208C98.7873 9.31933 99.1152 8.5625 99.7712 7.9375C100.408 7.3125 101.176 7 102.077 7C102.762 7 103.354 7.25391 103.854 7.76172C104.353 8.2793 104.603 8.875 104.603 9.54883C104.603 10.4277 104.279 11.1748 103.633 11.79C102.997 12.415 102.233 12.7275 101.342 12.7275ZM100.86 29.3077C100.774 29.7837 100.358 30.1299 99.8734 30.1299H97.2839C96.6593 30.1299 96.1868 29.5663 96.2972 28.953L98.639 15.953C98.7248 15.4766 99.1404 15.1299 99.6257 15.1299H102.228C102.853 15.1299 103.326 15.6941 103.214 16.3077L100.86 29.3077Z"
fill="#172940"
/>
<path
d="M117.092 15.2007C117.206 15.2475 117.294 15.3401 117.34 15.4541C117.388 15.5745 117.385 15.7093 117.331 15.8271L115.96 18.8118C115.913 18.9129 115.835 18.9956 115.736 19.0467C115.584 19.1253 115.403 19.1182 115.249 19.0452C114.774 18.8207 114.253 18.7168 113.686 18.7334C112.452 18.7334 111.502 19.2314 110.837 20.2275C110.592 20.6084 110.328 21.4971 110.044 22.8936L108.925 29.3014C108.842 29.7803 108.425 30.1299 107.938 30.1299H105.324C104.7 30.1299 104.227 29.5663 104.338 28.953L106.679 15.953C106.765 15.4766 107.181 15.1299 107.666 15.1299H109.877C110.294 15.1299 110.611 15.5043 110.541 15.9146C110.483 16.2565 110.954 16.4804 111.214 16.2501C111.45 16.0413 111.691 15.8583 111.938 15.7012C112.858 15.0957 113.842 14.7832 114.89 14.7637C115.649 14.7637 116.383 14.9093 117.092 15.2007Z"
fill="#172940"
/>
<path
d="M125.103 14.7637C127.502 14.7637 129.308 15.5303 130.522 17.0635C131.736 18.5967 132.123 20.4424 131.682 22.6006C131.663 22.7259 131.635 22.8575 131.6 22.9954C131.496 23.3992 131.112 23.6553 130.694 23.6553H121.47C120.944 23.6553 120.487 24.0659 120.567 24.5847C120.67 25.2456 120.953 25.7806 121.417 26.1895C122.073 26.7754 122.93 27.0684 123.987 27.0684C125.478 27.0684 126.817 26.5492 128.004 25.5108C128.075 25.4486 128.166 25.4131 128.261 25.4131C128.372 25.4131 128.479 25.4621 128.551 25.5472L129.989 27.2444C130.076 27.3465 130.113 27.4812 130.091 27.613C130.075 27.7066 130.031 27.7933 129.963 27.8594C128.17 29.598 125.958 30.4769 123.327 30.4961C121.104 30.4961 119.308 29.793 117.937 28.3867C116.557 26.9902 115.94 25.2031 116.087 23.0254C116.146 21.5312 116.557 20.1738 117.32 18.9531C118.064 17.7422 119.073 16.7754 120.345 16.0527C121.794 15.1934 123.38 14.7637 125.103 14.7637ZM121.538 19.7235C121.171 20.2863 121.656 20.9453 122.329 20.9453H126.531C127.064 20.9453 127.528 20.5197 127.402 20.0025C127.315 19.6429 127.161 19.3175 126.939 19.0264C126.45 18.3623 125.696 18.0352 124.678 18.0449C123.767 18.0449 122.984 18.3037 122.328 18.8213C122.021 19.0846 121.758 19.3854 121.538 19.7235Z"
fill="#172940"
/>
<path
d="M140.442 30.4961C138.288 30.4961 136.531 29.8174 135.17 28.46C133.8 27.1123 133.114 25.3691 133.114 23.2305C133.114 21.834 133.423 20.5449 134.04 19.3633C134.656 18.1719 135.518 17.1904 136.624 16.4189C138.23 15.3154 140.016 14.7637 141.984 14.7637C144.578 14.7637 146.568 15.624 147.954 17.3445C148.034 17.4433 148.059 17.5746 148.025 17.6968C148.003 17.7802 147.954 17.8543 147.886 17.9082L145.19 20.0543C145.12 20.1096 145.034 20.1396 144.946 20.1396C144.82 20.1396 144.702 20.0783 144.624 19.98C143.93 19.1099 142.996 18.6748 141.823 18.6748C140.687 18.6748 139.747 19.0215 139.003 19.7148C138.142 20.5352 137.711 21.6191 137.711 22.9668C137.711 24.0605 138.024 24.9346 138.651 25.5889C139.267 26.2529 140.095 26.585 141.132 26.585C142.208 26.585 143.163 26.193 143.997 25.4089C144.072 25.3377 144.172 25.2959 144.276 25.2959C144.387 25.2959 144.494 25.3432 144.568 25.4261L146.473 27.546C146.56 27.6421 146.597 27.7725 146.575 27.8996C146.559 27.9877 146.516 28.069 146.451 28.1302C144.768 29.7075 142.765 30.4961 140.442 30.4961Z"
fill="#172940"
/>
<path
d="M160.509 17.7163C160.424 18.1943 160.008 18.543 159.521 18.543H156.869C156.384 18.543 155.968 18.8896 155.882 19.366L154.959 24.4902C154.704 25.8672 155.164 26.5557 156.339 26.5557C156.828 26.5557 157.294 26.4096 157.737 26.1176C157.853 26.0414 158.003 26.0325 158.121 26.1053C158.193 26.15 158.246 26.2204 158.268 26.3023L158.912 28.6973C158.957 28.8645 158.935 29.0425 158.85 29.1935C158.802 29.2796 158.735 29.3543 158.653 29.4095C157.577 30.1339 156.365 30.4961 155.017 30.4961C153.392 30.4961 152.12 30.0273 151.199 29.0898C150.279 28.1719 149.976 26.79 150.289 24.9443L151.247 19.723C151.36 19.1089 150.887 18.543 150.261 18.543H150.073C149.45 18.543 148.978 17.9819 149.086 17.3697L149.335 15.9566C149.42 15.4785 149.836 15.1299 150.323 15.1299H151.255C151.741 15.1299 152.157 14.7823 152.242 14.3051L152.768 11.3589C152.847 10.9133 153.217 10.5766 153.669 10.5378L157.039 10.2491C157.128 10.2414 157.217 10.2637 157.291 10.3123C157.426 10.4 157.495 10.5597 157.466 10.7176L156.877 13.951C156.765 14.5648 157.238 15.1299 157.863 15.1299H159.77C160.394 15.1299 160.866 15.691 160.758 16.3032L160.509 17.7163Z"
fill="#172940"
/>
<path
d="M171.223 23.9775L172.661 15.9539C172.746 15.477 173.162 15.1299 173.648 15.1299H176.222C176.846 15.1299 177.319 15.6928 177.209 16.3058L174.88 29.3058C174.794 29.7827 174.379 30.1299 173.893 30.1299H171.345C171.105 30.1299 170.924 29.9134 170.966 29.6777C171.031 29.3178 170.587 29.0634 170.286 29.2715C169.103 30.0879 167.839 30.4961 166.494 30.4961C164.693 30.4961 163.332 29.9053 162.412 28.7236C161.491 27.542 161.198 25.9697 161.53 24.0068L162.983 15.9529C163.069 15.4765 163.484 15.1299 163.969 15.1299H166.588C167.212 15.1299 167.685 15.6935 167.574 16.3067L166.23 23.7725C166.093 24.6221 166.195 25.3008 166.538 25.8086C166.881 26.3066 167.419 26.5557 168.153 26.5557C169.181 26.5557 169.984 26.1748 170.562 25.4131C170.865 25.0811 171.086 24.6025 171.223 23.9775Z"
fill="#172940"
/>
<path
d="M183.815 30.5254C182.327 30.5254 181.006 30.2666 179.85 29.749C178.737 29.2718 177.935 28.6567 177.444 27.9035C177.384 27.8103 177.365 27.6967 177.385 27.5873C177.403 27.4881 177.453 27.3975 177.527 27.329L179.78 25.2413C179.844 25.1822 179.928 25.1494 180.015 25.1494C180.132 25.1494 180.24 25.2096 180.311 25.3024C180.643 25.7379 181.16 26.1264 181.862 26.4678C182.636 26.8486 183.409 27.0391 184.182 27.0391C184.789 27.0391 185.259 26.9219 185.592 26.6875C185.935 26.4531 186.106 26.1309 186.106 25.7207C186.106 25.4766 186.013 25.252 185.827 25.0469C185.651 24.832 185.436 24.6611 185.181 24.5342C184.848 24.3779 184.525 24.2412 184.212 24.124C183.575 23.9092 183.022 23.6943 182.552 23.4795C182.122 23.2744 181.657 23.001 181.157 22.6592C180.159 21.9561 179.659 20.9746 179.659 19.7148C179.659 18.25 180.257 17.0537 181.451 16.126C182.645 15.1982 184.217 14.7344 186.165 14.7344C187.438 14.7344 188.612 14.9541 189.689 15.3936C190.677 15.8039 191.421 16.3548 191.921 17.0462C191.99 17.1405 192.013 17.2591 191.993 17.3736C191.975 17.4763 191.923 17.5698 191.845 17.6388L189.675 19.5519C189.599 19.6191 189.501 19.6562 189.399 19.6562C189.276 19.6562 189.16 19.6011 189.076 19.5107C188.689 19.0916 188.223 18.7593 187.677 18.5137C187.07 18.2402 186.463 18.1035 185.856 18.1035C185.328 18.1035 184.907 18.2109 184.594 18.4258C184.28 18.6309 184.124 18.9043 184.124 19.2461C184.124 19.4609 184.217 19.6416 184.403 19.7881C184.638 19.9834 184.897 20.1396 185.181 20.2568C185.23 20.2764 185.372 20.3398 185.607 20.4473C185.852 20.5449 186.077 20.6328 186.282 20.7109C186.547 20.8184 186.767 20.9062 186.943 20.9746C188.157 21.5215 189.058 22.1367 189.645 22.8203C190.233 23.4941 190.526 24.3389 190.526 25.3545C190.526 26.9365 189.91 28.1914 188.676 29.1191C187.442 30.0566 185.822 30.5254 183.815 30.5254Z"
fill="#172940"
/>
</svg>
<svg
class="logo logo-light"
width="192"
height="40"
viewBox="0 0 192 40"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M52.484 25.2423C52.1794 25.1663 51.9256 25.0904 51.6971 24.9891C51.5282 24.9142 51.3871 24.8255 51.2635 24.723C51.1813 24.6548 51.146 24.5475 51.1563 24.4414C51.2787 23.1693 51.144 22.0473 51.2656 20.7858C51.7733 15.6708 54.9971 17.2914 57.891 16.4558C59.5527 15.9889 61.2145 15.0699 61.8217 13.2622C61.9211 12.9663 61.8336 12.6445 61.6269 12.4102C59.7313 10.2602 57.6327 8.36047 55.3525 6.7324C47.708 1.30326 37.7729 -0.951171 28.6491 0.367573C28.3035 0.417531 28.1223 0.799477 28.3112 1.09256C29.4667 2.88624 30.9912 4.35413 32.7394 5.43456C33.0572 5.6309 32.9295 6.05504 32.5648 5.97348C31.7074 5.78172 30.6058 5.40776 29.5697 4.67845C29.4698 4.60817 29.3412 4.59047 29.2277 4.63562C28.7646 4.81986 28.0957 5.08554 27.543 5.32355C27.2248 5.46056 27.1601 5.86986 27.4219 6.09629C32.0046 10.0587 38.6712 10.661 43.8914 7.49015C44.2094 7.29699 44.7185 7.69392 44.6161 8.05098C44.452 8.62295 44.2604 9.40919 44.0564 10.48C42.7618 17.0129 39.0302 16.5064 34.4102 14.8606C25.1786 11.523 19.9414 14.3731 15.2851 8.73124C14.9616 8.33922 14.3929 8.20331 14.008 8.53568C13.0468 9.3658 12.4779 10.5775 12.4779 11.8726C12.4779 13.4067 13.2718 14.7207 14.4484 15.5073C14.5956 15.6057 14.7912 15.5641 14.901 15.4254C15.1877 15.0634 15.4223 14.8235 15.7144 14.6717C16.034 14.5056 16.1899 14.9618 15.9201 15.2001C14.9311 16.0736 14.6473 17.114 14.001 19.1652C12.9856 22.381 13.4172 25.6728 8.67026 26.5337C6.15718 26.6603 6.20795 28.3568 5.29411 30.889C4.23354 33.9452 2.84475 35.2989 0.274456 37.9696C-0.0770783 38.3349 -0.107223 38.9189 0.279108 39.2474C1.30581 40.1202 2.36465 40.1682 3.44103 39.7261C6.10641 38.612 8.16256 35.1683 10.0918 32.94C12.2495 30.4585 17.4279 31.522 21.3372 29.0912C23.4455 27.8016 24.7122 26.1544 24.3071 23.6869C24.2419 23.2894 24.6978 23.0506 24.8631 23.418C25.177 24.1155 25.3828 24.8596 25.4683 25.6283C25.4907 25.8299 25.6713 25.979 25.8743 25.9676C30.1037 25.7304 35.5731 30.3838 40.6844 31.6428C40.9954 31.7194 41.2164 31.3613 41.0406 31.0942C40.7172 30.603 40.4423 30.0932 40.2233 29.5723C39.9972 29.0311 39.8259 28.506 39.7048 27.9992C39.6101 27.6028 40.1906 27.4963 40.3888 27.8527C41.6997 30.2097 44.3195 32.423 47.9656 32.6868C49.2094 32.7881 50.5802 32.6361 52.0017 32.2057C53.7025 31.6993 55.2764 31.0409 57.1548 31.3954C58.551 31.6486 59.8456 32.3576 60.6579 33.5477C61.7977 35.2059 64.2038 35.6452 65.4863 33.9122C65.6608 33.6764 65.6754 33.3611 65.5601 33.0915C62.7363 26.4892 55.566 26.0357 52.484 25.2423Z"
fill="white"
/>
<path
d="M91.5203 10.7853C91.6012 10.3409 91.9708 10.0057 92.422 9.96759L95.8522 9.67762C95.9412 9.6701 96.0301 9.69244 96.1049 9.74109C96.2397 9.82877 96.3088 9.98839 96.2803 10.1464L92.8335 29.3065C92.7478 29.7831 92.3321 30.1299 91.8467 30.1299H89.2523C89.0205 30.1299 88.8447 29.9215 88.8842 29.6937C88.9452 29.3423 88.5071 29.0965 88.2142 29.301C87.073 30.0977 85.8173 30.4961 84.4471 30.4961C82.5478 30.4961 80.9765 29.8174 79.7332 28.46C78.4801 27.1025 77.8535 25.4473 77.8535 23.4941C77.8535 20.9941 78.6269 18.9141 80.1737 17.2539C81.7206 15.5937 83.6883 14.7637 86.0771 14.7637C87.5158 14.7637 88.7315 15.1386 89.724 15.8884C90.0542 16.1378 90.5741 15.9823 90.6481 15.5759L91.5203 10.7853ZM85.71 26.585C86.8848 26.585 87.8491 26.165 88.6029 25.3252C89.3372 24.4854 89.7043 23.4551 89.7043 22.2344C89.7043 21.1699 89.4057 20.3105 88.8085 19.6562C88.2113 19.002 87.4281 18.6748 86.4589 18.6748C85.2743 18.6748 84.3149 19.0947 83.5807 19.9346C82.8366 20.7646 82.4646 21.7949 82.4646 23.0254C82.4646 24.0801 82.7681 24.9395 83.3751 25.6035C83.9722 26.2578 84.7506 26.585 85.71 26.585Z"
fill="white"
/>
<path
d="M101.342 12.7275C100.647 12.7275 100.045 12.4834 99.5362 11.9951C99.0369 11.5068 98.7873 10.9111 98.7873 10.208C98.7873 9.31933 99.1152 8.5625 99.7712 7.9375C100.408 7.3125 101.176 7 102.077 7C102.762 7 103.354 7.25391 103.854 7.76172C104.353 8.2793 104.603 8.875 104.603 9.54883C104.603 10.4277 104.279 11.1748 103.633 11.79C102.997 12.415 102.233 12.7275 101.342 12.7275ZM100.86 29.3077C100.774 29.7837 100.358 30.1299 99.8734 30.1299H97.2839C96.6593 30.1299 96.1868 29.5663 96.2972 28.953L98.639 15.953C98.7248 15.4766 99.1404 15.1299 99.6257 15.1299H102.228C102.853 15.1299 103.326 15.6941 103.214 16.3077L100.86 29.3077Z"
fill="white"
/>
<path
d="M117.092 15.2007C117.206 15.2475 117.294 15.3401 117.34 15.4541C117.388 15.5745 117.385 15.7093 117.331 15.8271L115.96 18.8118C115.913 18.9129 115.835 18.9956 115.736 19.0467C115.584 19.1253 115.403 19.1182 115.249 19.0452C114.774 18.8207 114.253 18.7168 113.686 18.7334C112.452 18.7334 111.502 19.2314 110.837 20.2275C110.592 20.6084 110.328 21.4971 110.044 22.8936L108.925 29.3014C108.842 29.7803 108.425 30.1299 107.938 30.1299H105.324C104.7 30.1299 104.227 29.5663 104.338 28.953L106.679 15.953C106.765 15.4766 107.181 15.1299 107.666 15.1299H109.877C110.294 15.1299 110.611 15.5043 110.541 15.9146C110.483 16.2565 110.954 16.4804 111.214 16.2501C111.45 16.0413 111.691 15.8583 111.938 15.7012C112.858 15.0957 113.842 14.7832 114.89 14.7637C115.649 14.7637 116.383 14.9093 117.092 15.2007Z"
fill="white"
/>
<path
d="M125.103 14.7637C127.502 14.7637 129.308 15.5303 130.522 17.0635C131.736 18.5967 132.123 20.4424 131.682 22.6006C131.663 22.7259 131.635 22.8575 131.6 22.9954C131.496 23.3992 131.112 23.6553 130.694 23.6553H121.47C120.944 23.6553 120.487 24.0659 120.567 24.5847C120.67 25.2456 120.953 25.7806 121.417 26.1895C122.073 26.7754 122.93 27.0684 123.987 27.0684C125.478 27.0684 126.817 26.5492 128.004 25.5108C128.075 25.4486 128.166 25.4131 128.261 25.4131C128.372 25.4131 128.479 25.4621 128.551 25.5472L129.989 27.2444C130.076 27.3465 130.113 27.4812 130.091 27.613C130.075 27.7066 130.031 27.7933 129.963 27.8594C128.17 29.598 125.958 30.4769 123.327 30.4961C121.104 30.4961 119.308 29.793 117.937 28.3867C116.557 26.9902 115.94 25.2031 116.087 23.0254C116.146 21.5312 116.557 20.1738 117.32 18.9531C118.064 17.7422 119.073 16.7754 120.345 16.0527C121.794 15.1934 123.38 14.7637 125.103 14.7637ZM121.538 19.7235C121.171 20.2863 121.656 20.9453 122.329 20.9453H126.531C127.064 20.9453 127.528 20.5197 127.402 20.0025C127.315 19.6429 127.161 19.3175 126.939 19.0264C126.45 18.3623 125.696 18.0352 124.678 18.0449C123.767 18.0449 122.984 18.3037 122.328 18.8213C122.021 19.0846 121.758 19.3854 121.538 19.7235Z"
fill="white"
/>
<path
d="M140.442 30.4961C138.288 30.4961 136.531 29.8174 135.17 28.46C133.8 27.1123 133.114 25.3691 133.114 23.2305C133.114 21.834 133.423 20.5449 134.04 19.3633C134.656 18.1719 135.518 17.1904 136.624 16.4189C138.23 15.3154 140.016 14.7637 141.984 14.7637C144.578 14.7637 146.568 15.624 147.954 17.3445C148.034 17.4433 148.059 17.5746 148.025 17.6968C148.003 17.7802 147.954 17.8543 147.886 17.9082L145.19 20.0543C145.12 20.1096 145.034 20.1396 144.946 20.1396C144.82 20.1396 144.702 20.0783 144.624 19.98C143.93 19.1099 142.996 18.6748 141.823 18.6748C140.687 18.6748 139.747 19.0215 139.003 19.7148C138.142 20.5352 137.711 21.6191 137.711 22.9668C137.711 24.0605 138.024 24.9346 138.651 25.5889C139.267 26.2529 140.095 26.585 141.132 26.585C142.208 26.585 143.163 26.193 143.997 25.4089C144.072 25.3377 144.172 25.2959 144.276 25.2959C144.387 25.2959 144.494 25.3432 144.568 25.4261L146.473 27.546C146.56 27.6421 146.597 27.7725 146.575 27.8996C146.559 27.9877 146.516 28.069 146.451 28.1302C144.768 29.7075 142.765 30.4961 140.442 30.4961Z"
fill="white"
/>
<path
d="M160.509 17.7163C160.424 18.1943 160.008 18.543 159.521 18.543H156.869C156.384 18.543 155.968 18.8896 155.882 19.366L154.959 24.4902C154.704 25.8672 155.164 26.5557 156.339 26.5557C156.828 26.5557 157.294 26.4096 157.737 26.1176C157.853 26.0414 158.003 26.0325 158.121 26.1053C158.193 26.15 158.246 26.2204 158.268 26.3023L158.912 28.6973C158.957 28.8645 158.935 29.0425 158.85 29.1935C158.802 29.2796 158.735 29.3543 158.653 29.4095C157.577 30.1339 156.365 30.4961 155.017 30.4961C153.392 30.4961 152.12 30.0273 151.199 29.0898C150.279 28.1719 149.976 26.79 150.289 24.9443L151.247 19.723C151.36 19.1089 150.887 18.543 150.261 18.543H150.073C149.45 18.543 148.978 17.9819 149.086 17.3697L149.335 15.9566C149.42 15.4785 149.836 15.1299 150.323 15.1299H151.255C151.741 15.1299 152.157 14.7823 152.242 14.3051L152.768 11.3589C152.847 10.9133 153.217 10.5766 153.669 10.5378L157.039 10.2491C157.128 10.2414 157.217 10.2637 157.291 10.3123C157.426 10.4 157.495 10.5597 157.466 10.7176L156.877 13.951C156.765 14.5648 157.238 15.1299 157.863 15.1299H159.77C160.394 15.1299 160.866 15.691 160.758 16.3032L160.509 17.7163Z"
fill="white"
/>
<path
d="M171.223 23.9775L172.661 15.9539C172.746 15.477 173.162 15.1299 173.648 15.1299H176.222C176.846 15.1299 177.319 15.6928 177.209 16.3058L174.88 29.3058C174.794 29.7827 174.379 30.1299 173.893 30.1299H171.345C171.105 30.1299 170.924 29.9134 170.966 29.6777C171.031 29.3178 170.587 29.0634 170.286 29.2715C169.103 30.0879 167.839 30.4961 166.494 30.4961C164.693 30.4961 163.332 29.9053 162.412 28.7236C161.491 27.542 161.198 25.9697 161.53 24.0068L162.983 15.9529C163.069 15.4765 163.484 15.1299 163.969 15.1299H166.588C167.212 15.1299 167.685 15.6935 167.574 16.3067L166.23 23.7725C166.093 24.6221 166.195 25.3008 166.538 25.8086C166.881 26.3066 167.419 26.5557 168.153 26.5557C169.181 26.5557 169.984 26.1748 170.562 25.4131C170.865 25.0811 171.086 24.6025 171.223 23.9775Z"
fill="white"
/>
<path
d="M183.815 30.5254C182.327 30.5254 181.006 30.2666 179.85 29.749C178.737 29.2718 177.935 28.6567 177.444 27.9035C177.384 27.8103 177.365 27.6967 177.385 27.5873C177.403 27.4881 177.453 27.3975 177.527 27.329L179.78 25.2413C179.844 25.1822 179.928 25.1494 180.015 25.1494C180.132 25.1494 180.24 25.2096 180.311 25.3024C180.643 25.7379 181.16 26.1264 181.862 26.4678C182.636 26.8486 183.409 27.0391 184.182 27.0391C184.789 27.0391 185.259 26.9219 185.592 26.6875C185.935 26.4531 186.106 26.1309 186.106 25.7207C186.106 25.4766 186.013 25.252 185.827 25.0469C185.651 24.832 185.436 24.6611 185.181 24.5342C184.848 24.3779 184.525 24.2412 184.212 24.124C183.575 23.9092 183.022 23.6943 182.552 23.4795C182.122 23.2744 181.657 23.001 181.157 22.6592C180.159 21.9561 179.659 20.9746 179.659 19.7148C179.659 18.25 180.257 17.0537 181.451 16.126C182.645 15.1982 184.217 14.7344 186.165 14.7344C187.438 14.7344 188.612 14.9541 189.689 15.3936C190.677 15.8039 191.421 16.3548 191.921 17.0462C191.99 17.1405 192.013 17.2591 191.993 17.3736C191.975 17.4763 191.923 17.5698 191.845 17.6388L189.675 19.5519C189.599 19.6191 189.501 19.6562 189.399 19.6562C189.276 19.6562 189.16 19.6011 189.076 19.5107C188.689 19.0916 188.223 18.7593 187.677 18.5137C187.07 18.2402 186.463 18.1035 185.856 18.1035C185.328 18.1035 184.907 18.2109 184.594 18.4258C184.28 18.6309 184.124 18.9043 184.124 19.2461C184.124 19.4609 184.217 19.6416 184.403 19.7881C184.638 19.9834 184.897 20.1396 185.181 20.2568C185.23 20.2764 185.372 20.3398 185.607 20.4473C185.852 20.5449 186.077 20.6328 186.282 20.7109C186.547 20.8184 186.767 20.9062 186.943 20.9746C188.157 21.5215 189.058 22.1367 189.645 22.8203C190.233 23.4941 190.526 24.3389 190.526 25.3545C190.526 26.9365 189.91 28.1914 188.676 29.1191C187.442 30.0566 185.822 30.5254 183.815 30.5254Z"
fill="white"
/>
</svg>
</template>
<style>
.logo-light {
display: none;
}
html.dark .logo-dark {
display: none;
}
html.dark .logo-light {
display: block;
}
</style>

View File

@@ -1,8 +0,0 @@
<template>
<svg width="1em" height="1em" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M2 8H22V14.8571H12V16H7.55556V14.8571H2V8ZM3.11111 13.7143H5.33333V10.2857H6.44444V13.7143H7.55556V9.14286H3.11111V13.7143ZM8.66667 9.14286V14.8571H10.8889V13.7143H13.1111V9.14286H8.66667ZM10.8889 10.2857H12V12.5714H10.8889V10.2857ZM14.2222 9.14286V13.7143H16.4444V10.2857H17.5556V13.7143H18.6667V10.2857H19.7778V13.7143H20.8889V9.14286H14.2222Z"
fill="#8B9097"
/>
</svg>
</template>

View File

@@ -1,8 +0,0 @@
<template>
<svg width="1em" height="1em" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M19.9992 7.54324C19.4 7.80812 18.7645 7.98209 18.1138 8.05937C18.7996 7.64947 19.313 7.00438 19.5581 6.2443C18.9144 6.62708 18.2088 6.89534 17.4733 7.04045C16.9793 6.51223 16.3245 6.16191 15.6106 6.04395C14.8968 5.926 14.164 6.04702 13.5261 6.3882C12.8882 6.72937 12.381 7.2716 12.0833 7.93057C11.7856 8.58954 11.7141 9.32833 11.8799 10.0321C10.5747 9.96673 9.29775 9.62777 8.13209 9.03724C6.96644 8.4467 5.93809 7.6178 5.11381 6.60433C4.82205 7.10512 4.66872 7.67441 4.66954 8.2539C4.66954 9.39126 5.24882 10.3961 6.12951 10.9843C5.60833 10.968 5.09861 10.8273 4.64286 10.5741V10.6149C4.64301 11.3724 4.90531 12.1065 5.38528 12.6928C5.86525 13.2791 6.53335 13.6814 7.27629 13.8317C6.79248 13.9627 6.28517 13.982 5.79278 13.8881C6.00225 14.5402 6.41052 15.1104 6.96043 15.519C7.51034 15.9276 8.17436 16.1541 8.8595 16.1668C8.17856 16.7012 7.39888 17.0963 6.56506 17.3294C5.73123 17.5625 4.8596 17.6291 4 17.5253C5.50055 18.4897 7.24733 19.0017 9.0314 19C15.0699 19 18.3721 14.0011 18.3721 9.66579C18.3721 9.5246 18.3681 9.38184 18.3619 9.24222C19.0046 8.778 19.5593 8.20293 20 7.54403L19.9992 7.54324Z"
fill="#8B9097"
/>
</svg>
</template>

View File

@@ -1,8 +0,0 @@
<template>
<svg width="1em" height="1em" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M20.5887 7.18575C21 8.745 21 12 21 12C21 12 21 15.255 20.5887 16.8143C20.3601 17.6761 19.6914 18.3542 18.8445 18.5835C17.3064 19 12 19 12 19C12 19 6.6963 19 5.1555 18.5835C4.305 18.3507 3.6372 17.6735 3.4113 16.8143C3 15.255 3 12 3 12C3 12 3 8.745 3.4113 7.18575C3.6399 6.32388 4.3086 5.64575 5.1555 5.4165C6.6963 5 12 5 12 5C12 5 17.3064 5 18.8445 5.4165C19.695 5.64925 20.3628 6.3265 20.5887 7.18575ZM10.2 15.0625L15.6 12L10.2 8.9375V15.0625Z"
fill="#8B9097"
/>
</svg>
</template>

View File

@@ -1,39 +0,0 @@
import { computed, onUnmounted, ref } from 'vue';
export function useLocalStorage(key: string) {
const internalValue = ref<string | null>(localStorage.getItem(key));
const storageListener = (event: StorageEvent) => {
if (event.storageArea === localStorage && event.key === key) internalValue.value = event.newValue;
};
addEventListener('storage', storageListener);
onUnmounted(() => removeEventListener('storage', storageListener));
const value = computed({
get() {
return internalValue.value;
},
set(newValue: string | null) {
const oldValue = internalValue.value;
internalValue.value = newValue;
if (newValue) {
localStorage.setItem(key, newValue);
} else {
localStorage.removeItem(key);
}
dispatchEvent(
new StorageEvent('storage', {
key,
oldValue,
newValue,
storageArea: localStorage,
}),
);
},
});
return value;
}

View File

@@ -1,205 +0,0 @@
import { fileURLToPath, URL } from 'node:url';
import { defineConfig } from 'vitepress';
import { tabsMarkdownPlugin } from 'vitepress-plugin-tabs';
import sidebar from './data/sidebar.js';
import { useMaterialIconsNoTranslate } from './lib/markdown-plugins/material-icons-no-translate.js';
export default defineConfig({
base: '/',
lang: 'en-US',
title: 'Directus Docs',
description: 'Explore our resources and powerful data engine to build your projects confidently.',
ignoreDeadLinks: true,
markdown: {
theme: {
light: 'github-light',
dark: 'github-dark',
},
toc: {
level: [2],
},
config(md) {
md.use(tabsMarkdownPlugin);
useMaterialIconsNoTranslate(md);
},
},
head: [
[
'script',
{
type: 'text/javascript',
async: '',
defer: '',
src: 'https://js-na1.hs-scripts.com/20534155.js',
},
],
[
'script',
{
type: 'text/javascript',
async: '',
src: 'https://ws.zoominfo.com/pixel/636535e8d10f825332bbd795',
'referrer-policy': 'unsafe-url',
},
],
[
'script',
{
type: 'text/javascript',
async: '',
src: 'https://www.googletagmanager.com/gtag/js?id=UA-24637628-7',
},
],
[
'script',
{},
`(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-PTLT3GH');`,
],
[
'script',
{},
`window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-24637628-7');
`,
],
['link', { rel: 'shortcut icon', type: 'image/svg+xml', href: '/favicon.svg' }],
[
'link',
{
rel: 'apple-touch-icon',
type: 'image/svg+xml',
sizes: '180x180',
href: '/favicon.svg',
},
],
[
'link',
{
rel: 'icon',
type: 'image/svg+xml',
sizes: '32x32',
href: '/favicon.svg',
},
],
[
'link',
{
rel: 'icon',
type: 'image/svg+xml',
sizes: '16x16',
href: '/favicon.svg',
},
],
[
'link',
{
rel: 'preconnect',
href: 'https://fonts.googleapis.com',
crossorigin: 'crossorigin',
},
],
[
'link',
{
rel: 'preconnect',
href: 'https://fonts.gstatic.com',
crossorigin: 'crossorigin',
},
],
[
'link',
{
rel: 'stylesheet',
href: 'https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200',
},
],
[
'link',
{
rel: 'stylesheet',
href: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;900&display=swap',
},
],
],
lastUpdated: true,
themeConfig: {
siteTitle: false,
logo: {
light: '/logo-light.svg',
dark: '/logo-dark.svg',
},
socialLinks: [
{ icon: 'github', link: 'https://github.com/directus/directus' },
{ icon: 'twitter', link: 'https://twitter.com/directus' },
{ icon: 'discord', link: 'https://directus.chat' },
],
nav: [
{ text: 'Website', link: 'https://directus.io/' },
{ text: 'Cloud Dashboard', link: 'https://directus.cloud/' },
{ text: 'Directus TV', link: 'https://directus.io/tv' },
],
algolia: {
appId: 'T5BDNEU205',
apiKey: '76eb519cf1a4492777a6991f75c5252b',
indexName: 'directus',
},
sidebar,
editLink: {
pattern: ({ filePath }) => {
if (filePath.includes('blog/')) {
return '/blog/guest-author';
} else {
return `https://github.com/directus/directus/edit/main/docs/${filePath}`;
}
},
},
},
sitemap: {
hostname: 'https://docs.directus.io',
},
transformPageData(pageData) {
switch (pageData.frontmatter['type']) {
case 'blog-post':
pageData.title = pageData.params?.['title'];
pageData.description = pageData.params?.['summary'];
pageData.frontmatter['head'] = setOGImage(pageData.params?.['image']);
break;
case 'guides-index':
pageData.title = pageData.params?.['title'];
pageData.description = pageData.params?.['summary'];
break;
default:
pageData.frontmatter['head'] = setOGImage('246e2f8a-98cd-4d54-9907-8927d1b9fb77');
}
function setOGImage(asset?: string) {
if (!asset) return [];
return [
['meta', { name: 'og:image', content: `https://marketing.directus.app/assets/${asset}?key=card` }],
['meta', { name: 'twitter:image', content: `https://marketing.directus.app/assets/${asset}?key=card` }],
['meta', { name: 'twitter:card', content: 'summary_large_image' }],
];
}
},
vite: {
resolve: {
alias: [
{
find: '@',
replacement: fileURLToPath(new URL('./', import.meta.url)),
},
{
find: /^.*\/VPSidebarItem\.vue$/,
replacement: fileURLToPath(new URL('./theme/components/VPSidebarItem.vue', import.meta.url)),
},
],
},
},
});

View File

@@ -1,44 +0,0 @@
import { readItems } from '@directus/sdk';
import { defineLoader } from 'vitepress';
import { client } from '../lib/directus.js';
export default defineLoader({
async load() {
const articles = (
await client.request(
readItems('developer_articles', {
fields: [
'*',
{ author: ['first_name', 'last_name', 'avatar', 'title'] },
{ tags: [{ directus_tags_id: ['title', 'slug', 'type'] }] },
],
filter: {
status: { _eq: 'published' },
},
sort: '-date_published',
}),
)
).map((article) => ({
id: article.slug,
title: article.title,
date_published: article.date_published,
summary: article.summary,
image: article.image,
author: article.author,
}));
const tags = await client.request(
readItems('docs_tags', {
// @ts-ignore
sort: '-count(developer_articles)',
}),
);
return {
blog: {
articles,
tags,
},
};
},
});

View File

@@ -1,297 +0,0 @@
export const gettingStarted = [
{ display: 'Quickstart Guide', path: '/getting-started/quickstart' },
{ display: 'Self-Hosted Installation', path: '/self-hosted/quickstart' },
];
export const sections = {
sdk: {
title: 'SDK Guides',
summary: 'Learn how to leverage our composable TypeScript SDK in your projects.',
cols: 1,
blocks: [
{
title: 'Getting Started',
items: [
{ display: 'SDK Quickstart', path: '/guides/sdk/getting-started' },
{ display: 'SDK Authentication', path: '/guides/sdk/authentication' },
{ display: 'SDK Types', path: '/guides/sdk/types' },
],
},
{
title: 'From the Blog',
items: [
{
display: 'Advanced Filtering',
path: '/blog/advanced-filtering-dates-aggregation-and-grouping-and-combining-filters',
},
],
},
],
},
frameworks: {
title: 'Framework Guides',
summary: 'Combine Directus with your favorite framework to create dynamic and efficient web applications.',
cols: 2,
blocks: [
{
title: 'Next.js',
items: [
{ display: 'Get Started with Next.js', path: '/guides/headless-cms/build-static-website/next' },
{ display: 'Set Up Live Preview With Next.js', path: '/guides/headless-cms/live-preview/nextjs' },
],
},
{
title: 'Nuxt',
items: [
{ display: 'Get Started with Nuxt', path: '/guides/headless-cms/build-static-website/nuxt-3' },
{ display: 'Set Up Live Preview With Nuxt', path: '/guides/headless-cms/live-preview/nuxt-3' },
],
},
{
title: 'From the Blog',
items: [
{ display: 'Get Started with SvelteKit', path: '/blog/getting-started-directus-sveltekit' },
{ display: 'Get Started with Astro', path: '/blog/getting-started-directus-astro' },
{ display: 'Get Started with Remix', path: '/blog/getting-started-with-directus-and-remix' },
{ display: 'Get Started with Eleventy', path: '/blog/getting-started-directus-and-eleventy-11ty-3' },
{ display: 'Get Started with SolidStart', path: '/blog/getting-started-solidstart' },
{ display: 'Get Started with Gatsby', path: '/blog/getting-started-with-directus-and-gatsby' },
],
},
],
},
'use-cases': {
title: 'Use Case Guides',
summary: 'Use Directus features to build various use cases in one project.',
cols: 1,
blocks: [
{
title: 'Headless CMS',
items: [
{ display: 'Build Content Approval Workflows', path: '/guides/headless-cms/approval-workflows' },
{ display: 'Implement Content Versioning', path: '/guides/headless-cms/content-versioning' },
{ display: 'Create Re-Usable Page Components', path: '/guides/headless-cms/reusable-components' },
{ display: 'Create Content Translations', path: '/guides/headless-cms/content-translations' },
{
display: 'Schedule Future Content',
paths: [
{ label: 'Static Sites', path: '/guides/headless-cms/schedule-content/static-sites' },
{ label: 'Dynamic Sites', path: '/guides/headless-cms/schedule-content/dynamic-sites' },
],
},
{
display: 'Trigger Site Builds',
paths: [
{ label: 'Netlify', path: '/guides/headless-cms/trigger-static-builds/netlify' },
{ label: 'Vercel', path: '/guides/headless-cms/trigger-static-builds/vercel' },
],
},
],
},
{
title: 'From the Blog',
items: [
{
display: 'SEO Tips & Tricks',
path: '/blog/directus-seo-tips-tricks',
},
],
},
],
},
'real-time': {
title: 'Realtime Guides',
summary: 'Access real-time data in your project with WebSockets, backed by your database.',
cols: 1,
blocks: [
{
title: 'Concepts',
items: [
{
display: 'Getting Started With Realtime',
paths: [
{ label: 'WebSockets', path: '/guides/real-time/getting-started/websockets' },
{ label: 'GraphQL', path: '/guides/real-time/getting-started/graphql' },
],
},
{ display: 'Authentication', path: '/guides/real-time/authentication' },
{ display: 'Operations', path: '/guides/real-time/operations' },
{
display: 'Subscriptions',
paths: [
{ label: 'WebSockets', path: '/guides/real-time/subscriptions/websockets' },
{ label: 'GraphQL', path: '/guides/real-time/subscriptions/graphql' },
],
},
],
},
{
title: 'Projects',
items: [
{
display: 'Build a Multi-User Chat',
paths: [
{ label: 'JavaScript', path: '/guides/real-time/chat/javascript' },
{ label: 'React', path: '/guides/real-time/chat/react' },
{ label: 'Vue', path: '/guides/real-time/chat/vue' },
],
},
{ display: 'Build a Live Poll Result', path: '/guides/real-time/live-poll' },
],
},
],
},
extensions: {
title: 'Extensions Guides',
summary: 'Learn how to extend Directus with various custom extension types.',
cols: 2,
blocks: [
{
title: 'Displays',
items: [
{ display: 'Use Displays To Format Date As An Age', path: '/guides/extensions/displays-date-to-age' },
{
display: 'Use Displays To Summarize Relational Items',
path: '/guides/extensions/displays-relational-summaries',
},
],
},
{
title: 'Endpoints',
items: [
{
display: 'Create a Public API Proxy',
path: '/guides/extensions/endpoints-api-proxy',
},
{
display: 'Create an Authenticated API Proxy (Twilio)',
path: '/guides/extensions/endpoints-api-proxy-twilio',
},
{
display: 'Create a Permissions-Based API Proxy (Stripe)',
path: '/guides/extensions/endpoints-privileged-endpoint-stripe',
},
],
},
{
title: 'Email Templates',
items: [{ display: 'Create An Email Template With Dynamic Values', path: '/guides/extensions/email-template' }],
},
{
title: 'Hooks',
items: [
{ display: 'Use Hooks to Create Stripe Customers', path: '/guides/extensions/hooks-add-stripe-customer' },
{
display: 'Use Hooks to Validate Phone Numbers With Twilio',
path: '/guides/extensions/hooks-validate-number-twilio',
},
],
},
{
title: 'Interfaces',
items: [
{
display: 'Create A Radio Selector With Icons, SVG, or Images',
path: '/guides/extensions/interfaces-radio-selector-icons',
},
{
display: 'Create A Searchable Dropdown With Items From Another Collection',
path: '/guides/extensions/interfaces-relational-dropdown',
},
],
},
{
title: 'Layouts',
items: [{ display: 'Create Your First Layout Extension', path: '/guides/extensions/layouts-getting-started' }],
},
{
title: 'Operations',
items: [
{
display: 'Exposing an npm Package as a Custom Operation',
path: '/guides/extensions/operations-npm-package',
},
{
display: 'Use Custom Operations to Send Bulk Email With SendGrid',
path: '/guides/extensions/operations-bulk-email-sendgrid',
},
{
display: 'Use Custom Operations to Add an Item Comment',
path: '/guides/extensions/operations-add-record-comments',
},
{
display: 'Use Custom Operations to Send SMS Notifications With Twilio',
path: '/guides/extensions/operations-send-sms-twilio',
},
],
},
{
title: 'Panels',
items: [
{
display: 'Create An Interactive Panel To Create Items',
path: '/guides/extensions/panels-create-items',
},
{
display: 'Create A Panel To Display External API Data With Vonage',
path: '/guides/extensions/panels-display-data-vonage',
},
{
display: 'Create An Interactive Panel To Send SMS With Twilio',
path: '/guides/extensions/panels-send-sms-twilio',
},
],
},
{
title: 'Modules',
items: [
{
display: 'Create a Custom Landing Page Module',
path: '/guides/extensions/modules-build-landing-page',
},
{
display: 'Use Native Layout Features In Your Modules',
path: '/guides/extensions/modules-native-layout-features',
},
],
},
{
title: 'From the Blog',
items: [
{
display: 'Using Hooks To Monitor & Error Track With Sentry',
path: '/blog/hooks-monitoring-error-tracking-sentry',
},
],
},
],
},
administration: {
title: 'Administration Guides',
summary: 'Learn key skills needed to successfully administer Directus in a real-world context.',
cols: 1,
blocks: [
{
title: 'Migrations',
items: [
{
display: 'Migrate Your Data Model',
paths: [
{ label: 'Node.js', path: '/guides/migration/node' },
{ label: 'Hoppscotch', path: '/guides/migration/hoppscotch' },
],
},
],
},
{
title: 'From the Blog',
items: [
{
display: 'Configuring Okta SSO',
path: '/blog/configuring-okta-sso',
},
],
},
],
},
};

View File

@@ -1,39 +0,0 @@
import { findWorkspaceDir } from '@pnpm/find-workspace-dir';
import { findWorkspacePackagesNoCheck, type Project } from '@pnpm/workspace.find-packages';
import { defineLoader } from 'vitepress';
export default defineLoader({
async load() {
const workspaceDir = await findWorkspaceDir(process.cwd());
if (!workspaceDir) throw new Error(`Couldn't find workspace dir`);
const workspacePackages = await findWorkspacePackagesNoCheck(workspaceDir);
const packages = Object.fromEntries(
workspacePackages
.filter(isValidPackage)
.map(({ manifest: { name, version } }) => [name, { version: getDetailedVersion(version) }]),
);
return packages;
},
});
function isValidPackage(workspacePackage: Project): workspacePackage is Project & {
manifest: Project['manifest'] & Required<Pick<Project['manifest'], 'name' | 'version'>>;
} {
const {
manifest: { name, version },
} = workspacePackage;
return name !== undefined && version !== undefined;
}
function getDetailedVersion(version: string) {
const splitVersion = version.split('.') as [string, string, string];
return {
full: version,
major: splitVersion[0],
minor: splitVersion.slice(0, 2).join('.'),
};
}

View File

@@ -1,747 +0,0 @@
import { formatTitle } from '@directus/format-title';
import { readItems } from '@directus/sdk';
import type { DefaultTheme } from 'vitepress';
import typeDocSidebar from '../../packages/typedoc-sidebar.json';
import { client } from '../lib/directus.js';
import { sections as guideSections } from './guides.js';
export default {
'/': sidebarDeveloperReference(),
'/user-guide/': sidebarUserGuide(),
'/plus/': await sidebarDirectusPlus(),
'/packages/': sidebarTypedocs(),
} as DefaultTheme.Sidebar;
function sidebarDeveloperReference() {
return [
{
text: 'Getting Started',
items: [
{
text: 'Introduction',
link: '/getting-started/introduction',
},
{
text: 'Quickstart Guide',
link: '/getting-started/quickstart',
},
{
text: 'Architecture',
link: '/getting-started/architecture',
},
{
text: 'Help & Support',
link: '/getting-started/support',
},
{
text: 'Resources',
link: '/getting-started/resources',
},
],
},
{
text: 'Developer Blog',
link: '/blog/',
activeMatch: '/blog/.*',
items: [],
},
{
text: 'Data Studio App',
collapsed: true,
items: [
{
link: '/app/data-model',
text: 'Data Model',
items: [
{
link: '/app/data-model/collections',
text: 'Collections',
},
{
link: '/app/data-model/fields',
text: 'Fields',
collapsed: true,
items: [
{
link: '/app/data-model/fields/text-numbers',
text: 'Text & Numbers',
},
{
link: '/app/data-model/fields/selection',
text: 'Selection',
},
{
link: '/app/data-model/fields/relational',
text: 'Relational',
},
{
link: '/app/data-model/fields/presentation',
text: 'Presentation',
},
{
link: '/app/data-model/fields/groups',
text: 'Groups',
},
{
link: '/app/data-model/fields/other',
text: 'Other',
},
],
},
{
link: '/app/data-model/relationships',
text: 'Relationships',
},
],
},
{
link: '/app/flows',
text: 'Flows',
items: [
{
link: '/app/flows/triggers',
text: 'Triggers',
},
{
link: '/app/flows/operations',
text: 'Operations',
},
],
},
],
},
{
text: 'API Reference',
collapsed: true,
items: [
{
link: '/reference/introduction',
text: 'Introduction',
},
{
link: '/reference/authentication',
text: 'Authentication',
},
{
link: '/reference/query',
text: 'Global Parameters',
},
{
link: '/reference/filter-rules',
text: 'Filter Rules',
},
{
link: '/reference/items',
text: 'Items',
},
{
link: '/reference/files',
text: 'Files',
},
{
link: '/reference/system/activity',
text: 'Activity',
},
{
link: '/reference/system/collections',
text: 'Collections',
},
{
link: '/reference/system/comments',
text: 'Comments',
},
{
link: '/reference/system/versions',
text: 'Content Versions',
},
{
link: '/reference/system/dashboards',
text: 'Dashboards',
},
{
link: '/reference/system/extensions',
text: 'Extensions',
},
{
link: '/reference/system/fields',
text: 'Fields',
},
{
link: '/reference/system/flows',
text: 'Flows',
},
{
link: '/reference/system/folders',
text: 'Folders',
},
{
link: '/reference/system/notifications',
text: 'Notifications',
},
{
link: '/reference/system/operations',
text: 'Operations',
},
{
link: '/reference/system/panels',
text: 'Panels',
},
{
link: '/reference/system/permissions',
text: 'Permissions',
},
{
link: '/reference/system/policies',
text: 'Policies',
},
{
link: '/reference/system/presets',
text: 'Presets',
},
{
link: '/reference/system/relations',
text: 'Relations',
},
{
link: '/reference/system/revisions',
text: 'Revisions',
},
{
link: '/reference/system/roles',
text: 'Roles',
},
{
link: '/reference/system/schema',
text: 'Schema',
},
{
link: '/reference/system/server',
text: 'Server',
},
{
link: '/reference/system/settings',
text: 'Settings',
},
{
link: '/reference/system/shares',
text: 'Shares',
},
{
link: '/reference/system/translations',
text: 'Custom Translations',
},
{
link: '/reference/system/users',
text: 'Users',
},
{
link: '/reference/system/utilities',
text: 'Utilities',
},
],
},
{
text: 'Guides',
collapsed: true,
items: [
{
text: 'All Guides',
link: '/guides/index.html',
},
...Object.entries(guideSections).map(([name, section]) => ({
text: section.title,
link: `/guides/${name}`,
})),
],
},
{
text: 'Use Cases',
collapsed: true,
items: [
{
text: 'Headless CMS',
items: [
{
text: 'Introduction',
link: '/use-cases/headless-cms/introduction',
},
{
text: 'Concepts',
link: '/use-cases/headless-cms/concepts',
},
{
text: 'Security Best Practices',
link: '/use-cases/headless-cms/security',
},
],
},
],
},
{
text: 'Extensions',
collapsed: true,
items: [
{
text: 'Fundamentals',
collapsed: true,
items: [
{
link: '/extensions/introduction',
text: 'Introduction',
},
{
link: '/extensions/installing-extensions',
text: 'Installing Extensions',
},
],
},
{
text: 'Developing Extensions',
collapsed: true,
items: [
{
link: '/extensions/creating-extensions',
text: 'Creating Extensions',
},
{
text: 'Extension Types',
collapsed: true,
items: [
{
link: '/extensions/displays',
text: 'Displays',
},
{
link: '/extensions/endpoints',
text: 'Endpoints',
},
{
link: '/extensions/hooks',
text: 'Hooks',
},
{
link: '/extensions/interfaces',
text: 'Interfaces',
},
{
link: '/extensions/layouts',
text: 'Layouts',
},
{
link: '/extensions/modules',
text: 'Modules',
},
{
link: '/extensions/operations',
text: 'Operations',
},
{
link: '/extensions/panels',
text: 'Panels',
},
{
link: '/extensions/themes',
text: 'Themes',
},
{
link: '/extensions/bundles',
text: 'Bundles',
},
],
},
{
text: 'Sandboxed Extensions',
collapsed: true,
items: [
{
link: '/extensions/sandbox/introduction',
text: 'Introduction',
},
{
link: '/extensions/sandbox/register',
text: 'Registering Extensions',
},
{
link: '/extensions/sandbox/sandbox-sdk',
text: 'Sandbox SDK',
},
],
},
{
text: 'Extension Services',
collapsed: true,
items: [
{
link: '/extensions/services/introduction',
text: 'Introduction',
},
{
link: '/extensions/services/accessing-items',
text: 'Accessing Items',
},
{
link: '/extensions/services/configuring-collections',
text: 'Configuring Collections, Fields, and Relations',
},
{
link: '/extensions/services/accessing-files',
text: 'Accessing Files',
},
{
link: '/extensions/services/working-with-users',
text: 'Working with Users',
},
],
},
],
},
{
text: 'Resources',
collapsed: true,
items: [
{
link: '/extensions/using-ui-components',
text: 'Components',
},
{
link: '/extensions/app-composables',
text: 'Composables',
},
{
link: '/contributing/codebase-overview.html#packages-packages',
text: 'Packages',
},
],
},
{
text: 'Marketplace <span class="badge">Beta</span>',
link: '/extensions/marketplace/publishing',
},
],
},
{
text: 'Contributing',
collapsed: true,
items: [
{ link: '/contributing/introduction', text: 'Introduction' },
{
text: 'Code',
items: [
{ link: '/contributing/feature-request-process', text: 'Request a Feature' },
{ link: '/contributing/pull-request-process', text: 'Pull Request Process' },
{ link: '/contributing/codebase-overview', text: 'Codebase Overview' },
{ link: '/contributing/running-locally', text: 'Running Dev Environment' },
{ link: '/contributing/tests', text: 'Tests' },
],
},
{ link: '/contributing/community', text: 'Community' },
{ link: '/contributing/sponsor', text: 'Sponsorship & Advocacy' },
],
},
{
text: 'Self-Hosted',
collapsed: true,
items: [
{
link: '/self-hosted/quickstart',
text: 'Quickstart',
},
{
link: '/self-hosted/config-options',
text: 'Config Options',
},
{
link: '/self-hosted/docker-guide',
text: 'Docker Guide',
},
{
link: '/self-hosted/cli',
text: 'CLI',
},
{
link: '/self-hosted/migrations',
text: 'Migrations',
},
{
link: '/self-hosted/email-templates',
text: 'Email Templates',
},
{
text: 'Single Sign-On (SSO)',
items: [
{ link: '/self-hosted/sso', text: 'Quickstart' },
{ link: '/self-hosted/sso-examples', text: 'Examples' },
],
},
{
link: '/self-hosted/upgrades-migrations',
text: 'Upgrades & Migrations',
},
],
},
{
text: 'Releases',
collapsible: true,
collapsed: true,
items: [
{
link: 'https://github.com/directus/directus/releases',
text: 'Release Notes',
},
{
link: '/releases/breaking-changes',
text: 'Breaking Changes',
},
],
},
];
}
function sidebarUserGuide() {
return [
{
text: 'Overview',
items: [
{
text: 'Data Studio App',
link: '/user-guide/overview/data-studio-app',
},
{
text: 'Quickstart Guide',
link: '/user-guide/overview/quickstart',
},
{
text: 'Glossary',
link: '/user-guide/overview/glossary',
},
],
},
{
text: 'Content Module',
collapsed: true,
items: [
{
link: '/user-guide/content-module/content',
text: 'Managing Content',
type: 'page',
items: [
{
link: '/user-guide/content-module/content/collections',
text: 'Collection Page',
type: 'page',
},
{
link: '/user-guide/content-module/content/items',
text: 'Item Page',
},
{
link: '/user-guide/content-module/content/shares',
text: 'Shares',
},
],
},
{
text: 'Layouts',
link: '/user-guide/content-module/layouts',
},
{
text: 'Import/Export',
link: '/user-guide/content-module/import-export',
},
{
text: 'Filters',
link: '/user-guide/content-module/filters',
},
{
text: 'Translation Strings',
link: '/user-guide/content-module/translation-strings',
},
{
text: 'Display Templates',
link: '/user-guide/content-module/display-templates',
},
],
},
{
text: 'User Management',
collapsed: true,
items: [
{
text: 'Key Concepts',
link: '/user-guide/user-management/users-roles-permissions',
},
{
text: 'User Directory',
link: '/user-guide/user-management/user-directory',
},
],
},
{
text: 'File Library',
collapsed: true,
items: [
{
text: 'Files',
link: '/user-guide/file-library/files',
},
{
text: 'Folders',
link: '/user-guide/file-library/folders',
},
],
},
{
text: 'Insights',
collapsed: true,
items: [
{
text: 'Dashboards',
link: '/user-guide/insights/dashboards',
},
{
text: 'Panels',
link: '/user-guide/insights/panels',
},
{
text: 'Charts',
link: '/user-guide/insights/charts',
},
],
},
{
text: 'Marketplace <span class="badge">Beta</span>',
collapsed: true,
items: [
{
text: 'Introduction',
link: '/user-guide/marketplace/overview',
},
],
},
{
text: 'Directus Cloud',
collapsed: true,
items: [
{
text: 'Overview',
link: '/user-guide/cloud/overview',
},
{
text: 'Projects',
link: '/user-guide/cloud/projects',
},
{
text: 'Environment Variables',
link: '/user-guide/cloud/variables',
},
{
text: 'Teams',
link: '/user-guide/cloud/teams',
},
{
text: 'Accounts',
link: '/user-guide/cloud/accounts',
},
{
text: 'Glossary',
link: '/user-guide/cloud/glossary',
},
],
},
{
text: 'General Settings',
collapsed: true,
items: [
{
text: 'Project Settings',
link: '/user-guide/settings/project-settings',
},
{
text: 'Custom Theming',
link: '/user-guide/settings/theming',
},
{
text: 'Preset and Bookmarks',
link: '/user-guide/settings/presets-bookmarks',
},
{
text: 'Activity Log',
link: '/user-guide/settings/activity-log',
},
{
text: 'System Logs',
link: '/user-guide/settings/system-logs',
},
],
},
];
}
function sidebarTypedocs() {
const sidebar = typeDocSidebar.map((item) => {
return typeDocSidebarFormat(item);
});
return sidebar;
}
function typeDocSidebarFormat(item: DefaultTheme.SidebarItem) {
if (item.link && !item.link.startsWith('/packages')) {
item.link = item.link.substring(item.link.indexOf('/packages'));
}
if (item.items) {
item.items = item.items.filter((subItem) => {
return subItem.link !== null || (subItem.items && subItem.items.length > 0);
});
}
if (item.text?.startsWith('@directus/')) {
item.items?.unshift({
text: 'Overview',
link: `/packages/${item.text}/`,
items: [],
collapsed: true,
});
item.text = formatTitle(item.text.replace('@directus/', ''));
item.text = item.text.replace('Sdk', 'SDK');
}
if (item.items && item.items.length > 0) {
item.items.map((subItem) => {
return typeDocSidebarFormat(subItem);
});
} else {
delete item.items;
delete item.collapsed;
}
return item;
}
async function sidebarDirectusPlus() {
const sections = await client.request(
readItems('dplus_docs_sections', {
fields: ['*', { articles: ['title', 'slug'] }],
filter: {
articles: {
status: { _eq: 'published' },
},
},
}),
);
const sidebar = sections.map((section) => {
return {
text: section.title,
collapsed: section.slug === 'overview' ? false : true,
items: section.articles.map((article) => ({
text: article.title,
link: `/plus/${article.slug}`,
})),
};
});
return sidebar;
}

View File

@@ -1 +0,0 @@
/// <reference types="vitepress/client" />

View File

@@ -1,25 +0,0 @@
/** Format date to be more readable */
export function getFriendlyDate(dateString: string) {
const date = new Date(dateString);
const year = date.getFullYear();
const month = new Intl.DateTimeFormat('en-US', { month: 'long' }).format(date);
const day = getDay(date.getDate());
return `${month} ${day}, ${year}`;
}
function getDay(day: number) {
const ordinalRules = new Intl.PluralRules('en-US', { type: 'ordinal' });
const suffixes = new Map([
['one', 'st'],
['two', 'nd'],
['few', 'rd'],
['other', 'th'],
]);
const rule = ordinalRules.select(day);
const suffix = suffixes.get(rule);
return `${day}${suffix}`;
}

View File

@@ -1,4 +0,0 @@
import { createDirectus, rest } from '@directus/sdk';
import type { Schema } from '../types/schema.js';
export const client = createDirectus<Schema>('https://marketing.directus.app').with(rest());

View File

@@ -1,12 +0,0 @@
// @ts-ignore
import iterator from 'markdown-it-for-inline';
const MATERIAL_ICON_RE = /<span(.*\bmi\b.*)>/;
export function useMaterialIconsNoTranslate(md: any) {
md.use(iterator, 'mi_no_translate', 'html_inline', (tokens: any, idx: number) => {
const match = tokens[idx].content.match(MATERIAL_ICON_RE);
if (match) tokens[idx].content = `<span translate="no"${match[1]}>`;
});
}

View File

@@ -1,102 +0,0 @@
<script setup lang="ts">
import { useData, useRoute } from 'vitepress';
import DefaultTheme from 'vitepress/theme';
import { computed } from 'vue';
import Feedback from '../components/Feedback.vue';
import Newsletter from '../components/Newsletter.vue';
import Cloud from '../components/Cloud.vue';
const { Layout } = DefaultTheme;
const { page } = useData();
const route = useRoute();
const title = computed(() => page.value.title);
const contributors = computed(() => page.value.frontmatter['contributors']);
const path = computed(() => route.path);
const isUserPage = computed(() => RegExp('^/user-guide/.*$').test(path.value));
const isDevPage = computed(() => !isUserPage.value);
const isPackagePage = computed(() => RegExp('^/packages/.+$').test(path.value));
</script>
<template>
<Layout>
<template #sidebar-nav-before>
<div class="sidebar-nav-before">
<div class="toggle">
<a href="/" :class="{ active: isDevPage }">Developers</a>
<a href="/user-guide/overview/data-studio-app" :class="{ active: isUserPage }">User Guide</a>
</div>
</div>
</template>
<template #doc-before>
<div v-if="isPackagePage" class="warning custom-block" style="padding-bottom: 16px; margin-bottom: 16px">
<p>
This is an auto-generated document to support extension builders understand the internal packages they can
utilize. To find our written guides, tutorials, and API/SDK reference, check out our
<a href="/">main docs</a>
.
</p>
</div>
</template>
<template #aside-outline-after>
<Newsletter class="newsletter" />
<Cloud class="cloud" />
</template>
<template #doc-footer-before>
<Feedback v-if="!isPackagePage" :url="path" :title="title" />
<Meta v-if="contributors" id="contributors" title-left="Contributors">
<template #left>
<div class="contributors">{{ contributors }}</div>
</template>
</Meta>
</template>
</Layout>
</template>
<style lang="scss" scoped>
.sidebar-nav-before {
padding: 1em 0;
border-bottom: 1px solid var(--vp-c-divider);
.toggle {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.5rem;
a {
display: block;
text-align: center;
font-size: 12px;
padding: 0.25rem;
font-weight: bold;
border-radius: 10rem;
border: 1px solid var(--vp-c-text-3);
color: var(--vp-c-text-2);
&:hover {
color: var(--vp-c-text-1);
border-color: var(--vp-c-text-2);
}
&.active {
background: var(--vp-c-brand-darkest);
color: white;
border-color: transparent;
}
}
}
}
.newsletter,
.cloud {
margin-top: 2em;
}
#contributors {
margin-bottom: 2em;
}
.contributors {
font-weight: 700;
}
</style>

View File

@@ -1,257 +0,0 @@
// Extended version of
https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/components/VPSidebarItem.vue
<script setup lang="ts">
import { useData } from 'vitepress';
import VPLink from 'vitepress/dist/client/theme-default/components/VPLink.vue';
import VPIconChevronRight from 'vitepress/dist/client/theme-default/components/icons/VPIconChevronRight.vue';
// @ts-ignore
import { useSidebarControl } from 'vitepress/dist/client/theme-default/composables/sidebar';
import type { DefaultTheme } from 'vitepress/theme';
import { computed, type HTMLAttributes } from 'vue';
type SidebarItemExtended = { activeMatch?: string; hideItems?: boolean; hidden?: boolean };
const props = defineProps<{
item: DefaultTheme.SidebarItem & SidebarItemExtended;
depth: number;
}>();
const { page } = useData();
const activeMatch = computed(() =>
props.item.activeMatch ? RegExp(props.item.activeMatch).test(`^/${page.value.relativePath}$`) : false,
);
const subItems =
props.item.items && props.item.hideItems
? props.item.items.map((item) => ({ ...item, hidden: props.item.hideItems }))
: props.item.items;
const { collapsed, collapsible, isLink, isActiveLink, hasActiveLink, hasChildren, toggle } = useSidebarControl(
computed(() => props.item),
);
const sectionTag = computed(() => (hasChildren.value ? 'section' : `div`));
const linkTag = computed(() => (isLink.value ? 'a' : 'div'));
const textTag = computed(() => {
// eslint-disable-next-line no-nested-ternary
return !hasChildren.value ? 'p' : props.depth + 2 === 7 ? 'p' : `h${props.depth + 2}`;
});
const itemRole = computed(() => (isLink.value ? undefined : 'button'));
const itemAttrs = computed<HTMLAttributes>(() => ({
...(itemRole.value && { role: itemRole.value }),
...(props.item.items && { tabindex: 0 }),
}));
const classes = computed(() => [
{ hidden: props.item.hidden },
[`level-${props.depth}`],
{ collapsible: collapsible.value },
{ collapsed: collapsed.value },
{ 'is-link': isLink.value },
{ 'is-active': isActiveLink.value || activeMatch.value },
{ 'has-active': hasActiveLink.value },
]);
function onItemInteraction(e: MouseEvent | Event) {
if ('key' in e && e.key !== 'Enter') {
return;
}
if (!props.item.link) toggle();
}
function onCaretClick() {
if (props.item.link) toggle();
}
</script>
<template>
<component :is="sectionTag" class="VPSidebarItem" :class="classes">
<div
v-if="item.text"
class="item"
v-bind="itemAttrs"
v-on="item.items ? { click: onItemInteraction, keydown: onItemInteraction } : {}"
>
<div class="indicator" />
<VPLink v-if="item.link" :tag="linkTag" class="link" :href="item.link">
<!-- eslint-disable-next-line vue/no-v-text-v-html-on-component vue/no-v-html -->
<component :is="textTag" class="text" v-html="item.text" />
</VPLink>
<!-- eslint-disable-next-line vue/no-v-text-v-html-on-component vue/no-v-html -->
<component :is="textTag" v-else class="text" v-html="item.text" />
<div
v-if="item.collapsed != null"
class="caret"
role="button"
aria-label="toggle section"
tabindex="0"
@click="onCaretClick"
@keydown.enter="onCaretClick"
>
<VPIconChevronRight class="caret-icon" />
</div>
</div>
<div v-if="item.items && item.items.length" class="items">
<template v-if="depth < 5">
<VPSidebarItem v-for="(i, index) in subItems" :key="i.text ?? index" :item="i" :depth="depth + 1" />
</template>
</div>
</component>
</template>
<style scoped>
.VPSidebarItem.level-0 {
padding-bottom: 24px;
}
.VPSidebarItem.collapsed.level-0 {
padding-bottom: 10px;
}
.item {
position: relative;
display: flex;
width: 100%;
}
.hidden {
display: none;
}
.VPSidebarItem.collapsible > .item {
cursor: pointer;
}
.indicator {
position: absolute;
top: 6px;
bottom: 6px;
left: -17px;
width: 1px;
transition: background-color 0.25s;
}
.VPSidebarItem.level-2.is-active > .item > .indicator,
.VPSidebarItem.level-3.is-active > .item > .indicator,
.VPSidebarItem.level-4.is-active > .item > .indicator,
.VPSidebarItem.level-5.is-active > .item > .indicator {
background-color: var(--vp-c-brand);
}
.link {
display: flex;
align-items: center;
flex-grow: 1;
}
.text {
flex-grow: 1;
padding: 4px 0;
line-height: 24px;
font-size: 14px;
transition: color 0.25s;
}
.VPSidebarItem.level-0 .text {
font-weight: 700;
color: var(--vp-c-text-1);
}
.VPSidebarItem.level-1 .text,
.VPSidebarItem.level-2 .text,
.VPSidebarItem.level-3 .text,
.VPSidebarItem.level-4 .text,
.VPSidebarItem.level-5 .text {
font-weight: 500;
color: var(--vp-c-text-2);
}
.VPSidebarItem.level-0.is-link > .item > .link:hover .text,
.VPSidebarItem.level-1.is-link > .item > .link:hover .text,
.VPSidebarItem.level-2.is-link > .item > .link:hover .text,
.VPSidebarItem.level-3.is-link > .item > .link:hover .text,
.VPSidebarItem.level-4.is-link > .item > .link:hover .text,
.VPSidebarItem.level-5.is-link > .item > .link:hover .text {
color: var(--vp-c-brand);
}
.VPSidebarItem.level-0.has-active > .item > .text,
.VPSidebarItem.level-1.has-active > .item > .text,
.VPSidebarItem.level-2.has-active > .item > .text,
.VPSidebarItem.level-3.has-active > .item > .text,
.VPSidebarItem.level-4.has-active > .item > .text,
.VPSidebarItem.level-5.has-active > .item > .text,
.VPSidebarItem.level-0.has-active > .item > .link > .text,
.VPSidebarItem.level-1.has-active > .item > .link > .text,
.VPSidebarItem.level-2.has-active > .item > .link > .text,
.VPSidebarItem.level-3.has-active > .item > .link > .text,
.VPSidebarItem.level-4.has-active > .item > .link > .text,
.VPSidebarItem.level-5.has-active > .item > .link > .text {
color: var(--vp-c-text-1);
}
.VPSidebarItem.level-0.is-active > .item .link > .text,
.VPSidebarItem.level-1.is-active > .item .link > .text,
.VPSidebarItem.level-2.is-active > .item .link > .text,
.VPSidebarItem.level-3.is-active > .item .link > .text,
.VPSidebarItem.level-4.is-active > .item .link > .text,
.VPSidebarItem.level-5.is-active > .item .link > .text {
color: var(--vp-c-brand);
}
.caret {
display: flex;
justify-content: center;
align-items: center;
margin-right: -7px;
width: 32px;
height: 32px;
color: var(--vp-c-text-3);
cursor: pointer;
transition: color 0.25s;
flex-shrink: 0;
}
.item:hover .caret {
color: var(--vp-c-text-2);
}
.item:hover .caret:hover {
color: var(--vp-c-text-1);
}
.caret-icon {
width: 18px;
height: 18px;
fill: currentColor;
transform: rotate(90deg);
transition: transform 0.25s;
}
.VPSidebarItem.collapsed .caret-icon {
transform: rotate(0);
}
.VPSidebarItem.level-1 .items,
.VPSidebarItem.level-2 .items,
.VPSidebarItem.level-3 .items,
.VPSidebarItem.level-4 .items,
.VPSidebarItem.level-5 .items {
border-left: 1px solid var(--vp-c-divider);
padding-left: 16px;
}
.VPSidebarItem.collapsed .items {
display: none;
}
</style>

View File

@@ -1,83 +0,0 @@
span[mi] {
font-family: 'Material Symbols Outlined';
font-weight: normal;
font-style: normal;
font-size: 18px;
font-variation-settings: 'wght' 600;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
font-feature-settings: 'liga';
-webkit-font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
vertical-align: -4px;
color: var(--vp-c-text-1);
}
span[mi][prmry] {
color: var(--vp-c-brand);
}
span[mi][dark] {
color: var(--vp-c-gray);
}
span[mi][dngr] {
color: var(--vp-c-red);
}
span[mi][warn] {
color: var(--vp-c-yellow);
}
span[mi][muted] {
color: var(--vp-c-gray);
}
span[mi][btn] {
color: var(--vp-c-text-inverse-1);
background-color: var(--vp-c-brand);
border-radius: 50%;
width: 24px;
height: 24px;
vertical-align: middle;
display: inline-flex;
align-items: center;
justify-content: center;
margin-bottom: 4px;
}
span[mi][btn][dngr] {
background-color: var(--vp-c-red-dimm-3);
color: var(--vp-c-red);
}
span[mi][btn][sec] {
background-color: var(--vp-c-purple-dimm-3);
color: var(--vp-c-purple);
}
span[mi][btn][warn] {
background-color: var(--vp-c-yellow-dimm-3);
color: var(--vp-c-yellow);
}
span[mi][btn][outline] {
background-color: transparent;
border: var(--theme--border-width) solid var(--vp-c-purple);
color: var(--vp-c-purple);
}
span[mi][btn][action] {
background-color: var(--vp-c-green-dimm-3);
color: var(--vp-c-green);
}
span[mi][btn][muted] {
background-color: var(--vp-c-gray-light-4);
color: var(--vp-c-gray);
}

View File

@@ -1,37 +0,0 @@
/* eslint-disable vue/multi-word-component-names */
/* eslint-disable vue/no-reserved-component-names */
import type { Theme } from 'vitepress';
import { enhanceAppWithTabs } from 'vitepress-plugin-tabs/client';
import DefaultTheme from 'vitepress/theme';
import Article from '../components/Article.vue';
import Button from '../components/Button.vue';
import Card from '../components/Card.vue';
import Divider from '../components/Divider.vue';
import GuideMeta from '../components/GuideMeta.vue';
import Meta from '../components/Meta.vue';
import SnippetToggler from '../components/SnippetToggler.vue';
import Tabs from '../components/Tabs.vue';
import Layout from './Layout.vue';
import './icons.css';
import './overrides.css';
import './vars.css';
const theme: Theme = {
extends: DefaultTheme,
Layout,
enhanceApp({ app }) {
enhanceAppWithTabs(app);
app.component('Article', Article);
app.component('Button', Button);
app.component('Card', Card);
app.component('GuideMeta', GuideMeta);
app.component('Meta', Meta);
app.component('Divider', Divider);
app.component('SnippetToggler', SnippetToggler);
app.component('Tabs', Tabs);
},
};
export default theme;

View File

@@ -1,131 +0,0 @@
/**
- * Make logo size of the sidebar
+ * Switch to mobile navbar earlier to prevent overlapping page
* -------------------------------------------------------------------------- */
@media only screen and (min-width: 390px) {
.VPNav .VPNavBar .VPNavBarTitle .logo {
height: unset;
}
}
/**
* Switch to mobile navbar earlier to prevent overlapping page
* -------------------------------------------------------------------------- */
@media (max-width: 959px) {
.VPNav .VPNavBar .VPNavBarMenu,
.VPNav .VPNavBar .VPNavBarExtra {
display: none;
}
.VPNav .VPNavBar .VPNavBarHamburger {
display: flex;
}
.VPNav .VPNavScreen {
display: unset;
}
}
/**
* Border radius on inline media
* -------------------------------------------------------------------------- */
.vp-doc img,
.vp-doc video {
border-radius: 8px;
width: 100%;
box-shadow: 0px 0px 6px 0px rgba(23, 41, 64, 0.2);
}
/**
* Match font weight of app in headings
* -------------------------------------------------------------------------- */
.vp-doc h1,
.vp-doc h2,
.vp-doc h3,
.vp-doc h4,
.vp-doc h5,
.vp-doc h6 {
font-weight: 700;
}
.vp-doc h4 {
margin-top: 16px;
}
/**
* Hubspot color-scheme weirdness
* -------------------------------------------------------------------------- */
#hubspot-messages-iframe-container {
color-scheme: auto;
}
/**
* Fix spacing of lists in custom blocks
* -------------------------------------------------------------------------- */
.vp-doc .custom-block li + li {
margin: 0;
}
/**
* Same font weight for links inside custom blocks as outside
* -------------------------------------------------------------------------- */
.vp-doc .custom-block a {
font-weight: 500;
}
/**
* Remove padding on bottom of homepage
* -------------------------------------------------------------------------- */
.VPHome {
padding-bottom: 0 !important;
}
/**
* Add spacing between h3 and snippet toggler for "API Reference" pages
* -------------------------------------------------------------------------- */
.vp-doc h3 + .snippet-toggler {
margin-top: 1em;
margin-bottom: 1em;
}
/**
* Reduce spacing at bottom of sidebar section
* -------------------------------------------------------------------------- */
.VPSidebarItem.level-0 {
padding-bottom: 10px !important;
}
/**
* Light theme for tabs component
* -------------------------------------------------------------------------- */
:root {
--vp-plugin-tabs-tab-bg: transparent;
}
.plugin-tabs {
border: 1px solid var(--vp-plugin-tabs-tab-divider);
}
.plugin-tabs--tab-list::after {
height: 1px;
}
.plugin-tabs--tab {
border-width: 1px;
}
/**
* Sidebar Badge
* -------------------------------------------------------------------------- */
.VPSidebarItem span.badge {
border: 1px solid var(--vp-c-divider);
background-color: var(--vp-c-bg-alt);
font-size: 0.75em;
padding: 0.1rem 0.4rem;
border-radius: 10rem;
vertical-align: top;
transition: background-color 0.25s;
}
.VPSidebarItem.is-active span.badge {
border: 1px solid var(--vp-custom-block-tip-border);
color: var(--vp-custom-block-tip-text);
background-color: var(--vp-custom-block-tip-bg);
}

View File

@@ -1,139 +0,0 @@
/**
* VitePress variables overrides
*
* See https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css
* for all available variables
* -------------------------------------------------------------------------- */
/**
* Colors Base
* -------------------------------------------------------------------------- */
:root {
--vp-c-black: #21262e;
--vp-c-black-pure: #172940;
--vp-c-black-soft: #161b22;
--vp-c-black-mute: #1d1d21;
--vp-c-gray: #666672;
--vp-c-gray-light-1: #8596ab;
--vp-c-gray-light-2: #a2b5cd;
--vp-c-gray-light-3: #d3dae4;
--vp-c-gray-light-4: #e4eaf1;
--vp-c-gray-light-5: #f0f4f9;
--vp-c-purple: #6644ff;
--vp-c-purple-light: #a08bff;
--vp-c-purple-lighter: #ddddff;
--vp-c-purple-lightest: #f0ecff;
--vp-c-purple-dark: #7359e7;
--vp-c-purple-darker: #4422dd;
--vp-c-purple-dimm-1: rgba(102, 68, 255, 0.5);
--vp-c-purple-dimm-2: rgba(102, 68, 255, 0.25);
--vp-c-purple-dimm-3: rgba(102, 68, 255, 0.09);
--vp-c-yellow: #fbc54f;
--vp-c-yellow-light: #fff7ed;
--vp-c-yellow-lighter: #fbbf24;
--vp-c-yellow-dark: #b45309;
--vp-c-yellow-darker: #92400e;
--vp-c-yellow-dimm-1: rgba(234, 179, 8, 0.5);
--vp-c-yellow-dimm-2: rgba(234, 179, 8, 0.2);
--vp-c-yellow-dimm-3: rgba(234, 179, 8, 0.05);
--vp-c-red: #e35169;
--vp-c-red-light: #fceef0;
--vp-c-red-lighter: #fceef0;
--vp-c-red-dark: #e11d48;
--vp-c-red-darker: #be123c;
--vp-c-red-dimm-1: rgba(244, 63, 94, 0.5);
--vp-c-red-dimm-2: rgba(244, 63, 94, 0.2);
--vp-c-red-dimm-3: rgba(244, 63, 94, 0.05);
--vp-c-pink: #fe97dc;
}
/**
* Colors Theme
* -------------------------------------------------------------------------- */
:root {
--vp-c-brand: var(--vp-c-purple);
--vp-c-brand-light: var(--vp-c-purple-light);
--vp-c-brand-lighter: var(--vp-c-purple-lighter);
--vp-c-brand-dark: var(--vp-c-purple-dark);
--vp-c-brand-darker: var(--vp-c-purple-darker);
--vp-c-divider: rgba(60, 60, 67, 0.12);
}
.dark {
--vp-c-purple: #8866ff;
--vp-c-purple-dimm-1: rgba(136, 102, 255, 0.5);
--vp-c-purple-dimm-2: rgba(136, 102, 255, 0.25);
--vp-c-purple-dimm-3: rgba(159, 132, 255, 0.1);
--vp-c-divider: rgba(82, 82, 89, 0.32);
}
/**
* Globals
* -------------------------------------------------------------------------- */
:root {
--rounded-none: 0;
--rounded-sm: 0.125rem;
--rounded: 0.25rem;
--rounded-md: 0.375rem;
--rounded-lg: 0.5rem;
--rounded-xl: 0.75rem;
--rounded-2xl: 1rem;
--rounded-3xl: 1.5rem;
--rounded-full: 9999px;
}
/**
* Component: Custom Block
* -------------------------------------------------------------------------- */
:root {
--vp-custom-block-tip-border: var(--vp-c-purple-dimm-1);
--vp-custom-block-tip-text: var(--vp-c-purple-darker);
--vp-custom-block-tip-bg: var(--vp-c-purple-dimm-3);
--vp-custom-block-tip-code-bg: var(--vp-custom-block-tip-bg);
--vp-custom-block-warning-border: var(--vp-c-yellow-dimm-1);
--vp-custom-block-warning-text: var(--vp-c-yellow-darker);
--vp-custom-block-warning-bg: var(--vp-c-yellow-dimm-3);
--vp-custom-block-warning-code-bg: var(--vp-custom-block-tip-bg);
--vp-custom-block-danger-border: var(--vp-c-red-dimm-1);
--vp-custom-block-danger-text: var(--vp-c-red-darker);
--vp-custom-block-danger-bg: var(--vp-c-red-dimm-3);
--vp-custom-block-danger-code-bg: var(--vp-custom-block-tip-bg);
}
.dark {
--vp-custom-block-tip-border: var(--vp-c-purple-dimm-2);
--vp-custom-block-tip-text: var(--vp-c-purple-light);
--vp-custom-block-warning-border: var(--vp-c-yellow-dimm-2);
--vp-custom-block-warning-text: var(--vp-c-yellow);
--vp-custom-block-danger-border: var(--vp-c-red-dimm-2);
--vp-custom-block-danger-text: var(--vp-c-red);
}
/**
* Component: Docs Landing Page
* -------------------------------------------------------------------------- */
:root {
--vp-docs-section-bg: #0e1c2f;
--footer-link-color: inherit;
}
.dark {
--vp-docs-section-bg: #000;
--footer-link-color: white;
}

View File

@@ -1,9 +0,0 @@
{
"extends": "@directus/tsconfig/vue3",
"compilerOptions": {
"paths": {
"@/*": ["./*"]
}
},
"include": ["env.d.ts", "**/*.vue"]
}

View File

@@ -1,61 +0,0 @@
interface Author {
first_name: string;
last_name: string;
avatar: string;
title: string;
}
interface Contributor {
name: string;
}
export interface DeveloperArticle {
title: string;
date_published: string;
slug: string;
image: string;
status: string;
summary: string;
content: string;
author: Author;
contributors: Contributor[];
tags: DocsTagJunction[];
}
interface DocsTagJunction {
docs_tags_id: DocsTag;
}
interface DeveloperArticleJunction {
developer_articles_id: DeveloperArticle;
}
export interface DocsTag {
id: string;
title: string;
slug: string;
type: string;
developer_articles: DeveloperArticleJunction[];
}
export interface Schema {
developer_articles: DeveloperArticle[];
docs_tags: DocsTag[];
dplus_docs_sections: DplusDocsSection[];
dplus_docs_articles: DplusDocsArticle[];
}
export interface DplusDocsArticle {
title: string;
slug: string;
status: string;
summary: string;
content: string;
section?: string | DplusDocsSection;
}
export interface DplusDocsSection {
title: string;
slug: string;
articles: DplusDocsArticle[];
}

View File

@@ -1,354 +0,0 @@
#
# due to our netlify configuration which declares .vitepress/dist
# as the build directory, the _redirects file isn't detected
#
# to preserve the readability of the _redirects file, we've put
# together a quick generator which still references this file,
# but creates the appropriate entries in the netlify.toml
#
/app/data-model.html https://directus.io/docs/getting-started/data-model
/app/data-model/collections.html https://directus.io/docs/guides/data-model/collections
/app/data-model/fields.html https://directus.io/docs/guides/data-model/fields
/app/data-model/fields/groups.html https://directus.io/docs/guides/data-model/interfaces
/app/data-model/fields/other.html https://directus.io/docs/guides/data-model/interfaces
/app/data-model/fields/presentation.html https://directus.io/docs/guides/data-model/interfaces
/app/data-model/fields/relational.html https://directus.io/docs/guides/data-model/interfaces
/app/data-model/fields/selection.html https://directus.io/docs/guides/data-model/interfaces
/app/data-model/fields/text-numbers.html https://directus.io/docs/guides/data-model/interfaces
/app/data-model/relationships.html https://directus.io/docs/guides/data-model/relationships
/app/faq.html https://directus.io/docs/community/reporting-and-support/troubleshooting-steps
/app/flows.html https://directus.io/docs/guides/automate/flows
/app/flows/operations.html https://directus.io/docs/guides/automate/operations
/app/flows/triggers.html https://directus.io/docs/guides/automate/triggers
/app/presets-bookmarks.html https://directus.io/docs/guides/content/explore
/app/security.html https://directus.io/docs/configuration/security-limits
/blog/guest-author.html https://directus.io/docs/community/programs/guest-authors
/blog/ https://directus.io/docs/tutorials
/contributing/code-of-conduct.html https://directus.io/docs/community/overview/conduct
/contributing/codebase-overview.html https://directus.io/docs/community/codebase/overview
/contributing/community.html https://directus.io/docs/community/overview/welcome
/contributing/feature-request-process.html https://directus.io/docs/community/contribution/feature-requests
/contributing/introduction.html https://directus.io/docs/community/overview/welcome
/contributing/pull-request-process.html https://directus.io/docs/community/contribution/pull-requests
/contributing/running-locally.html https://directus.io/docs/community/codebase/dev-environment
/contributing/sponsor.html https://directus.io/docs/community/overview/welcome
/contributing/tests.html https://directus.io/docs/community/codebase/testing
/contributing/translations.html https://directus.io/docs/community/contribution/translations
/extensions/app-composables.html https://directus.io/docs/guides/extensions/app-extensions/composables
/extensions/bundles.html https://directus.io/docs/guides/extensions/bundles
/extensions/creating-extensions.html https://directus.io/docs/guides/extensions/cli
/extensions/displays.html https://directus.io/docs/guides/extensions/app-extensions/displays
/extensions/endpoints.html https://directus.io/docs/guides/extensions/api-extensions/endpoints
/extensions/hooks.html https://directus.io/docs/guides/extensions/api-extensions/hooks
/extensions/installing-extensions.html https://directus.io/docs/self-hosting/including-extensions
/extensions/interfaces.html https://directus.io/docs/guides/extensions/app-extensions/interfaces
/extensions/introduction.html https://directus.io/docs/guides/extensions/overview
/extensions/layouts.html https://directus.io/docs/guides/extensions/app-extensions/layouts
/extensions/marketplace/publishing.html https://directus.io/docs/guides/extensions/marketplace/publishing
/extensions/modules.html https://directus.io/docs/guides/extensions/app-extensions/modules
/extensions/operations.html https://directus.io/docs/guides/extensions/api-extensions/operations
/extensions/panels.html https://directus.io/docs/guides/extensions/app-extensions/panels
/extensions/sandbox/introduction.html https://directus.io/docs/guides/extensions/api-extensions/sandbox
/extensions/sandbox/register.html https://directus.io/docs/guides/extensions/api-extensions/sandbox
/extensions/sandbox/sandbox-sdk.html https://directus.io/docs/guides/extensions/api-extensions/sandbox
/extensions/services/accessing-files.html https://directus.io/docs/guides/extensions/api-extensions/services
/extensions/services/accessing-items.html https://directus.io/docs/guides/extensions/api-extensions/services
/extensions/services/configuring-collections.html https://directus.io/docs/guides/extensions/api-extensions/services
/extensions/services/introduction.html https://directus.io/docs/guides/extensions/api-extensions/services
/extensions/services/working-with-users.html https://directus.io/docs/guides/extensions/api-extensions/services
/extensions/themes.html https://directus.io/docs/guides/extensions/app-extensions/themes
/extensions/using-ui-components.html https://directus.io/docs/guides/extensions/app-extensions/ui-library
/getting-started/architecture.html https://directus.io/docs/getting-started/overview
/getting-started/introduction.html https://directus.io/docs/getting-started/overview
/getting-started/quickstart.html https://directus.io/docs/getting-started/create-a-project
/getting-started/resources.html https://directus.io/docs/getting-started/resources
/getting-started/support.html https://directus.io/docs/getting-started/overview
/guides/extensions/displays-date-to-age.html https://directus.io/docs/tutorials/extensions/
/guides/extensions/displays-relational-summaries.html https://directus.io/docs/tutorials/extensions/summarize-relational-items-in-a-custom-display-extension
/guides/extensions/email-template.html https://directus.io/docs/tutorials/extensions/use-dynamic-values-in-custom-email-templates
/guides/extensions/endpoints-api-proxy-twilio.html https://directus.io/docs/tutorials/extensions/proxy-an-external-api-in-a-custom-endpoint-extension
/guides/extensions/endpoints-api-proxy.html https://directus.io/docs/tutorials/extensions/proxy-an-external-api-in-a-custom-endpoint-extension
/guides/extensions/endpoints-privileged-endpoint-stripe.html https://directus.io/docs/tutorials/extensions/check-permissions-in-a-custom-endpoint
/guides/extensions/hooks-add-stripe-customer.html https://directus.io/docs/tutorials/extensions/create-new-customers-in-stripe-in-a-custom-hook
/guides/extensions/hooks-validate-number-twilio.html https://directus.io/docs/tutorials/extensions/validate-phone-numbers-with-twilio-in-a-custom-hook
/guides/extensions/interfaces-radio-selector-icons.html https://directus.io/docs/tutorials/extensions/
/guides/extensions/interfaces-relational-dropdown.html https://directus.io/docs/tutorials/extensions/
/guides/extensions/layouts-getting-started.html https://directus.io/docs/tutorials/extensions/
/guides/extensions/modules-build-landing-page.html https://directus.io/docs/tutorials/extensions/implement-navigation-in-multipage-custom-modules
/guides/extensions/modules-native-layout-features.html https://directus.io/docs/tutorials/extensions/understand-available-slots-in-custom-modules
/guides/extensions/operations-add-record-comments.html https://directus.io/docs/tutorials/extensions/send-sms-messages-with-twilio-in-custom-operations
/guides/extensions/operations-bulk-email-sendgrid.html https://directus.io/docs/tutorials/extensions/
/guides/extensions/operations-npm-package.html https://directus.io/docs/tutorials/extensions/use-npm-packages-in-custom-operations
/guides/extensions/operations-send-sms-twilio.html https://directus.io/docs/tutorials/extensions/send-sms-messages-with-twilio-in-custom-operations
/guides/extensions/panels-create-items.html https://directus.io/docs/tutorials/extensions/create-collection-items-in-custom-panels
/guides/extensions/panels-display-data-vonage.html https://directus.io/docs/tutorials/extensions/display-external-api-data-from-vonage-in-custom-panels
/guides/extensions/panels-send-sms-twilio.html https://directus.io/docs/tutorials/extensions/send-sms-messages-with-twilio-in-custom-panels
/guides/flows/flows-for-loop.html https://directus.io/docs/tutorials/extensions/use-dynamic-values-in-custom-email-templates
/guides/flows/slugify-text-with-run-script.html https://directus.io/docs/tutorials/workflows
/guides/headless-cms/approval-workflows.html https://directus.io/docs/tutorials/workflows/build-content-approval-workflows-with-custom-permissions
/guides/headless-cms/build-static-website/ https://directus.io/docs/tutorials/getting-started/fetch-data-from-directus-with-nextjs
/guides/headless-cms/build-static-website/next.html https://directus.io/docs/tutorials/getting-started/fetch-data-from-directus-with-nextjs
/guides/headless-cms/build-static-website/nuxt-3.html https://directus.io/docs/tutorials/getting-started/fetch-data-from-directus-with-nuxt
/guides/headless-cms/content-translations.html https://directus.io/docs/configuration/translations
/guides/headless-cms/content-versioning.html https://directus.io/docs/guides/content/content-versioning
/guides/headless-cms/live-preview/ https://directus.io/docs/guides/content/live-preview
/guides/headless-cms/live-preview/nextjs.html https://directus.io/docs/tutorials/getting-started/set-up-live-preview-with-next-js
/guides/headless-cms/live-preview/nuxt-3.html https://directus.io/docs/tutorials/getting-started/set-up-live-preview-with-nuxt
/guides/headless-cms/reusable-components.html https://directus.io/docs/tutorials/getting-started/create-reusable-blocks-with-many-to-any-relationships
/guides/headless-cms/schedule-content/dynamic-sites.html https://directus.io/docs/tutorials/workflows/schedule-future-content-with-directus-automate
/guides/headless-cms/schedule-content/ https://directus.io/docs/tutorials/workflows/schedule-future-content-with-directus-automate
/guides/headless-cms/schedule-content/static-sites.html https://directus.io/docs/tutorials/workflows/schedule-future-content-with-directus-automate
/guides/headless-cms/trigger-static-builds/ https://directus.io/docs/tutorials/workflows/trigger-netlify-site-builds-with-directus-automate
/guides/headless-cms/trigger-static-builds/netlify.html https://directus.io/docs/tutorials/workflows/trigger-netlify-site-builds-with-directus-automate
/guides/headless-cms/trigger-static-builds/vercel.html https://directus.io/docs/tutorials/workflows/trigger-vercel-site-builds-with-directus-automate
/guides/ https://directus.io/docs/tutorials
/guides/migration/hoppscotch.html https://directus.io/docs/tutorials/migration/promoting-changes-between-environments-in-directus
/guides/migration/ https://directus.io/docs/tutorials/migration/promoting-changes-between-environments-in-directus
/guides/migration/node.html https://directus.io/docs/tutorials/migration/promoting-changes-between-environments-in-directus
/guides/real-time/authentication.html https://directus.io/docs/configuration/realtime
/guides/real-time/chat/ https://directus.io/docs/tutorials/projects/build-a-multi-user-chat-with-javascript-and-directus-realtime
/guides/real-time/chat/javascript.html https://directus.io/docs/tutorials/projects/build-a-multi-user-chat-with-javascript-and-directus-realtime
/guides/real-time/chat/react.html https://directus.io/docs/tutorials/projects/build-a-multi-user-chat-with-react-and-directus-realtime
/guides/real-time/chat/vue.html https://directus.io/docs/tutorials/projects/build-a-multi-user-chat-with-vue-js-and-directus-realtime
/guides/real-time/getting-started/graphql.html https://directus.io/docs/configuration/realtime
/guides/real-time/getting-started/ https://directus.io/docs/configuration/realtime
/guides/real-time/getting-started/websockets-js.html https://directus.io/docs/configuration/realtime
/guides/real-time/getting-started/websockets.html https://directus.io/docs/configuration/realtime
/guides/real-time/live-poll.html https://directus.io/docs/guides/realtime
/guides/real-time/operations.html https://directus.io/docs/guides/realtime/actions
/guides/real-time/subscriptions/graphql.html https://directus.io/docs/guides/realtime/subscriptions
/guides/real-time/subscriptions/ https://directus.io/docs/guides/realtime/subscriptions
/guides/real-time/subscriptions/websockets.html https://directus.io/docs/guides/realtime/subscriptions
/guides/sdk/authentication.html https://directus.io/docs/guides/connect/sdk
/guides/sdk/getting-started.html https://directus.io/docs/guides/connect/sdk
/guides/sdk/types.html https://directus.io/docs/guides/connect/sdk
/guides/template_shell.html https://directus.io/docs/
/ https://directus.io/docs/
/plus/ https://directus.io/docs/
/reference/authentication.html https://directus.io/docs/api/authentication
/reference/files.html https://directus.io/docs/api/files
/reference/filter-rules.html https://directus.io/docs/api
/reference/introduction.html https://directus.io/docs/api
/reference/items.html https://directus.io/docs/api/items
/reference/old-sdk.html https://directus.io/docs/api
/reference/query.html https://directus.io/docs/api
/reference/system/activity.html https://directus.io/docs/api
/reference/system/collections.html https://directus.io/docs/api/collections
/reference/system/comments.html https://directus.io/docs/api/comments
/reference/system/dashboards.html https://directus.io/docs/api/dashboards
/reference/system/extensions.html https://directus.io/docs/api/extensions
/reference/system/fields.html https://directus.io/docs/api/fields
/reference/system/flows.html https://directus.io/docs/api/flows
/reference/system/folders.html https://directus.io/docs/api/folders
/reference/system/notifications.html https://directus.io/docs/api/notifications
/reference/system/operations.html https://directus.io/docs/api/operations
/reference/system/panels.html https://directus.io/docs/api/panels
/reference/system/permissions.html https://directus.io/docs/api/permissions
/reference/system/policies.html https://directus.io/docs/api/policies
/reference/system/presets.html https://directus.io/docs/api/presets
/reference/system/relations.html https://directus.io/docs/api/relations
/reference/system/revisions.html https://directus.io/docs/api/revisions
/reference/system/roles.html https://directus.io/docs/api/roles
/reference/system/schema.html https://directus.io/docs/api/schema
/reference/system/server.html https://directus.io/docs/api/server
/reference/system/settings.html https://directus.io/docs/api/settings
/reference/system/shares.html https://directus.io/docs/api/shares
/reference/system/translations.html https://directus.io/docs/api/translations
/reference/system/users.html https://directus.io/docs/api/users
/reference/system/utilities.html https://directus.io/docs/api/utilities
/reference/system/versions.html https://directus.io/docs/api/versions
/reference/system/webhooks.html https://directus.io/docs/api
/releases/breaking-changes.html https://directus.io/docs/releases/breaking-changes
/self-hosted/cli.html https://directus.io/docs/getting-started/overview
/self-hosted/config-options.html https://directus.io/docs/configuration/general
/self-hosted/docker-guide.html https://directus.io/docs/getting-started/overview
/self-hosted/email-templates.html https://directus.io/docs/getting-started/overview
/self-hosted/migrations.html https://directus.io/docs/getting-started/overview
/self-hosted/quickstart.html https://directus.io/docs/getting-started/create-a-project
/self-hosted/sso-examples.html https://directus.io/docs/guides/auth/sso
/self-hosted/sso.html https://directus.io/docs/guides/auth/sso
/self-hosted/upgrades-migrations.html https://directus.io/docs/tutorials/migration/promoting-changes-between-environments-in-directus
/use-cases/headless-cms/concepts.html https://directus.io/docs/getting-started/overview
/use-cases/headless-cms/introduction.html https://directus.io/docs/getting-started/overview
/use-cases/headless-cms/security.html https://directus.io/docs/getting-started/overview
/user-guide/cloud/accounts.html https://directus.io/docs/cloud/getting-started/accounts
/user-guide/cloud/glossary.html https://directus.io/docs/getting-started/overview
/user-guide/cloud/overview.html https://directus.io/docs/cloud/getting-started/introduction
/user-guide/cloud/projects.html https://directus.io/docs/cloud/projects/create
/user-guide/cloud/teams.html https://directus.io/docs/cloud/getting-started/teams
/user-guide/cloud/variables.html https://directus.io/docs/cloud/configuration/environment-variables
/user-guide/content-module/content.html https://directus.io/docs/guides/content/explore
/user-guide/content-module/content/collections.html https://directus.io/docs/guides/content/explore
/user-guide/content-module/content/items.html https://directus.io/docs/guides/content/editor
/user-guide/content-module/content/shares.html https://directus.io/docs/guides/content/editor
/user-guide/content-module/display-templates.html https://directus.io/docs/guides/content/layouts
/user-guide/content-module/filters.html https://directus.io/docs/guides/content/explore
/user-guide/content-module/import-export.html https://directus.io/docs/guides/content/import-export
/user-guide/content-module/layouts.html https://directus.io/docs/guides/content/layouts
/user-guide/content-module/translation-strings.html https://directus.io/docs/guides/content/translations
/user-guide/file-library/files.html https://directus.io/docs/getting-started/upload-files
/user-guide/file-library/folders.html https://directus.io/docs/guides/files/manage
/user-guide/insights/charts.html https://directus.io/docs/guides/insights/panels
/user-guide/insights/dashboards.html https://directus.io/docs/guides/insights/overview
/user-guide/insights/panels.html https://directus.io/docs/guides/insights/panels
/user-guide/marketplace/overview.html https://directus.io/docs/guides/extensions/marketplace
/user-guide/overview/data-studio-app.html https://directus.io/docs/guides/content/editor
/user-guide/overview/glossary.html https://directus.io/docs/guides/content/editor
/user-guide/overview/quickstart.html https://directus.io/docs/guides/content/editor
/user-guide/settings/activity-log.html https://directus.io/docs/guides/auth/accountability
/user-guide/settings/presets-bookmarks.html https://directus.io/docs/guides/content/explore
/user-guide/settings/project-settings.html https://directus.io/docs/configuration/general
/user-guide/settings/system-logs.html https://directus.io/docs/configuration/logging
/user-guide/settings/theming.html https://directus.io/docs/configuration/theming
/user-guide/user-management/permissions.html https://directus.io/docs/guides/auth/access-control
/user-guide/user-management/roles.html https://directus.io/docs/guides/auth/access-control
/user-guide/user-management/user-directory.html https://directus.io/docs/guides/auth/creating-users
/user-guide/user-management/users-roles-permissions.html https://directus.io/docs/guides/auth/access-control
/user-guide/user-management/users.html https://directus.io/docs/guides/auth/creating-users
/blog/getting-started-with-directus-and-angular.html https://directus.io/docs/tutorials/getting-started/fetch-data-from-directus-with-angular
/blog/migrating-notion-to-directus.html https://directus.io/docs/tutorials/migration/migrate-from-notion-to-directus
/blog/hello-world.html https://directus.io/docs/tutorials
/blog/getting-started-directus-astro.html https://directus.io/docs/tutorials/getting-started/fetch-data-from-directus-with-astro
/blog/creating-git-hub-issues-with-directus-automate.html https://directus.io/docs/tutorials/workflows/create-github-issues-with-directus-automate
/blog/directus-auth-nextauth.html https://directus.io/docs/tutorials/getting-started/using-authentication-in-next-js
/blog/migrating-nuxt-content-to-directus.html https://directus.io/docs/tutorials/migration/migrate-from-nuxt-content-to-directus
/blog/getting-started-with-directus-and-flutter.html https://directus.io/docs/tutorials/getting-started/fetch-data-from-directus-with-flutter
/blog/implementing-internationalization-with-svelte-kit-and-directus.html https://directus.io/docs/tutorials/getting-started/implement-multilingual-content-with-directus-and-svelte-kit
/blog/tagging-files-automatically-clarifai-directus-automate.html https://directus.io/docs/tutorials/workflows/tag-images-with-clarifai-and-directus-automate
/blog/generating-images-with-dalle-and-directus-automate.html https://directus.io/docs/tutorials/workflows/generate-images-with-dall-e-and-directus-automate
/blog/generating-social-posts-gpt-4-directus-automa.html https://directus.io/docs/tutorials/workflows/generate-social-posts-with-gpt-4-and-directus-automate
/blog/configuring-okta-sso.html https://directus.io/docs/tutorials/self-hosting/configure-okta-as-a-single-sign-on-provider
/blog/monitoring-pipeline-flows-extensions.html https://directus.io/docs/tutorials/tips-and-tricks/build-a-monitoring-pipeline-for-flows-and-extensions
/blog/the-changelog-1.html https://directus.io/docs/releases/changelog
/blog/getting-started-with-directus-django.html https://directus.io/docs/tutorials/getting-started/fetch-data-from-directus-with-django
/blog/build-an-e-commerce-website-with-directus-and-next-js.html https://directus.io/docs/tutorials/projects/build-an-ecommerce-platform-with-next-js-stripe-and-directus-automate
/blog/getting-started-directus-and-eleventy-11ty-3.html https://directus.io/docs/tutorials/getting-started/fetch-data-from-directus-with-eleventy-3
/blog/devcycle-feature-flag-control-panel.html https://directus.io/docs/tutorials
/blog/getting-started-with-directus-and-flask.html https://directus.io/docs/tutorials/getting-started/fetch-data-from-directus-with-flask
/blog/announcing-panel-quest-hackathon.html https://directus.io/docs/tutorials
/blog/building-directus-garden.html https://directus.io/docs/tutorials/projects/build-directus-garden-a-passive-collaborative-event-booth-demo
/blog/ai-santa-roast-app-with-directus-nuxt.html https://directus.io/docs/tutorials/projects/how-i-built-an-ai-open-source-santa-roast-app-with-directus-and-nuxt#naughty-or-nice-scoring-algorithm
/blog/getting-started-directus-hugo.html https://directus.io/docs/tutorials/getting-started
/blog/external-weather-api-data-custom-panel-extension.html https://directus.io/docs/tutorials/extensions/display-external-weather-api-data-in-custom-panels
# Temporarily remove 301 on sentry article https://github.com/directus/directus/pull/24828
# /blog/hooks-monitoring-error-tracking-sentry.html https://directus.io/docs/tutorials
/blog/using-directus-as-a-baby-health-tracker.html https://directus.io/docs/tutorials/projects/use-directus-as-a-baby-health-tracker-with-owlet-and-ops-genie
/blog/preview-and-content-versioning-with-nextjs.html https://directus.io/docs/tutorials/workflows/combine-live-preview-and-content-versioning-with-next-js
/blog/the-changelog-3.html https://directus.io/docs/releases/changelog
/blog/the-changelog-4.html https://directus.io/docs/releases/changelog
/blog/the-changelog-2.html https://directus.io/docs/releases/changelog
/blog/the-changelog-5.html https://directus.io/docs/releases/changelog
/blog/directus-ai-hackathon-vote.html https://directus.io/docs/tutorials
/blog/directus-panel-quest-hackathon-projects.html https://directus.io/docs/tutorials
/blog/directus-and-iot-sensor-data-with-an-esp-32.html https://directus.io/docs/tutorials/projects/integrate-directus-with-esp-32-hardware-sensors
/blog/integrating-algolia-indexing-and-directus.html https://directus.io/docs/tutorials/extensions/integrate-algolia-indexing-with-custom-hooks
/blog/integrating-elasticsearch-indexing-with-directus.html https://directus.io/docs/tutorials/extensions/integrate-elasticsearch-indexing-with-custom-hooks
/blog/integrating-meilisearch-indexing-with-directus.html https://directus.io/docs/tutorials/extensions/integrate-meilisearch-indexing-with-custom-hooks
/blog/getting-started-with-directus-and-gatsby.html https://directus.io/docs/tutorials/getting-started
/blog/nuxt-directus-getting-started.html https://directus.io/docs/tutorials/getting-started/fetch-data-from-directus-with-nuxt
/blog/directus-auth-sveltekit.html https://directus.io/docs/tutorials/getting-started/using-authentication-in-sveltekit
/blog/deploy-directus-digital-ocean-docker.html https://directus.io/docs/tutorials/self-hosting/deploy-directus-to-digital-ocean
/blog/sync-google-calendar-directus-automate.html https://directus.io/docs/tutorials
/blog/importing-files-in-directus-automate.html https://directus.io/docs/tutorials/tips-and-tricks/importing-files-in-directus-automate
/blog/building-a-personal-travel-journal-with-vue-js-and-directus.html https://directus.io/docs/tutorials
/blog/passwordless-sms-authentication-with-plivo-and-directus-automate.html https://directus.io/docs/tutorials
/blog/build-a-url-shortener-with-react-type-script-and-directus.html https://directus.io/docs/tutorials
/blog/building-a-testimonial-widget-with-sveltekit-and-directus.html https://directus.io/docs/tutorials/projects/build-a-testimonial-widget-with-sveltekit-and-directus
/blog/building-a-job-board-platform-with-directus-and-solid-start-js.html https://directus.io/docs/tutorials
/blog/building-a-hotel-booking-system-with-directus-next-js-and-stripe.html https://directus.io/docs/tutorials/projects/build-an-hotel-booking-platform-with-next-js-stripe-and-directus-automate
/blog/building-a-video-streaming-app-with-sveltekit-and-directus.html https://directus.io/docs/tutorials/projects/build-a-video-streaming-app-with-sveltekit-and-directus
/blog/getting-started-with-directus-and-laravel.html https://directus.io/docs/tutorials/getting-started/fetch-data-from-directus-with-laravel
/blog/getting-started-directus-ios.html https://directus.io/docs/tutorials/getting-started/fetch-data-from-directus-in-ios-with-swift
/blog/using-directus-auth-with-ios.html https://directus.io/docs/tutorials/getting-started/implement-directus-auth-with-ios
/blog/deploy-directus-ubuntu-server.html https://directus.io/docs/tutorials/self-hosting/deploy-directus-to-an-ubuntu-server
/blog/deploying-directus-to-aws-ec2-with-docker.html https://directus.io/docs/tutorials/self-hosting/deploy-directus-to-aws-ec2
/blog/deploying-directus-to-google-cloud-platform-with-docker.html https://directus.io/docs/tutorials/self-hosting/deploy-directus-to-google-cloud-platform
/blog/implement-directus-auth-in-next-js-14.html https://directus.io/docs/tutorials/getting-started/using-authentication-in-next-js
/blog/getting-started-with-directus-and-android-with-kotlin.html https://directus.io/docs/tutorials/getting-started/fetch-data-from-directus-in-android-with-kotlin
/blog/tracking-github-issues-and-pull-requests-with-directus-automate.html https://directus.io/docs/tutorials
/blog/advanced-filtering-dates-aggregation-and-grouping-and-combining-filters.html https://directus.io/docs/tutorials
/blog/migrating-from-word-press-to-directus.html https://directus.io/docs/tutorials/migration/migrate-from-wordpress-to-directus
/blog/understanding-policy-based-access-control.html https://directus.io/docs/tutorials
/blog/building-the-leap-week-registration-and-referral-system.html https://directus.io/docs/tutorials/projects/build-the-leap-week-registration-and-referral-system
/blog/building-a-job-board.html https://directus.io/docs/tutorials
/blog/5-automations-to-level-up-your-blog-with-directus.html https://directus.io/docs/tutorials
/blog/wedding-invite-vonage.html https://directus.io/docs/tutorials
/blog/building-user-feedback-widget-with-vuejs-directus.html https://directus.io/docs/tutorials/projects/build-a-user-feedback-widget-with-vue-js
/blog/mastering-multilingual-content-crowdin.html https://directus.io/docs/tutorials/workflows/integrating-multilingual-content-with-directus-and-crowdin
/blog/getting-started-with-directus-and-preact.html https://directus.io/docs/tutorials
/blog/getting-started-with-directus-and-qwik-city.html https://directus.io/docs/tutorials
/blog/getting-started-with-directus-and-remix.html https://directus.io/docs/tutorials
/blog/directus-seo-tips-tricks.html https://directus.io/docs/tutorials/tips-and-tricks/search-engine-optimization-best-practices
/blog/getting-started-solidstart.html https://directus.io/docs/tutorials/getting-started/fetch-data-from-directus-with-solidstart
/blog/getting-started-directus-sveltekit.html https://directus.io/docs/tutorials/getting-started/fetch-data-from-directus-with-sveltekit
/blog/enrich-user-data-clearbit-directus-automate.html https://directus.io/docs/tutorials/workflows/enrich-user-data-with-clearbit-and-directus-automate
/blog/devs-intro-to-composable.html https://directus.io/docs/guides/extensions/app-extensions/composables
/blog/building-a-notebook-chrome-extension-with-directus.html https://directus.io/docs/tutorials/projects/build-a-notebook-chrome-extension-with-directus-auth
/blog/building-a-support-system-in-the-directus-data-studio.html https://directus.io/docs/tutorials
/blog/deploying-directus-to-azure-web-apps-with-docker.html https://directus.io/docs/tutorials/self-hosting/deploy-directus-to-azure-web-apps
/blog/getting-started-with-directus-spring-boot.html https://directus.io/docs/tutorials/getting-started/fetch-data-from-directus-with-spring-boot
/blog/google-docs-preview.html https://directus.io/docs/tutorials/tips-and-tricks/preview-files-in-live-preview-with-google-docs-previews
/blog/automatic-transcripts-deepgram.html https://directus.io/docs/tutorials/workflows/generate-transcripts-with-deepgram-and-directus-automate
/blog/building-a-form-data-collection-and-email-notification-system-with-directus-and-next-js.html https://directus.io/docs/tutorials
/blog/announcing-directus-ai-hackathon.html https://directus.io/docs/tutorials
/blog/detecting-high-risk-phone-numbers-vonage-automate.html https://directus.io/docs/tutorials/workflows/detect-high-risk-phone-numbers-with-vonage-and-directus-automate
/blog/implementing-pagination-and-infinite-scrolling-in-next-js.html https://directus.io/docs/tutorials/tips-and-tricks/implement-pagination-and-infinite-scrolling-in-next-js
/blog/understanding-kubernetes.html https://directus.io/docs/tutorials/self-hosting/understanding-kubernetes
/blog/building-ai-venture-game.html https://directus.io/docs/tutorials/projects/building-ai-venture-an-ai-powered-game-with-directus
/blog/content-versioning-pre-release.html https://directus.io/docs/guides/content/content-versioning
/blog/tags/connect.html https://directus.io/docs/tutorials
/blog/tags/automate.html https://directus.io/docs/tutorials
/blog/tags/concept.html https://directus.io/docs/tutorials
/blog/tags/extensions.html https://directus.io/docs/tutorials
/blog/tags/nuxt.html https://directus.io/docs/tutorials
/blog/tags/vue.html https://directus.io/docs/tutorials
/blog/tags/auth.html https://directus.io/docs/tutorials
/blog/tags/guest-author.html https://directus.io/docs/tutorials
/blog/tags/vonage.html https://directus.io/docs/tutorials
/blog/tags/netlify.html https://directus.io/docs/tutorials
/blog/tags/ios.html https://directus.io/docs/tutorials
/blog/tags/migration.html https://directus.io/docs/tutorials
/blog/tags/solid-js.html https://directus.io/docs/tutorials
/blog/tags/next.html https://directus.io/docs/tutorials
/blog/tags/getting-started.html https://directus.io/docs/tutorials
/blog/tags/insights.html https://directus.io/docs/tutorials
/blog/tags/react.html https://directus.io/docs/tutorials
/blog/tags/tips-tricks.html https://directus.io/docs/tutorials
/blog/tags/changelog.html https://directus.io/docs/tutorials
/blog/tags/svelte.html https://directus.io/docs/tutorials
/blog/tags/project.html https://directus.io/docs/tutorials
/blog/tags/node.html https://directus.io/docs/tutorials
/blog/tags/update.html https://directus.io/docs/tutorials
/blog/tags/self-hosting.html https://directus.io/docs/tutorials
/blog/tags/astro.html https://directus.io/docs/tutorials
/blog/tags/deployment.html https://directus.io/docs/tutorials
/blog/tags/ai.html https://directus.io/docs/tutorials
/guides/sdk.html https://directus.io/docs/guides/connect/sdk
/guides/frameworks.html https://directus.io/docs/tutorials/getting-started
/guides/use-cases.html https://directus.io/docs/tutorials/projects
/guides/real-time.html https://directus.io/docs/guides/realtime/authentication
/guides/extensions.html https://directus.io/docs/guides/extensions/overview
/guides/administration.html https://directus.io/docs/tutorials/migration/promoting-changes-between-environments-in-directus
/plus/multi-tenant-saas.html https://directus.io/plus
/plus/onboarding-checklist.html https://directus.io/plus
/plus/streaming-platform.html https://directus.io/plus
/plus/introduction.html https://directus.io/plus
/plus/pim.html https://directus.io/plus
/plus/plus-installation.html https://directus.io/plus
/plus/headless-lms.html https://directus.io/plus
/plus/status-page.html https://directus.io/plus

View File

@@ -1,453 +0,0 @@
---
description:
The data model describes the structure of your database's schema using Collections, database tables, and Fields.
readTime: 15 min read
---
# Data Model
> The Directus data studio enables no-code configuration and management for any SQL database, with no arbitrary
> restrictions on how you build your data model. You get control over table, column and relationship configuration, as
> well as how users view and interact with data inside the data studio.
<!--
::: tip Before You Begin
Learn Directus
Please see the [Quickstart Guide]().
Configuration > Overview
:::
-->
::: tip Learn More
Remember, you will have full access to manage your database using SQL. Directus will mirror any changes. You can also
configure your data model programmatically via the API. To learn more, see our API documentation on
[Collections](/reference/system/collections), [Fields](/reference/system/fields), and
[Relations](/reference/system/relations).
:::
## Relational Data Models
In order to understand how Directus handles data models, you will need an understanding of what relational data models
are. This section provides a brief summary of the core concepts. It may be useful as a review, or for business users
working on your team that want a simple explanation of how data models work. If you have a firm knowledge of relational
data model concepts, such as databases, data tables, columns, data types, primary and foreign keys, rows, relationships,
and schemas then feel free to jump to [Data Models in Directus](#data-models-in-directus).
### Databases
Directus is an SQL database wrapper. A database is a set of data stored in a computer, in a structured way, making it
organized, accessible, and scalable. The specific way you structure your data within a database is called your data
model.
![A Database Schema](https://cdn.directus.io/docs/v9/configuration/data-model/data-model-20220805/database-schema-20220805A.webp)
### Database vs Excel
To make a comparison most business users can relate with, storing data in a database is _somewhat_ similar to storing
data in Excel spreadsheets. You know how you can build a table on one sheet in Excel, build another table on another
sheet, then link the rows of each table together? That is pretty much how a relational data model works. But there are
some key points where Excel and relational databases differ.
![Data in an Excel Spreadsheet](https://cdn.directus.io/docs/v9/configuration/data-model/data-model-20220805/spreadsheet-20220805A.webp)
Many times, we store data as a table in Excel, but that's not always the case, as the program serves tons of other
purposes. Excel lets you make your data stylized _(bold, italicized, colored, custom fonts, etc.)_, set dynamic
functions in cells, add graphics like charts and graphs, and input any kind of data into any cell you'd like with no
enforced structure. Your Excel spreadsheet is a blank canvas, designed to store up to tens of thousands of rows of
information.
![A data table](https://cdn.directus.io/docs/v9/configuration/data-model/data-model-20220805/datatable-20220805A.webp)
There is no stylization within databases. They strictly store raw data values in a structured way. Any time you want to
style data, build a function, put data into a graph, _etc.,_ you must create that functionality in your app or website.
Databases store raw, un-stylized, structured data and are designed to handle millions, _and in some cases billions and
trillions_, of rows of information.
### Data Tables
![A Data Table: rows and columns](https://cdn.directus.io/docs/v9/configuration/data-model/data-model-20220805/rows-and-columns.webp)
<!-- image should note rows and columns. -->
SQL databases store data across data tables. Data tables typically store information about one distinct type of record,
object, or observation, such as a financial transaction, blog post, geo-position, user, IoT event, _or anything_. Data
tables are further broken down into columns and rows.
### Columns
![A Column](https://cdn.directus.io/docs/v9/configuration/data-model/data-model-20220805/columns-20220805A.webp)
Columns are categories that store one kind of information. Each column has a unique, descriptive name and stores one
unit of information in each of its [cell values](#cell-values). Columns keep the data organized, consistent, and easily
accessible. The columns you choose to add to a data table will completely depend on the information you need to store.
<!-- For example, in a database for IoT devices monitoring the weather, an `iot_events` data table may contain columns `device_id`, `location`, `time`, `temperature`, `pressure`, `humidity`, etc. -->
### Cell Values
![Cell Values](https://cdn.directus.io/docs/v9/configuration/data-model/data-model-20220805/datatable-cell-value-20220805A.webp)
Each value in a column is stored in its own cell. In general, you want to create columns that save _atomic_ values. That
means create the column to store the smallest indivisible units There is no restriction for the kinds of information to
include in a column, but there are good and bad practices. For example:
- **A Bad Column:** A `city_state_zipcode` column.
- **Good Columns:** Separate `city`, `state` and `zipcode` columns.
### Data Types
To further maintain structure and consistency, when you create a column, you must also define its data type. For
example, an `age` column might be assigned `INTEGER` and a `blog_content` column may be assigned a `STRING` or `TEXT`
data type. There are countless incongruent, unexpected, and potentially dangerous behaviors that could emerge when a
program tries to process data with the wrong data type.
To give an example, if you type the character `2`, it may be stored as an `INTEGER` or as `STRING`. If you stored `2` as
an `INTEGER`, when you try to add `2 + 2`, the computer will typically calculate `4`. In some languages, if you stored
the character `"2"` as a `STRING`, when you try to add `"2" + "2"`, the computer will concatenate them into `22`, while
in others, trying to do this may crash the program!
Therefore when you work with data, it is important to know what its data type is because the wrong data type can cause
unexpected and even dangerous behaviors in your program.
### Rows
![Rows](https://cdn.directus.io/docs/v9/configuration/data-model/data-model-20220805/row-20220805A.webp)
Each row stores data associated to a unique record, event, object, entity, observation, etc. Data tables can contain
millions, _even billions and trillions_ of rows of data.
### Primary Keys
![Primary Key](https://cdn.directus.io/docs/v9/configuration/data-model/data-model-20220805/primary-keys-20220805A.webp)
In order to uniquely identify and track each row, every data table must have a primary key column. A primary key is a
unique ID that identifies a specific row. Any pattern or system could be used to generate primary keys, so long as it
guarantees each key is unique. Perhaps the most common is incrementing integers, where the primary key on each new row
increments as follows `1`, `2`, `3`, `4`, etc... The primary key column guarantees you can always find a row and
differentiate it from other rows.
### Foreign Keys
![Foreign Keys](https://cdn.directus.io/docs/v9/configuration/data-model/data-model-20220805/foreign-keys-20220805A.webp)
Since primary keys uniquely identify each and every row in a data table, they are the perfect tool to create
relationships. If you want to relationally link rows between two data tables, you create a column to store _foreign
keys_, which are simply primary keys from a foreign table. This is called a _foreign key column_, to signify it stores
the keys from a foreign table.
### Parent vs. Related Tables
When we talk about two related tables, we refer to them as the _parent table_ and the _related table_. These two terms
are based solely on perspective, similar to the terms _this_ and _that_ or the terms _here_ and _there_, signifying the
perspective from which you are looking at the relationship.
For example, within the data model, a [many-to-one relationship](#types-of-relationships) is the same as a
[one-to-many relationship](#types-of-relationships), the term used just depends on which collection you consider the
parent.
### Types of Relationships
There are several ways you can relationally link tables:
- **One to One** — Each row in the parent data table can link to one row _(max)_ in the related table.
- **Many to One** — Many rows in the parent data table can link to one row in the related table.
- **One to Many** — Each row in a data table can link to many rows in another data table.\
_Note: in a data model, Many-to-One and One-to-Many relationships are identical. The naming difference is a matter of perspective._
- **Many to Many** — Many rows in the parent table can link to many rows in the related table. M2M relationships require
a third table, called junction table. An M2M is nothing more than an O2M and an M2O stored on the junction table.
- **Many to Any** — Many Rows in a data table can link to many rows across any other table in the database. Similar to
M2M relationships, M2As require a junction data table as well as an additional column on the junction table to store
the related tables' names.
To learn more about how these relationships work conceptually, as well as how they are handled within Directus, see our
guide on [relationships](/app/data-model/relationships).
### Database Schemas
![Data Table to schema](https://cdn.directus.io/docs/v9/configuration/data-model/data-model-20220805/datatable-to-schema-20220805A.webp)
In our examples so far, we have seen and described actual [data tables](#data-tables). As you design your relational
data model, you will need to create a schema to keep track of its complexity.
A schema is a blueprint for your data model, which defines its data tables, columns in each table, details about each
column and relationships between tables. It does not include the actual data points stored. Here is a simple schema of
two relationally linked tables:
```
table_one
- column1 (primary key)
- column2 (data type, optionally explain what the column stores)
- column3 (...)
```
```
table_two
- column1 (primary key)
- column2 (...)
- column3 (...)
- table_one_id (foreign key, relationally links rows via table_one.column1)
```
In the schema above, we defined two tables with overtly generic names `table_one`, `table_two` and `column1`, `column2`,
etc. The names you choose for data tables and columns are up to you. Ideally, you should pick unique, memorable names
that identify the data contents stored within.
In this documentation, we bend the rules of traditional database schemas in two ways. First, we sometimes add a
full-length explanation of what a column is. Notice in `table_one.column2` there is a note after the column name
`(data type, optionally....`. It is common practice to include a column's data type in a table schema, but full
descriptions are not. Second, in our examples, if columns exist in the table but its data type or other details are
irrelevant to the current learning point, we omit their details so you can focus on the important columns.
Please note too, that with more complex schemas, containing dozens _(or maybe hundreds!)_ of relationally linked data
tables, you usually include datatype information as well as a visualization of how each and every table interlinks.
![A Complex Schema](https://cdn.directus.io/docs/v9/configuration/data-model/data-model-20220805/complex-schema.webp)
### Avoid Data Duplication
Relational databases allow us to build data models that avoid data duplication, or in other words, when you have the
same data stored in multiple locations in your database. Data Duplication is inefficient and dangerous.
To give an example, let's consider a `blog` table. In a blog, you may want to display the author's details, so you add
an `author_name` column.
```
blog
- id
- title
- content
- author_name (string, stores author's full name)
```
The table above stores the author name directly inside of the `blog` data table. However, let's imagine that along with
our blog posts, we want to display more information about authors, such as their email address, social media, etc. We
_could_ put this author information into the `blog` data table.
```
blog
- id
- title
- content
- author_name (string, stores author's full name)
- author_email (string, stores author's email)
- author_img (string, stores link to author's profile picture)
```
You might be starting to notice this data table no longer represents one single object, but two: blog posts and authors.
This is _almost always_ a sign the data should be split across different tables and relationally linked.
Now let's also imagine that authors are one type of user. All user details are stored in a `users` data table and its
data is displayed on each user profile page, for chat messaging and other types of transactions, _this is a common
situation in many projects_. In this case, the author name and other details would also need to exist in the `users`
table.
![Duplicate Data](https://cdn.directus.io/docs/v9/configuration/data-model/data-model-20220805/duplicate-data-20220829A.webp)
This creates duplicate data. There are two big problems with this:
First, it becomes difficult or impossible to maintain accurate information. If the author decides to change their social
media information under `users`, someone would have to go through and update author details on every single row
containing their blog posts. With just 10 or even 100 blog posts, this would be annoying but perhaps not a massive
problem. However, as volume of data grows to millions or billions of rows, updating duplicate data becomes a serious
problem.
Furthermore, an error on an author's name and personal information may not be a truly dangerous situation, but
inaccurate information would be catastrophic in data tables containing banking transactions or medical records!
Second, it wastes storage space and slows down performance. With a data model containing a few hundred blog posts,
duplicate data may not take up enough space to cause huge drops in performance. But again, if you have the same
information repeated again and again over millions or billions of rows, storage is wasted on a massive scale.
### Why We Use Relational Data Models
As shown in the previous section, you want to make sure that every single data point is unique. This is where the
_relational_ part of relational data models comes into play. To avoid data duplication, it is always best practice to
_normalize your data model_, which is the technical term used to describe designing a data model so that there is no
duplicate information stored _at all_. Instead of storing all information needed in a given situation in one table, like
we saw when mixing up blog and author information above, database normalization is the process of splitting up this
information across tables and relationally linking it all together so that information is never repeated.
There is a lot to learn to master database normalization and a thorough education in the practice goes beyond the scope
of this document. There are plenty of resources to learn about it online. However, to provide one simple example by
improving the example `blog` data table provided in the previous [Avoid Data Duplication](#avoid-data-duplication)
section:
```
blog
- id
- title
- content
- author_name
- author_email
- author_img
```
As described in the section on [Rows](/app/data-model#rows), we want each row in a data table to represent one unique
record, event, object, entity, observation, etc. To do this, we can remove the `author_name` column from the `blog`
table and replace it with an `author_id` foreign key column, which stores foreign keys from the `users` table.
```
blog
- id
- title
- content
- author_id (stores foreign key from users.id)
```
```
users
- id
- name
- email
- role
- email
- twitter
```
Notice the difference. Previously, we placed the author's name directly into a column on the `blog` data table
_(creating duplicate data)_. Here, we replaced `author_name`, `author_email` and `author_img`, with the `author_id`
column, which contains foreign keys from `users`. From here, we can use the foreign key to relationally link data
between `blog` and `users`.
### Working With Relational Data Models
![Database, Backend, Frontend](https://cdn.directus.io/docs/v9/configuration/data-model/data-model-20220805/database-backend-frontend-20220805A.webp)
Once you've designed your data model conceptually, you typically build and interact with it using SQL, or Structured
Querying Language. This language is used to create, read & query, update, and delete anything and everything in the
database.
Once the initial data tables are designed and built, a common next step is to build a backend using something like
Node.js or Flask. In your backend, you must code custom API endpoints and logic to create, read, query, update, and
delete data for your specific data model. However, when the backend accesses data, it is still raw, with no stylization.
Raw data is easy to work with for computers, but often quite difficult to work with for humans.
To those who are unfamiliar, the SQL language, raw data, and traditional relational database jargon can feel unintuitive
and overly-technical.
It may not be practical to teach everyone on the team how to work with and think in terms of raw data. In some cases,
business users may find it difficult or nearly impossible to work with raw data. People are accustomed to see
information displayed _colorfully, stylized, embedded on a map, etc._ For example, _most people in most situations_
would find it easy to work with pinpoints on a map, yet find it nearly impossible to identify a position on a world map
from raw latitude and longitude points stored as JSON.
```json
{
"location": {
"lat": 36.08801,
"lng": 120.379771
}
}
```
Therefore, developers need to build front-ends with polished UIs and custom display logic to make working with the data
human-friendly. However, even for developers with strong SQL database skills, building out APIs and GUIs to build and
manage a data model is time consuming.
## Data Models in Directus
<video title="Settings > Data Model" autoplay playsinline muted loop controls>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/data-model-20220805/data-model-overview-20220805A.mp4" type="video/mp4" />
</video>
All relational data model concepts listed above apply in Directus. You get complete, un-opinionated, relational data
model design and configuration. The difference is that Directus handles all SQL, builds the API, and provides a Data
Studio which lets business users work with data in a human-friendly way.
The Data Studio also offers features and functionalities to display and interact with your data intuitively. Once your
data model is configured, the data is accessible across the other [modules](/user-guide/overview/glossary#modules).
<!-- Data model configuration takes place across the following pages and menus:
**Settings > Data Model > [Collection] > [Field] > Field Configuration Drawer > [Section]** -->
You have the power to do the following things, without a line of code or SQL:
- View, configure, and manage your relational data model and asset storage.
- Configure how data is displayed within the Data Studio.
- Configure how data is interacted with by users in the Data Studio.
- Translate any and all text in the Data Studio into any language.
Directus replaces traditional relational database jargon with more user-friendly terms and concepts. Please keep in mind
that while traditional relational database jargon strictly encompasses database concepts, some of the new Directus terms
encompass these relational database concepts _plus display and interaction logic_. The following sections will introduce
Directus terms and map them to classic relational database concepts.
## Collections
<video title="Collections" autoplay playsinline muted loop controls>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/data-model-20220805/collections-20220805A.mp4" type="video/mp4" />
</video>
A collection _is a set of [items](#items)_. This can be a 1-1 match-up with a data table in SQL, a group of other
collections, or a readonly view.
You access all collections, including built-in system collections required to power your project, under **Settings >
Data Model**. From there, click a collection to open its configurations page. To learn more, see our guide on
[collections](/app/data-model/collections).
## Fields
<video title="Fields" autoplay playsinline muted loop controls>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/data-model-20220805/fields-20220805A.mp4" type="video/mp4" />
</video>
Fields are database columns, but with a twist.
Remember, SQL database columns store pure, raw data. From there, developers build out custom logic and UIs to determine
how this data is displayed and interacted with. In Directus, fields encompass column configurations, as well as custom
configuration over how the data is displayed and interacted with in the Data Studio. Directus also has
[alias fields](/user-guide/overview/glossary#alias), which are virtual and do not match directly to a column. To learn
more, see our guide on [fields](/app/data-model/fields).
## Items
<video title="Collections" autoplay playsinline muted loop controls>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/data-model-20220805/items-20220805A.mp4" type="video/mp4" />
</video>
Items are data table rows, but with a twist.
Remember from our discussion above about traditional databases, the ideal relational database is _normalized_.
Unfortunately, normalized data is not always the easiest for people to imagine or envision because related data is
spread across multiple data tables. Therefore, when you access an item, you may get more than just the current
collection's row level-data, _in some cases an item may provide access to the data in related rows._
You access items from other app modules, such as [Content](/user-guide/content-module/content),
[User Directory](/user-guide/user-management/user-directory), and [File Library](/user-guide/file-library/files).
## Data Type Superset
Directus abstracts type differences between SQL vendors with a
[Data Type Superset](/user-guide/overview/glossary#data-type-superset).
## Keys and IDs
<video title="Keys and IDs" autoplay playsinline muted loop controls>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/data-model-20220805/keys-and-ids-20220805A.mp4" type="video/mp4" />
</video>
Primary keys are called IDs in Directus fairly frequently. When you
[create a collection](/app/data-model/collections#create-a-collection), you must add an `id` field. Directus supports
the following types of IDs:
- **Auto-Incremented Integer** — IDs increment `1`, `2`, `3` up to `2^31-1` or `2,147,483,647`.
- **Auto-Incremented Big Integer** — IDs increment `1`, `2`, `3` up to `2^63-1` or `9,223,372,036,854,775,807`. _(only
available in MySQL and PostgreSQL)_
- **Generated UUID** — Universally Unique Identifier. Creates a completely unique ID. IDs generated with this system
_(not just in your database, but anywhere this system is used)_ are so statistically unlikely to be repeated that for
all practical purposes they are unique.
- **Manually Entered String** — You manually type out a unique string as the ID for each item.
## Relationships
<video title="Relations" autoplay playsinline muted loop controls>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/data-model-20220805/relationships-20220805A.mp4" type="video/mp4" />
</video>
Directus supports all standard [types of relationships](#types-of-relationships), as well as a few more of its own
compound types. To learn more, see our guide on [relationships](/app/data-model/relationships).

View File

@@ -1,412 +0,0 @@
# Collections
> Collections are a set of items. This can be a 1-1 data table in SQL, a group of other collections, or a readonly view.
> They come with all the same power and functionality of data tables, despite the less technical name.
<!--
::: tip Before You Begin
Quickstart Guide
Read Data Model Introduction
:::
::: tip Learn more
API documentation
:::
-->
## Overview
<video title="Overview" autoplay playsinline muted loop controls>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/data-model-20220805/collections-20220805A.mp4" type="video/mp4" />
</video>
Collections are data tables. Typically, you access items within a collection in the
[Content Module](/user-guide/content-module/content).
## System Collections
<video title="System Collections" autoplay playsinline muted loop controls>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/collections/collections-20220805/system-collections-20220805A.mp4" type="video/mp4" />
</video>
System collections store the data and configuration details required to power your project.
Since system collections store information that powers your Directus project. You cannot reconfigure system collections
or any of their default fields, as any reconfigurations would break your project. However, you _can_ create and
configure new fields on a system collection. This lets you safely customize your data model as desired.
System collections are not displayed in the Content Module. The following table will go over the logic and functionality
each system collection is responsible for, as well as where to find relevant App and API documentation.
| System Collection | Purpose | APP | API |
| ----------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
| Activity | Tracks and stores all events within Directus, giving full accountability over everything. | [Activity Log](/user-guide/settings/activity-log) | [Activity](/reference/system/activity) |
| Collections | Stores data table & configuration details each and every Collection. | [Collections](/app/data-model/collections) | [Collections](/reference/system/collections) |
| Comments | Stores comments left on items in collections. | [Comments](/user-guide/content-module/content/items) | [Comments](/reference/system/comments) |
| Dashboards | Stores dashboard configuration details and all relationally linked panels. | [Insights](/user-guide/insights/dashboards) | **N/A** |
| Fields | Stores configuration details for each field. | [Fields](/app/data-model/fields) | [Fields](/reference/system/fields) |
| Files | This stores file location from asset storage as well as any data associated with that file. | [File Library](/user-guide/file-library/files) | [Files](/reference/files) |
| Flows | Stores basic logic required for Flows, which enable event-triggered task automation. | [Flows](/app/flows) | [Flows](/reference/system/flows) |
| Folders | Stores information required for Folders, which provide virtual file asset management. | [Folders](/user-guide/file-library/folders) | [Folders](/reference/system/folders) |
| Migrations | Used by our install/upgrade process to track when migration scripts for a specific release have been run. | It has no relevant app functionality. | **N/A** |
| Notifications | Stores details about in-app notifications. | [Module Bar](/user-guide/overview/data-studio-app#_1-module-bar) and [Sidebar](/user-guide/overview/data-studio-app#_4-sidebar) | [Notifications](/reference/system/notifications) |
| Operations | Stores information required for Operations, which are a part of [Flows](#flows). | [Operations](/app/flows/operations) | [Operations](/reference/system/operations) |
| Panels | This stores information about individual analytics panels, which are displayed on [Dashboards](#dashboards). | [Insights](/user-guide/insights/panels) | **N/A** |
| Permissions | This stores the access permissions configured for roles. | [Users, Roles & Permissions](/user-guide/user-management/users-roles-permissions) | [Permissions](/reference/system/permissions) |
| Presets | This stores details for presets and bookmarks. | [Presents & Bookmarks](/user-guide/settings/presets-bookmarks) | [Presets](/reference/system/presets) |
| Relations | This stores information about relationships between collections. | [Relationships](/app/data-model/relationships) | [Relations](/reference/system/relations) |
| Revisions | Revisions are changes/edits made to Items. | [Revert an Item](/user-guide/content-module/content/items#revert-an-item) | [Revisions](/reference/system/revisions) |
| Roles | Stores information about each role created. | [Users, Roles, and Permissions](/user-guide/user-management/users-roles-permissions) | [Roles](/reference/system/roles) |
| Sessions | Stores information about each user session, for system purposes. | **N/A** | **N/A** |
| Settings | Stores all configurations made within **Settings > Project Settings**. | [Project Settings](/user-guide/settings/project-settings) | [settings](/reference/system/settings) |
| Shares | Stores all information regarding data shares. | [Data Sharing](/user-guide/content-module/content/shares) | |
| Users | Stores information about each user within the platform. | [User Directory](/user-guide/user-management/user-directory) | [Users](/reference/system/relations) |
## Create a Collection
<video title="Create a Collection" autoplay playsinline muted loop controls>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/collections/collections-20220805/create-a-collection-20220805A.mp4
" type="video/mp4" />
</video>
To create a collection, follow these steps.
1. Navigate to **Settings > Data Model**, click <span mi btn>add</span> in the page header, and a drawer will open.
2. Enter a unique **Collection Name**. This cannot be modified later, but can be translated.\
This is used as the data table key, API collection key, and default collection name.
3. Optional: Make any other configurations as desired.
- **Singleton** — Toggles whether the collection is a [Singleton](#collection-setup).
- **Primary Key Field** — Sets the name of the primary key field, defaults to `id`.
- **Type** — Sets the [type of ID](/app/data-model#keys-and-ids) to use for this collection.
4. Click <span mi btn>arrow_forward</span> to confirm.
5. Enable and rename the other Optional Fields as desired:
- **Status** — Stores item status.
- **Sort** — Adds a field which enables drag-and-drop sorting of items.
- **Created On** — Logs the date an item was created.
- **Created By** — Logs the user that created this item.
- **Updated On** — Logs the date an item was last updated.
- **Updated By** — Stores the last user to edit the file.
6. Click <span mi btn>check</span> to confirm and create the collection.
::: danger Immutable Keys
The collection name from step two cannot be modified after collection creation. However, you can override how it is
displayed with [Collection Naming Translations](#collection-setup).
:::
::: warning Composite Keys
Directus does not currently support composite keys. If your project uses composite keys, you will need to make an
adjustment to the data model.
:::
::: warning SQL Views
Directus does not currently support creation of virtual tables via SQL Views.
:::
::: tip Database Tables
Remember, a collection is simply a database table. Therefore, you can import or create a table directly in the database
and it will automatically appear within your Directus project. The first time you manage that table, a
`directus_collections` record will be created with default values.
:::
## Toggle Collection Visibility
<video title="Hide a Collection" autoplay playsinline muted loop controls>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/collections/collections-20220805/toggle-collection-visibility-20220805A.mp4" type="video/mp4" />
</video>
To toggle whether a collection is hidden by default in the Content Module, follow these steps.
1. Navigate to **Settings > Data Model**.
2. Click <span mi icon>more_vert</span> to open the collection's Context Menu.
3. Select **View Content** or **Make Collection Hidden** to adjust visibility as desired.
::: tip
Assuming a user has [access permissions](/user-guide/user-management/users-roles-permissions), hidden collections can
still be viewed. They must right-click on the Navigation Bar and choose <span mi icon>visibility</span> **Show Hidden
Collections**.
:::
## Create a Folder
<video title="Create a Folder" autoplay playsinline muted loop controls>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/collections/collections-20220805/create-a-folder-20220805A.mp4" type="video/mp4" />
</video>
Folders allow you to sub-nest and group how collections are displayed. This feature simply changes how the collections
are displayed under **Settings > Data Model** and in the Content Module. It has no impact on the data model. To create a
folder, follow these steps.
1. Navigate to **Settings > Data Model**.
2. Click <span mi btn muted>create_new_folder</span> in the page header.
3. Set a folder key, which will also be used as the folder's name.
4. Optional: Set the folder icon, color, note and translations as desired.
5. Click **Save** to create your folder.
## Toggle Folder Display
<video title="Toggle Folder Display" autoplay playsinline muted loop controls>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/collections/collections-20220805/toggle-folder-display-20220805A.mp4" type="video/mp4" />
</video>
To toggle folder display, follow these steps.
1. Navigate to **Settings > Data Model**.
2. Click <span mi icon>folder</span> on the desired collection to toggle the following displays:
- **Start Open**
- **Start Collapsed**
- **Always Open**
## Sort and Nest Collections
<video title="Configure a Collection" autoplay playsinline muted loop controls>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/collections/collections-20220805/sort-and-nest-collections.mp4" type="video/mp4" />
</video>
To sort and nest collections in **Settings > Data Model** and **Content Module**, follow these steps.
1. Navigate to **Settings > Data Model**.
2. Click and drag <span mi icon>drag_handle</span> to position collections as desired.\
To nest a collection, drag below and to the right of an intended parent folder or collection.
## Configure a Collection
<video title="Configure a Collection" autoplay playsinline muted loop controls>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/collections/collections-20220805/configure-a-collection-20220805A.mp4" type="video/mp4" />
</video>
To configure a collection, follow these steps.
1. Navigate to **Settings > Data Model** and click the desired collection.\
The collection's configuration page will open.
2. Make configurations as desired. Configuration options are broken into seven categories.
- [Fields and Layout](#fields-layout)
- [Collection Setup](#collection-setup)
- [Content Versioning](#content-versioning)
- [Archive](#archive)
- [Sort](#sort-field)
- [Accountability](#accountability)
- [Duplication](#duplication)
3. Click <span mi btn>check</span> to confirm.
### Fields & Layout
<video title="Fields and Layout" autoplay playsinline muted loop controls>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/collections/collections-20220805/fields-and-layout-20220805A.mp4" type="video/mp4" />
</video>
This section allows you to create and configure fields, as well as configure how fields are displayed on the
[Item Details Page](/user-guide/content-module/content/items). To learn more, please see the documentation on
[fields](/app/data-model/fields).
### Collection Setup
<video title="Collection Setup" autoplay playsinline muted loop controls>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/collections/collections-20220805/collection-setup-20220805A.mp4" type="video/mp4" />
</video>
These controls allow you to modify how the collection is displayed within the Content Module.
- **Collection Name** — Displays the collection's name, which cannot be modified. However, you can override the
displayed name with Collection Naming Translations, shown lower on this list.
- **Note** — Set a helpful note that explains the collection's purpose.
- **Icon** — Set an icon used throughout the app when referencing this collection.
- **Color** — Set a color for the icon, shown in the Navigation Bar and its page header.
- **Display Template** — Create a [Display Template](/user-guide/content-module/display-templates) for the collection.
- **Hidden** — Toggle whether the collection should be globally hidden in the other app modules, even for admin users.
You cannot hide collections for admins via permissions like other roles, so this comes in handy to tidy up the Content
Module.
- **Singleton** — Toggle to bypass the [Collection Page](/user-guide/content-module/content/collections) and take users
to the [Item Details Page](/user-guide/content-module/content/items).
- **Collection Naming Translations** — Translate the collection name across multiple languages. When the default
language is changed in [Project Settings](/user-guide/settings/project-settings#general) or
[User Details Page](/user-guide/user-management/user-directory#user-details-page), the relevant translation, if any
exists, will be used throughout the app.
::: tip Collection Naming Translations
By default, Directus uses the [Title Formatter](/user-guide/overview/glossary#title-formatter) to display collection
keys as human readable names, but you can also use translations to explicitly rename more technical table keys.
:::
::: tip What's a Singleton?
A collection that only contains one single item. For example, a website's **About Us** page.
:::
### Content Versioning
This feature allows users to create multiple versions of each collection item or singleton.
- **Versioning** — Toggles whether versioning is enabled for the collection.
### Archive
<video title="Archive" autoplay playsinline muted loop controls>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/collections/collections-20220805/archive-20220805A.mp4" type="video/mp4" />
</video>
Selects a field to handle archiving items. This provides a _soft-delete_ functionality for items in a collection.
Archived items will still exist in the collection and database, but are filtered within the Data Studio. To configure an
archive field, set the following four input fields as desired.
- **Archive Field** — Selects the archive field from a dropdown menu.
- **Archive App Filter** — Toggles whether app users can
[filter for archived items](/user-guide/content-module/content/collections#view-archived-items).
- **Archive Value** — A value that is assigned to the field when an item is
[archived](/user-guide/content-module/content/items#archive-an-item).
- **Unarchive Value** — A value that is assigned to the field when an item is
[unarchived](/user-guide/content-module/content/items#archive-an-item).
::: tip Automatic Setup
When you [create a collection](#create-a-collection), you have the option to create an optional Status Field. If you
choose to include this field, the collection's archive settings will be automatically configured for you.
:::
::: tip Archive Field Values
The archive fields can contain any number of additional values besides the archived and unarchived values defined above.
:::
::: tip Archived Item Management via API
Archived items are hidden in the app by default, but they are still returned normally via the API unless explicitly
filtered out. This gives you the flexibility to manage archived items however you want when working with the API.
:::
### Sort Field
<video autoplay playsinline muted loop controls title="Batch Edit Items">
<source src="https://cdn.directus.io/docs/v9/app-guide/content/content-collections/content-collections-20220415A/manually-sort-items-20220415A.mp4" type="video/mp4" />
</video>
The sort feature enables users to
[manually sort Items](/user-guide/content-module/content/collections#manually-sort-items) within the Data Studio. This
is typically shown on the **Content Module > Collection Page**. It can also be used for sorting items within
[Junction Collections](/user-guide/overview/glossary#junction-collections). A standard field, configured with an
`INTEGER` data type, is required. As shown in the video, fields which cannot serve as sort fields will be grayed out and
unselectable in the Sort Field dropdown.
**Sort Field** — Select a field to custom sort and order items. Click **Deselect** to disable.
To configure a sort field, follow these steps.
1. [Create a Field](/app/data-model/fields#create-a-field-standard) with an `INTEGER` data type.
2. Choose a field from the dropdown under **Settings > Data Model > [Collection] > Sort**.
3. Click <span mi btn>check</span> to confirm.
Once you sort field is configured, you may want to learn how to
[manually sort Items](/user-guide/content-module/content/collections#manually-sort-items).
::: tip Automatic Setup
When you [create a Collection](/app/data-model/collections#create-a-collection), you have the option of creating a
**Sort** field. If you choose to include this field, the collection's sort settings will automatically be configured for
you.
:::
::: tip Interface Sorting
To configure manual sorting within a relational Interface (e.g., M2M, O2M, or M2A), configure as above, but also set the
**Sort Field** within the relationship section of the field's configuration drawer.
:::
::: tip Configure to Hidden
You can also set this field to be **Hidden** so it doesn't show up within the Item Details Page.
:::
### Accountability
<video title="Accountability" autoplay playsinline muted loop controls>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/collections/collections-20220805/acountability-20220805A.mp4" type="video/mp4" />
</video>
By default, your Directus Project tracks all [activity](/reference/system/activity) and
[revisions](/reference/system/revisions) for collections. However, you can override this and choose what data is
tracked.
- **Activity & Revision Tracking** — The following options are supported:
- **Track Activity & Revisions**
- **Only Track Activity**
- **Do Not Track Anything**
::: tip Accountability vs. Telemetry
Accountability is a log of _who does what_ in your project. It is for your team's own use. This is different from
[telemetry](/self-hosted/config-options#telemetry), which is configured under
[environment variables](/self-hosted/config-options#telemetry).
:::
### Duplication
<video title="Duplication" autoplay playsinline muted loop controls>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/collections/collections-20220805/duplicate-20220805A.mp4" type="video/mp4" />
</video>
The **Save as Copy** option on the Item Details Page offers a way to effectively duplicate the current item. Since there
may be unique or relational data within the item, it's important to control exactly what will be copied. Duplication
lets you configure which parent & relational field values will be copied when you use **Save as Copy** on an item.
- **Item Duplication Fields** — Check the field(s) to copy values for when duplicating an item.
## Delete a Collection
<video title="Delete a Collection" autoplay playsinline muted loop controls>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/collections/collections-20220805/delete-a-collection-20220805A.mp4" type="video/mp4" />
</video>
To delete a collection, follow these steps.
1. Navigate to **Settings > Data Model > [Collection Name]**.
2. Click <span mi btn dngr>delete</span> in the page header.
3. Confirm this decision by clicking **Delete** in the dialog.
::: danger
This action is permanent and irreversible. Please proceed with caution.
:::
## Set Up Live Preview For a Collection
The live preview feature allows users to instantly preview draft content without publishing or manually refreshing the
browser.
To enable this feature, navigate to the **Settings** menu and select **Data Model.** Choose the collection you want to
allow the live preview feature.
Add your URL as the **Preview URL** in the collection settings and select the `ID`. For example, the preview URL of a
posts collection running on localhost:3000 will be `http://localhost:3000/posts/ID`.
<video title="Set live preview URL" autoplay playsinline muted loop controls>
<source src="https://marketing.directus.app/assets/6d6bc718-ceb9-4e55-9a2d-d377156da7f9.mp4" type="video/mp4" />
</video>
::: tip Preview URL
You can set up any URL pattern with dynamic values derived from the specific items you wish to preview
:::

View File

@@ -1,187 +0,0 @@
# Fields
> Fields are database columns. Fields also enable you to manage the backend logic _(such as conditional display logic,
> input verification rules, etc.)_ and frontend design used to display its data _(e.g., whether data will display in a
> table or on a map)_.
## Overview
<video title="Fields Overview" autoplay muted loop controls playsinline>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/data-model-20220805/fields-20220805A.mp4" type="video/mp4" />
</video>
To access a collection's fields, navigate to **Settings > Data Model > [Collection]**. From here, you can click a field
to access its **Configuration Drawer** and make advanced configurations. You also have the following controls for each
field.
**Fields and Layout** — Create, view, and configure a collection's fields as well as adjust how they are displayed and
ordered on the [Item Details Page](/user-guide/content-module/content/collections#item-page). This section also provides
access to the **Field Context Menu** and **Field Configuration Drawer**, described below.
**Field Context Menu <span mi icon>more_vert</span>** — Contains the following controls:
- [<span mi icon>edit</span> Edit Field](#configure-a-field) — Opens the **Field Configuration Drawer**.
- [<span mi icon>content_copy</span> Duplicate Field](#duplicate-a-field) — Duplicates a field along with all of its
configuration options.
- [<span mi icon>visibility_off</span> Hide Field on Detail](#toggle-field-visibility-for-admins) — Toggle field
visibility on the Item Detail Page for Admin Users.
- [Width](#adjust-field-width) — Fields have three different width options:
- <span mi icon>border_vertical</span> Half Width — The field input is shown at half the form width.
- <span mi icon>border_right</span> Full Width — The default. The field input is shown at the full form width.
- <span mi icon>aspect_ratio</span> Fill Width — The field input is shown filling the full width of the page area.
**The Field Configuration Drawer** — Provides all [field configuration](#configure-a-field) options.
## Create a Field (Standard)
<video title="Create a Field (Standard)" autoplay muted loop controls playsinline>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/fields/fields-20220805/create-a-field-20220805A.mp4" type="video/mp4" />
</video>
To make field configuration as intuitive and easy as possible, a template wizard is provided so that you can create
fields pre-configured for common use-cases. When you create a field this way, you will still have full power to
[configure the field](#configure-a-field) as desired.
1. Navigate to **Settings > Data Model > [Collection]**.
2. Under **Fields & Layout**, click the **Create Field** button.\
A side drawer will open, with various pre-configured Interfaces to choose from.
3. Click to select the desired field and a basic configuration menu will open.
4. Add a **Field Key**, _which is also used as the default field name_.\
Optional: Configure the other field details as desired.\
Optional: Click [Continue in Advanced Field Creation Mode](#create-a-field-advanced).
5. When you are ready, click **Save** to confirm.
## Create a Field (Advanced)
<video title="Create a Field (Advanced)" autoplay muted loop controls playsinline>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/fields/fields-20220805/create-a-field-advanced-20220805A.mp4" type="video/mp4" />
</video>
This field creation method opens the **Field Configuration Drawer** so you can customize every field detail from the
start. To create a field in advanced mode, follow these steps.
1. Navigate to **Settings > Data Model > [Collection Name]**.
2. Under **Fields & Layout**, click the **Create Field in Advanced Mode** button.\
A dropdown menu will appear with various field types to choose from.
3. Click to choose the field type and the **Field Configuration Drawer** will open.
4. Configure your field as desired.
5. Click <span mi btn>check</span> to confirm.
::: tip Database Columns
Remember, a field is a database column. Therefore, you can create a column directly in the database and it will
automatically appear within Directus. You can then enhance the experience further by configuring it as desired.
:::
## Configure a Field
<video title="Configure a Field" autoplay muted loop controls playsinline>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/fields/fields-20220805/configure-a-field-20220805A.mp4" type="video/mp4" />
</video>
Fields are configured in the **Field Configuration Drawer**, which is composed of eight sections. These provide
extensive customization options, from the database column's details, to how it is displayed and interacted with, and
even custom input validation and conditional display logic. To configure a field, follow these steps.
1. Navigate to **Settings > Data Model > [Collection Name]**.
2. Under **Fields & Layout**, click the field you want to update.\
The **Field Configuration Drawer** will open.
3. Navigate to one of these sections and configure the field as desired:
- **Schema** — Defines the database column schema for the field.
- **Relationship** — Controls _and only appears when configuring relational_ field details.
- **Translations** — Controls _and only appears when configuring translation_ field details.
- **Field** — Sets details for the field input, which is displayed on the
[item page](/user-guide/content-module/content/items).
- **Interface** — Configures how you interact with the field's values.
- **Display** — Configures how field values are displayed in the Data Studio.
- **Validation** — Creates a filter to determine valid user input.
- **Conditions** — Alters the current field's setup based on the values of other fields.
4. Click <span mi btn>check</span> to confirm.
::: tip Fields in System Collections
While all out-of-the-box fields within system collections are locked from configuration or deletion, you are able to
create new fields within system collections.
:::
## Duplicate a Field
<video title="Duplicate a Field" autoplay muted loop controls playsinline>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/fields/fields-20220805/duplicate-a-field-20220805A.mp4" type="video/mp4" />
</video>
To duplicate a field, follow these steps.
1. Navigate to **Settings > Data Model > [Collection Name]**.
2. Click the <span mi icon>more_vert</span> icon for the field you want to duplicate.
3. Click the <span mi icon>content_copy</span> **Duplicate Field** option.
4. Choose the collection you'd like to create the field in and set the Field Name.
::: warning Relational and Primary Key Fields
Currently, it is not possible to duplicate relational fields or a collection's primary key.
:::
::: tip Duplicates Configurations Only
When you duplicate a field, all of its configuration settings will be copied as well. However, values stored within that
field will not be copied.
:::
## Toggle Field Visibility (for Admins)
<video title="Toggle Field Visibility (for Admins)" autoplay muted loop controls playsinline>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/fields/fields-20220805/toggle-field-visibility-20220805A.mp4" type="video/mp4" />
</video>
For users with any _non-admin_ role, a field's visibility can be adjusted via
[access permissions](/user-guide/user-management/users-roles-permissions). However, you may want to hide certain fields
for admins as well. This is handy if the field is distracting or has no need to be seen on the item details page.
## Adjust Field Width
<video title="Group and Sort Field" autoplay muted loop controls playsinline>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/fields/fields-20220805/adjust-field-width-20220805A.mp4" type="video/mp4" />
</video>
Adjusting the field width in **Fields and Layout** will change field width on the
[Item Detail Page](/user-guide/content-module/content#item-page). To adjust field width, follow these steps.
1. Click <span mi icon>more_vert</span> to open the field's context menu.
1. Choose one of the following:
- <span mi icon>border_vertical</span> **Half Width** — The field is shown at half the form width.
- <span mi icon>border_right</span> **Full Width** — The default. The field is shown at the full form width.
- <span mi icon>aspect_ratio</span> **Fill Width** — The field is shown filling the full width of the page area.
## Manually Sort Fields
<video title="Group and Sort Field" autoplay muted loop controls playsinline>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/fields/fields-20220805/manually-sort-fields-20220805A.mp4" type="video/mp4" />
</video>
Adjusting the field order in **Fields and Layout** will change its order on the
[Item Page](/user-guide/content-module/content/collections#item-page). To manually sort fields, click
<span mi icon>drag_indicator</span> to drag and drop the field as desired.
## Delete a Field
<video autoplay muted loop controls title="" playsinline>
<source src="https://cdn.directus.io/docs/v9/configuration/data-model/fields/fields-20220805/delete-a-field-20220805A.mp4" type="video/mp4" />
</video>
To permanently delete a field and all its stored values, follow these steps.
1. Navigate to **Settings > Data Model > [Collection Name]**.
2. Click the <span mi icon>more_vert</span> icon for the field you want to delete.
3. Click the <span mi icon dngr>delete</span> **Delete Field** option.
4. Confirm this decision by clicking **Delete** in the dialog.
::: danger Irreversible Change
This action is permanent and cannot be undone. Please proceed with caution.
:::

View File

@@ -1,39 +0,0 @@
# Groups
> Interfaces are how users interact with fields on the Item Detail page. These are the standard Groups interfaces.
## Accordion
![An accordion interface that allows use to expand and collapse different fields.](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-accordion.webp)
Group interface that allows user to show and hide certain fields within the group by clicking on each field.
- **Accordion Mode**: `Max 1 Section Open` - this will close all other sections when you open a section.
- **Start**: `All Closed`, `First Opened`
## Detail Group
![A group of form fields that are currently hidden behind a toggle.](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-detailgroup-closed.webp)
![A group of form fields that are currently visible but can be hidden behind a toggle.](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-detailgroup-open.webp)
Group interface that allows user to show or hide all fields within the group by clicking on the header toggle.
- **Start**: `Start Open`, `Start Closed`
- **Header Icon**: Icon to use beside the group label.
- **Header Color**: Color of the detail group header.
## Raw Group
![A group of form fields](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-rawgroup.webp)
Interface that groups multiple fields together, but always displays them.
- **No options available**
## Other Helpful Info
Any fields that you add within a Group will also maintain that grouping throughout the App Studio in filters, dropdowns,
and more.
![Content collection interface that shows a highlighted dropdown with several different groups of fields.](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-group-filter.webp)

View File

@@ -1,28 +0,0 @@
# Other
> Interfaces are how users interact with fields on the Item Detail page. These are the standard Other interfaces.
## Hash
![Form text input. Value is "value to hash on save"](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-hash.webp)
![Form text input that shows the value is securely hashed.](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-hash-secure.webp)
Text input that allows users to hash the value on save. Both the API and SDK provide methods to
[verify the hash](https://docs.directus.io/reference/system/utilities.html#verify-a-hash).
- **Types**: `Hash`
- **Placeholder**: Placeholder to display.
- **Masked**: Hide the true values on input before save.
## Slider
![A standard form text input](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-slider.webp)
Range input that allows users to select a number with an interactive slider.
- **Types**: `Integer`, `Decimal`, `Big Integer`, `Float`
- **Minimum Value**: Minimum value that can be set by the user.
- **Maximum Value**: Maximum value that can be set by the user.
- **Step Interval**: Specify the size of each increment (or step) of the slider control.
- **Always show value**: Always display the numeric value to the user.

View File

@@ -1,41 +0,0 @@
# Presentation
> Interfaces are how users interact with fields on the Item Detail page. These are the standard Presentation interfaces.
## Divider
![A horizontal divider between two form fields](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-divider.webp)
A horizontal divider to separate fields into different sections.
- **Title**: Enter a title, or leave blank to only show the divider line.
- **Color**: Color of the divider.
- **Icon**: Icon to display.
- **Inline Title**: Show title inside the divider line or above the line.
## Button Links
![A group of two buttons. One primary button. One default button.](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-buttonlinks.webp)
![A form for creating new Button Links. Form has four fields: "Label", "Icon", "Type", "URL"](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-buttonlinks-new.webp)
Group of one or more buttons that link to a preset or dynamic url.
- **Links**: The group of button links.
- **Label**: Label for the button.
- **Icon**: Icon displayed beside the button label.
- **Type**: `Primary`, `Normal`, `Info`, `Success`, `Warning`, `Danger`
- **URL**: URL to send the user to when the button is clicked. You can use field values from the Item to create the
URL dynamically.
## Notice
![A standard warning notice in yellow with a hazard icon.](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-notice.webp)
An alert or notice interface to notify users of important information.
- **Color**: `Primary`, `Normal`, `Info`, `Success`, `Warning`, `Danger`
- **Icon**: Icon to display in the Notice.
- **Text**: Enter your notice content. You can also use
[Translation Strings](/user-guide/content-module/translation-strings.html) here to provide notices in different
languages.

View File

@@ -1,136 +0,0 @@
# Relational
> Interfaces are how users interact with fields on the Item Detail page. These are the standard Relational interfaces.
> We recommend that you review [Relationships](/app/data-model/relationships) before working with Relational interfaces.
## File
![A file type form input where user can pick from three options: "Upload File From Device", "Choose Files from Library", "Import File from URL"](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-file.webp)
Interface that allows users to upload a single file of any mime-type, choose an existing file from the
[File Library](/user-guide/file-library/files), or import a file from a URL.
- **Folder**: Folder for the uploaded files. Does not affect the location of existing files.
## Image
![A file type form input where user can pick from three options: "Upload File From Device", "Choose Files from Library", "Import File from URL"](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-image.webp)
Interface that allows users to upload a single image file, choose an existing image from the
[File Library](/user-guide/file-library/files), or import an image from a URL.
- **Folder**: Folder for the uploaded files. Does not affect the location of existing files.
- **Crop to Fit**: Crop the image as needed when displaying the image.
## Files
![A file type form input where user can select and upload multiple files.](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-files.webp)
Interface that allows users to upload multiple files, choose an existing image from the
[File Library](/user-guide/file-library/files), or import an image from a URL.
This field will create a [Many-To-Many (M2M)](/app/data-model/relationships#many-to-many-m2m) junction collection when
added to the [Data Model](/app/data-model) for your [Collection](/app/data-model/collections).
- **Folder**: Folder for the uploaded files. Does not affect the location of existing files.
- [**Display Template**](/user-guide/content-module/display-templates): Fields or custom text that represent the
specific item through various places in the App Studio.
- **Creating Items**: Allow users to upload new files.
- **Selecting Items**: Allow users to select existing files.
- **Per Page**: The number of Items to show per page.
## Builder (M2A)
![A form interface that allows users to create a relationship from the current item by selecting different items from multiple, distinct Collections.](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-m2a.webp)
Interface that allows users to create relationships between the current item and multiple items from multiple, distinct
collections. See [Many-to-Any (M2A) Relationships](/app/data-model/relationships#many-to-any-m2a).
Useful in many different contexts including
[creating re-usable page components](/guides/headless-cms/reusable-components).
- **Creating Items**: Allow users to create new Items in the M2A collection.
- **Selecting Items**: Allow users to select existing files in the M2A collection.
- **Per Page**: The number of Items to show per page.
- **Allow Duplicates**: Allow users to add the same Item multiple times.
## Many To Many
![A form interface that allows users to select multiple different items from a single collection. Buttons for "Create New" and "Add Existing".](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-m2m.webp)
Interface that allows users to create relationships between the current item and many different items from a single
collection.
This field will create a [Many-To-Many (M2M)](/app/data-model/relationships#many-to-many-m2m) junction collection when
added to the [Data Model](/app/data-model) for your [Collection](/app/data-model/collections).
- **Layout**: `List`, `Table`
- **Creating Items**: Allow users to create new Items in the M2M collection.
- **Selecting Items**: Allow users to select existing files in the M2M collection.
- **Per Page**: The number of Items to show per page.
- **Junction Fields Location**: `Top`, `Bottom`
- **Allow Duplicates**: Allow users to add the same Item multiple times.
- **Filter**: [Filter Rule](/reference/filter-rules) to filter down the list of Items a user can select.
- **Item link**: Show a link to the item.
## One to Many
![A form interface that allows users to select multiple items from a single collection. Buttons for "Create New" and "Add Existing".](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-o2m.webp)
Interface that allows users to create a relationship between the current item and many items from a single collection.
Adding a One To Many field to the data model will create a corresponding Many to One field in the child collection. See
[One-to-Many (O2M) Relationships](/app/data-model/relationships#one-to-many-o2m).
- **Layout**: `List`, `Table`
- **Creating Items**: Allow users to create new Items in the M2A collection.
- **Selecting Items**: Allow users to select existing files in the M2A collection.
- **Per Page**: The number of Items to show per page.
- **Junction Fields Location**: `Top`, `Bottom`
- **Allow Duplicates**: Allow users to add the same Item multiple times.
- **Filter**: [Filter Rule](/reference/filter-rules) to filter down the list of Items a user can select.
- **Item link**: Show a link to the item.
## Tree View
![A form interface that shows multiple parent and child items from the same collection. Buttons for "Create New" and "Add Existing".](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-treeview.webp)
Special One-to-Many (O2M) interface that allows users to create and manage recursive relationships between items from
the same collection.
The Tree View interface is only available on self-referencing (recursive) relationships. See
[Many-to-Any (M2A) Relationships](/app/data-model/relationships#many-to-any-m2a).
- [**Display Template**](/user-guide/content-module/display-templates): Fields or custom text that represent the
specific item through various places in the App Studio.
- **Creating Items**: Allow users to create new Items in the collection.
- **Selecting Items**: Allow users to select existing files in the collection.
- **Filter**: [Filter Rule](/reference/filter-rules) to filter down the list of Items a user can select.
## Many to One
![A form interface that allows a user to select a single item from a collection."](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-m2o.webp)
Interface that allows users to create a relationship between the current item and a single item from a single
collection.
See [Many-to-One (M20) Relationships](/app/data-model/relationships#many-to-one-m2o)
- [**Display Template**](/user-guide/content-module/display-templates): Fields or custom text that represent the
specific item through various places in the App Studio.
- **Creating Items**: `Enable Create Button`
- **Selecting Items**: `Enable Select Button`
- **Filter**: [Filter Rule](/reference/filter-rules) to filter down the list of Items a user can select.
## Translations
![A form interface with two columns and two fields per column - "Title" and "Content". One column contains the English translation for each field. Second column contains the French translation for each field.](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-translations.webp)
Special relational Interface designed specifically to handle translations. See
[Translations (O2M)](/app/data-model/relationships#translations-o2m).
- **Language Indicator Field**: The field from your `languages` collection that identifies the language to the user.
- **Language Direction Field**: The field from your `languages` collection that identifies the text direction for a
selected language.
- **Default Language**: Default language to use.
- **Use Current User Language**: Default to the current users language.

View File

@@ -1,156 +0,0 @@
# Selection
> Interfaces are how users interact with fields on the Item Detail page. These are the standard Selection interfaces.
## Toggle
![A toggle form input with label named "Enabled"](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-toggle.webp)
A checkbox input that allows user to toggle value between on and off / true and false.
- **Types**: `Boolean`
- **Default Value**: `true`, `false`, `null`
- **Icon On**: Icon that shows whenever the toggle is enabled.
- **Icon Off**: Icon that shows whenever the toggle is disabled.
- **Color On**: Color of the icon whenever the toggle is enabled.
- **Color Off**: Color of the icon whenever the toggle is disabled.
- **Label**: The text displayed to the user beside the toggle.
## Datetime
![A date picker input. User can select a calendar date and input a time. ](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-datetime.webp)
Date picker input that allows user to select a date and time.
- **Types**: `DateTime`, `Date`, `Time`, `Timestamp`
- **Include Seconds**: Show seconds in the interface
- **Use 24-Hour Format**: Use 24 hour time system instead of 12 hour
## Repeater
![A standard form text input](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-repeater.webp)
![A standard form text input](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-repeater-open.webp)
Interface for repeating groups of fields.
You can use any [Text & Number](/app/data-model/fields/text-numbers), [Selection](/app/data-model/fields/selection), or
[Other](/app/data-model/fields/other) fields within a Repeater. [Relational](/app/data-model/fields/relational),
[Presentation](/app/data-model/fields/presentation), or [Group](/app/data-model/fields/groups) fields are not allowed.
Value is stored as a JSON array of objects.
- **Types**: `JSON`
- **Template**: Add a JSON template that users that add to the field with a button click.
- **"Create New" Label**: Label for the Create New button below the field on Item Detail page.
- **Sort**: Method of sorting the items groups within the repeater.
- **Edit Fields**: The configuration for fields within the Repeater.
- **Field**: Name of the field.
- **Field Width**: Width of field on the Item Detail page.
- **Type**: Type of value.
- **Note**: A helpful note for the user.
- **Interface**: The interface to use for the fields.
- **Interface Options**: Option configuration for the selected Interface.
- **Display**: The display to use for the preview template.
- **Display Options**: Option configuration for the selected Display.
## Map
![An interactive map interface that shows a single point on the east coast of the United States. Map has buttons for zoom, search, and full screen.](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-map.webp)
Interface that shows geospatial data on an interactive map.
- **Types**: `Point`, `LineString`, `Polygon`, `Multipoint`, `MultiLineString`, `MultiPolygon`, `Geometry (All)`, `JSON`
- **Default View**: The default location and zoom settings on the map to show by default
## Color
![A text input for color hex codes that allows user to select color modes ](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-color.webp)
A color picker interface that allows users to input color codes and convert between different color modes.
- **Types**: `String`
- **Opacity**: Enable opacity values to be adjusted by the user.
- **Preset Colors**: Preset colors that users can select.
## Dropdown
![A select input with a dropdown of options.](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-dropdown.webp)
Input that allows user to select a value from a list of options.
- **Types**: `String`, `Integer`, `Big Integer`, `Float`
- **Choices**: Options for the dropdown.
- **Text**: Label the user sees.
- **Value**: Raw value that gets stored.
- **Allow Other**: Allow user to enter custom values other than preset values.
- **Allow None**: Allow user to deselect an option.
- **Icon**: Icon displayed beside the dropdown.
- **Placeholder**: Placeholder text for the dropdown.
## Icon
![A select input with a dropdown grid of icon choices.](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-icon.webp)
Search input that allows user to select from a list of icons.
- **Types**: `String`
## Checkboxes
![A form input with multiple checkboxes.](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-checkboxes.webp)
Input that allows user to select multiple checkboxes.
- **Types**: `JSON`, `CSV`
- **Choices**: Options for the checkboxes.
- **Text**: Label the user sees.
- **Value**: Raw value that gets stored.
- **Allow Other**: Allow user to enter custom values other than preset Choices.
- **Color**: Color of the checkboxes.
- **Icon On**: Icon that shows whenever a checkbox is checked.
- **Icon Off**: Icon that shows whenever a checkbox is unchecked.
- **Items Shown**: Number of checkboxes shown.
## Checkboxes (Tree)
![A form input with a nested tree of multiple parent and child checkboxes.](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-checkboxes-tree.webp)
Nested tree of checkboxes that can be expanded or collapsed.
- **Types**: `JSON`, `CSV`
- **Choices**: Options for the checkbox tree.
- **Text**: Label the user sees.
- **Value**: Raw value that gets stored.
- **Children**: Child checkboxes nested below the current choice.
- **Value Combining**: Controls what value is stored when nested selections are made.
## Dropdown (Multiple)
![A select input where user can select multiple options from a dropdown.](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-dropdown-multiple.webp)
Input that allows user to select multiple values from a list of options.
- **Types**: `JSON`, `CSV`
- **Choices**: Options for the dropdown.
- **Text**: Label the user sees.
- **Value**: Raw value that gets stored.
- **Allow Other**: Allow user to enter custom values other than preset choices.
- **Allow None**: Allow user to deselect an option.
- **Icon**: Icon displayed beside the dropdown.
- **Placeholder**: Placeholder text for the dropdown.
## Radio Buttons
![A radio button form input with different options to select](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-radio-buttons.webp)
Radio button input that allows users to select a single value from multiple choices.
- **Types**: `String`, `Integer`, `Big Integer`, `Float`
- **Choices**: Options for the radio buttons.
- **Text**: Label the user sees.
- **Value**: Raw value that gets stored.
- **Allow Other**: Allow user to enter custom values other than preset choices.
- **Color**: Color of the radio buttons.
- **Icon On**: Icon that shows whenever a radio buttons is checked.
- **Icon Off**: Icon that shows whenever a radio buttons is unchecked.

View File

@@ -1,110 +0,0 @@
# Text & Numbers
> Interfaces are how users interact with fields on the Item Detail page. These are the standard Text & Number
> interfaces.
## Input
![A standard form text input](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-input.webp)
A standard form input.
- **Types**: `String`, `Text`, `UUID`, `Integer`, `Big Integer`, `Float`, `Decimal`
- **Soft Limit**: Used to limit the number of characters within the Data Studio. There is no hard limit in the database.
- **Font**: Type of font used to display the input value.
- **Trim**: Trim the whitespace at start and end of the value.
- **Masked**: Hide the real value.
- **Cleared Value**: When a user clears the value, save as an empty string.
- **Slugify**: Make the entered value URL safe.
## Autocomplete Input (API)
![An autocomplete form text input that shows a dropdown list of options based on a search query](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-autocomplete.webp)
A search input that will populate dropdown choices by making a request to a given URL.
- **Types**: `String`, `Text`
- **URL**: The URL where the request will be sent to.
- **Results Path**: The path to the array containing the search results.
- **Text Path**: The label that shows for each search result in the dropdown.
- **Value Path**: The value that is stored when you select an option from the dropdown.
- **Trigger**: `Throttle`, `Debounce` - The method used to trigger the web request as you type a query.
- **Rate**: The delay in `milliseconds` used in the Trigger function.
## Block Editor
![Showcase of the block editor with example blocks](https://marketing.directus.app/assets/f631a2e1-cb27-434a-939b-eb15132ac46a.png)
Allows users to create and edit content using blocks. These blocks represent individual pieces of content, such as text,
images, videos, buttons, and more, that can be assembled and re-arranged within a flexible layout.
- **Types**: `JSON`
- **Toolbar**: Allows for customization of visible formatting options.
- **Folder**: Default folder to store uploaded files. Does not affect existing files.
- **Font**: Type of font used in Edit mode.
## Code
![A code editor input](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-code.webp)
Code editor for pre-formatted text.
- **Types**: `String`, `Text`, `JSON`, `Geometry (All)`
- **Language**: Select a language for syntax highlighting.
- **Line Number**: `Enabled` - Show the line number for each line of code in the editor.
- **Line Wrapping**: `Enabled` - Wrap text inside the code editor to prevent horizontal scrolling.
- **Template**: Preset value that the user can add to the field value by clicking "Fill with Template Value" with adding
/ editing an item.
## Textarea
![A standard form textarea input](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-textarea.webp)
Textarea input for longer plain text.
- **Types**: `Text`
- **Soft Limit**: Used to limit the number of characters within the Data Studio. There is no hard limit in the database.
## WYSIWYG
![A What You See Is What You Get (WYSIWYG) form input that has a toolbar for formatting](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-wysiwyg.webp)
The What You See Is What You Get (WYSIWYG) interface provides a text area with rich formatting options in the toolbar.
- **Types**: `Text`
- **Toolbar**: Allows for customization of visible formatting options
- **Folder**: Default folder to store uploaded files. Does not affect existing files.
- **Static Access Token**: Static access token appended to the assets' URL.
- **Soft Limit**: Used to limit the number of characters within the Data Studio. There is no hard limit in the database.
- **Custom Formats**: JSON array of formatting that is passed to the `style_formats` config option of the WYSIWYG editor
instance (TinyMCE). [See TinyMCE documentation](https://www.tiny.cloud/docs/demo/format-custom/)
- **Options Override**: JSON object to override the default config option of the WYSIWYG editor instance (TinyMCE).
[See TinyMCE documentation](https://www.tiny.cloud/docs/configure/)
## Markdown
![A markdown text editor with a toolbar with formatting options. Edit and preview tabs.](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-markdown.webp)
Markdown text editor with formatting options in the toolbar. You can switch between Edit and Preview modes.
- **Types**: `Text`
- **Toolbar**: Allows for customization of visible formatting options.
- **Folder**: Default folder to store uploaded files. Does not affect existing files.
- **Static Access Token**: Static access token appended to the assets' URL.
- **Soft Limit**: Used to limit the number of characters within the Data Studio. There is no hard limit in the database.
- **Editor Font**: Type of font used in Edit mode.
- **Preview Font**: Type of font used in Preview mode.
- **Custom Blocks**: Add custom markdown syntax types.
## Tags
![A standard form text input where user can select, add, and remove tags.](https://cdn.directus.io/docs/v9/configuration/data-model/fields/interfaces-20230308/interface-tags.webp)
A text input that allows users to apply any number of tags. When adding new tag, press Enter to save the tag.
- **Types**: `JSON`, `CSV`
- **Presets**: Preset tags that the user can select.
- **Alphabetize**: Force tags to display in alphabetical order.
- **Allow Other**: Allow users to add new tags not in the Presets.
- **Whitespace**: Setting to control the whitespace within a tag.
- **Capitalization**: Force tags to be stored as lowercase, uppercase, or title case.

View File

@@ -1,425 +0,0 @@
# Relationships
> Relationships are a crucial part of any relational database. Directus supports all standard relationship types, as
> well as a few more of its own _compound_ types, which are custom-tailored to make certain _common but complex_ tasks a
> breeze.
::: tip Before You Begin
Regardless of the relationship you want to configure, we recommend you read every section of this document, in order, at
least once. This is because you must understand how M2Os work in Directus to understand O2Ms, you must understand M2Os
and O2Ms to understand M2Ms, etc.
:::
## Overview
The Data Studio makes _the process_ of configuring relational data models easier, faster, and more intuitive by offering
no-code configuration. Directus _does not_ enforce opinionated schemas, rule systems, or other arbitrary limitations to
your data models. Therefore, aside from any technical limitations of your project's infrastructure or core requirements
for any relational data model, _like having a primary key field for every collection or a data type assigned to every
field,_ you are free to build the data model as you want.
In this guide, we will go over the following topics:
- What kinds of relationships exist within Directus.
- How to configure a desired relationship within the Data Studio.
- How relationships are implemented in the data model and displayed in the Data Studio.
- When it might be appropriate to use a given type of relationship.
By the end, you'll understand everything needed to start building data models in Directus, even if relational data model
concepts are a new concept to you.
### Directus vs Classic Data Model Terms
When we use classic data model terms, _such as data table, column, row, etc..._ this signals that the explanation is
focused strictly on what happens in the database. When Directus terminology is used, _such as collection, field, item,
etc..._ this signals that the explanation includes Directus logic and functionality.
## Many-to-One (M2O)
<!-- <video title="Configure an M2O Relationship" autoplay playsinline muted loop controls>
<source src="" type="video/mp4" />
</video> -->
In an M2O relationship, multiple items from the parent collection are linked to one item in a related collection. For
example, there are many cities in a country, but a city can only be in one country.
To create an M2O relationship, we add a foreign key field to the parent collection, which links items from the parent
collection to items in the related collection. If we have two tables, `cities` and `countries`, we can create a
`cities.country_id` foreign key field.
![Many-to-One Relational Diagram](https://cdn.directus.io/docs/v9/configuration/data-model/relationships/relations-20221026/m2o-20221026A.webp)
Let's take a look at the schema.
```
cities
- id
- name
- country_id (a foreign key field, stores a key from countries.id)
```
```
countries
- id
- name
```
Note the following things from the schema above:
- An M2O relationship requires just one column within the parent table.
- When an M2O relational field is configured in Directus, an Item Page on the parent collection will enable access to
the item from the related collection. So in our example above, an Item Page in `cities` will enable access to the
related country from the `countries` table.
However, in the Directus Data Studio, an M2O field does not automatically provide access to the parent collection's
items within the related collection. In our example, this means that when you open an Item Page in `countries`, you will
not see related cities.
This is where O2M fields come in to play.
::: tip Configure an M2O
The easiest way to configure an M2O field is to follow the guide on how to
[create a field (standard)](/app/data-model/fields#create-a-field-standard) and select the M2O Interface from the
template wizard.
:::
## One-to-Many (O2M)
Within a relational database, an O2M relationship is the exact same type of relationship as an M2O. Remember, at the end
of the [M2O](#many-to-one-m2o) section, we learned that configuring an M2O in Directus does not let us access related
items within an Item Page on the related collection. In Directus, configuring an O2M creates an
[Alias](/user-guide/overview/glossary#alias) field, which lets us access related items. To demonstrate this, let's
continue with the `cities` and `countries` example relationship used in the M2O section.
![One-to-Many Relational Diagram](https://cdn.directus.io/docs/v9/configuration/data-model/relationships/relations-20221026/o2m-20221026A.webp)
Let's take a look at the schema.
```
countries
- id
- name
- cities (the O2M alias field, does not exist in the data table. Allows access to all cities linked to a country.)
```
```
cities
- id
- name
- country_id (the M2O field)
```
Note the following points from the schema above. When we create an O2M in Directus:
- Since the perspective is flipped, we now consider `countries` to be the parent collection.
- It isn't always necessary to create an O2M. In some cases, you won't want or need to access items from both sides.
- At first glance, this O2M alias field might make it _look and feel_ like a new column was created, but the O2M field
is purely _virtual_. It creates an Interface within the Data Studio to access items from an O2M perspective. In other
words, the O2M alias field allows us to access any related items from `cities` within an Item Details Page in the
`countries` collection.
<!-- <video title="Configure an M2O Relationship" autoplay playsinline muted loop controls>
<source src="" type="video/mp4" />
</video> -->
::: tip Configure an O2M
The easiest way to configure an O2M is to follow the guide on how to
[create a field (standard)](/app/data-model/fields#create-a-field-standard) and select the O2M alias field type from the
template wizard.
:::
## One-to-One (O2O)
Directus does not include a dedicated One-to-One (O2O) relationship type or Interface. However, in the database, O2O is
almost exactly the same as an M2O. The only difference is that an O2O enforces _cardinality_. In other words, one item
from the parent collection can be linked with one item on the related collection, and vice-versa.
For example, each country has one capital city, and vice versa. This is an O2O. To demonstrate how it works, let's add
this O2O to the `cities` and `countries` example relationship used in the previous sections.
The first strategy you may think of it to add a new `capital_city` field on the `countries` collection, storing the name
of the capital city directly. But this would create [duplicate data](/app/data-model#avoid-data-duplication), because
the same city would exist in both `countries.capital_city` as well as `cities.name`. But remember, we want to _avoid
duplicate data!_
![Duplicate Data from Capital Cities](https://cdn.directus.io/docs/v9/configuration/data-model/relationships/relations-20221026/o2o-duplicate-20221026A.webp)
Instead, we want to use an O2O relationship. Let's try adding a `cities.capital_of` field.
![An inefficient One-to-One Relationship](https://cdn.directus.io/docs/v9/configuration/data-model/relationships/relations-20221026/o2o-inefficient-20221026A.webp)
Let's take a look at the schema.
```
cities
- id
- name
- country_id
- capital_of (The O2O field. Actually an M2O, configured to store unique values, stores foreign key from countries.id)
```
```
countries
- id
- name
- cities
```
The O2O relationship in the schema above works, and in some cases it may not matter which collection to configure the
O2O onto. But in this case it is sub-optimal. Since _most cities_ are not capital cities, the column will mostly contain
`NULL` values. However, every single country has a capital city. So if we create the O2O on the `countries` collection,
it will be much more efficient.
![A One-to-One Relationship](https://cdn.directus.io/docs/v9/configuration/data-model/relationships/relations-20221026/o2o-20221026A.webp)
Let's take a look at the schema.
```
countries
- id
- name
- cities
- capital_city (The O2O field. Actually an M2O, configured to store unique values, stores foreign key from cities.id)
```
```
cities
- id
- name
- country_id
```
Note the following points from the schema above. When we create an O2O in Directus:
- We can add the O2O field on either collection. However, in some cases it is more efficient to add it to a specific
collection.
- Since the O2O field is really just an M2O field behind the scenes, and since Directus doesn't automatically display
M2O fields in the related collection, you may want to [configure an O2M field](#one-to-many-o2m) so that you can
access items from the related collection as well.
::: tip Configure an O2O
<!-- <video title="Configure an O2O Relationship" autoplay playsinline muted loop controls>
<source src="" type="video/mp4" />
</video> -->
The easiest way to configure an O2O is to follow the guide on how to
[create a field (standard)](/app/data-model/fields#create-a-field-standard) and select the **M2O** field type from the
template wizard. Then, configure the field's schema, toggling on **Unique** so that each value in the M2O field is
unique, resulting in an O2O relationship.
:::
## Many-to-Many (M2M)
The relationship types we have seen so far only required one foreign key column to link the parent collection and
related collection. An M2M relationship is composed _of two foreign key columns_ stored within an additional table,
called a _junction table_, which stores each linked row between the parent table and related table.
Junction tables are required in M2M relationships because the number of relationships created can _(and often will!)_
outnumber the number of rows in either data table. In other words, if you have `x` rows in the parent column and `y`
rows in the related column, you need room to store up to `x * y` rows. Junction tables provide a place to store all the
relationships between rows, no matter how many exist.
To demonstrate this, let's think about the relationship between recipes and ingredients: a _recipe_ can have many
_ingredients_, and _ingredients_ can be in many _recipes_.
![Many-to-Many Relational Diagram](https://cdn.directus.io/docs/v9/configuration/data-model/relationships/relationships-20220805/m2m-20220805A.webp)
Let's take a look at the schema.
```
recipes
- id
- name
- ingredients (An M2M alias field. Does not exist in the database, allows access to ingredients linked from recipe_ingredients)
```
```
recipes_ingredients (junction collection)
- id
- recipe (stores foreign key from recipes.id)
- ingredient (stores foreign key from ingredients.id)
- quantity (A "context" field. Stores other data associated with the relationship. These are optional.)
```
```
ingredients
- id
- name
```
Note the following points from the schema above. When we create an M2M in Directus:
- Our junction collection, `recipe_ingredients`, each row contains two foreign key columns. This is what creates the
relationships between the two tables.
- Assuming the M2M alias field is created within the `recipes` collection, Directus does not automatically add a field
to display recipes within the `ingredients` collection. However, you can easily add such a field when creating the M2M
field within the `recipes` collection, by clicking on `Continue in Advanced Field Creation Mode`, opening the
`Relationship` tab and selecting `Add M2M to "ingredients"`:
```
ingredients
- id
- name
- recipes (an M2M alias field, does not exist in the database, enables access to all the recipes related to an ingredient)
```
- Notice that the junction collection also has a `quantity` field, which tracks how much of each ingredient is needed
for the recipe. This is called a _contextual field_. The Data Studio provides full access to the junction collection,
so you can add any number of contextual fields needed to the junction collection.
- You can also have a self-referencing M2M relationship that connects items in the _same collection_. One example is
"Related Articles", where each article relates to many other articles.
::: tip Configure an M2M
<!-- <video title="Configure an O2O Relationship" autoplay playsinline muted loop controls>
<source src="" type="video/mp4" />
</video> -->
The easiest way to configure an M2M is to follow the guide on how to
[create a field (standard)](/app/data-model/fields#create-a-field-standard) and select **Many to Many** from the
template wizard.
:::
## Many-to-Any (M2A)
Sometimes called a _matrix field_ or _replicator_, an M2A relationship allows you to link items from the parent
collection to any item in any collection in the database. When you configure an M2A in Directus, an M2A
[Alias](/user-guide/overview/glossary#alias) field is created as well as a junction collection, like we saw on M2M
relationships. The difference is that the junction collection on an M2A also has a field to store the **collection key**
_(the name of the collection)_ for related collections.
One common example of when M2As are used is for _page builders_, which have a `pages` collection that combines multiple
collections for each type of page section, such as `heading`, `text_bodies`, `image`, `video`, _etc_.
![Many-to-Any Relational Diagram](https://cdn.directus.io/docs/v9/configuration/data-model/relationships/relationships-20220805/m2a-20220805A.webp)
Let's take a look at the schema:
```
pages
- id
- name
- sections (An M2A alias field, does not exist in the database. Provides access to items from page_sections.)
```
```
page_sections (junction collection)
- id
- pages_id (An M2O, stores foreign keys from pages.id)
- collection (An M2O, stores name of the related collection, for example headings, text_bodies, or images.)
- item (An M2O, stores foreign keys from headings.id, text.id, images.id, etc.)
```
```
headings
- id
- title
```
```
text_bodies
- id
- text
```
```
images
- id
- file
```
Note the following points from the schema above. When we create an M2A in Directus:
- Compared to the M2O and M2M relationships, there may be a lower likelihood that you will need to configure alias
fields on related collections, such as `headings`, `text_bodies` and `images`, as these collections may not be as
useful without the parent collection.
- Each collection has a unique collection name, so this serves as an adequate foreign key in the
`page_sections.collection` field.
::: tip Configure an M2A
<!--
<video title="Configure an M2A Relationship" autoplay playsinline muted loop controls>
<source src="" type="video/mp4" />
</video>
-->
The easiest way to configure an M2A is to follow the guide on how to
[create a field (standard)](/app/data-model/fields#create-a-field-standard) and select the **Many to Any** Interface
from the template wizard.
:::
## Translations (O2M)
Directus provides this special relational Interface designed specifically to handle translations. When you create a
Translations O2M in the Data Studio, the following things happen within your data model. A Translations O2M alias field
is created. A junction collection and a `languages` collection are created. All your translations are stored within
context fields, configured by you, on the junction collection. Therefore, when you create a Translations O2M, you also
create an M2M relationship behind the scenes. So remember, it is called the Translations O2M because we interact with
the Translations O2M alias field. But behind the scenes, it is powered by an M2M.
To demonstrate, let's create a Translations O2M relationship for `articles`, a common content type that you may want to
translate.
![Translations O2M](https://cdn.directus.io/docs/v9/configuration/data-model/relationships/relationships-20220805/o2m-translations-20220805A.webp)
Let's take a look at the schema.
```
articles
- id
- author (a field that is not translated)
- date_published (a field that is not translated)
- translations (A Translations O2M alias field, does not exist within the data table. Allows access to items from article_translations)
```
```
article_translations
- id
- article_id (an M2O, stores foreign key article.id)
- language_id (an M2O, stores foreign key languages.language_code)
- title (A context field, created by you. Stores a translation of the Article Title)
- text (A context field, created by you. Stores a translation of the Article Text)
```
```
languages
- language_code (A primary key. A manually typed language code, e.g., "en-US")
- name (Stores the language name, e.g., "English")
```
Note the following points from the schema above. When we create a Translations O2M:
- As demonstrated by `article_translations.title` and `article_translations.text`, any _translated_ fields should be
added as context fields on the junction collection.
- You are not bound to use this for translations. You can build your data model as desired. You could create individual
fields for each translation, such as `title_english`, `title_german`, `title_french`, and so on. However, this is not
easily extensible and it creates a sub-optimal experience to have every single translation of every field on the item
details page. The Translations O2M alias field is designed specifically to make the translation process easier.
- There may come a time when you want to make a pre-existing parent field translatable. To do this, you can
[duplicate a field](/app/data-model/fields#duplicate-a-field), move it to the translation collection, and then delete
the parent field. However, be aware that duplicating a field does _not_ duplicate any existing field values.
::: tip Configure a Translations O2M
<!--
<video title="Configure a Translations O2M" autoplay playsinline muted loop controls>
<source src="" type="video/mp4" />
</video>
-->
The easiest way to configure a Translations relationship is to follow the guide on how to
[create a field (standard)](/app/data-model/fields#create-a-field-standard) and select the **Translations O2M**
Interface from the template wizard.
:::

View File

@@ -1,15 +0,0 @@
# Frequently Asked Questions
>
## Is it possible to update the admin user password via CLI?
You can do this with the following command:
```sh
npx directus users passwd --email admin@example.com --password newpasswordhere
```
## Why isn't Directus properly saving Chinese characters or emoji?
Please ensure that the encoding for your database, tables, and fields are set to `utf8mb4`.

View File

@@ -1,390 +0,0 @@
---
description:
Flows enable custom, event-driven data processing and task automation within Directus. Each flow is composed of one
trigger, followed by a series of operations.
readTime: 5 min read
---
# Flows
> Flows enable custom, event-driven data processing and task automation within Directus. Each flow is composed of one
> trigger, followed by a series of operations.
::: tip Before You Begin
Please be sure to see the [Quickstart Guide](/getting-started/quickstart) to get a basic overview of the platform.
:::
::: tip Learn More
There is also dedicated API documentation on [Flows](/reference/system/flows) and
[Operations](/reference/system/operations).
:::
<!--
@TODO What is Task Automation?
Oversimplification of the internet:
- Store data
- Get Data
- Process Data
- Send Data
Database data stored as rows and columns
Web Request -> JSON
JSON -> Operations and Transformations
Scripts ->
Control Flow ->
Events ->
Async/Sync ->
### JSON vs JavaScript Objects
```json
{
"key": "string", // string
"key2": 1, // number
"key3": {}, // json
"key4": [], // array
"key5": true, // boolean
"key6": null // null
}
```
-->
## What's a Flow?
![What's a Flow?](https://cdn.directus.io/docs/v9/configuration/flows/flows/flows-20220603A/whats-a-flow-20220603A.webp)
<!--
<video title="What's a Flow" autoplay playsinline muted loop controls>
<source src="" type="video/mp4" />
</video>
-->
Each flow is made up of three elements: A trigger, operations, and a data chain.
### **Triggers**
Each flow begins with a [trigger](/app/flows/triggers), which defines the action or event that starts the Flow. This
action or event could be some type of transaction within the app, an incoming webhook, a cron job, etc.
### Operations
An [operation](/app/flows/operations) is an action or process performed within the flow. These enable you to manage
data: _send off emails, push in-app notifications, send webhooks, and beyond_.
To put it in conceptual terms, operations do three things:
- **Get data** from Directus or another outside service.
- **Process data** a.k.a. transform it, validate it, or whatever.
- **Send data** to Directus or another outside service.
::: tip Developers
You can even develop your own [custom operations](/extensions/operations) to fit any use-case.
:::
### Data Chains
In order for a flow's operations to track and access the same data, each flow creates its own
[data chain](#the-data-chain). Every operation has access to this data chain and each operation appends some value onto
this object after it runs. This means you can dynamically access data from a previous operation in the current operation
with [data chain variables](#data-chain-variables).
### Control Flow
Not every operation that executes in a flow does so successfully. In some cases, your operations are going to fail.
Perhaps an operation tried to access data that doesn't exist, or a webhook operation fails for some reason, or perhaps
you set a [condition](/app/flows/operations#condition) operation, which _fails by design_ when its condition is not met.
These kinds of failed operations do not immediately stop your flow. Instead, flows let you implement
[control flow](https://en.wikipedia.org/wiki/Control_flow), by providing **success paths** and **failure paths** within
a flow:
<!-- ![Control Flow](image.webp) -->
- **Success** — If `operation1` executes successfully, then `operation2` executes.
- **Failure** — Else if `operation1` fails on execution, then `operation3` executes.
_And there we have it!_ These are the conceptual cornerstones of any flow. Now you'll need to know how to actually
create a flow, which we discuss in the next section.
## Configure A Flow
<video autoplay playsinline muted loop controls title="Create a Flow">
<source src="https://cdn.directus.io/docs/v9/configuration/flows/flows/flows-20220603A/create-a-flow-20220603A.mp4" type="video/mp4" />
</video>
### Create a Flow
1. Navigate to **Settings > Flows** and click <span mi btn>add</span> in the page header. A drawer will open.
2. Under **Flow Setup**, fill in a **Name** for the flow and the following _optional_ details:
- **Status** — Sets the flow to active or inactive.
- **Icon** — Adds an icon to help quickly identify the flow.
- **Description** — Sets a brief verbal description of the flow.
- **Color** — Sets a color to help identify the flow.
- **Activity and Logs Tracking** — Lets you **Track Activity and Logs**, **Activity**, or **Neither**.
::: tip
To learn more, see the section below on [Logs](#logs) as well as the [Activity Log](/user-guide/settings/activity-log)
documentation.
:::
### Configure a Trigger
3. Click <span mi btn>arrow_forward</span> to navigate to **Trigger Setup**. Select a [trigger](/app/flows/triggers)
type and configure as desired.
4. Click <span mi btn>done</span> in the menu header to confirm.
You'll now see your trigger in an empty grid area. Its time to start adding operations.
### Configure an Operation
5. On the trigger panel, click <span mi>add</span> and the **Create Operation** side drawer will open.
6. Choose a **Name**, an [operation](/app/flows/operations) type, and configure as desired.\
Directus will convert the name into a unique operation key, used on the [data chain](#the-data-chain).\
If you don't choose a name, the system will auto-generate a name and key for you.
7. Next, click <span mi btn>done</span> in the page header to confirm and return to the flow grid area.
8. From here, you can make the following optional configurations:
- **Reposition** — You can drag and drop panels to reposition as desired.
- **Unlink/Relink** — Click and drag <span mi icon prmry>adjust</span> or <span mi icon prmry>arrow_forward</span> to
unlink/relink flows.
- **Duplicate an Operation** — To duplicate an operation, click <span mi icon>more_vert</span> to open its context
menu. Click <span mi icon>control_point_duplicate</span> and a duplicate of the operation (and its configuration
details) will be created.
- **Copy an Operation** — To copy and paste an operation into another flow, click <span mi icon>more_vert</span> to
open its context menu. Click <span mi icon>input</span> and a popup menu will open. Choose the desired flow from
the dropdown and click **Copy**.
- **<span mi icon>data_object</span> Toggle Raw Editor** — Click <span mi icon>data_object</span> on an operations'
form input fields to toggle the input type between standard and raw value. This allows you to add a raw value or
[Data Chain Variables](#data-chain-variables) within any type of configuration option, even dropdown menus,
checkboxes, and radio buttons.
- **Delete an Operation** — To delete an operation, click <span mi icon>more_vert</span> then
<span mi icon dngr>delete</span>. A popup menu will appear. Click **Delete** to confirm.
9. On the newly created operation panel:
- Click <span mi icon>add</span> to add an operation to the **success path**.
- Click <span mi icon>remove</span> to add an operation to the **failure path**.
10. Repeat steps 5-10 to build out your flow as desired.
11. Click <span mi btn>done</span> to confirm and create your flow.
12. Click <span mi btn>arrow_back</span> to return to the flows list.
13. Once created, you may need to re-edit your flow, toggle it to inactive, or delete it.
### Edit a Flow
1. Navigate to the desired flow.
2. Click <span mi btn muted>edit</span> in the flow page header and make reconfigurations as desired.
3. Click <span mi btn>done</span> to confirm.
### Toggle a Flow to Inactive
1. Navigate to **Settings > Flows** and click <span mi icon>more_vert</span> on the desired flow.
2. Click **<span mi icon>check</span> Set Flow to Active** or **<span mi icon>block</span> Set Flow to Inactive**.
### Delete a Flow
1. Click <span mi icon>more_vert</span> on the desired flow to open its context menu.
2. Click <span mi icon dngr>delete</span> and a popup menu will appear. Click **Delete** to confirm.
Now that we know how to create and configure a flow, it's time to get a firmer understanding of the data chain.
## The Data Chain
<!--
<video title="The data chain" autoplay playsinline muted loop controls>
<source src="https://cdn.directus.io/docs/v9/" type="video/mp4" />
</video>
-->
Remember, each flow creates its own JSON object to store any data generated.
When the flow begins, four keys are appended to the data chain: `$trigger`, `$accountability`, `$env`, and `$last`.
Then, as each operation runs, it has access to this data chain. Once an operation finishes, its data is appended under
its `<operationKey>`. When the operation doesn't generate data, `null` is appended under its key.
The following is a highly generic example of a data chain.
```json
{
"$trigger": {
// Contains data generated by the flow's trigger.
// This could include headers, access tokens, payloads, etc.
// Every data chain has a $trigger key.
},
"$accountability": {
// Provides details on who/what started the flow.
// This could include user's id, role, ip address, etc...
// Every data chain has an $accountability key.
},
"$env": {
// Environment variables allowed in `FLOWS_ENV_ALLOW_LIST`.
// This could include PUBLIC_URL, PORT, etc...
// Every data chain has an $env key.
},
"$last": {
// The value appended under $last changes after each operation.
// It stores data of the last operation that executed in the flow.
// That way, you don't have to remember the previous operation's unique keyname.
// It's a handy little convenience tool!
// Every data chain has a $last key.
},
"operationKey1": "A value", // The data (if any) generated by the first operation.
"operationKey2": {
"nestedKey": ["nested val", "nested val 2"] // It will be common to have nested JSON data.
},
"operationKey3": null // A null value is appended if no data generated.
}
```
As you can see, the example above doesn't have any substantial data inside each key. In reality, there's going to be a
lot of data and it will always be slightly different, based on your flow's unique configuration. During configuration
and debugging, you'll need to use a tool like [The Log](#logs) to view your data chain and make sure each operation is
accessing and generating data as you intended.
::: tip
In our examples, we are using generic _placeholders_ for operation keys, like `<operationKey>`, which might look funny
to low-code users. In practice, operation keys will actually have unique and descriptive names, like `send_email_7538`.
:::
::: tip
Remember, `$trigger`, `$accountability`, and `$last` begin with `$`, but not `operationKeys`.
:::
## Data Chain Variables
<!--
<video title="Use data chain Keys as Variables" autoplay playsinline muted loop controls>
<source src="https://cdn.directus.io/docs/v9/" type="video/mp4" />
</video>
-->
While [configuring your operations](#configure-an-operation), you can use keys from the data chain as variables to
access data. Simply wrap the variable with quotes and _double mustaches_. For example:
```json
"{{ $accountability }}"
```
will get the data nested under the `$accountability` key, producing something like this:
```json
{
"user": "4b11492d-631d-4b8a-bca7-2beasdfadf58b",
"role": "12c79228-5361-4905-929b-d69d3w46trs6b",
"admin": true,
"app": true,
"ip": "127.0.0.1",
"userAgent": "Amazon CloudFront"
}
```
You can mix your own hard-coded JSON alongside variables.\
You can also use dot-notation and array indexing to retrieve sub-nested values.
```json
{
"key0": "a hard-coded value",
"key1": "{{ $trigger.payload }}",
"key2": "{{ operationKey.payload.friend_list[0] }}"
}
```
You **cannot** pass any type of computation using double-moustache syntax.
```json
{
"key": "{{ 2 + 2 }}",
"key2": "{{ $trigger.payload.toLowerCase() }}"
}
```
::: tip
To perform computations on flow data, use the [script operation](/app/flows/operations#script) or a
[webhook](/app/flows/operations#webhook).
:::
Certain operations use dropdowns, toggles, checkboxes, and other input options. However, you can bypass this entirely to
input raw values directly with [Toggle to Raw Editor](#configure-an-operation). You can use double-moustache syntax to
access data dynamically in these input options as well.
<!--
<video autoplay playsinline muted loop controls title="">
<source src="https://cdn.directus.io/docs/v9/configuration/flows/flows/" type="video/mp4" />
</video>
-->
## Logs
<video autoplay playsinline muted loop controls title="">
<source src="https://cdn.directus.io/docs/v9/configuration/flows/flows/flows-20220603A/logs-20220603A.mp4" type="video/mp4" />
</video>
Accessible from the sidebar, logs store information for each flow execution. Each log will display information from
triggers as well as each operation in the flow. To access a flow's logs, follow these steps.
1. Navigate to **Settings > Flows** and click the desired flow.
2. Click **<span mi icon prmry>fact_check</span> Logs** in the sidebar. A side drawer will open, displaying the flow's
logs.
3. Click a log and another side drawer will open, allowing you to peer through its data.
4. When finished, click <span mi btn muted>close</span> to close the drawer.
Logs are not a 1:1 mapping to the data chain. Each trigger and operation gets its own dropdown, which stores its
relevant data. Here's what you'll get from each of these:
**Trigger**
- **Options** — The values you input when you configured the trigger.\
_(These configuration options are not stored on the data chain)_.
- **Payload** — Displays the data appended under `$trigger`.
- **Accountability** — Displays data appended under `$accountability`.
Note that `$accountability` is not nested under the `$trigger` key. However, it is listed under the Trigger in the Log
because `$accountability` is generated by the trigger.
**`<OperationKey>`**
- **Options** — The values you input when you configured the operation.\
_(These configuration options are not stored on the data chain)_.
- **Payload** — Displays the data appended under this `<operationKey>`.
Remember, the [Log to Console](/app/flows/operations#log-to-console) operation is a key debugging tool. It does not
append data to the data chain. You will view your log message under **Options**. Therefore, anything you log will always
be displayed as nested under a `message` key. For example, if you decide to log `"The last operation was a success"`, it
will be displayed as:
```
{
"message": "The last operation was a success"
}
```
::: warning Logs are stored in the database
Keep in mind that if you've configured a flow to track logs, all this information is stored in the database. You may
need to periodically delete this data.
:::
::: tip Where is `$last`?
You may notice `$last` is not in the Logs. Remember, `$last` constantly updates to store the data of the most recently
executed operation. The log shows the results of the entire flow. Therefore `$last` would simply be the very last
operation in the flow.
:::
::: tip More on Debugging
You may find a tool like [Postman](https://www.postman.com/) quite helpful for viewing data and debugging flows.
:::

View File

@@ -1,458 +0,0 @@
---
description:
Operations are the individual actions in a flow. They enable you to do things like manage data within Directus,
transform the flow's data, send information off to outside services, set conditional logic, trigger other flows, and
beyond!
readTime: 5 min read
---
# Operations
> Operations are the individual actions in a flow. They enable you to do things like manage data within Directus,
> transform the flow's data, send information off to outside services, set conditional logic, trigger other flows, _and
> beyond!_
::: tip Before You Begin
On this page, we'll explain what each operation does, the value it appends to the data chain, how to make use of its
configuration options, as well as any well as other relevant details. We will assume you have read the documentation on
[Flows](/app/flows) and [Triggers](/app/flows/triggers).
:::
## Condition
![Condition](https://cdn.directus.io/docs/v9/configuration/flows/operations/operations-20220603A/condition-20220603A.webp)
A **Condition** operation lets you choose a **success path** or **failure path** by validating data passed into it with
[Filter Rules](/reference/filter-rules).
**Options**
- **Condition Rules** — Create conditions with [Filter Rules](/reference/filter-rules).
**Payload**
This operation does not generate data. If the filter rule is configured properly, it will append a `null` value on its
`operationKey`, regardless of if the condition was met or not. However, if the filter rule is misconfigured, it will
append an array containing an object you can use to help debug the misconfiguration.
**More Details**
::: warning
When using an [Event Hook](/app/flows/triggers#event-hook) configured to be **Filter (Blocking)**, if your flow ends
with a condition that executes with a `reject` path, it will cancel your database transaction.
:::
## Run Script
<!--
<video autoplay playsinline muted loop controls title="Run Script">
<source src="" type="video/mp4" />
</video>
-->
This operation lets you add a custom script using vanilla JavaScript or TypeScript. The script will be executed securely
in an isolated sandbox. No interactions take place between the sandbox and the host except for sharing input and output
values. This means, for example, no access to the file system and no ability to do network requests.
**Options**
The operation provides a default function template. The _optional_ `data` parameter lets you pass in the data chain as
an argument.
**Payload**
The function's `return` value will be appended under its `<operationKey>`.
**More Details**
As an example, let's say you have this function in a script operation, named `myScript`.
```JSON
// A key from the data chain
{
"previousOperation": {
"value": 5
}
}
```
Then you add the following logic via Run Script.
```TypeScript
// Your function in the myScript operation
module.exports = function(data) {
return {
timesTwo: data.previousOperation.value * 2
}
}
```
The returned value will be appended under the `myScript` operation key.
```JSON
{
"previousOperation": {
"value": 5
},
"myScript": {
"timesTwo": 10
}
}
```
::: tip
Make sure your `return` value is valid JSON.
:::
::: tip Throwing Errors
If you throw an error in a **Run Script** operation, it will immediately break your flow chain and stop execution of
subsequent flows. If you used a ["Blocking" Event hook](/app/flows/triggers#event-hook), throwing an error will cancel
the original event transaction to the database.
:::
::: tip Node Modules
To prevent unauthorized access to the underlying server, node modules can't be used in the **Run Script** operation. If
you require a third party library for your custom script, you can create a custom
[operation extension](/extensions/operations) instead.
:::
## Create Data
![Create Data](https://cdn.directus.io/docs/v9/configuration/flows/operations/operations-20220603A/create-data-20220603A.webp)
This operation creates item(s) in a collection.
**Options**
- **Collection** — Select the collection you'd like to create items in.
- **Permissions** — Select the scope of permissions used for this operation.
- **Emit Events** — Toggle whether the event is emitted.
- **Payload** — Defines the payload to create item(s) in a collection.
**Payload**
An array with the ID(s) of all items created will be appended under its `<operationKey>`.
**More Details**
::: warning
**Emit Events** toggles the event's _visibility_ throughout Directus. For example, if toggled on, this operation will
trigger relevant event hooks in other flows or custom extensions. If toggled off, the operation will not trigger other
event hooks. Imagine a situation where you have a flow being triggered by `<collection>.items.create` which contains an
operation that then tries to create another item in that `<collection>`. This would throw an infinite loop where the
operation triggers its own flow, endlessly. However, if you toggle **Emit Events** off, then this operation no longer
triggers other event hooks.
:::
::: tip
To learn about payload requirements when creating an item, see [API Reference > Items](/reference/items).
:::
## Delete Data
![Delete Data](https://cdn.directus.io/docs/v9/configuration/flows/operations/operations-20220603A/delete-data-20220603A.webp)
This operation deletes item(s) from a collection.
**Options**
- **Collection** — Select the collection you'd like to delete items from.
- **Permissions** — Set the scope of permissions used for this operation.
- **Emit Events** — Toggle whether the event is emitted.
- **IDs** — Set Item IDs and press enter to confirm. Click the ID to remove.
- **Query** — Select items to delete with a query. To learn more, see [Filter Rules](/reference/filter-rules).
**Payload**
An array with the ID(s) of all items deleted will be appended under its `<operationKey>`.
**More Details**
::: warning
**Emit Events** toggles the event's _visibility_ throughout Directus. For example, if togged on, this operation will
trigger relevant event hooks in other flows or custom extensions. If toggled off, the operation will not trigger other
event hooks. Imagine a situation where you have a flow being triggered by `<collection>.items.delete` which contains an
operation that then tries to delete another item in that `<collection>`. This would throw an infinite loop where the
operation triggers its own flow, endlessly. However, if you toggle **Emit Events** off, then this operation no longer
triggers other event hooks.
:::
## Read Data
![Read Data](https://cdn.directus.io/docs/v9/configuration/flows/operations/operations-20220603A/read-data-20220603A.webp)
This operation reads item(s) from a collection and adds them onto the data chain. You may select Items by their ID or by
running a query.
**Options**
- **Permissions** — Set the scope of permissions used for this operation.
- **Collections** — Select the collection you'd like to read items from.
- **IDs** — Input the ID for items you wish to read and press enter. Click the ID to remove.
- **Query** — Select the items with a query. To learn more, see [Filter Rules](/reference/filter-rules).
- **Emit Events** — Toggle whether the event is emitted.
**Payload**
An array containing all items read will be appended under its `<operationKey>`.
**More Details**
::: warning
**Emit Events** toggles the event's _visibility_ throughout Directus. For example, if togged on, this operation will
trigger relevant event hooks in other flows or custom extensions. If toggled off, the operation will not trigger other
event hooks. Imagine a situation where you have a flow being triggered by `<collection>.items.read` which contains an
operation that then tries to read another item in that `<collection>`. This would throw an infinite loop where the
operation triggers its own flow, endlessly. However, if you toggle **Emit Events** off, then this operation no longer
triggers other event hooks.
:::
## Update Data
![Update Data](https://cdn.directus.io/docs/v9/configuration/flows/operations/operations-20220603A/update-data-20220603A.webp)
This operation updates item(s) in a collection. You may select item(s) to update by their ID or by running a query.
**Options**
- **Collection** — Select the collection on which you'd like to update items in.
- **Permissions** — Set the role that this operation will inherit permissions from.
- **Emit Events** — Toggle whether the event is emitted.
- **IDs** — Input the ID for Item(s) you wish to update and press enter. Click the ID to remove.
- **Payload** — Update Items in a collection, using one of the following formats:
- Single object with data, to update items specified in **IDs** or **Query** to the same values.
- Single object with keys and data, to update multiple items to the same values.
- Array of objects with data including primary keys, to update multiple items to different values.
- To learn more, see [API > Items](/reference/items).
- **Query** — Select items to update with a query. To learn more, see [Filter Rules](/reference/filter-rules).
**Payload**
An array containing all items updated will be appended under its `<operationKey>`.
**More Details**
::: warning
**Emit Events** toggles the event's _visibility_ throughout Directus. For example, if togged on, this operation will
trigger relevant event hooks in other flows or custom extensions. If toggled off, the operation will not trigger other
event hooks. Imagine a situation where you have a flow being triggered by `<collection>.items.update` which contains an
operation that then tries to update another item in that `<collection>`. This would throw an infinite loop where the
operation triggers its own flow, endlessly updating items. However, if you toggle **Emit Events** off, then this
operation no longer triggers other event hooks.
:::
::: tip
To learn about `payload` requirements when updating an item, see [API Reference > Items](/reference/items).
:::
## JSON Web Token (JWT)
This operation lets you sign and verify a JSON Web Token (JWT) using the
[`jsonwebtoken`](https://www.npmjs.com/package/jsonwebtoken) package.
**Options**
- **Operation** — Select the operation you'd like to perform.
- **Payload** — The string or JSON payload to sign.
- **Token** — The JSON Web Token to verify or decode.
- **Secret** — The secret key used to sign or verify a token.
- **Options** — The options object provided to the operation. For the list of available options, see the
[documentation of `jsonwebtoken`](https://www.npmjs.com/package/jsonwebtoken#usage).
**Payload**
Based on the operation selected, a JSON Web Token (JWT) or `payload` will be appended under its `<operationKey>`.
## Log to Console
![Log to Console](https://cdn.directus.io/docs/v9/configuration/flows/operations/operations-20220603A/log-to-console-20220603A.webp)
This operation outputs information to the server-side console as well as the [Logs](/app/flows#logs) within the Data
Studio. This is a key tool for troubleshooting flow configuration. A Log operation's key will have a null value on the
data chain.
**Options**
- **Message** — Sets a [log message](/app/flows#logs).
**Payload**
This operation does not generate data for the data chain as its messages are for debugging and troubleshooting. It will
append a `null` value on the `operationKey`.
**More Details**
For more details, see the section on [Logs](/app/flows#logs).
## Send Email
![Send Email](https://cdn.directus.io/docs/v9/configuration/flows/operations/operations-20220603A/send-email-20220603A.webp)
This operation sends off emails.
**Options**
- **To** — Sets the email addresses. Hit `↵` `Enter` (PC) or `return` (Mac) to save the email. Click on a pill to remove
it.
- **Subject** — Set the subject line.
- **Body** — Use a Markdown or WYSIWYG editor to create the email body.
**Payload**
This operation does not generate data for the data chain. It will append a `null` value on the `operationKey`.
**More Details**
::: tip Batch Emails
You can input an array of emails in the `To` input option to send off multiple emails.
:::
::: tip
If you are testing out this operation from `localhost:8080`, be sure to check your spam box, because your email provider
may send it there automatically.
:::
## Send Notification
![Send Notification](https://cdn.directus.io/docs/v9/configuration/flows/operations/operations-20220603A/send-notification-20220603A.webp)
This operation pushes notifications to Directus Users. If the operation executes successfully, a list containing the IDs
of all sent notifications generated is appended under this operation's key.
**Options**
- **Users** — Define a user by their UUID. Hit `↵` `Enter` (PC) or `return` (Mac) to save it. Click on a pill to remove
it.
- **Permissions** — Define the role that this operation will inherit permissions from.
- **Title** — Set the title of the notification.
- **Message** — Set the main body of the notification.
**Payload**
This operation does not generate data. It will append a `null` value on its `operationKey`.
**More Details**
::: tip Batch Notifications
You can input an array of UUIDs in the `To` input option to send off multiple notifications.
:::
## Webhook / Request URL
![Webhook / Request URL](https://cdn.directus.io/docs/v9/configuration/flows/operations/operations-20220603A/webhook-20220603A.webp)
This operation makes a request to another URL.
**Options**
- **Method** — Choose to make a GET, POST, PATCH, DELETE, or other type of request.
- **URL** — Define the URL to send the request to.
- **Headers** — Create a new `header:value` to pass along with the request.
- **Request Body** — Set the request body's data.
**Payload**
When an operation completes successfully, the `response` is appended under its `<operationKey>`.
## Sleep
![Sleep](https://cdn.directus.io/docs/v9/configuration/flows/operations/operations-20220603A/sleep-20220603A.webp)
This operation creates a delay in the Flow for a given amount of milliseconds, then continues to the next operation.
**Options**
- **Milliseconds** — Define the number of milliseconds to sleep.
**Payload**
This operation does not generate data. It will append a `null` value on its `operationKey`.
## Transform Payload
![Transform Payload](https://cdn.directus.io/docs/v9/configuration/flows/operations/operations-20220603A/transform-payload-20220603A.webp)
This operation lets you custom define your own JSON payload for use in subsequent operations. This enables you to take
multiple sources of data and "tidy them up" into a single payload.
**Options**
- **JSON** — Define JSON to insert into the data chain.
**Payload**
When an operation completes successfully, the value you defined under the **JSON** configuration operation is appended
onto its `operationKey`.
**More Details**
When does the Transform Payload operation come in handy? Let's say you need to create a payload with data from a
`users_collection`, `widgets_collection` and some 3rd party resource which processes the data. You can add a
[Read Data](#read-data) operation for `collection_a`, another Read Data operation for `collection_b`, and a
[Webhook](#webhook--request-url) operation for the third party resource.
Then you could stitch together all this data to create a new JSON object, like so:
```
{
"note": "You can add a hard-coded value!",
"name": "{{users_collection.username}}",
"widget_id": "{{widgets_collection.id}}",
"results": "{{webhookKey.subnestedValue}}"
}
```
## Trigger Flow
![Trigger Flow](https://cdn.directus.io/docs/v9/configuration/flows/operations/operations-20220603A/trigger-flow-20220603A.webp)
This operation starts another flow and _(optionally)_ passes data into it. It should be used in combination with the
[Another Flow](/app/flows/triggers#another-flow) trigger.
**Options**
- **Flow** — Define a flow by its primary key UUID.
- **Payload** — Defines a JSON `payload` to pass into `$trigger` on the flow it triggered.
**Payload**
If you've configured a **Response Body** in the trigger of the other flow, this will be appended under this
`operationKey`. If no **Response Body** is configured, `null` is appended under this `operationKey`.
**More Details**
::: tip Flows for-loops
If you pass an array to the other flow, the other flow will run once for each item in the array.
:::

View File

@@ -1,207 +0,0 @@
---
description:
A Trigger initiates a Flow on some internal or external event, such as an in-app activity, incoming webhook, cron job,
execution of Operation(s) from other Flows, and beyond!
readTime: 3 min read
---
# Triggers
> A trigger defines the event that starts your flow. This could be from an internal or external activity, such as
> changes to data, logins, errors, incoming webhooks, cron jobs, operations from other flows, or even the click of a
> button within the Data Studio.
::: tip Before You Begin
Please be sure to read the overview documentation on [Flows](/app/flows).
:::
## Event Hook
![Event Hooks](https://cdn.directus.io/docs/v9/configuration/flows/triggers/triggers-20220603A/event-hook-20220602A.webp)
Event Hooks are triggered by events within the platform. The logic is based on [Custom API Hooks](/extensions/hooks).
Any data generated by the event will be nested in the `$trigger`.
- **Type** — Choose the type of event hook:
- [Filter (Blocking)](#filters) — This pauses the transaction and passes the `payload` to your flow, allowing you to
validate or transform `payload` or even prevent the event transaction.
- [Action (Non-Blocking)](#actions) — Actions do not block the event. A non-blocking action is mostly useful for
completing tasks in response to an event, without modifying the event or slowing the API.
- **Scope** — Set the types of events that trip this trigger.
- **Collections** — Set the collections _whose events_ trip this trigger.
- **Response Body** — This is optional. Its also only for **Filter (Blocking)** events. Defines data to replace the
event's original `payload`. Choose to return:
- **Data of Last Operation** — Replaces event `payload` with value from `$last`.
- **All Data** — Replaces event `payload` with the entire data chain.
- **Other** — Replaces event `payload` with value from an `<operationKey>`.
::: tip Events with Multiple Items
If you create items in a single request, the Flow will trigger once for each and every item. If you create three items,
the flow runs three separate times. On each run, just one item will be in the payload.
:::
### Filters
<!-- ![Blocking Events](image.webp) -->
A Filter will halt the event transaction, copy the event `payload` into the flow, let the whole flow run,
**_optionally_** replace the original `payload` by configuring **Response Body**, then resume the event's transaction to
the database.
For example, let's say you configure the scope to be `item.create`.
- A request to create an item is sent to Directus.
- The create item event halts.
- The event's `payload` is copied into the `$trigger` of the flow.
- The flow runs.
- _Optional:_ If you defined a **Response Body**, this replaces the event's `payload`.
- The event transaction is committed or cancelled, based on your flow's logic.
::: tip Response Body
If no **Response Body** is configured, the original payload will not be modified, but you'd still have the ability to
cancel the transaction.
:::
::: tip Cancelling Transactions
To completely cancel a transaction, you'll need to throw an error within a
[script operation](/app/flows/operations#script) or end the flow on a [failure path](/app/flows#control-flow).
:::
### Actions
<!-- ![Non-Blocking Events](image.webp) -->
An Action lets the event commit its transaction to the database without waiting for the flow to finish. Therefore,
Actions give you a more performant API, but you have no ability to validate or transform the payload before the
transaction is committed to the database. However, a copy of the `payload` is still added into `$trigger` to use as
desired.
For example, let's imagine once more that you configure the scope to be `item.create`.
- A request to create an item is sent to Directus.
- The create item event is transacted.
- The event's `payload` is copied into the `$trigger` of the flow.
- The flow runs.
To recap, Filters let you modify the original `payload` of an event but block the API until the flow finishes. Actions
still provide the `payload` to the flow, but don't let you modify it before transaction; so you get a faster API but no
control over the event transaction.
## Webhook
![Webhook](https://cdn.directus.io/docs/v9/configuration/flows/triggers/triggers-20220603A/webhook-20220602A.webp)
Triggers on an incoming HTTP request to: `/flows/trigger/:this-webhook-trigger-id` which you can copy from the webhook
trigger panel.
- **Method** — Choose whether the flow will be triggered by a `GET` or `POST` request from the dropdown.
- **Asynchronous** — Toggle whether or not the trigger responds asynchronously.
- **Response Body** — Optional. Defines data to return as a response. Choose to return:
- **Data of Last Operation** — Responds with value from `$last`.
- **All Data** — Responds with the entire data chain.
- **Other** — Responds with value from an `<operationKey>`.
- **Cache** — Choose whether responses to `GET` requests should be stored and served from cache or the cache should be
bypassed.
::: tip Response Body
If no data is defined in **Response Body**, the response is empty.
:::
::: tip Asynchronous
If **Asynchronous** is enabled, the flows system will immediately resolve/respond to the request without returning a
**Response Body**, which frees up the API immediately. If it is disabled, the flows system will wait for the flow to
finish and return whatever value is in **Response Body**. This slows the API, but lets you send data.
:::
## Schedule (CRON)
![Schedule a Cron Job](https://cdn.directus.io/docs/v9/configuration/flows/triggers/triggers-20220603A/cron-20220602A.webp)
This trigger enables you to create data at scheduled intervals, via
[6-point cron job syntax](https://github.com/node-schedule/node-schedule#cron-style-scheduling).
- **Interval** — Set the cron job interval to schedule when the flow triggers.
**Syntax Explanation**
```
┌────────────── second (0-59)
│ ┌──────────── minute (0-59)
│ │ ┌────────── hour (0-23)
│ │ │ ┌──────── day of month (1-31)
│ │ │ │ ┌────── month (1-12)
│ │ │ │ │ ┌──── day of week (0-7)
│ │ │ │ │ │
│ │ │ │ │ │
* * * * * *
```
## Another Flow
![Another Flow](https://cdn.directus.io/docs/v9/configuration/flows/triggers/triggers-20220603A/another-flow-20220602A.webp)
This trigger executes by the [trigger flow](/app/flows/operations#another-flow) operation, allowing you to chain flows.
- **Response Body** — Optional. Defines data to return and append under the `<operationKey>` of
[trigger flow](/app/flows/operations#another-flow) operation that tripped the trigger. Choose to return:
- **Data of Last Operation** — Responds with value from `$last`.
- **All Data** — Responds with the entire data chain.
- **Other** — Responds with value from an `<operationKey>`.
::: tip Flows for-loops
If you pass an array to this trigger, it will run once for each item in the array.
:::
## Manual
![A pane is open in front of a Flow. It shows various options detailed below.](https://cdn.directus.io/docs/v9/configuration/flows/triggers-20230227A/manual-20230227A.webp)
This Trigger starts your flow on a manual click of a button within the Data Studio. When you use this trigger, a
**Flows** menu containing a button will appear in the sidebar of the specified collection page(s) and/or its item pages.
The manual trigger must take in item ID(s) before you can click it. So on the collection page, the button will be
_grayed out_ until you select some number of items. From the item page, the current item's ID is passed in
automatically. These item IDs are passed in to `$trigger`.
- **Collections** — Choose the Collection(s) to add the button to.
- **Location** — Choose to display the button on the [Item Page](/user-guide/content-module/content#item-page),
[Collection Page](/user-guide/content-module/content#collection-page), or both.
- **Asynchronous** — Toggle whether the Flow executes asynchronously. If enabled, you can immediately trigger the flow
again. If not, you must wait for the flow to complete to use it again.
- **Collection Page (Requires Selection)** — Toggle whether a selection is required in the Collection Page to trigger.
- **Require Confirmation** - Toggle whether a confirmation dialog will be shown before the flow is executed.
After the operation runs, a toast notification will appear in your
[sidebar](/user-guide/overview/data-studio-app#4-sidebar) indicating whether the flow ran successfully.
### Confirmation Dialog
![A modal is shown reading "Please provide alert information" with two text fields - one for text and one for content.](https://cdn.directus.io/docs/v9/configuration/flows/triggers-20230227A/manual-confirmation-dialog-20230227A.webp)
If enabled, a confirmation dialog will be shown in a modal before the flow is executed. There are further options to set
up a confirmation dialog.
- **Confirmation Description** - Text shown at the top of the modal.
- **Confirmation Input Fields** - Set up one or more inputs to be filled by users before executing the flow.
![A pane is open in front of a Flow. It shows various options detailed below.](https://cdn.directus.io/docs/v9/configuration/flows/triggers-20230227A/manual-input-settings-20230227A.webp)
Each input field can have its own data type, interface, and display options. Some convenience options are also provided
to immediately alter the user input (such as trimming whitespace and slugifying text).
Data provided by users when triggering a manual flow with a confirmation dialog will be accessible in `$trigger.body` in
the data chain.

View File

@@ -1,99 +0,0 @@
---
description:
Presets store the state of a Collection Page. They can be used to set layout defaults or define bookmarks to specific
datasets.
readTime: 3 min read
---
# Presets
> Presets are items which store the state of a Collection Page. This allows you to set layout defaults or define
> bookmarks to specific datasets.
::: tip Before You Begin
We recommend you read through the [Quickstart Guide](/getting-started/quickstart) to get an overview of the platform
first, then see our guide on the [Collection Page](/user-guide/content-module/content/collections) so you're familiar
with its features and functionalities.
:::
::: tip Learn More
To manage Presets and Bookmarks programmatically, see our [API guide on Presets](/reference/system/presets).
:::
Remember, a [Collection Page](/user-guide/content-module/content/collections) enables you to customize how its items are
presented. That is, it lets you sort, search, or filter items and even change
[Layouts](/user-guide/content-module/layouts). In some cases, you may need to apply the same display adjustments again
and again. Presets save these adjustments, like a snapshot. You can create presets for all project collections, as well
as `directus_activity`, `directus_files` and `directus_users`.
Admins can access and manage all presets under **Settings > Presets and Bookmarks**.
## Create a Preset
<video title="Create a Preset" autoplay playsinline muted loop controls>
<source src="https://cdn.directus.io/docs/v9/configuration/presets-bookmarks/presets-bookmarks-20220819/create-a-preset-20220819B.mp4" type="video/mp4" />
</video>
There are two types or presets, Defaults and Bookmarks.
A _Default_ determines how a user will initially view a Collection Page. When a user makes adjustments to a given
Collection Page, these are automatically saved as the Default, so the adjustments remain even if the user navigates away
and then returns.
A _Bookmark_ creates another custom display of the Collection Page, which can be accessed any time on the
[Navigation Bar](/user-guide/overview/data-studio-app#_2-navigation-bar). Users can also
[create a Bookmark](/user-guide/content-module/content/collections#create-a-bookmark) for personal use from within the
Content Module.
The method to create a preset shown here is for Admin use only. For Administrators, the process to create either a
Default or a Bookmark is almost exactly the same. The key difference is that if you set a value for **Name**, the preset
becomes a Bookmark. If **Name** is left blank, the preset will be a Default. To create a preset, follow these steps.
1. Navigate to **Settings > Presets & Bookmarks**.
2. Click <span mi btn>add</span> in the header.
3. Configure your preset item's values as desired:
- **Collection** — Sets the collection this preset will apply to. Notice the layout preview below, which populates
with live data from the selected collection.
- **Scope** — Defines which users have access to this preset.
- **Layout** — Selects a Layout for the preset, which is adjusted from the Sidebar.
- **Name** — Sets a name, which determines if the preset is a Default or Bookmark. Note that this field supports
[Translation Strings](/user-guide/content-module/translation-strings.html).
4. Scroll down to the **Layout Preview** section and make any other adjustments or configurations as desired. Each
preset saves all of the information needed to recreate this Layout Preview, just as it is shown.
5. Click <span mi btn>check</span> to confirm.
::: tip Preset Priority
Multiple Defaults can be created for the same Collection Page for the same user. When this happens, the preset priority
is: User, then Role, then Global.
:::
## Edit a Preset
<video title="Edit a Preset" autoplay playsinline muted loop controls>
<source src="https://cdn.directus.io/docs/v9/configuration/presets-bookmarks/presets-bookmarks-20220819/edit-a-preset-20220819A.mp4" type="video/mp4" />
</video>
1. Navigate to **Settings > Presets & Bookmarks > [preset]**.
2. Reconfigure your preset as desired.
3. Click <span mi btn>check</span> to confirm.
## Delete a Preset
<video title="Delete a Preset" autoplay playsinline muted loop controls>
<source src="https://cdn.directus.io/docs/v9/configuration/presets-bookmarks/presets-bookmarks-20220819/delete-a-preset-20220819A.mp4" type="video/mp4" />
</video>
1. Navigate to **Settings > Presets & Bookmarks > [preset]**.
2. Click <span mi btn dngr>delete</span> in the page header and a popup will appear. Click **Confirm**.
::: danger Irreversible Change
This action is permanent and cannot be undone. Please proceed with caution.
:::

View File

@@ -1,21 +0,0 @@
# Security
## HTTPS
## Password Salting/Hashing/Algorithm & Password Policy
## Static Tokens
## Encrypted Fields & Encrypted Database
## Private File Links
## MFA (Required) & SSO
## Granular Access Control & IP WHite-Listing & App Access
## Rate Limiting
## Encrypted
## Parameterized Database Queries

View File

@@ -1,21 +0,0 @@
---
layout: doc
prev: false
next: false
type: 'blog-post'
---
<script setup>
import { useData } from 'vitepress'
import Badge from '@/components/Badge.vue'
import PostMeta from '@/components/blog/PostMeta.vue'
const { params } = useData()
</script>
<Badge><a href="/blog/">Developer Blog</a></Badge>
<h1>{{ params.title }}</h1>
<PostMeta :params="params" />
<!-- @content -->

View File

@@ -1,36 +0,0 @@
import { readItems } from '@directus/sdk';
import { client } from '../.vitepress/lib/directus.js';
export default {
async paths() {
const articles = (
await client.request(
readItems('developer_articles', {
fields: [
'*',
{ author: ['first_name', 'last_name', 'avatar', 'title'] },
{ tags: [{ docs_tags_id: ['title', 'slug', 'type'] }] },
],
}),
)
).map((article) => ({
params: {
slug: article.slug,
title: article.title,
date_published: article.date_published,
summary: article.summary,
image: article.image,
author: article.author,
tags: article.tags.map((tag) => ({
title: tag.docs_tags_id.title,
slug: tag.docs_tags_id.slug,
type: tag.docs_tags_id.type,
})),
contributors: article.contributors,
},
content: article.content,
}));
return articles;
},
};

View File

@@ -1,108 +0,0 @@
---
description: Our guest author program invites authors to join and get paid to write tutorials for the Directus community.
---
<script setup>
import Badge from '@/components/Badge.vue';
</script>
<Badge><a href="/blog/">Developer Blog</a></Badge>
# Guest Authors
Our guest author program invites authors to join and get paid to write tutorials for the Directus community. We accept
new guest authors into the program periodically via application form.
Once you have been invited to join the program, you will be able to see all of the posts we want created and can
indicate which tutorials you are interested in writing.
<Card
title="Apply now!"
h="2"
text="Next application period closes on December 1st 2024."
url="https://directus.typeform.com/to/nUgCrJ5J" />
## How Does It Work?
1. Submit an application via form to become a guest author.
2. Our team reviews applications periodically and admits authors based on the current technical needs of our author
profiles.
3. We onboard authors and share more about the tutorial assigning process, which happens in GitHub.
4. We release tutorials wed like to see written and assign a budget per tutorial within each tutorials details.
5. Authors say which tutorials theyd like to work on and we assign them on a monthly basis.
6. Once a tutorial is assigned to a guest author, they manage, write and submit it via GitHub for a review.
7. Once reviewed and accepted, the guest authors upload their tutorial into our Directus backend ready for the Directus
team to publish it.
This process ensures that we work with you to create the best possible content, while also ensuring that the content
fits within our publication needs.
## Questions
:::details How much will I get paid?
We will indicate our budget for each content piece when we post it for consideration, typically these budgets will be
either $250 or $500 depending on the size and complexity of the piece.
:::
:::details Payment Methods
We require you to have a bank account in a country listed [here](https://help.bill.com/direct/s/article/360007172671).
:::
:::details Payment Process
At the end of each month, you will need to submit an invoice to us for the completed pieces. The invoice will be made
with 30 day payment terms so you should receive payment 30 days after the invoice is accepted.
:::
:::details Post Length
Were flexible, posts should strike a balance between being informative and concise.
:::
:::details Timeline
Once youve been accepted into the program, getting tutorials assigned, written and reviewed is done on a monthly basis.
:::
:::details Submitting Ideas
No, we are not currently accepting tutorial ideas outside of the requested content issues.
:::
:::details Language
We currently only accept contributions in English.
:::
:::details Content Sharing
Yes, so long as you add our post as the canonical source to each content post.
:::
:::details Content Ownership
You own your content, but you grant us unlimited license to post and promote your content.
:::
:::details Updating Tutorials
No, once a piece is ready for publishing, we consider it done, there is no expectation to keep it updated and if we do
want a piece updating, we will open a new issue and offer it out as a new piece of work, original authors have first
right of refusal.
:::
:::details Eligibility
You must be aged 18+ to join the program.

View File

@@ -1,13 +0,0 @@
---
layout: page
title: Blog
description: Project tutorials, tips & tricks, and best practices from the Directus team and community.
---
<script setup>
import BlogHero from '@/components/blog/BlogHero.vue';
import BlogIndex from '@/components/blog/BlogIndex.vue';
</script>
<BlogHero />
<BlogIndex />

View File

@@ -1,15 +0,0 @@
---
layout: page
title: Blog
description: Project tutorials, tips & tricks, and best practices from the Directus team and community.
---
<script setup>
import TagsIndex from '@/components/blog/TagsIndex.vue';
import BlogHero from '@/components/blog/BlogHero.vue';
import { useData } from 'vitepress';
const { params } = useData();
</script>
<BlogHero />
<TagsIndex :tag="params" />

View File

@@ -1,53 +0,0 @@
import { readItems } from '@directus/sdk';
import { client } from '../../.vitepress/lib/directus.js';
export default {
async paths() {
const tags = (
await client.request(
readItems('docs_tags', {
fields: [
'*',
{
developer_articles: [
{
developer_articles_id: [
'title',
'date_published',
'slug',
'image',
{ author: ['first_name', 'last_name', 'avatar', 'title'] },
'status',
],
},
],
},
],
}),
)
).map((tag) => ({
params: {
slug: tag.slug,
title: tag.title,
type: tag.type,
articles: tag.developer_articles
.map((article) => {
if (!article.developer_articles_id) return;
if (article.developer_articles_id.status !== 'published') return;
return {
title: article.developer_articles_id.title,
slug: article.developer_articles_id.slug,
date_published: article.developer_articles_id.date_published,
image: article.developer_articles_id.image,
author: article.developer_articles_id.author,
};
})
.filter((article) => article)
.sort((a, b) => +new Date(b!.date_published) - +new Date(a!.date_published)),
},
}));
return tags;
},
};

View File

@@ -1,61 +0,0 @@
# Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making
participation in our project and our community a harassment-free experience for everyone, regardless of age, body size,
disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education,
socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or advances
- Trolling, insulting/derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or electronic address, without explicit permission
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take
appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits,
issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any
contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the
project or its community. Examples of representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed representative at an online or offline
event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at
info@directus.io. All complaints will be reviewed and investigated and will result in a response that is deemed
necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to
the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent
repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at
https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq

View File

@@ -1,120 +0,0 @@
---
description:
The core concepts behind Directus are simple, however the problems that must be solved to honor them can be remarkably
complex. We strive to design and engineer the most elegant solutions possible, so that our codebase remains
accessible.
readTime: 3 min read
---
# Codebase Overview
> **The core concepts behind Directus are simple, however the problems that must be solved to honor them can be
> remarkably complex.** We strive to design and engineer the most elegant solutions possible, so that our codebase
> remains accessible.
## Monorepo
The primary Directus repository is located at [`directus/directus`](https://github.com/directus/directus) and houses the
Data Studio (Vue.js 3 w/ Composition API), API (Node.js), API Specification (OpenAPI), and other smaller packages used
internally. Directus follows a monorepo design similar to React or Babel — this page will outline our monorepo's design
and structure.
## The API (`/api`)
Contains the Directus API (REST+GraphQL), written in Node.js. The source code is located in `/api/src` and the below
folders are inside there.
| Folder | Content |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/cli` | The CLI commands and matching functions that the `directus` package ships with. |
| `/controllers` | Route handler controllers for the endpoints in the API. |
| `/database` | Database manipulation abstraction, system migrations, and system data. Also where you'd find the main query runner. |
| `/errors` | Classes for the different errors the API is expected to throw. Used to set the HTTP status and error codes. |
| `/middleware` | Various (express) routing middleware. Includes things like cache-checker, authenticator, etc. |
| `/services` | Internal services. The main abstraction for interfacing with the data in the database. Both GraphQL and REST requests are "translated" to use these services as the main logic in the platform. |
| `/types` | TypeScript types that are shared between the different parts of the API. |
| `/utils` | Various utility functions. |
## The Data Studio App (`/app`)
Contains the Directus Data Studio App, written in Vue.js 3 w/ the Composition API.
| Folder | Content |
| --------- | ------------------------------------------------------- |
| `/public` | Assets that are included with the app, but not bundled. |
| `/src` | App source code. |
The source code is located in `/app/src` and the below folders are inside there.
| Folder | Content |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `/assets` | Files that are included within the app. Are bundled / optimized in the build step. |
| `/components` | (Base) components that are used across the platform. Contains "basic building blocks" like button, input, etc. |
| `/composables` | Reusable parts of reactive logic that can be used between Vue components. Includes things reactively calculating time from now, fetching a single item, etc. |
| `/directives` | Custom Vue directives (e.g. `v-tooltip`). |
| `/displays` | Components to display of data within the app. |
| `/interfaces` | The core-included interfaces that allow editing and viewing individual pieces of data. |
| `/lang` | Translations abstraction, and language files. The language yaml files are maintained through [Crowdin](https://locales.directus.io). |
| `/layouts` | The core-included layouts that change the way items are represented inside the collection view |
| `/modules` | The core-included modules that structure major parts the app. |
| `/operations` | Operations are steps in a flow |
| `/panels` | Panels display data in the insight dashboards |
| `/routes` | The routes in the app. Modules define their own routes, so this only includes the "system" things that don't belong to module, like login. |
| `/stores` | [Pinia](https://pinia.esm.dev) based stores used for global state tracking. |
| `/styles` | All general styles, css-vars, mixins and themes are stored inside here. Every component has their own component styles, these are just the global styles. |
| `/types` | TypeScript types that are shared between the different parts of the App. |
| `/utils` | Utility functions used in various parts of the app. |
| `/views` | The (two) main views used in the app: public / private. Also contains "internal" coupled components for those two views. |
::: tip Component Library
Directus comes shipped with it's own [Vue Component Library](https://components.directus.io) that you can use to enrich
your extensions or when developing locally. These components can be used in any of the "app extensions", including
Interfaces, Displays, Modules, Layouts, and Panels.
:::
## Packages (`/packages`)
The various sub-packages of the platform. Including the file-storage adapters, schema, specs, etc.
| Package Name | Description |
| ------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [@directus/composables](/packages/@directus/composables/) | Shared Vue composables for Directus use |
| [@directus/constants](/packages/@directus/constants/) | Shared constants for Directus |
| [create-directus-extension](https://github.com/directus/directus/tree/main/packages/create-directus-extension) | A small util that will scaffold a Directus extension |
| [create-directus-project](https://github.com/directus/directus/tree/main/packages/create-directus-project) | A small installer util that will create a directory, add boilerplate folders, and install Directus through npm |
| [@directus/env](/packages/@directus/env/) | Environment variable configuration extraction for Directus |
| [@directus/errors](/packages/@directus/errors/) | Utility functions to help creating and checking against Directus errors |
| [@directus/extensions-registry](/packages/@directus/extensions-registry/) | Abstraction for exploring Directus extensions on a package registry |
| [@directus/extensions-sdk](/packages/@directus/extensions-sdk/) | A toolkit to develop extensions to extend Directus |
| [@directus/extensions](/packages/@directus/extensions/) | Shared utilities, types and constants related to Directus extensions |
| [@directus/format-title](/packages/@directus/format-title/) | Custom formatter that converts any string into Title Case |
| [@directus/memory](/packages/@directus/memory/) | Memory / Redis abstraction for Directus |
| [@directus/pressure](/packages/@directus/pressure/) | Pressure based rate limiter |
| [@directus/release-notes-generator](https://github.com/directus/directus/tree/main/packages/release-notes-generator) | Package that generates release notes for Directus monorepo |
| [@directus/schema](https://github.com/directus/directus/tree/main/packages/schema) | Utility for extracting information about the database schema |
| [@directus/specs](https://github.com/directus/directus/tree/main/packages/specs) | OpenAPI Specification of the Directus API |
| [@directus/sdk](/packages/@directus/sdk/) | The JS SDK provides an intuitive interface for the Directus API from within a JavaScript-powered project (browsers and Node.js). |
| [@directus/storage-driver-azure](https://github.com/directus/directus/tree/main/packages/storage-driver-azure) | Azure file storage abstraction for `@directus/storage` |
| [@directus/storage-driver-cloudinary](https://github.com/directus/directus/tree/main/packages/storage-driver-cloudinary) | Cloudinary file storage abstraction for `@directus/storage` |
| [@directus/storage-driver-gcs](https://github.com/directus/directus/tree/main/packages/storage-driver-gcs) | GCS file storage abstraction for `@directus/storage` |
| [@directus/storage-driver-local](https://github.com/directus/directus/tree/main/packages/storage-driver-local) | Local file storage abstraction for `@directus/storage` |
| [@directus/storage-driver-s3](https://github.com/directus/directus/tree/main/packages/storage-driver-s3) | S3 file storage abstraction for `@directus/storage` |
| [@directus/storage-driver-supabase](https://github.com/directus/directus/tree/main/packages/storage-driver-supabase) | Supabase file storage driver for `@directus/storage` |
| [@directus/storage](https://github.com/directus/directus/tree/main/packages/storage) | Object storage abstraction layer for Directus |
| [@directus/stores](/packages/@directus/stores/) | Shared Directus Studio state for use in components, extensions, and the `@directus/app` routes. Stores are [Pinia](https://www.npmjs.com/package/pinia)-based stores. |
| [@directus/system-data](/packages/@directus/system-data/) | Definitions and types for Directus system collections |
| [@directus/tsconfig](https://github.com/directus/directus/tree/main/packages/tsconfig) | The shared TS Config files used by the projects in the Directus ecosystem. |
| [@directus/types](/packages/@directus/types/) | Shared types for Directus |
| [@directus/update-check](/packages/@directus/update-check/) | Check if an update is available for a given package |
| [@directus/utils](https://github.com/directus/directus/tree/main/packages/utils) | Utilities shared between the Directus packages |
## The JavaScript SDK (`/sdk`)
Contains the new Directus JavaScript SDK available as [@directus/sdk](/packages/@directus/sdk/) package.
## Tests (`/tests`)
Tests are maintained on a per-package base. This folder contains the platform-wide (end-to-end) tests. See
[Tests](/contributing/tests) for more information.

View File

@@ -1,61 +0,0 @@
---
description: Support Directus by being a leader in our online and in-person communities.
---
# Community Contributions
What makes Directus so special is our amazing community, which takes all of us to maintain and grow. All members of our
community, regardless of role, must abide by our [Code of Conduct](/contributing/code-of-conduct).
## Education
We want everyone to be as successful as possible in understanding what Directus is, the key concepts, how it relates to
their problem, and how to successfully implement it.
### Documentation
If you spot small errors or inconsistencies with our documentation, please feel free to open a Pull Request and label it
`Documentation`. Each page in the docs has an edit button at the bottom that will take you directly to an editing
interface in GitHub.
If you want to contribute guides, or create new documentation, please first open an issue on GitHub and wait for
response from the Core team - we are selective about what makes it into docs, but will have other ways to contribute
content in the future.
## Online Community
Our online community meets in two places - [Discord](https://directus.chat) and
[GitHub](https://github.com/directus/directus/discussions). GitHub is used for discussing feature requests and bugs, and
Discord is used for community discussion and requests for implementation help.
The Directus community is growing quickly, which also means there are more and more people with questions. Helping out
your fellow developers by providing answers on [Discord](https://directus.chat) is a great way to help the project.
Questions are opened in one of our help channels, and all are encouraged to respond.
::: info Feature Requests & Code Contributions
To learn more about how we use GitHub for Feature Requests and Code Contributions, check out our
[Code Contributions guide](/contributing/introduction).
:::
### Moderation
As well as the Core Team, we have a set of community moderators who support us in creating a kind and well-organized
community. The role of a moderator is to:
- Remove spam and other content that breaks our server rules.
- Remove server members who consistently break rules, or break certain rules where there is zero-tolerance.
- Move questions to help channels when posted elsewhere.
- Be model community members.
This is an elevated role that is generally given at discretion of the Core Team. If this is of interest to you, please
reach out to a member of the team.
## In-Person Communities
We have a truly global user base, and run a number of [regional User Groups and events](https://directus.io/events) to
bring together local Directus developers and users.
If you are interested in setting up your own, please reach out to a member of the core team. We can always start with a
one-off to gauge local interest.

View File

@@ -1,34 +0,0 @@
# Request a Feature
If you have a great idea for an improvement of the platform, or any other feedback, please make sure to open a new
Discussion on [our GitHub Discussions board](https://github.com/directus/directus/discussions).
## Opening a New Feature Request
New feature requests can be opened under the Feature Requests section of
[GitHub Discussions](https://github.com/directus/directus/discussions). Feature Requests follow a Request-for-Comment
(RFC) format that allows anybody to fully understand what you're proposing, and help speed up the review and triaging
process.
**While the form might seem intimidating at first, please do fill out all sections with as much detail as possible. The
less ambiguity around _how_ a feature should work, the easier it is to review, triage, and develop a feature.**
## Review Process
Once a draft feature request gets 15 or more upvotes, it will be moved to the "Open Feature Requests" category, at which
point the feature will be considered for inclusion into the project. Once we go over to active development for the
feature, a new Issue will be created to track the progress of implementation.
However, if a feature request doesn't reach at least 15 upvotes within 3 months of it being created, it will be closed
due to a lack of community interest.
## Implementing Accepted Feature Requests
It's important to note that Accepted Feature Requests are _not_ an ordered list of things that will be worked on next.
Even though we strive to prioritize what's being worked on based on community feedback, it's likely that there will be
times when a feature request is blocked by something else that's prioritized by the team. The implementation of accepted
feature request is triaged based on a combination of popularity, team availability, timelines, scope of the feature, and
overall project goals.
If you'd like to implement and open a PR for one of the open feature requests yourself, please leave a comment on the
feature request so we can create an Issue to assign and track accordingly.

View File

@@ -1,82 +0,0 @@
---
description: An introduction to the process, rules and guidelines that for all code contributions to Directus.
readTime: 6 min read
---
# Contributing
Heya! Welcome to Directus, and thank you for taking the time to contribute back to our project! ❤️
There are many ways in which you can contribute, but before you do, please make sure you're aware of
[the Code of Conduct](/contributing/code-of-conduct). Our contributors and maintainers work extremely hard to maintain
Directus as premium open-source software. Please be respectful of those efforts throughout our ecosystem. **Trolling,
harassing, insulting, or other unacceptable behavior by participants will not be tolerated.**
<Card
title="Code of Conduct"
text="We expect all of our contributors to know and follow this code."
url="/contributing/code-of-conduct"
/>
## Code Contributions
Check out our docs providing an [overview to the codebase](/contributing/codebase-overview), how to
[run Directus locally](/contributing/running-locally) for development, and how to
[write and run tests](/contributing/tests).
### Create a Pull Request
The whole Directus project is open source, and community code contributions are always welcome! Fixing issues or
implementing new features is an excellent way to contribute back to the platform.
Please do make sure you read through [our Pull Request Process](/contributing/pull-request-process) before you start!
That ensures you have the highest likelihood that your contribution will make it to the core codebase.
## Community Contributions
We love all kinds of [community contributions](/contributing/community), including making our online spaces as
respectful and productive as possible, running in-person user groups throughout the world, or producing educational
material to support developers in understanding and implementing Directus.
## Sponsorship & Advocacy
Directus requires significant resources to build and maintain, especially as our community rapidly grows. You can
support the project by becoming a [GitHub Sponsor](https://github.com/sponsors/directus), by providing testimonials and
reviews, and by sharing Directus with others.
## Other Ways To Support Directus
### Request a New Feature
If you have a great idea for an improvement of the platform, or any other feedback, please make sure to open a new
discussion on [our GitHub Discussions board](https://github.com/directus/directus/discussions). Feature requests are
reviewed and triaged according to our feature request workflow. For more information, please see our
[process for handling feature requests](/contributing/feature-request-process).
### Report a Bug
If you happen to run into a bug, please post an issue on
[our main GitHub Issue board](https://github.com/directus/directus/issues).
Please be as detailed as you can in the bug report. The more information available, the easier it is for other
contributors to help you find a solution. For example, it might be worth adding a schema snapshot file or a database
dump.
### Report Security Vulnerability
If you believe you have discovered a security vulnerability within a Directus product or service, please reach out to us
directly over email: [security@directus.io](mailto:security@directus.io). We will then open a
[GitHub Security Advisory](https://github.com/directus/directus/security/advisories) for tracking the fix.
We value the members of the independent security research community who uncover issues and work with our core team to
release patches. Our policy is to credit all researchers in the fix's release notes. In order to receive credit,
security researchers must follow responsible disclosure practices, including:
- They do not publish the vulnerability prior to the Directus team releasing a fix for it
- They do not divulge exact details of the issue, for example exploits or proof-of-concepts
### Translate the App
Every button, element, and other piece of text in the app is fully translatable, providing full internationalization for
the Directus platform. [Our Crowdin integration](https://locales.directus.io) makes creating and updating translations a
breeze. [Lear more about how we implement community translations into Directus](/contributing/translations).

View File

@@ -1,59 +0,0 @@
# Pull Request Process
Pull Requests (PRs) are a fantastic way to contribute back to the project. It's one of the fastest ways to see a bug fix
or new feature you care about land in the platform.
Reviewing and maintaining community submitted code is a very time consuming process. To ensure the core team can give
every PR the tender love and care it deserves, and not let valuable PRs go stale, we require that:
**Every pull request must be in answer to an existing open [Issue](https://github.com/directus/directus/issues).**
We use [GitHub Issues](https://github.com/directus/directus/issues) as a living to-do list of tasks to work on next.
Each PR resolving a related issue ensures that it aligns with the core team's planning and long-term goals. Please leave
a comment on the issue related to your PR so you can be marked as the assignee. This ensures no one else will
accidentally work on the same issue at the same time.
## Choosing What to Implement
We welcome PRs for any open [Issue](https://github.com/directus/directus/issues). Issues labeled
["Good First Issue"](https://github.com/directus/directus/issues?q=is:issue+is:open+label:%22:star:+Good+First+Issue%22)
are typically easier to resolve for those who haven't contributed to the codebase before, and are therefore a great
starting point.
## Implementing an Accepted Feature Request
If you're looking to implement a feature request that hasn't been converted to an issue yet, please contact the core
team through a comment on the feature request before starting work. There's probably a good reason it isn't
ready-to-be-implemented yet (unknown timelines, conflicts with other projects, blockers, etc). By collaborating early,
we ensure your PR can be merged as efficiently as possible!
## Copyright License Agreement (CLA)
All code contributors are required to sign the Contributor License Agreement (CLA). When you start a pull request, a
GitHub Action will prompt you to review the CLA and sign it by adding your name to
[contributors.yml](https://github.com/directus/directus/blob/main/contributors.yml). To clarify the intellectual
property rights in the project and any Contributions, Directus requires that You accept the
[Contributor License Agreement](https://github.com/directus/directus/blob/main/cla.md). This license is for Your
protection as a contributor as well as the protection of Directus, recipients of software distributed or made available
by Directus, and other contributors; it does not change your rights to use your own Contributions for any other purpose.
## Changesets
To properly generate changelogs and determine the right version number after a change is merged, we rely on
[changesets](https://github.com/changesets/changesets). Each pull request should include a changeset that describes
whether the change is a patch/minor/major version bump, and describe what the change is. Changesets should be written in
past tense.
A changeset can be generated via the following command:
```shell
pnpm changeset
```
### Changeset Bump Definitions
The following are the types of version bumps that can be specified in a changeset:
- Major (x.0.0) - A change has been made that is incompatible with previous versions.
- Minor (0.x.0) - A feature has been added that is backwards compatible with previous versions.
- Patch (0.0.x) - A bug has been fixed that is backwards compatible with previous versions.

View File

@@ -1,225 +0,0 @@
---
description:
This guide explains how to setup and run a _Development_ environment for Directus so that you can work on the
platform's source code.
readTime: 4 min read
---
# Running Dev Environment
> This guide explains how to setup and run a _Development_ environment for Directus so that you can work on the
> platform's source code. To install the _Production_ version, please follow to our
> [Docker Guide](/self-hosted/docker-guide).
::: tip Minimum Requirements
You will need to have [version 22 of Node.js](https://nodejs.org/en/download) for the Development environment of
Directus.
You will also need to have the package manager [pnpm](https://pnpm.io) installed. It's recommended to install
[pnpm via Corepack](https://pnpm.io/installation#using-corepack) for automatic use of the correct version.
:::
## 1. Fork the Directus repository
Go to the [repository](https://github.com/directus/directus) and fork it to your GitHub account. A fork is your copy of
the Directus repository which allows you to freely experiment with changes without affecting the original project.
## 2. Clone from your repository
```bash
git clone git@github.com:YOUR-USERNAME/directus.git
```
## 3. Make a new branch
```bash
git checkout -b YOUR-BRANCH-NAME
```
## 4. Install dependencies and build the project
```bash
pnpm install
pnpm build
```
## 5. Setup local configuration
### Create a `.env` file in `/api`
Create an `.env` file under the `api` folder using vars from the online
[config help](https://docs.directus.io/self-hosted/config-options).
::: tip Config Values
The `SECRET` config option from [Security](https://docs.directus.io/self-hosted/config-options.html#security) is
mandatory in production.
Also the [Database Configuration](https://docs.directus.io/self-hosted/config-options.html#database) must be specified.
You might want to use the [docker-compose.yml](https://github.com/directus/directus/blob/main/docker-compose.yml) file
to spin up a test database or a local mail server.
:::
### Upload/Extensions Folder
If you are using the local storage driver, your files will upload to `/api/uploads`. If you are locally developing
extensions from the extensions folder, that folder should be located at `/api/extensions`.
## 6. Initialize the database
For this step, you'll need to already have a SQL database up-and-running, except if you're using the SQLite driver,
which will create the database (file) for you.
::: tip Admin Account
Adding the `ADMIN_EMAIL` & `ADMIN_PASSWORD` to the `.env` file before running the `bootstrap` command, will populate the
admin user with the provided credentials instead of random values. `ADMIN_TOKEN` sets the API token for the admin user.
:::
To start the initialization run the following command:
```bash
pnpm --filter api cli bootstrap
```
This will set-up the required tables for Directus and make sure all the migrations have run.
## 7. Start the development server
You can run all packages in development with the following command:
```bash
pnpm --recursive dev
```
::: warning Race Conditions
When running multiple or all packages, sometimes `ts-node` may not start up the API properly because of race conditions
due to changes happening to other packages. You can either rerun the command to restart the API or opt to choose what
packages to work on as described below.
:::
If you wish to choose what packages to work on, you should run the `dev` script for that package. You can see their
names and list of scripts in their related `package.json`.
Example of running the API only:
```bash
pnpm --filter api dev
```
If you want to work on multiple packages at once, you should create a new instance of your terminal for each package.
Example of running both the API and App at the same time:
<table>
<tr>
<th>
Terminal 1 [Api]
</th>
<th>
Terminal 2 [App]
</th>
</tr>
<tr>
<td>
```bash
pnpm --filter api dev
```
</td>
<td>
```bash
pnpm --filter app dev
```
</td>
</tr>
</table>
---
::: tip
If you encounter errors during this installation process, make sure your node version meets the minimum requirements
:::
## 8. Make your fixes/changes
At this point you are ready to start working on Directus! Before diving in however, it's worth reading through the
introduction to [Contributing](/contributing/introduction).
### Debugging The App
There are several ways to debug the app but the easiest way to do it is with the
[Vue Devtools](https://devtools.vuejs.org/). It's recommended to use the Vue Devtools with Chrome.
::: tip Computed Debugging
To debug computed properties, it can be helpful to have a look at this
[Vue Guide](https://vuejs.org/guide/extras/reactivity-in-depth.html#reactivity-debugging).
:::
### Debugging The API in VS Code
To debug the API, we recommend to use [Visual Studio Code](https://code.visualstudio.com/) with it's built in debugger.
1. First you need to setup the config for the debugger. Create the following file `./directus/api/.vscode/launch.json`
and paste in the following structure.
```json
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Api",
"skipFiles": ["<node_internals>/**"],
"cwd": "${workspaceFolder}/api",
"runtimeExecutable": "pnpm",
"runtimeArgs": ["run", "dev"]
}
]
}
```
2. Make sure that you have caching disabled as it otherwise returns the cached response. To disable this, go to your
`.env` file in the API and set `CACHE_ENABLED` to `false`.
3. In the `tsconfig.json`, set `sourceMap` to true.
4. Now you can start the API by going to the debugger view in VS Code, select to debug the API and press
`Start Debugging`. This runs the API and allows you to set breakpoints.
## 9. Running tests
Tests run automatically through GitHub Actions. However you may wish to run the tests locally especially when you write
tests yourself.
Install [Docker](https://docs.docker.com/get-docker) and ensure that the service is running.
```bash
# Ensure that you are testing on the lastest codebase
pnpm build
# Run the unit tests
pnpm test
# Clean up in case you ran the blackbox tests before
docker compose -f tests/blackbox/docker-compose.yml down -v
# Start the necessary containers for the blackbox tests
docker compose -f tests/blackbox/docker-compose.yml up -d --wait
# Run the blackbox tests
pnpm test:blackbox
```

View File

@@ -1,46 +0,0 @@
---
description:
You can support the project by becoming a Sponsor, by providing testimonials and reviews, and by sharing Directus with
others.
---
# Sponsorship & Advocacy
Directus requires significant resources to build and maintain, especially as our community rapidly grows. If you'd like
to help keep development active, please consider supporting Directus through sponsorship or advocacy.
## Sponsorship
If you would like to help ensure Directus stays free, please consider providing financial support through
[GitHub Sponsors](https://github.com/sponsors/directus) for as little as $1/month.
There's a range of perks available, including access to an exclusive Discord channel.
## Advocacy
### GitHub Stars
As an open-source project, stars are a primary metric by which we measure our success. It only takes a few seconds to
head to the [Directus GitHub Repo](https://github.com/directus/directus) and click the star. With enough activity in a
short period of time, we get _even more exposure_ on GitHub's trending page!
### Social Posts
Engage with us on social media. Follow us! A quick post mentioning [@directus](https://twitter.com/directus) goes a long
way! _Plus, we re-tweet the team's favorite shout-outs to our followers._
### Testimonials
_One sentence._ Even that was a sentence and it was only two words! If you're a fan of Directus, we'd love to hear why
with a short endorsement... More importantly, potential new users _need to hear your testimonial_ to see if the product
is right for them! You can leave a short written or video testimonial [here](https://testimonial.to/directus).
### Reviews
The following popular rating platforms allow you to post your rating and testimonial publicly, which automatically gives
it more exposure.
- [G2](https://www.g2.com/products/directus/reviews)
- [Capterra](https://www.capterra.com/p/156619/Directus)
- [TrustRadius](https://www.trustradius.com/products/directus/reviews)
- [Product Hunt](https://www.producthunt.com/posts/directus-9)

View File

@@ -1,202 +0,0 @@
# Tests
> Tests ensure that the platform continues to work as intended when the existing codebase is modified.
The current test strategy for Directus consists of `Blackbox Tests` testing the overall functionality of the platform as
well as `Unit Tests` testing individual parts of the codebase.
## Running Unit Tests
Use the following command to perform unit tests in all packages:
```bash
pnpm --workspace-root test
```
Use one of the following commands to perform more specific actions with unit tests (mix and match as desired):
```bash
# Run tests for a specific package (for example only in the API / App package)
pnpm --filter api test
pnpm --filter app test
# Start tests in watch mode
pnpm --filter api test -- --watch
# Enable coverage report
pnpm --filter api test -- --coverage
# Run specific test files using a filter pattern
pnpm --filter api test -- app.test.ts
pnpm --filter api test -- utils
```
::: tip Relative Commands
If you are already in a directory of a specific package, you may omit the `--filter` flag in `pnpm` commands since the
commands will be executed relative to the current directory.
```bash
# Run API tests, from within the "/api" directory
pnpm test
```
:::
## Running Blackbox Tests
Install [Docker](https://docs.docker.com/get-docker/) and ensure that the service is up and running.
Run the following commands to start the blackbox tests:
```bash
# Ensure that you are testing against the lastest state of the codebase
pnpm --workspace-root build
# Clean up in case you ran the tests before
pnpm --filter tests-blackbox exec docker compose down --volumes
# Start the containers required for the tests
pnpm --filter tests-blackbox exec docker compose up --detach --wait
# Deploy Directus and run the tests
pnpm --workspace-root test:blackbox
```
Subsequent test runs can be issued with the following command, if only modifications to the blackbox tests themselves
have been made:
```bash
pnpm --filter tests-blackbox test
```
### Testing Specific Database Vendors
Provide a CSV of database vendors via the `TEST_DB` environment variable to target only a specific subset:
```bash
# Example targeting multiple vendors
TEST_DB=cockroachdb,postgres pnpm --workspace-root test:blackbox
# Example targeting a single vendor
TEST_DB=sqlite3 pnpm --workspace-root test:blackbox
```
If tests are only run against a subset of databases, it also makes sense to only start the corresponding containers:
```bash
# Start the containers that are always required
pnpm --filter tests-blackbox exec docker compose up auth-saml redis minio minio-mc --detach --wait
# Start the specific database container (for example 'postgres')
pnpm --filter tests-blackbox exec docker compose up postgres --detach --wait
```
### Using an Existing Directus Instance
Normally, the test suite will spin up a fresh copy of the Directus API built from the current state of the codebase. To
use an already running instance of Directus instead, enable the `TEST_LOCAL` flag:
```bash
TEST_DB=cockroachdb TEST_LOCAL=true pnpm --workspace-root test:blackbox
```
Note: The tests expect the instance running at `localhost:8055`. Make sure to connect the instance to the test database
container found in the `tests/blackbox/docker-compose.yml` file.
### Server Logs
For debugging purposes, server logs can be enabled by specifying a log level using the `TEST_SAVE_LOGS` flag, for
example:
```bash
TEST_SAVE_LOGS=info pnpm --workspace-root test:blackbox
```
The log files will be available under `tests/blackbox/server-logs-*`.
## Writing Unit Tests
Unit Tests are written throughout the codebase in a vite native unit test framework called [Vitest](https://vitest.dev).
### Example
```
/directus/api/src/utils/get-date-formatted.test.ts
```
```ts
import { afterEach, beforeEach, expect, test, vi } from 'vitest';
import { getDateFormatted } from './get-date-formatted.js';
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
function getUtcDateForString(date: string) {
const now = new Date(date);
// account for timezone difference depending on the machine where this test is ran
const timezoneOffsetInMinutes = now.getTimezoneOffset();
const timezoneOffsetInMilliseconds = timezoneOffsetInMinutes * 60 * 1000;
const nowUTC = new Date(now.valueOf() + timezoneOffsetInMilliseconds);
return nowUTC;
}
test.each([
{ utc: '2023-01-01T01:23:45.678Z', expected: '20230101-12345' },
{ utc: '2023-01-11T01:23:45.678Z', expected: '20230111-12345' },
{ utc: '2023-11-01T01:23:45.678Z', expected: '20231101-12345' },
{ utc: '2023-11-11T12:34:56.789Z', expected: '20231111-123456' },
{ utc: '2023-06-01T01:23:45.678Z', expected: '20230601-12345' },
{ utc: '2023-06-11T12:34:56.789Z', expected: '20230611-123456' },
])('should format $utc into "$expected"', ({ utc, expected }) => {
const nowUTC = getUtcDateForString(utc);
vi.setSystemTime(nowUTC);
expect(getDateFormatted()).toBe(expected);
});
```
## Writing Blackbox Tests
### Example
```
/directus/tests/blackbox/routes/server/ping.test.ts
```
```ts
import { getUrl } from '@common/config';
import request from 'supertest';
import vendors from '@common/get-dbs-to-test';
import { requestGraphQL } from '@common/transport';
describe('/server', () => {
describe('GET /ping', () => {
it.each(vendors)('%s', async (vendor) => {
// Action
const response = await request(getUrl(vendor))
.get('/server/ping')
.expect('Content-Type', /text\/html/)
.expect(200);
const gqlResponse = await requestGraphQL(getUrl(vendor), true, null, {
query: {
server_ping: true,
},
});
// Assert
expect(response.text).toBe('pong');
expect(gqlResponse.body.data.server_ping).toBe('pong');
});
});
});
```

View File

@@ -1,55 +0,0 @@
---
description:
Directus supports internationalization across the entire App. Many languages are currently supported, with more being
added all the time.
readTime: 2 min read
---
# App Translations
> The platform supports internationalization across the entire App. Many languages are currently supported, with more
> being added all the time. Anyone can add or refine any languages through the integration with Crowdin.
## Crowdin
Our [Crowdin](https://locales.directus.io) page provides an external interface for managing all of the different
language translations for the App. You can update and extend existing languages, or request a new language be added.
## Working with Existing Languages
1. Navigate to [Crowdin](https://locales.directus.io)
2. Click on the desired language
3. Click **Translate All** in the header
4. Log in to Crowdin, or register an account as needed
5. Select a source string using the left-hand navigation
6. Add or edit the translation in the lower text area
7. Click "SAVE" below the translation
It is important to keep the character length approximately the same as the source string (English) to avoid truncation
or a drastically different wrapping. For example, some text translations will go in a smaller button with limited space
for text and no ability to wrap.
Crowdin provides useful TM and MT suggestions, however you should always confirm that these are context appropriate, as
they may not accurately map to the source meaning.
If you feel you do not have enough information on how this string is used, you can always ask for additional context
using the "Comment" section.
## Releasing New Translations
As soon as a translation is edited on Crowdin, a pull request is created by Crowdin in our repo, which contains the
corresponding changes.
This pull request is usually merged into main before publishing a new release.
::: warning Translations on GitHub
Editing translations directly in the GitHub repo is not recommended, as these changes will be overwritten by Crowdin
again (unless we do a manual sync).
:::
## Requesting a New Language
To add a new language to the Crowdin service, you can make a request via Crowdin's **Discussions** section, or reach out
to a Core Team member via [Discord](https://directus.chat).

View File

@@ -1,467 +0,0 @@
.env
.npmrc
(A|a)sync
(A|a)utocomplete
(A|a)xios
(B|b)ackend
(B|b)ackend-as-a-(S|s)ervice
(B|b)alancer
(B|b)arebones
(B|b)asemap
(B|b)lackbox
(B|b)ugfixes
(C|)ancelling
(C|c)ancelled
(C|c)hangesets?
(C|c)harset
(C|c)heckboxes
(C|c)loudinary
(C|c)loudinary's
(C|c)odebase
(C|c)omposables?
(C|c)onfigs?
(C|c)ustomizable
(D|d)atacenter
(D|d)atastores
(D|d)atetimes?
(D|d)elisting
(D|d)ev
(D|d)onut
(D|d)ropdown
(D|d)ropdowns
(DN|dn)
(E|e)ntrypoints?
(F|f)avicon
(F|f)ilesystem
(F|f)ormatter
(F|f)rontend
(F|f)rontmatter
(H|h)ardcode
(H|h)ealthcheck
(H|h)ostname
(I|i)nit
(I|i)ntegrations
(L|l)odash
(M|m)emcache
(M|m)imetype
(M|m)isconfigured
(M|m)ultitenancy
(N|n)atively
(N|n)on-(S|s)andboxed
(P|p)olyfill
(Q|q)uickstart
(R|r)eal-(T|t)ime
(R|r)ealtime
(R|r)ealtime's
(R|r)econfigures
(R|r)elink
(R|r)epo
(R|r)esumable
(R|r)ollup
(S|s)andboxed
(S|s)chemas?
(S|s)endmail
(S|s)erverless
(S|s)lugify
(S|s)ortable
(S|s)tepline
(S|s)ubheader
(S|s)upabase
(T|t)extarea
(T|t)heming
(T|t)imeseries
(T|t)ooltips?
(T|t)riage
(T|t)riaging
(t|T)wilio
(U|u)narchive
(U|u)narchived
(U|u)narchiving
(U|u)nfilterable
(U|u)nlink
(U|u)psert
(U|u)ptime
(U|u)tils?
(URI|URIs)
(URL|url)
(V|v)ite
(V|v)itest
(V|v)onage[.]?
(V|v)ue
(W|w)alkthrough
(W|w)ebhook.?
(W|w)ebhooks?
(W|w)ebSocket
(W|w)ebSockets
[0-9]*(KB|MB|GB|K|px|pm|am)
[0-9]*[Kk]i?B
[0-9]*x[0-9]*
[0-9]*x[0-9]*px
[A-Z0-9]{6}
[M|m]onorepo
2FA
45K
ACL
ACS
Alibaba
allowlist
allowList
alwaysdata
AMI
anonymized
api
API
APIs
app-centric
architected
Argon2's
argon2(d|i)?
Argon2(d|i)?
auth
Auth
Auth0
authenticator
autoscaling
AWS
backend
Base64
Blackbox
buildout
Butterfield
Butterfield
camelCase
Capterra
Cardgroup
cardinality
CDNs?
changelogs
CIDR
cjs
CLA
CLI
CloudFront
Cloudinary's
Cloudron
CloudSQL
CMS
CockroachDB
codebase.
CodeDeploy
CollectionsService
CommonJS
composables
config-options
Corepack
CORS
CPUs?
create-directus-extension
create-directus-project
cron
Cron
CRON
crowdin
Crowdin
CrowdIn
Crowdin.
Crowdin's
CRUDS
cryptographic
CSRF
csv
CSV
CTAs?
customizable
customizations
DateTime
DDoS
destructure
destructured
devs
Devtools
DigitalOcean
DigitalOcean's
directus
Directus
directus_(.*)
Directus-(.*)
Directus.
Directus's
Dockerfile
Dockerhub
DOM
double-moustache
dropdown
Duh
e.g.,
EB
EC2
eCommerce
ELB
Elestio
entrypoint
Entrypoint
env
ENV
esm
ESM
Exif
fallbacks
falsy
FavIcon
FieldsService
filepath
FilesService
free-form
frontend
G2
Gantt
gcloud
GCP
GCS
GDPR
geo-coordinates
geospatial
Geospatial
getter
GHCR
glibc
Gmail
granularly
graphql
GraphQL
greenlight
GUID
GUIs?
hardcoded
headshot
Heya
Hoppscotch
HTTPS?
IAM
IETF
IETF's
IIS
iisnode
incongruent
input-multiline
intwined
IoT
ISO8601
iss
ItemsService
iteratively
Jamstack
Javascript
JPG
JS
JS-SDK
json
JSON\.?
JWT
kanban
Kanban
Keycloak
Knex
Koyeb
KPIs
Kubernetes
LDAP
LiquidJS
localhost
lowercased
LTS
M1
M2A
M2As
M2M
M2Ms
M2O
M2Os
macOS
Mailgun
Mapbox
MDN
Memorystore
MiB
middleware
misconfiguration
misconfigurations
mixins
Mongo
monorepo's
more_horiz
multitenant
mysql_native_password
namespace
netcup
Netlify
nitty-gritty
no-brainer
node[^\s]*
NodeJS
Nodemailer
NoSQL
npm
NPM
npx
nullable
Nuxt
O2M
O2Ms
O2O
OAuth2?
OIDC
Okta
omnichannel
on-prem
OpenAI
OpenAPI
OpenID
OracleDB
OracleDB's
OSS
OTP
overwriteable
pageClass
param
paywalled
performant
personalization
phpMyAdmin
Phusion
Pinia
Pinia-based
pino
Pino
Plesk
PM2
PNG
pnpm
PokéAPI
Pokémon
Polyfilling
Postgres
Postgres13
PR
pre
pre-configured
pre-defined
pre-existing
pre-formatted
pre-installed
programmatically
Programmatically
ProgressEvent
Proxy
Proxying
PRs
pub/sub
px
px.
reactively
readme
readonly
readTime
rebase
reconfigurations
reconnection
reframe
REKT
relationally
relationally-linked
RelationsService
repo
repurpose
S3
saml
SAML
scalable
Scalable
SCSS
sdk
SDK('s)?
SDL
semver
SemVer
SemVer-like
SendGrid
SEO
SES
sexualized
SFC
SIGKILL
siloed
slugification
slugify
slugifying
SMS
SMTP
socio-economic
src
SSG
SSL
SSO
SSR
StackOverflow
STARTTLS
stylization
subdirectory
subfolder
substring
SuperAdmins?
superset
Superset
svg
SVGs?
TablePlus
tfa
TFA
TileJSON
TinyMCE
TLS
toc
TrustRadius
truthy
TTL
TTY
TUS
TypeDoc
UI
UID
UIDs
UIs?
unarchives
Ungrouped
unintuitive
unpaused
unselectable
unstyled
upscaled
upvotes
userbase
uuid
UUID
UUIDs
UUIDv4
UX
v[0-9]{1,2}
validator
varchar
vCPU
Vercel
viewability
VitePress
volta
VPC
Vue
Vue's
WebDAV
webhosting
webpage
whitelabeling
whitespace
wireframe
XSS
yaml
YAML
Zeet

View File

@@ -1,273 +0,0 @@
---
description: Learn about the extension SDK composables and how to utilize them when developing app extensions.
contributors: Esther Agbaje
---
# App Extension Composables
There are several composables available as part of the Directus Extensions SDK that make working with Directus easier.
Rather than needing to rewrite logic from scratch, extension developers can leverage primitives like `useApi()` or
`useStores()`, to handle common complexities when building extensions.
## `useApi()`
The `useApi` composable is a wrapper around the `axios` library that uses the session cookie and provides concurrency
control when making multiple requests.
Use the `useApi` composable when you need to make authorized API requests from your App extension.
```html
<script setup>
import { useApi } from '@directus/extensions-sdk';
const api = useApi();
async function fetchData() {
const response = await api.get('ENDPOINT_URL');
data.value = response.data;
};
fetchData();
</script>
```
## `useStores()`
`useStores` serves as the primary way for App extensions to interact with data and features within a Directus instance.
Within `useStores` are stores like the `usePermissionsStore`, `useCollectionsStore` and `useFieldsStore` among others.
```html
<script setup>
import { useStores } from '@directus/extensions-sdk';
const { useFieldsStore, usePermissionsStore, useCollectionsStore } = useStores();
const fieldsStore = useFieldsStore();
const permissionsStore = usePermissionsStore();
const collectionStore = useCollectionsStore();
</script>
```
### `useFieldsStore()`
The `useFieldsStore` is used to access and modify collections and fields.
Use this store when you need to:
- retrieve information about a collection's field
- perform mutations on a collection's field such as create, update, upsert, or delete
- retrieve translations for collection's field (useful for internationalization)
```html
<script setup>
import { useStores } from '@directus/extensions-sdk';
const { useFieldsStore } = useStores();
const fieldsStore = useFieldsStore();
// create a field
const newField = await fieldStore.createField('collection_key', {
name: 'title',
});
// update a field
const updatedField = await fieldStore.updateField(
'collection_key',
'field_key',
{
name: 'new title',
}
);
</script>
```
For a deep dive on how to use the `useFieldsStore` composable,
[see the implementation](https://github.com/directus/directus/blob/main/app/src/stores/fields.ts) in our codebase.
### `usePermissionsStore()`
The `usePermissionsStore` is used to check for access control before performing operations within your App extension.
```html
<script setup>
import { useStores } from '@directus/extensions-sdk';
const { usePermissionsStore } = useStores();
const permissionsStore = usePermissionsStore();
// check if user can create a collection
const canCreate = permissionsStore.hasPermission('collection_name', 'create');
// check if user can read a collection
const canRead = permissionsStore.hasPermission('collection_name', 'read');
</script>
```
For a deep dive on how to use the `usePermissionsStore` composable,
[see the implementation](https://github.com/directus/directus/blob/main/app/src/stores/permissions.ts) in our codebase.
### `useCollectionsStore()`
`useCollectionsStore` provides access to collections directly from your App extension.
Use this store when you need to:
- perform CRUD operations on a collection such as create, update, upsert, or delete
- retrieve translations for a collection (useful for internationalization)
- retrieve all collections or visible collections within a Directus instance
```html
<script setup>
import { useStores } from '@directus/extensions-sdk';
const { useCollectionsStore } = useStores();
const collectionsStore = useCollectionsStore();
// get all collections
collectionsStore.collections.value;
// get all visible collections
collectionsStore.visibleCollections.value;
// get a collection
collectionStore.getCollection("collection_key");
// delete a collection
await collectionStore.deleteCollection("collection_key");
// upsert (create or update) a collection
await collectionStore.upsertCollection("collection_key", {...});
</script>
```
For a deep dive on how to use the `useCollectionsStore` composable,
[see the implementation](https://github.com/directus/directus/blob/main/app/src/stores/collections.ts) in our codebase.
::: info Explore all Stores within `useStores`
While `useFieldsStore`, `usePermissionsStore` and `useCollectionsStore` cover the common scenarios, the `useStore`
composable contains additional sub-stores. Reference the full list in of sub-stores in our
[codebase](https://github.com/directus/directus/blob/main/app/src/composables/use-system.ts).
:::
## `useCollection()`
The `useCollection` composable provides access to metadata about collections (such as name, fields, type, icon).
Use `useCollection` composable when you need to retrieve:
- metadata about a collection (such as name, type, icon)
- fields within a collection and their default values
- the primary key and user created field
- accountability scope
```html
<script setup>
import { useCollection } from '@directus/extensions-sdk';
const { info, fields, defaults, primaryKeyField } = useCollection('collection_name');
info.value;
// => [{ name: 'collection_name', icon: 'box', type: 'table', ... }]
fields.value;
// => [{ name: 'title', type: 'string', ... }]
defaults.value;
// => { title: 'default_value' }
primaryKeyField.value;
// => { name: 'id', type: 'uuid', ... }
</script>
```
::: tip `useCollection` vs `useCollectionsStore`
For full capabilities like retrieving, updating and deleting collection items, use the `useCollectionsStore` composable
instead.
:::
For a deep dive on how to use the `useCollection` composable,
[see the implementation](https://github.com/directus/directus/blob/main/packages/composables/src/use-collection.ts) in
our codebase.
## `useItems()`
The `useItems` composable is used to retrieve items in a collection and provides pagination features.
### Fetching items in a collection
```html
<script setup>
import { useItems } from '@directus/extensions-sdk';
const collectionRef = ref('collection_key');
const query = {
fields: ref(['*']),
limit: ref(1),
sort: ref(null),
search: ref(null),
filter: ref(null),
page: ref(1),
}
const { getItems, items } = useItems(collectionRef, query);
query.search.value = 'search_value' // update query search
query.limit.value = 10 // update query limit
await getItems(); // fetch the items
const data = items.value; // read the items
</script>
```
### Fetching the item and page count
```html
<script setup>
import { useItems } from '@directus/extensions-sdk';
const collectionRef = ref('collection_key')
const { getItemCount, itemCount, totalPages } = useItems(collectionRef);
await getItemCount(); // fetch the item count
const data = itemCount.value; // read the item count
const pages = totalPages.value; // read the total pages
</script>
```
### Fetching the total count
```html
<script setup>
import { useItems } from '@directus/extensions-sdk';
const collectionRef = ref('collection_key')
const { getTotalCount, totalCount } = useItems(collectionRef);
await getTotalCount(); // fetch the total item count
const data = totalCount.value; // read the total item count
</script>
```
For a deep dive on how to use the `useItems()` composable,
[see the implementation](https://github.com/directus/directus/blob/main/packages/composables/src/use-items.ts) in our
codebase.
::: tip Explore all Composables
While these core composables cover many common use cases, for a complete reference of all available Extension SDK
composables within Directus, check out our
[GitHub repository](https://github.com/directus/directus/blob/main/app/src/composables/use-system.ts).
:::

View File

@@ -1,63 +0,0 @@
---
description: A guide on how to build extension bundles in Directus.
readTime: 5 min read
---
# Extension Bundles
> Extension bundles can be used when an extension consists of a combination of several related sub-extensions which are
> supposed to be installed together. They allow you to combine and share dependencies between one or more extensions and
> are developed using JavaScript / Node.js.
## Create a Bundle
When [scaffolding your Directus extension](/extensions/creating-extensions#scaffolding-your-directus-extension), select
the `bundle` type. This will create a new empty bundle.
## Entries
In your bundle's `package.json` file, the `directus:extension` object has an `entries` array that describes all of the
items contained within the bundle.
Example of an entry:
```json
{
"type": "interface",
"name": "my-interface",
"source": "src/my-interface/index.ts"
}
```
Entries in a bundle are located within a `src` directory in the bundle.
## Partial
For bundle type extensions `package.json` file, the `directus:extension` object supports an additional optional
`partial` property. This boolean property controls whether the bundles entries can be individually disabled. This is
enabled by default.
## Add New Extensions To a Bundle
### Create New
1. Navigate to your bundle extension directory in your terminal.
2. Use the `npm run add` command and select an extension type.
This will scaffold a new blank extension for you to work on.
::: tip
The bundle extension type currently doesn't support the migration extension type.
:::
### Add Existing
1. Move your extension directory within your bundle's `src` directory.
2. Add an entry to the bundle's `package.json`.
## Remove an Extension From a Bundle
1. Delete the extension directory from your `src` directory.
2. Remove the entry from your `package.json`.

View File

@@ -1,163 +0,0 @@
---
description: A guide on how to scaffold your Directus Extension.
readTime: 5 min read
contributors: Rijk Van Zanten, Esther Agbaje, Kevin Lewis, Lukas Zelenka
---
# Creating Extensions
To create an extension, use the `create-directus-extension` utility:
```shell
npx create-directus-extension@latest
```
After specifying the name of the extension, the type of the extension and the programming language you want to use, the
utility will create a folder with the recommended file structure to create an extension.
If you want to combine and share dependencies between one or more extensions, use the
[bundle extension type](/extensions/bundles).
## Building Your Extension
Before your extension can be used by Directus, it has to be built. If you used the `create-directus-extension` utility
to scaffold your extension, building your extension is as easy as running:
```bash
npm run build
```
The generated `package.json` contains a script that calls the `directus-extension` CLI which is part of
`@directus/extensions-sdk`:
```json
{
"scripts": {
"build": "directus-extension build"
}
}
```
If you prefer to scaffold your extension manually, you can use the `directus-extension` CLI binary directly. The
`--help` flag provides useful information regarding the available options and flags.
Internally, the CLI uses Rollup to bundle your extension to a single entrypoint.
::: tip Watch
The CLI supports rebuilding extensions whenever a file has changed by using the `--watch` flag.
:::
::: tip Automatically Reload Extensions
To automatically reload extensions every time you make a change, without having to restart Directus, in your
`docker-compose.yml` file, set `EXTENSIONS_AUTO_RELOAD=true`.
:::
### Configuring the CLI
Most of the time, it should be sufficient to use the CLI as is. But, in some cases it might be necessary to customize it
to your specific needs. This can be done by creating a `extension.config.js` file at the root of your extension package.
An example with the currently available options will look something like:
```js
export default {
plugins: [],
watch: {
clearScreen: false
}
};
```
#### Supported Options
- [`plugins`](https://rollupjs.org/configuration-options/#plugins) — An array of Rollup plugins that will be used when
building extensions in addition to the built-in ones.
- [`watch.clearScreen`](https://rollupjs.org/configuration-options/#watch-clearscreen) — Controls whether or not to
clear the screen when a rebuild is triggered.
::: tip CommonJS or ESM
By using the `type` field inside your `package.json` file or using the appropriate file extension (`.mjs` or `.cjs`),
the config file can be loaded as a CommonJS or ESM file.
:::
::: tip Component Library
Directus comes shipped with it's own [Vue Component Library](https://components.directus.io) that you can use to enrich
your extensions. These components can be used in any of the "app extensions", including Interfaces, Displays, Modules,
Layouts, and Panels.
:::
### Usage with Docker
After you have created your custom extension, a new folder was created (for example: `custom-extension`). Now, you need
to go to the `package.json` within this newly created folder and copy the `name` of the extension (for example:
`directus-extension-custom-extension`). Then, you just need to mount a volume in your Docker container pointing to this
folder, but remember that you need to use the `name` of the extension as the folder name within Directus. For example,
if you use a `docker-compose.yaml` it would be something like this:
```yaml
volumes:
- ./custom-extension:/directus/extensions/directus-extension-custom-extension
```
## Extension Folder Structure
The folder created by the utility is in fact an npm package. It comes with a few pre-installed packages depending on the
extension type and the programming language you chose. The most important one is `@directus/extensions-sdk`. This
package includes a CLI, which allows you to build your extension and to scaffold additional extensions, Typescript
helpers, and other utilities.
Inside the created folder there is a `src/` folder. This folder contains the entrypoint of your extension. If you write
additional source files, they should go into this folder.
::: tip Entrypoint
The entrypoint is either called `index.js` or `index.ts`, depending on which programming language you chose.
:::
The generated `package.json` file contains an additional `directus:extension` field with the following sub-fields:
- `type` — The type of the extension
- `path` — The path to the built extension
- `source` — The path to the source entrypoint
- `host` — A semver string that indicates with which versions of the Directus host, the extension is compatible with
The CLI will use those fields by default to determine the input and output file paths and how the extension should be
built.
## Marketplace Requirements
By default, App Extensions and [Sandboxed Extensions](/extensions/sandbox/introduction) are available from the
[Directus Marketplace](/user-guide/marketplace/overview) in all Directus projects (Directus Professional and Enterprise
Cloud, and self-hosted). If you are building an API or Hybrid extension and want to publish it to the Marketplace, it
must use the Sandbox SDK.
## Developing Your Extension
To learn more about how to develop extensions of a specific type, refer to the individual guides:
### App Extensions
- [Interfaces](/extensions/interfaces)
- [Layouts](/extensions/layouts)
- [Displays](/extensions/displays)
- [Panels](/extensions/panels)
- [Modules](/extensions/modules)
- [Themes](/extensions/themes)
### API Extensions
- [Endpoints](/extensions/endpoints)
- [Hooks](/extensions/hooks)
### Hybrid Extensions
- [Operations](/extensions/operations)
- [Bundles](/extensions/bundles)

View File

@@ -1,148 +0,0 @@
---
description: A guide on how to build custom Display Extensions in Directus.
readTime: 4 min read
---
# Custom Displays
> Displays are small inline components that allow you to create new ways of viewing field values throughout the App.
> They are developed using Vue.js. [Learn more about Displays](/user-guide/overview/glossary#displays).
## Extension Entrypoint
The entrypoint of your display is the `index` file inside the `src/` folder of your extension package. It exports a
configuration object with options to configure the behavior of your display. When loading your display, this object is
imported by the Directus host.
Example of an entrypoint:
```js
import DisplayComponent from './display.vue';
export default {
id: 'custom',
name: 'Custom',
icon: 'box',
description: 'This is my custom display!',
component: DisplayComponent,
options: null,
types: ['string'],
};
```
#### Available Options
- `id` — The unique key for this display. It is good practice to scope proprietary displays with an author prefix.
- `name` — The human-readable name for this display.
- `icon` — An icon name from the [material icon set](/user-guide/overview/glossary#material-icons), or the extended list
of Directus custom icons.
- `description` — A short description (<80 characters) of this display shown in the App.
- `component` — A reference to your display component.
- `options` — The options of your display. Can be either an options object or a dedicated Vue component.
- `types` — An array of supported [types](/user-guide/overview/glossary#types).
- `localTypes` — An array of local types. Accepts `standard`, `file`, `files`, `m2o`, `o2m`, `m2m`, `m2a`,
`presentation`, `translations` and `group`. Defaults to `standard`.
- `fields` — If this option is set, the display will fetch relational fields. Can either be an array of fields or a
function that returns an array of fields.
## Display Component
The display component is the part of your extension that will be rendered by the Directus App whenever your display
should be used to show the value of a field. This display component has to be Vue component. The most straightforward
way to write a Vue component is to use the Vue Single File Component syntax.
Example of a display component using the Vue SFC syntax:
```vue
<template>
<div>Value: {{ value }}</div>
</template>
<script>
export default {
props: {
value: {
type: String,
default: null,
},
},
};
</script>
```
The current value of the field is provided to the component via the `value` prop. If you use the `fields` option to
fetch relational fields, the `value` prop will be an object with the requested fields as keys and their respective
values.
#### Available Props
- `value` — The value of the field.
- `interface` - The interface of the field.
- `interfaceOptions` - The options for the field's interface.
- `type` — The type of the field.
- `collection` — The collection name of the field.
- `field` — The key of the field.
Other than this simple API to communicate with the Directus App, the display component is a blank canvas, allowing you
to create anything you need.
::: warning Vue Version
The Directus App uses Vue 3. There might be 3rd party libraries that aren't yet compatible with Vue 3.
:::
### Functional Component
Instead of defining the component inside a Vue SFC file, you can use a functional component. This allows you to make
simple displays that don't need a full component rendered:
```js
export default {
id: 'custom',
name: 'Custom',
icon: 'box',
description: 'This is my custom display!',
component: function ({ value }) {
return value.toLowerCase();
},
options: null,
types: ['string'],
};
```
## Accessing Internal Systems
To access internal systems like the API or the stores, you can use the `useApi()` and `useStores()` composables exported
by the `@directus/extensions-sdk` package. They can be used inside a `setup()` function like this:
```js
import { useApi, useStores } from '@directus/extensions-sdk';
export default {
setup() {
const api = useApi();
const { useCollectionsStore } = useStores();
const collectionsStore = useCollectionsStore();
// ...
},
};
```
::: tip Vue Options API
If you prefer to use the Vue Options API, you can inject the `api` and `stores` properties directly.
:::
## Guides
Learn how to build displays with our official guides:
<GuidesListExtensions type="Displays" />
<script setup>
import GuidesListExtensions from '@/components/guides/GuidesListExtensions.vue';
</script>

View File

@@ -1,79 +0,0 @@
---
description: A guide on how to build custom API endpoints in Directus.
readTime: 3 min read
---
# Custom API Endpoints
> Custom API Endpoints register new API routes which can be used to infinitely extend the core functionality of the
> platform. They are developed using JavaScript / Node.js.
## Extension Entrypoint
The entrypoint of your endpoint is the `index` file inside the `src/` folder of your extension package. It exports a
register function to register one or more custom routes. Each route of your endpoint will be a sub-route of
`/<extension-name>`.
::: tip Extension Name
The extension name is usually the name of the folder where you put your extension when deploying it.
:::
Example of an entrypoint:
```js
export default (router) => {
router.get('/', (req, res) => res.send('Hello, World!'));
};
```
Alternatively, you can export a configuration object to be able to customize the root route:
```js
export default {
id: 'greet',
handler: (router) => {
router.get('/', (req, res) => res.send('Hello, World!'));
router.get('/intro', (req, res) => res.send('Nice to meet you.'));
router.get('/goodbye', (req, res) => res.send('Goodbye!'));
},
};
```
The routes of this endpoint are accessible at `/greet`, `/greet/intro` and `/greet/goodbye`.
#### Available Options
- `id` — The unique key for this endpoint. Each route of your endpoint will be a sub-route of `/<id>`.
- `handler` — The endpoint's registration handler function.
## Register Function
The register function receives the two parameters `router` and `context`. `router` is an Express router instance.
`context` is an object with the following properties:
- `services` — All API internal services.
- `database` — Knex instance that is connected to the current database.
- `getSchema` — Async function that reads the full available schema for use in services
- `env` — Parsed environment variables.
- `logger` — [Pino](https://github.com/pinojs/pino) instance.
- `emitter` — [Event emitter](https://github.com/directus/directus/blob/main/api/src/emitter.ts) instance that can be
used to trigger custom events for other extensions.
::: warning Event loop
When implementing custom events using the emitter make sure you never directly or indirectly emit the same event your
hook is currently handling as that would result in an infinite loop!
:::
## Guides
Learn how to build endpoints with our official guides:
<GuidesListExtensions type="Endpoints" />
<script setup>
import GuidesListExtensions from '@/components/guides/GuidesListExtensions.vue';
</script>

View File

@@ -1,331 +0,0 @@
---
description: A guide on how to build custom hooks in Directus.
readTime: 7 min read
---
# Custom API Hooks
> Custom API Hooks allow running custom logic when a specified event occurs within your project. There are different
> types of events to choose from. They are developed using JavaScript / Node.js.
## Extension Entrypoint
The entrypoint of your hook is the `index` file inside the `src/` folder of your extension package. It exports a
register function to register one or more event listeners.
Example of an entrypoint:
```js
export default ({ filter, action }) => {
filter('items.create', () => {
console.log('Creating Item!');
});
action('items.create', () => {
console.log('Item created!');
});
};
```
## Events
Your hook can emit on a variety of different events. An event is defined by its type and its name.
There are five event types to choose from:
- [Filter](#filter)
- [Action](#action)
- [Init](#init)
- [Schedule](#schedule)
- [Embed](#embed)
Use filter hooks when you want the hook to run before the event. Use action hooks when you want the hook to run after
the event.
### Filter
Filter hooks act on the event's payload before the event is emitted. They allow you to check, modify, or cancel an
event.
Below is an example of canceling a `create` event by throwing a Directus error.
```js
import { createError } from '@directus/errors';
const InvalidPayloadError = createError('INVALID_PAYLOAD_ERROR', 'Something went wrong...', 500);
export default ({ filter }) => {
filter('items.create', async (input) => {
if (LOGIC_TO_CANCEL_EVENT) {
throw new InvalidPayloadError();
}
return input;
});
};
```
The filter register function receives two parameters:
- The event name
- A callback function that is executed whenever the event is emitted.
The callback function itself receives three parameters:
- The modifiable payload
- An event-specific meta object
- A context object
The context object has the following properties:
- `database` — The current database transaction
- `schema` — The current API schema in use
- `accountability` — Information about the current user
::: warning Performance
Filters can impact performance when not carefully implemented, as they are executed in a blocking manner. This applies
in particular to filters firing on `read` events, where a single request can result in a large amount of database reads.
:::
### Action
Action hooks execute after a defined event and receive data related to the event. Use action hooks when you need to
automate responses to CRUD events on items or server actions.
The action register function receives two parameters:
- The event name
- A callback function that is executed whenever the event is emitted.
The callback function itself receives two parameters:
- An event-specific meta object
- A context object
The context object has the following properties:
- `database` — The current database transaction
- `schema` — The current API schema in use
- `accountability` — Information about the current user
### Init
Init hooks execute at a defined point within the life cycle of Directus. Use init hook objects to inject logic into
internal services.
The init register function receives two parameters:
- The event name
- A callback function that is executed whenever the event is emitted.
The callback function itself receives one parameter:
- An event-specific meta object
### Schedule
Schedule hooks execute at certain points in time rather than when Directus performs a specific action. This is supported
through [`node-schedule`](https://www.npmjs.com/package/node-schedule).
To set up a scheduled event, provide a cron statement as the first parameter to the `schedule()` function. For example
`schedule('15 14 1 * *', <...>)` (at 14:15 on day-of-month 1) or `schedule('5 4 * * sun', <...>)` (at 04:05 on Sunday).
Below is an example of registering a schedule hook.
```js
import axios from 'axios';
export default ({ schedule }) => {
schedule('*/15 * * * *', async () => {
await axios.post('http://example.com/webhook', { message: 'Another 15 minutes passed...' });
});
};
```
### Embed
Inject custom JavaScript or CSS into the `<head>` and `<body>` tags within the Data Studio.
The embed register function receives two parameters:
- The position to embed, either `head` or `body`.
- The value to embed, either a string or a function that returns a string.
Below is an example of registering embed hooks.
```js
export default ({ embed }, { env }) => {
// Google Tag Manager Example
embed(
'head',
() => `<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','${env.GTM_ID}');</script>
<!-- End Google Tag Manager -->`
);
// Sentry Example
embed(
'head',
'<script src="https://browser.sentry-cdn.com/7.21.1/bundle.min.js" integrity="sha384-xOL2QebDu7YNMtC6jW2i5RpQ5RcWOyQMTwrWBiEDezpjjXM7mXhYGz3vze77V91Q" crossorigin="anonymous"></script>'
);
embed(
'body',
() => `<script>
Sentry.init({
dsn: "${env.SENTRY_DSN}" // "https://examplePublicKey@o0.ingest.sentry.io/0",
release: "my-project-name@${env.npm_package_version}",
integrations: [new Sentry.BrowserTracing()],
// We recommend adjusting this value in production, or using tracesSampler
// for finer control
tracesSampleRate: 1.0,
});
</script>`
);
};
```
## Available Events
::: tabs
== Filter Events
| Name | Payload | Meta |
| ------------------------------ | ------------------------------------ | ------------------------------------------- |
| `websocket.message` | The message send over the WebSocket | |
| `request.not_found` | `false` | `request`, `response` |
| `request.error` | The request errors | -- |
| `database.error` | The database error | `client` |
| `auth.login` | The login payload | `status`, `user`, `provider` |
| `auth.jwt` | The auth token | `status`, `user`, `provider`, `type` |
| `auth.create`<sup>[1]</sup> | The created user | `identifier`, `provider`, `providerPayload` |
| `auth.update`<sup>[2]</sup> | The updated auth token<sup>[3]</sup> | `identifier`, `provider`, `providerPayload` |
| `authenticate` | The empty accountability object | `req` |
| `email.send` | The email payload | -- |
| `(<collection>.)items.query` | The items query | `collection` |
| `(<collection>.)items.read` | The read item | `query`, `collection` |
| `(<collection>.)items.create` | The new item | `collection` |
| `(<collection>.)items.update` | The updated item | `keys`, `collection` |
| `(<collection>.)items.promote` | The promoted item | `collection`, `item`, `version` |
| `(<collection>.)items.delete` | The keys of the item | `collection` |
| `<system-collection>.query` | The items query | `collection` |
| `<system-collection>.read` | The read item | `query`, `collection` |
| `<system-collection>.create` | The new item | `collection` |
| `<system-collection>.update` | The updated item | `keys`, `collection` |
| `<system-collection>.delete` | The keys of the item | `collection` |
<sup>[1]</sup> Available for `ldap`, `oauth2`, `openid` and `saml` driver.
<sup>[2]</sup> Available for `ldap`, `oauth2` and `openid` driver.
<sup>[3]</sup> Available for `oauth2` and `openid` driver, only if set by provider.
== Action Events
| Name | Meta |
| ------------------------------ | --------------------------------------------------- |
| `websocket.message` | `message`, `client` |
| `websocket.error` | `client`, `event` |
| `websocket.close` | `client`, `event` |
| `websocket.connect` | `client` |
| `websocket.auth.success` | `client` |
| `websocket.auth.failure` | `client` |
| `server.start` | `server` |
| `server.stop` | `server` |
| `response` | `request`, `response`, `ip`, `duration`, `finished` |
| `auth.login` | `payload`, `status`, `user`, `provider` |
| `files.upload` | `payload`, `key`, `collection` |
| `(<collection>.)items.read` | `payload`, `query`, `collection` |
| `(<collection>.)items.create` | `payload`, `key`, `collection` |
| `(<collection>.)items.update` | `payload`, `keys`, `collection` |
| `(<collection>.)items.promote` | `payload`, `collection`, `item`, `version` |
| `(<collection>.)items.delete` | `keys`, `collection` |
| `(<collection>.)items.sort` | `collection`, `item`, `to` |
| `<system-collection>.read` | `payload`, `query`, `collection` |
| `<system-collection>.create` | `payload`, `key`, `collection` |
| `<system-collection>.update` | `payload`, `keys`, `collection` |
| `<system-collection>.delete` | `keys`, `collection` |
== Init Events
| Name | Meta |
| ---------------------- | --------- |
| `cli.before` | `program` |
| `cli.after` | `program` |
| `app.before` | `app` |
| `app.after` | `app` |
| `routes.before` | `app` |
| `routes.after` | `app` |
| `routes.custom.before` | `app` |
| `routes.custom.after` | `app` |
| `middlewares.before` | `app` |
| `middlewares.after` | `app` |
:::
::: tip System Collections
---
`<system-collection>` should be replaced with one of the
[system collection](/app/data-model/collections#system-collections) names.
---
Directus reads system collection data to perform correctly, both in the Data Studio and the generated APIs. Be careful
when modifying the output of system collection read/query events, as this can cause Directus core functionality to
break.
---
**Event Exceptions**
| Collection | Detail |
| ------------- | ------------------------------------------------------------------------ |
| `collections` | No `read` action event |
| `fields` | No `read` action event |
| `files` | `create` and `update` events need to be emitted manually on file upload. |
| `relations` | No `delete` event |
:::
## Register Function
The register function receives an object containing the type-specific register functions as the first parameter:
- `filter` — Listen for a filter event
- `action` — Listen for an action event
- `init` — Listen for an init event
- `schedule` — Execute a function at certain points in time
- `embed` — Inject custom JavaScript or CSS within the Data Studio
The second parameter is a context object with the following properties:
- `services` — All API internal services
- `database` — Knex instance that is connected to the current database
- `getSchema` — Async function that reads the full available schema for use in services
- `env` — Parsed environment variables
- `logger` — [Pino](https://github.com/pinojs/pino) instance.
- `emitter` — [Event emitter](https://github.com/directus/directus/blob/main/api/src/emitter.ts) instance that can be
used to emit custom events for other extensions.
::: warning Event loop
When implementing custom events using the emitter make sure you never directly or indirectly emit the same event your
hook is currently handling as that would result in an infinite loop!
:::
## Guides
Learn how to build hooks with our official guides:
<GuidesListExtensions type="Hooks" />
<script setup>
import GuidesListExtensions from '@/components/guides/GuidesListExtensions.vue';
</script>

Some files were not shown because too many files have changed in this diff Show More