diff --git a/docs/tutorialkit.dev/src/content/docs/reference/configuration.mdx b/docs/tutorialkit.dev/src/content/docs/reference/configuration.mdx
index 619a4b4ca..e22dfc681 100644
--- a/docs/tutorialkit.dev/src/content/docs/reference/configuration.mdx
+++ b/docs/tutorialkit.dev/src/content/docs/reference/configuration.mdx
@@ -404,6 +404,28 @@ type TemplateType = "html" | "node" | "angular-cli" | "create-react-app" | "java
```
+##### `custom`
+
+Assign custom fields to a chapter/part/lesson.
+
+
+This is useful when you want to consume items for the default `tutorial` collection
+in order to implement custom features.
+
+```yaml
+custom:
+ publishedAt: 2024-16-10
+ tags: tutorialkit,astro,vite
+```
+
+```ts
+import { getCollection } from 'astro:content';
+const collection = await getCollection('tutorial');
+for (const entry of collection) {
+ console.log("This part was published at:", entry.data?.custom?.publishedAt)
+}
+```
+
## Configure the Tutorialkit Astro integration
`@tutorialkit/astro` is an integration for Astro. You can configure the integration in your `astro.config.ts` file.
diff --git a/e2e/src/components/CustomMetadata.astro b/e2e/src/components/CustomMetadata.astro
new file mode 100644
index 000000000..8af5d7b4d
--- /dev/null
+++ b/e2e/src/components/CustomMetadata.astro
@@ -0,0 +1,11 @@
+---
+import { getCollection } from 'astro:content';
+const collection = await getCollection('tutorial');
+
+const lesson = collection.find((c) => c.data.type === 'lesson' && c.slug.startsWith(Astro.params.slug!))!;
+const { custom } = lesson.data;
+---
+
+
Custom metadata
+
+{JSON.stringify(custom, null,2)}
diff --git a/e2e/src/content/tutorial/tests/metadata/custom/content.mdx b/e2e/src/content/tutorial/tests/metadata/custom/content.mdx
new file mode 100644
index 000000000..397dfdc4d
--- /dev/null
+++ b/e2e/src/content/tutorial/tests/metadata/custom/content.mdx
@@ -0,0 +1,15 @@
+---
+type: lesson
+title: Custom
+terminal:
+ panels: terminal
+custom:
+ custom-message: 'Hello world'
+ numeric-field: 5173
+---
+
+import CustomMetaData from "@components/CustomMetadata.astro"
+
+# Metadata test - Custom
+
+
\ No newline at end of file
diff --git a/e2e/src/content/tutorial/tests/metadata/meta.md b/e2e/src/content/tutorial/tests/metadata/meta.md
new file mode 100644
index 000000000..e13ff569b
--- /dev/null
+++ b/e2e/src/content/tutorial/tests/metadata/meta.md
@@ -0,0 +1,4 @@
+---
+type: chapter
+title: Metadata
+---
diff --git a/e2e/test/metadata.test.ts b/e2e/test/metadata.test.ts
new file mode 100644
index 000000000..541ebca5a
--- /dev/null
+++ b/e2e/test/metadata.test.ts
@@ -0,0 +1,13 @@
+import { test, expect } from '@playwright/test';
+
+const BASE_URL = '/tests/metadata';
+
+test('developer can pass custom metadata to lesson', async ({ page }) => {
+ await page.goto(`${BASE_URL}/custom`);
+ await expect(page.getByRole('heading', { level: 1, name: 'Metadata test - Custom' })).toBeVisible();
+
+ await expect(page.getByRole('heading', { level: 2, name: 'Custom metadata' })).toBeVisible();
+
+ await expect(page.getByText('"custom-message": "Hello world"')).toBeVisible();
+ await expect(page.getByText('"numeric-field": 5173')).toBeVisible();
+});
diff --git a/packages/template/src/content/tutorial/1-basics/1-introduction/1-welcome/content.md b/packages/template/src/content/tutorial/1-basics/1-introduction/1-welcome/content.md
index cf48965a5..1acc9d03f 100644
--- a/packages/template/src/content/tutorial/1-basics/1-introduction/1-welcome/content.md
+++ b/packages/template/src/content/tutorial/1-basics/1-introduction/1-welcome/content.md
@@ -12,8 +12,11 @@ prepareCommands:
terminal:
panels: ['terminal', 'output']
meta:
- description: "This is lesson 1"
- image: "/logo.svg"
+ description: "This is lesson 1"
+ image: "/logo.svg"
+custom:
+ publishedAt: "2024-10-16"
+
---
# Kitchen Sink [Heading 1]
diff --git a/packages/types/src/entities/index.ts b/packages/types/src/entities/index.ts
index 81c828701..3d0abf5f6 100644
--- a/packages/types/src/entities/index.ts
+++ b/packages/types/src/entities/index.ts
@@ -1,5 +1,5 @@
import type { I18nSchema } from '../schemas/i18n.js';
-import type { ChapterSchema, LessonSchema, PartSchema } from '../schemas/index.js';
+import type { ChapterSchema, CustomSchema, LessonSchema, PartSchema } from '../schemas/index.js';
import type { MetaTagsSchema } from '../schemas/metatags.js';
export type * from './nav.js';
@@ -60,6 +60,8 @@ export type I18n = Required>;
export type MetaTagsConfig = MetaTagsSchema;
+export type CustomConfig = CustomSchema;
+
export interface Tutorial {
logoLink?: string;
firstPartId?: string;
diff --git a/packages/types/src/schemas/common.ts b/packages/types/src/schemas/common.ts
index faa8be849..cd854b563 100644
--- a/packages/types/src/schemas/common.ts
+++ b/packages/types/src/schemas/common.ts
@@ -203,13 +203,18 @@ export const editorSchema = z.union([
}),
]);
+const customSchema = z.record(z.string(), z.any());
+
export type TerminalPanelType = z.infer;
export type TerminalSchema = z.infer;
export type EditorSchema = z.infer;
+export type CustomSchema = z.infer;
export const webcontainerSchema = commandsSchema.extend({
meta: metaTagsSchema.optional(),
+ custom: customSchema.optional().describe('Assign custom fields to a chapter/part/lesson in the Astro collection'),
+
previews: previewSchema
.optional()
.describe(