diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc
index 7963900b..5daaec9e 100644
--- a/docs/modules/ROOT/nav.adoc
+++ b/docs/modules/ROOT/nav.adoc
@@ -1 +1,2 @@
* xref:index.adoc[Quarkus Qute Web]
+* xref:index.adoc[Quarkus Qute Markdown]
diff --git a/docs/modules/ROOT/pages/markdown.adoc b/docs/modules/ROOT/pages/markdown.adoc
new file mode 100644
index 00000000..6324f691
--- /dev/null
+++ b/docs/modules/ROOT/pages/markdown.adoc
@@ -0,0 +1,107 @@
+= Quarkus Qute Markdown
+
+include::./includes/attributes.adoc[]
+
+The goal of this extension is to provide a simple way to render Markdown templates using Qute. It adds a new Qute section
+named `markdown` or `md` that can be used to convert the content of the section to HTML using a Markdown processor.
+
+== Installation
+
+To install the extension, add the following dependency to your project:
+
+[source,xml,subs=attributes+]
+----
+
+ io.quarkiverse.qute-markdown
+ quarkus-qute-web-markdown
+ 999-SNAPSHOT
+
+----
+
+== Usage
+
+This extension can be used to render Markdown templates inside a Qute template or in a web application using the `quarkus-qute-web` extension.
+
+=== Standalone Usage
+
+Create a new Qute template with `foo.txt` name.
+
+[source,html]
+----
+
+{#md}
+# Hello World
+{/md}
+----
+
+Create a new resource class and inject the template using the `@Inject` annotation. Then, use the `render` method to render the template.
+[source,java]
+----
+package com.foo;
+
+import io.quarkus.qute.Template;
+import jakarta.inject.Inject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.core.MediaType;
+
+@Path("/hello")
+public class GreetingResource {
+
+ @Inject
+ Template foo;
+
+ @GET
+ @Produces(MediaType.TEXT_PLAIN)
+ public String hello() {
+ return foo.render();
+ }
+}
+----
+
+Finally, start your application and open the following URL in your browser: `http://localhost:8080/hello`.
+
+The output should be the following:
+
+[source,html]
+----
+
Hello World
+----
+
+=== Integration with Qute Web
+
+This extension can be combined with the `quarkus-qute-web` extension to render Markdown templates in a web application.
+Add the `quarkus-qute-web` extension to your project, inside the `pom.xml` file:
+
+[source,xml,subs=attributes+]
+----
+
+ io.quarkiverse.qute.web
+ quarkus-qute-web
+ ${project.version}
+
+----
+
+Then, create a new Qute template with the `.txt` or `.html` extension.
+
+[source,html]
+----
+
+
+
+
+
+ My Qute Page with Markdown
+
+
+ Hello World!
+ {#md}
+ # This is a Markdown section
+ It will be converted to HTML using a Markdown processor.
+ {/md}
+
+
+----
+
+Finally, start your application and open the following URL in your browser: `http://localhost:8080/markdown`.
\ No newline at end of file
diff --git a/examples/markdown-example/pom.xml b/examples/markdown-example/pom.xml
new file mode 100644
index 00000000..7d635c7f
--- /dev/null
+++ b/examples/markdown-example/pom.xml
@@ -0,0 +1,124 @@
+
+
+ 4.0.0
+ io.quarkiverse.qute.web
+ quarkus-qute-web-restclient-example
+ 1.0.0-SNAPSHOT
+
+ 3.10.1
+ 17
+ UTF-8
+ UTF-8
+ quarkus-bom
+ io.quarkus
+ 3.6.6
+ 3.0.0
+ 999-SNAPSHOT
+ true
+ 3.0.0-M7
+
+
+
+
+ ${quarkus.platform.group-id}
+ ${quarkus.platform.artifact-id}
+ ${quarkus.platform.version}
+ pom
+ import
+
+
+
+
+
+ io.quarkus
+ quarkus-rest-client-reactive-jackson
+
+
+ io.quarkiverse.qute.web
+ quarkus-qute-web
+ ${qute.web.version}
+
+
+ io.quarkiverse.qute.web
+ quarkus-qute-web-markdown
+ ${qute.web.markdown.version}
+
+
+
+
+
+ ${quarkus.platform.group-id}
+ quarkus-maven-plugin
+ ${quarkus.platform.version}
+ true
+
+
+
+ build
+ generate-code
+ generate-code-tests
+
+
+
+
+
+ maven-compiler-plugin
+ ${compiler-plugin.version}
+
+
+ -parameters
+
+
+
+
+ maven-surefire-plugin
+ ${surefire-plugin.version}
+
+
+
+ org.jboss.logmanager.LogManager
+ ${maven.home}
+
+
+
+
+ maven-failsafe-plugin
+ ${surefire-plugin.version}
+
+
+
+ integration-test
+ verify
+
+
+
+
+ ${project.build.directory}/${project.build.finalName}-runner
+
+ org.jboss.logmanager.LogManager
+ ${maven.home}
+
+
+
+
+
+
+
+
+
+ native
+
+
+ native
+
+
+
+ false
+ native
+
+
+
+
diff --git a/examples/markdown-example/src/main/resources/application.properties b/examples/markdown-example/src/main/resources/application.properties
new file mode 100644
index 00000000..d89858e0
--- /dev/null
+++ b/examples/markdown-example/src/main/resources/application.properties
@@ -0,0 +1 @@
+quarkus.tls.trust-all=true
\ No newline at end of file
diff --git a/examples/markdown-example/src/main/resources/templates/pub/markdown.html b/examples/markdown-example/src/main/resources/templates/pub/markdown.html
new file mode 100644
index 00000000..1ae01981
--- /dev/null
+++ b/examples/markdown-example/src/main/resources/templates/pub/markdown.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+ My Qute Page with Markdown
+
+
+Hello World!
+{#md}
+## This is a Markdown section
+It will be converted to HTML using a Markdown processor.
+{/md}
+
+
\ No newline at end of file
diff --git a/markdown/deployment/pom.xml b/markdown/deployment/pom.xml
new file mode 100644
index 00000000..62d1fb52
--- /dev/null
+++ b/markdown/deployment/pom.xml
@@ -0,0 +1,46 @@
+
+
+ 4.0.0
+
+
+ io.quarkiverse.qute.web
+ quarkus-qute-web-markdown-parent
+ 999-SNAPSHOT
+
+ quarkus-qute-web-markdown-deployment
+ Quarkus Qute Web - Markdown - Deployment
+
+
+
+ io.quarkiverse.qute.web
+ quarkus-qute-web-markdown
+ ${project.version}
+
+
+ io.quarkus
+ quarkus-junit5-internal
+ test
+
+
+ io.quarkus
+ quarkus-qute-deployment
+
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${quarkus.version}
+
+
+
+
+
+
+
diff --git a/markdown/deployment/src/main/java/io/quarkiverse/qute/web/markdown/deployment/QuteWebMarkdownProcessor.java b/markdown/deployment/src/main/java/io/quarkiverse/qute/web/markdown/deployment/QuteWebMarkdownProcessor.java
new file mode 100644
index 00000000..121e9591
--- /dev/null
+++ b/markdown/deployment/src/main/java/io/quarkiverse/qute/web/markdown/deployment/QuteWebMarkdownProcessor.java
@@ -0,0 +1,22 @@
+package io.quarkiverse.qute.web.markdown.deployment;
+
+import io.quarkiverse.qute.web.markdown.runtime.MarkdownSectionHelperFactory;
+import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
+import io.quarkus.deployment.annotations.BuildProducer;
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.builditem.FeatureBuildItem;
+
+class QuteWebMarkdownProcessor {
+
+ private static final String FEATURE = "qute-web-markdown";
+
+ @BuildStep
+ FeatureBuildItem feature() {
+ return new FeatureBuildItem(FEATURE);
+ }
+
+ @BuildStep
+ void process(BuildProducer additionalBeans) {
+ additionalBeans.produce(new AdditionalBeanBuildItem(MarkdownSectionHelperFactory.class));
+ }
+}
diff --git a/markdown/deployment/src/test/java/io/quarkiverse/qute/web/markdown/test/QuarkusMarkdownTest.java b/markdown/deployment/src/test/java/io/quarkiverse/qute/web/markdown/test/QuarkusMarkdownTest.java
new file mode 100644
index 00000000..98a1f645
--- /dev/null
+++ b/markdown/deployment/src/test/java/io/quarkiverse/qute/web/markdown/test/QuarkusMarkdownTest.java
@@ -0,0 +1,66 @@
+package io.quarkiverse.qute.web.markdown.test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import io.quarkiverse.qute.web.markdown.runtime.MarkdownSectionHelperFactory;
+import io.quarkus.qute.Engine;
+
+public class QuarkusMarkdownTest {
+
+ @Test
+ public void testMd() {
+ Engine engine = Engine.builder().addDefaults()
+ .addSectionHelper(new MarkdownSectionHelperFactory()).build();
+ assertEquals("...
\n", engine.parse("{#md}...{/md}").render());
+ }
+
+ @Test
+ public void testMarkdown() {
+ Engine engine = Engine.builder().addDefaults()
+ .addSectionHelper(new MarkdownSectionHelperFactory()).build();
+ assertEquals("...
\n", engine.parse("{#markdown}...{/markdown}").render());
+ }
+
+ @Test
+ public void testH1() {
+ Engine engine = Engine.builder().addDefaults()
+ .addSectionHelper(new MarkdownSectionHelperFactory()).build();
+ assertEquals("Quarkus and Roq
\n", engine.parse("{#md}# Quarkus and Roq{/md}").render());
+ }
+
+ @Test
+ void testJsonObjectValueResolver() {
+
+ Engine engine = Engine.builder().addDefaults()
+ .addSectionHelper(new MarkdownSectionHelperFactory()).build();
+
+ String result = engine.parse("""
+ Quarkus and Qute
+ {#md}
+ # Qute and Roq
+ Here is a list:
+ {#for item in items}
+ - an {item} as a list item
+ {/for}
+ {/md}
+ """).data("items", List.of("apple", "banana", "cherry"))
+ .render();
+
+ Assertions.assertEquals("""
+ Quarkus and Qute
+ Qute and Roq
+ Here is a list:
+
+ - an apple as a list item
+ - an banana as a list item
+ - an cherry as a list item
+
+ """, result);
+
+ }
+}
diff --git a/markdown/deployment/src/test/java/io/quarkiverse/qute/web/markdown/test/QuteMarkdownSectionHelperTest.java b/markdown/deployment/src/test/java/io/quarkiverse/qute/web/markdown/test/QuteMarkdownSectionHelperTest.java
new file mode 100644
index 00000000..e0a213fc
--- /dev/null
+++ b/markdown/deployment/src/test/java/io/quarkiverse/qute/web/markdown/test/QuteMarkdownSectionHelperTest.java
@@ -0,0 +1,33 @@
+package io.quarkiverse.qute.web.markdown.test;
+
+import jakarta.inject.Inject;
+
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.qute.Template;
+import io.quarkus.test.QuarkusUnitTest;
+
+public class QuteMarkdownSectionHelperTest {
+
+ @RegisterExtension
+ static final QuarkusUnitTest quarkusApp = new QuarkusUnitTest()
+ .withApplicationRoot(
+ app -> app.addAsResource(new StringAsset(
+ "{#markdown}# Qute and Roq{/markdown}"),
+ "templates/foo.txt"));
+
+ @Inject
+ Template foo;
+
+ @Test
+ void testJsonObjectValueResolver() {
+ String result = foo.render();
+
+ System.out.println(result);
+
+ Assertions.assertEquals("Qute and Roq
\n", result);
+ }
+}
diff --git a/markdown/deployment/src/test/java/io/quarkiverse/qute/web/markdown/test/QuteMarkdownSectionWithInnerSectionTest.java b/markdown/deployment/src/test/java/io/quarkiverse/qute/web/markdown/test/QuteMarkdownSectionWithInnerSectionTest.java
new file mode 100644
index 00000000..0e8427c3
--- /dev/null
+++ b/markdown/deployment/src/test/java/io/quarkiverse/qute/web/markdown/test/QuteMarkdownSectionWithInnerSectionTest.java
@@ -0,0 +1,53 @@
+package io.quarkiverse.qute.web.markdown.test;
+
+import java.util.List;
+
+import jakarta.inject.Inject;
+
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.qute.Template;
+import io.quarkus.test.QuarkusUnitTest;
+
+public class QuteMarkdownSectionWithInnerSectionTest {
+
+ @RegisterExtension
+ static final QuarkusUnitTest quarkusApp = new QuarkusUnitTest()
+ .withApplicationRoot(
+ app -> app.addAsResource(new StringAsset(
+ """
+ Quarkus and Qute
+ {#md}
+ # Qute and Roq
+ Here is a list:
+ {#for item in items}
+ - an {item} as a list item
+ {/for}
+ {/md}
+ """),
+ "templates/foo.txt"));
+
+ @Inject
+ Template foo;
+
+ @Test
+ void testJsonObjectValueResolver() {
+ String result = foo.data("items", List.of("apple", "banana", "cherry"))
+ .render();
+
+ Assertions.assertEquals("""
+ Quarkus and Qute
+ Qute and Roq
+ Here is a list:
+
+ - an apple as a list item
+ - an banana as a list item
+ - an cherry as a list item
+
+ """, result);
+
+ }
+}
diff --git a/markdown/pom.xml b/markdown/pom.xml
new file mode 100644
index 00000000..3c5142a0
--- /dev/null
+++ b/markdown/pom.xml
@@ -0,0 +1,20 @@
+
+
+ 4.0.0
+
+
+ io.quarkiverse.qute.web
+ quarkus-qute-web-parent
+ 999-SNAPSHOT
+ ../pom.xml
+
+ quarkus-qute-web-markdown-parent
+ 999-SNAPSHOT
+ pom
+ Quarkus Qute Web - Markdown - Parent
+
+
+ deployment
+ runtime
+
+
diff --git a/markdown/runtime/pom.xml b/markdown/runtime/pom.xml
new file mode 100644
index 00000000..c956518a
--- /dev/null
+++ b/markdown/runtime/pom.xml
@@ -0,0 +1,57 @@
+
+
+ 4.0.0
+
+
+ io.quarkiverse.qute.web
+ quarkus-qute-web-markdown-parent
+ 999-SNAPSHOT
+
+ quarkus-qute-web-markdown
+ Quarkus Qute - Markdown - Runtime
+
+
+
+ io.quarkus
+ quarkus-qute
+
+
+ org.commonmark
+ commonmark
+ ${commonmark.version}
+
+
+
+
+
+
+ io.quarkus
+ quarkus-extension-maven-plugin
+ ${quarkus.version}
+
+
+ compile
+
+ extension-descriptor
+
+
+ ${project.groupId}:${project.artifactId}-deployment:${project.version}
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${quarkus.version}
+
+
+
+
+
+
+
diff --git a/markdown/runtime/src/main/java/io/quarkiverse/qute/web/markdown/runtime/MarkdownSectionHelperFactory.java b/markdown/runtime/src/main/java/io/quarkiverse/qute/web/markdown/runtime/MarkdownSectionHelperFactory.java
new file mode 100644
index 00000000..1eb7b51b
--- /dev/null
+++ b/markdown/runtime/src/main/java/io/quarkiverse/qute/web/markdown/runtime/MarkdownSectionHelperFactory.java
@@ -0,0 +1,40 @@
+package io.quarkiverse.qute.web.markdown.runtime;
+
+import java.util.List;
+import java.util.concurrent.CompletionStage;
+
+import io.quarkiverse.qute.web.markdown.runtime.commonmark.CommonMarkConverter;
+import io.quarkus.qute.CompletedStage;
+import io.quarkus.qute.EngineConfiguration;
+import io.quarkus.qute.ResultNode;
+import io.quarkus.qute.SectionHelper;
+import io.quarkus.qute.SectionHelperFactory;
+import io.quarkus.qute.SingleResultNode;
+
+@EngineConfiguration
+public class MarkdownSectionHelperFactory implements SectionHelperFactory {
+ private static final List MARKDOWN_SECTIONS = List.of("markdown", "md");
+
+ @Override
+ public List getDefaultAliases() {
+ return MARKDOWN_SECTIONS;
+ }
+
+ @Override
+ public MarkdownSectionHelper initialize(SectionInitContext context) {
+ return new MarkdownSectionHelper();
+ }
+
+ static class MarkdownSectionHelper implements SectionHelper {
+ private final CommonMarkConverter converter = new CommonMarkConverter();
+
+ @Override
+ public CompletionStage resolve(SectionResolutionContext context) {
+ return context.execute().thenCompose(rn -> {
+ StringBuilder sb = new StringBuilder();
+ rn.process(sb::append);
+ return CompletedStage.of(new SingleResultNode(converter.apply(sb.toString())));
+ });
+ }
+ }
+}
diff --git a/markdown/runtime/src/main/java/io/quarkiverse/qute/web/markdown/runtime/commonmark/CommonMarkConverter.java b/markdown/runtime/src/main/java/io/quarkiverse/qute/web/markdown/runtime/commonmark/CommonMarkConverter.java
new file mode 100644
index 00000000..7a20653c
--- /dev/null
+++ b/markdown/runtime/src/main/java/io/quarkiverse/qute/web/markdown/runtime/commonmark/CommonMarkConverter.java
@@ -0,0 +1,16 @@
+package io.quarkiverse.qute.web.markdown.runtime.commonmark;
+
+import org.commonmark.node.Node;
+import org.commonmark.parser.Parser;
+import org.commonmark.renderer.html.HtmlRenderer;
+
+public class CommonMarkConverter {
+
+ private final Parser parser = Parser.builder().build();
+ private final HtmlRenderer htmlRenderer = HtmlRenderer.builder().build();
+
+ public String apply(String markdown) {
+ Node node = parser.parse(markdown);
+ return htmlRenderer.render(node);
+ }
+}
diff --git a/markdown/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/markdown/runtime/src/main/resources/META-INF/quarkus-extension.yaml
new file mode 100644
index 00000000..63821329
--- /dev/null
+++ b/markdown/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -0,0 +1,15 @@
+name: Qute Web Markdown
+#description: Do something useful.
+metadata:
+ keywords:
+ - qute-web-markdown
+ - roq
+ - qute
+ - website
+ - templates
+ - markdown
+ - md
+ guide: https://quarkiverse.github.io/quarkiverse-docs/quarkus-qute-web/dev/index.html # To create and publish this guide, see https://github.com/quarkiverse/quarkiverse/wiki#documenting-your-extension
+ categories:
+ - "miscellaneous"
+ status: "preview"
diff --git a/pom.xml b/pom.xml
index c5673a55..79e5e84a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -15,6 +15,7 @@
deployment
runtime
docs
+ markdown
scm:git:git@github.com:quarkiverse/quarkus-qute-web.git
@@ -28,6 +29,7 @@
UTF-8
UTF-8
3.6.9
+ 0.22.0