diff --git a/src/frontend/config/sidebar/integrations.topics.ts b/src/frontend/config/sidebar/integrations.topics.ts
index d4f2ea622..adda040d1 100644
--- a/src/frontend/config/sidebar/integrations.topics.ts
+++ b/src/frontend/config/sidebar/integrations.topics.ts
@@ -1336,6 +1336,7 @@ export const integrationTopics: StarlightSidebarTopicsUserConfig = {
{ label: 'JavaScript', slug: 'integrations/frameworks/javascript' },
{ label: 'Node.js extensions', slug: 'integrations/frameworks/nodejs-extensions' },
{ label: 'Orleans', slug: 'integrations/frameworks/orleans' },
+ { label: 'Perl', slug: 'integrations/frameworks/perl' },
{ label: 'PowerShell', slug: 'integrations/frameworks/powershell' },
{ label: 'Python', slug: 'integrations/frameworks/python' },
{ label: 'Rust', slug: 'integrations/frameworks/rust' },
diff --git a/src/frontend/src/assets/icons/perl-096-1500.svg b/src/frontend/src/assets/icons/perl-096-1500.svg
new file mode 100644
index 000000000..35b3f2eba
--- /dev/null
+++ b/src/frontend/src/assets/icons/perl-096-1500.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/frontend/src/content/docs/community/contributor-guide.mdx b/src/frontend/src/content/docs/community/contributor-guide.mdx
index 29b866d03..aca43ae5b 100644
--- a/src/frontend/src/content/docs/community/contributor-guide.mdx
+++ b/src/frontend/src/content/docs/community/contributor-guide.mdx
@@ -174,6 +174,48 @@ Without the blank lines between steps, the inner content won't render correctly.
+## 🧩 Adding a new framework integration
+
+If you've built a new Community Toolkit hosting integration (e.g. `CommunityToolkit.Aspire.Hosting.`) and want to document it on `aspire.dev`, you'll need to touch several files. Here's a summary of the steps, using the Perl integration as an example:
+
+
+
+1. **Create the documentation page**
+
+ Add a new MDX file at `src/frontend/src/content/docs/integrations/frameworks/.mdx`. Use an existing framework page (such as `python.mdx` or `java.mdx`) as a template. Include frontmatter with a `title`, any required component imports, and a `Badge` indicating it's a Community Toolkit integration.
+
+1. **Add an icon asset**
+
+ Place an SVG icon for the framework in `src/frontend/src/assets/icons/`. Reference it in your MDX page with an `Image` component import.
+
+1. **Register the sidebar entry**
+
+ In `src/frontend/config/sidebar/integrations.topics.ts`, add a new entry for your framework in the frameworks list (keep alphabetical order):
+
+ ```ts
+ { label: '', slug: 'integrations/frameworks/' },
+ ```
+
+1. **Add the NuGet package name**
+
+ In `src/frontend/src/data/aspire-integration-names.json`, add the full NuGet package name for your integration (keep alphabetical order):
+
+ ```json
+ "CommunityToolkit.Aspire.Hosting.",
+ ```
+
+1. **Add license attribution**
+
+ If your integration uses assets or technologies with specific licenses, add entries in `src/frontend/src/data/thanks-license-titles.ts`:
+
+ ```ts
+ '': ': ',
+ ```
+
+
+
+After making these changes, run `pnpm dev` (or `aspire start`) locally to verify the page renders correctly, the sidebar navigation works, and the icon displays as expected.
+
## ✍️ Writing style guide
diff --git a/src/frontend/src/content/docs/integrations/frameworks/perl.mdx b/src/frontend/src/content/docs/integrations/frameworks/perl.mdx
new file mode 100644
index 000000000..aec50a572
--- /dev/null
+++ b/src/frontend/src/content/docs/integrations/frameworks/perl.mdx
@@ -0,0 +1,390 @@
+---
+title: Perl integration
+---
+
+import { Badge } from '@astrojs/starlight/components';
+import { Image } from 'astro:assets';
+import perlIcon from '@assets/icons/perl-096-1500.svg';
+
+
+
+
+
+## Using the Perl Hosting Integration
+
+A guide for developers using `CommunityToolkit.Aspire.Hosting.Perl` for the first time, or as a
+reference when revisiting the API. This document explains how key hosting API calls map to on-disk
+directory layout, environment variable configuration, and runtime behavior.
+
+---
+
+## Table of Contents
+
+1. [Quick Start](#quick-start)
+2. [Core Concepts](#core-concepts)
+3. [The `appDirectory` Parameter](#the-appdirectory-parameter)
+4. [WithLocalLib](#withlocallib)
+5. [Package Management](#package-management)
+ - [WithCpanMinus + WithPackage](#withcpanminus--withpackage)
+ - [WithCpanMinus + WithProjectDependencies](#withcpanminus--withprojectdependencies)
+ - [WithCarton + WithProjectDependencies](#withcarton--withprojectdependencies)
+6. [WithPerlbrewEnvironment](#withperlbrewenvironment)
+7. [Common Pitfalls](#common-pitfalls)
+8. [Reporting an Issue](#reporting-issues)
+
+---
+
+## Quick Start
+
+Install the package in your AppHost project:
+
+```dotnetcli
+dotnet add package CommunityToolkit.Aspire.Hosting.Perl
+```
+
+Add a Perl script resource in your `AppHost.cs`:
+
+```csharp
+var builder = DistributedApplication.CreateBuilder(args);
+
+builder.AddPerlScript("my-worker", "scripts", "Worker.pl")
+ .WithCpanMinus()
+ .WithPackage("Some::Module", skipTest: true)
+ .WithLocalLib("local");
+
+builder.Build().Run();
+```
+
+---
+
+## Core Concepts
+
+The integration provides two entry points for adding Perl resources:
+
+| Method | Purpose |
+|--------|---------|
+| `AddPerlScript(name, appDirectory, scriptName)` | Adds a Perl script (worker, CLI tool, etc.) |
+| `AddPerlApi(name, appDirectory, scriptName)` | Adds a Perl API server (e.g., Mojolicious `daemon`) |
+
+Both create a `PerlAppResource` that appears in the Aspire dashboard. All subsequent configuration
+methods (`.WithCpanMinus()`, `.WithLocalLib()`, etc.) chain off the resource builder.
+
+---
+
+## The `appDirectory` Parameter
+
+`appDirectory` is the **anchor for all relative path resolution** in the integration. It determines:
+
+- The resource's `WorkingDirectory` — where Perl runs
+- Where `WithLocalLib("local")` resolves to
+- Where cpanfile discovery happens (for `WithProjectDependencies`)
+- The base for the script path
+
+`appDirectory` is resolved relative to the **AppHost project directory** (the folder containing
+the `.csproj`).
+
+### `"."` — AppHost-rooted
+
+When `appDirectory` is `"."`, the working directory is the AppHost project folder itself. Files like
+`cpanfile`, `cpanfile.snapshot`, and the `local/` directory all live alongside the `.csproj`:
+
+```
+MyApp.AppHost/
+├── AppHost.cs
+├── MyApp.AppHost.csproj
+├── cpanfile ← discovered here
+├── cpanfile.snapshot
+├── local/ ← WithLocalLib("local") resolves here
+│ └── lib/perl5/...
+└── Properties/
+scripts/
+└── API.pl ← script path "../scripts/API.pl"
+```
+
+### `"../scripts"` — sibling folder
+
+When `appDirectory` is `"../scripts"`, the working directory shifts to a sibling `scripts/` folder.
+Everything resolves relative to that folder:
+
+```
+MyApp.AppHost/
+├── AppHost.cs
+├── MyApp.AppHost.csproj
+└── Properties/
+scripts/ ← working directory
+├── Worker.pl ← script path "Worker.pl"
+└── local/ ← WithLocalLib("local") resolves here
+ └── lib/perl5/...
+```
+
+> **Key insight:** The script path in `AddPerlScript` / `AddPerlApi` is relative to `appDirectory`,
+> and so is everything else — `WithLocalLib`, cpanfile discovery, and the process working directory.
+
+---
+
+## WithLocalLib
+
+```csharp
+.WithLocalLib("local") // relative path — resolved against appDirectory
+.WithLocalLib("/opt/lib") // rooted Unix-style path — used as-is
+.WithLocalLib("C:\\perl-lib") // rooted Windows path — used as-is
+```
+
+`WithLocalLib` configures [local::lib](https://metacpan.org/pod/local::lib)-style module isolation.
+The `path` parameter is resolved **relative to the resource's working directory** (`appDirectory`),
+not relative to the AppHost project, unless the path is already rooted.
+
+Implementation note: `WithLocalLib` path resolution uses `Path.IsPathRooted(configuredPath)`.
+If `true`, the value is used directly. If `false`, it is combined with the resource working
+directory and converted to an absolute path.
+
+### What it sets
+
+| Environment Variable | Value |
+|---------------------|-------|
+| `PERL5LIB` | `/lib/perl5` |
+| `PERL_LOCAL_LIB_ROOT` | `` |
+| `PERL_MM_OPT` | `INSTALL_BASE=` |
+| `PERL_MB_OPT` | `--install_base ` |
+
+These ensure that:
+- Perl finds modules in the local directory at runtime (`@INC`)
+- Package managers install modules into the local directory
+- No `sudo` or system-level permissions required
+
+### Resolution examples
+
+| `appDirectory` | `WithLocalLib(...)` | Resolved absolute path |
+|----------------|---------------------|----------------------|
+| `"."` | `"local"` | `/local` |
+| `"../scripts"` | `"local"` | `/../scripts/local` |
+| `"."` | `"/opt/perl-libs"` | `/opt/perl-libs` (Linux/macOS) |
+| `"."` | `"C:\\perl-libs"` | `C:\\perl-libs` (Windows) |
+
+---
+
+## Package Management
+
+While I highly recommend you use cpanm or Carton, the integration aims to support three package managers and two installation strategies:
+
+| Package Manager | Individual Packages | Project Dependencies |
+|----------------|-------------------|---------------------|
+| **cpan** (default) | ✅ `.WithPackage("Module")` | ❌ Not supported (auto-switches to cpanm when calling `.WithProjectDependencies()`) |
+| **cpanm** (App::cpanminus) | ✅ `.WithCpanMinus().WithPackage("Module")` | ✅ `.WithCpanMinus().WithProjectDependencies()` |
+| **Carton** | ❌ Not supported | ✅ `.WithCarton().WithProjectDependencies()` |
+
+> The default package manager is `cpan`, but it is automatically switched to `cpanm` when
+> `WithProjectDependencies()` is called, since `cpan` does not support `--installdeps`.
+> `WithLocalLib()` will also currently swap to `cpanm` because it wasn't clear to me at time of release how to integrate it with cpan.
+
+### WithCpanMinus + WithPackage
+
+Installs individual modules by name before the application starts.
+
+```csharp
+builder.AddPerlScript("worker", "../scripts", "Worker.pl")
+ .WithCpanMinus()
+ .WithPackage("OpenTelemetry::SDK", skipTest: true)
+ .WithLocalLib("local");
+```
+
+**What happens at startup:**
+1. A child installer resource runs `cpanm --notest --local-lib /local OpenTelemetry::SDK`
+2. The module is installed into `scripts/local/lib/perl5/`
+3. After installation, the main script starts with `PERL5LIB` pointing to the local directory
+
+**Resulting directory structure:**
+
+```
+my-example/
+├── MyExample.AppHost/
+│ ├── AppHost.cs
+│ └── MyExample.AppHost.csproj
+└── scripts/ ← working directory (appDirectory = "../scripts")
+ ├── Worker.pl
+ └── local/
+ └── lib/
+ └── perl5/
+ └── OpenTelemetry/
+ └── SDK.pm
+```
+
+**Options:**
+
+| Parameter | Effect |
+|-----------|--------|
+| `force: true` | Passes `--force` — reinstalls even if already present |
+| `skipTest: true` | Passes `--notest` — skips running the module's test suite |
+
+### WithCpanMinus + WithProjectDependencies
+
+Installs all modules listed in a `cpanfile` in the working directory.
+
+```csharp
+builder.AddPerlApi("api", ".", "../scripts/API.pl")
+ .WithCpanMinus()
+ .WithProjectDependencies()
+ .WithLocalLib("local");
+```
+
+**What happens at startup:**
+1. The integration looks for `cpanfile` in the working directory
+2. Runs `cpanm --installdeps --notest .` (with `--local-lib` if configured)
+3. All dependencies from the cpanfile are installed
+
+**Expected cpanfile location:** `/cpanfile`
+
+### WithCarton + WithProjectDependencies
+
+[Carton](https://metacpan.org/pod/Carton) is a dependency manager for Perl that provides
+reproducible builds via a lock file (`cpanfile.snapshot`).
+
+```csharp
+builder.AddPerlApi("api", ".", "../scripts/API.pl")
+ .WithCarton()
+ .WithProjectDependencies(cartonDeployment: false)
+ .WithLocalLib("local");
+```
+
+**What happens at startup:**
+1. The integration looks for `cpanfile` and optionally `cpanfile.snapshot` in the working directory
+2. Runs `carton install` (or `carton install --deployment` if `cartonDeployment: true`)
+3. Carton creates `local/` adjacent to the `cpanfile`
+
+**Deployment mode (`cartonDeployment: true`):** Installs exact versions from `cpanfile.snapshot`,
+ensuring production builds match development. Fails if the snapshot is missing or out of date.
+
+**Resulting directory structure (appDirectory = "."):**
+
+```
+my-example/
+├── MyApp.AppHost/ ← working directory (appDirectory = ".")
+│ ├── AppHost.cs
+│ ├── MyApp.AppHost.csproj
+│ ├── cpanfile
+│ ├── cpanfile.snapshot
+│ └── local/
+│ └── lib/
+│ └── perl5/
+│ ├── Mojolicious/
+│ └── ...
+└── scripts/
+ └── API.pl ← script path "../scripts/API.pl"
+```
+
+> **Important:** Carton only supports project-level dependency installation. Calling `.WithPackage()`
+> after `.WithCarton()` will throw an `InvalidOperationException`. If you need to install individual
+> modules alongside Carton-managed dependencies, use `.WithCpanMinus()` on a separate resource.
+
+---
+
+## WithPerlbrewEnvironment
+
+[Perlbrew](https://perlbrew.pl/) manages multiple Perl installations. This method configures the
+resource to use a specific perlbrew-managed Perl version.
+
+```csharp
+builder.AddPerlScript("perlbrew-worker", "../scripts", "Worker.pl")
+ .WithPerlbrewEnvironment("perl-5.42.0");
+```
+
+**What it configures:**
+- Resolves the Perl binary from the perlbrew installation
+- Sets `PERLBREW_ROOT`, `PERLBREW_PERL`, and `PERLBREW_HOME`
+- Prepends the perlbrew `bin/` to `PATH`
+
+**Interaction with WithLocalLib:** If `.WithLocalLib("local")` is chained, modules are installed
+into the local directory, not the perlbrew tree. This keeps the perlbrew installation clean and
+allows per-project isolation. `WithLocalLib` is optional when using perlbrew.
+
+> **Note:** Perlbrew is Linux-only. On Windows, the integration will display a notification
+> recommending [Berrybrew](https://github.com/stevieb9/berrybrew). Windows support for Berrybrew
+> is on the roadmap.
+
+---
+
+## Common Pitfalls
+
+### WithLocalLib resolves relative to `appDirectory`, not the AppHost
+
+```csharp
+// appDirectory = "../scripts", WithLocalLib("local")
+// ✅ Resolves to: scripts/local/
+// ❌ Does NOT resolve to: MyApp.AppHost/local/
+```
+
+If you expect the `local/` folder next to your `.csproj`, set `appDirectory` to `"."`.
+
+### Choosing to skip WithLocalLib modifies shared Perl installs
+
+It is valid to skip `WithLocalLib` if you intentionally want a shared/global module install.
+That can be useful for common libraries on dev machines.
+
+The tradeoff is that installs target your platform Perl distribution instead of a project-local
+folder. In practice this often means:
+
+- Linux (especially OS-managed Perl): writes to system or user Perl paths and may require elevated permissions
+- Windows: writes into the active Strawberry Perl or ActiveState Perl environment
+
+This can be convenient, but it can also create drift across machines and affect unrelated projects.
+Proceed with caution.
+
+### Mixing WithCarton and WithPackage
+
+Carton manages all dependencies through `cpanfile`. Calling `.WithPackage()` after `.WithCarton()`
+will throw an `InvalidOperationException`:
+
+```csharp
+// ❌ This throws — Carton does not support individual module installation
+builder.AddPerlApi("api", ".", "api.pl")
+ .WithCarton()
+ .WithPackage("Some::Module");
+
+// ✅ Instead, add the module to your cpanfile:
+// requires 'Some::Module';
+```
+
+### Script path is relative to `appDirectory`
+
+The `scriptName` parameter is resolved relative to `appDirectory`. Don't include the `appDirectory`
+in the script path:
+
+```csharp
+// ❌ Double-nests the path
+builder.AddPerlScript("worker", "../scripts", "../scripts/Worker.pl");
+
+// ✅ Correct — script path is relative to appDirectory
+builder.AddPerlScript("worker", "../scripts", "Worker.pl");
+```
+
+### cpanfile must be in the working directory
+
+`WithProjectDependencies` looks for `cpanfile` in the resource's working directory (`appDirectory`).
+If your cpanfile is in a different location, adjust `appDirectory` accordingly.
+
+### cpanfile example
+
+Use a `cpanfile` to declare project dependencies for `WithProjectDependencies()`.
+
+```perl
+requires 'Mojolicious', '>= 9.0';
+requires 'OpenTelemetry::SDK';
+
+on 'test' => sub {
+ requires 'Test::More', '>= 1.302190';
+};
+```
+
+Further reading:
+- [CPAN::cpanfile reference](https://github.com/miyagawa/cpanfile/blob/master/README.md)
+
+## Reporting Issues
+
+If you notice an issue with these docs while using the integration, please file an issue at [microsoft/aspire.dev](https://github.com/microsoft/aspire.dev)
\ No newline at end of file
diff --git a/src/frontend/src/data/aspire-integration-names.json b/src/frontend/src/data/aspire-integration-names.json
index 098d801bf..23b70f67e 100644
--- a/src/frontend/src/data/aspire-integration-names.json
+++ b/src/frontend/src/data/aspire-integration-names.json
@@ -117,6 +117,7 @@
"CommunityToolkit.Aspire.Hosting.Ollama",
"CommunityToolkit.Aspire.Hosting.OpenTelemetryCollector",
"CommunityToolkit.Aspire.Hosting.PapercutSmtp",
+ "CommunityToolkit.Aspire.Hosting.Perl",
"CommunityToolkit.Aspire.Hosting.PostgreSQL.Extensions",
"CommunityToolkit.Aspire.Hosting.PowerShell",
"CommunityToolkit.Aspire.Hosting.Python.Extensions",
diff --git a/src/frontend/src/data/thanks-license-titles.ts b/src/frontend/src/data/thanks-license-titles.ts
index 57f292c8a..372618bc8 100644
--- a/src/frontend/src/data/thanks-license-titles.ts
+++ b/src/frontend/src/data/thanks-license-titles.ts
@@ -49,4 +49,6 @@ export const licenseTitles = {
dapr: 'Apache-2.0: https://github.com/dapr/dapr/blob/master/LICENSE',
hex1b: 'MIT: https://hex1b.dev',
asciinema: 'GPL-3.0: https://github.com/asciinema/asciinema/blob/develop/LICENSE',
+ perlAssets: 'CC-BY-4.0: https://github.com/metacpan/perl-assets?tab=CC-BY-4.0-1-ov-file#readme',
+ perl: 'Artistic License: https://dev.perl.org/licenses/artistic.html',
} as const;
\ No newline at end of file