Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/frontend/src/content/docs/dashboard/explore.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -579,13 +579,17 @@ Selecting **View response** opens the text visualizer with the command's result

<Image src={notificationCenterResponse} alt="Aspire dashboard text visualizer showing a command response opened from the notification center." />

<Aside type="note">
Markdown command results render as formatted Markdown in the text visualizer (for example, headings and tables), not as raw Markdown source.
</Aside>

The unread count resets when you open the notification center. To clear all notifications, select the **Dismiss all** button in the dialog.

<Aside type="tip">
Notifications are capped at 100 entries. When the limit is reached, the oldest notifications are removed automatically.
</Aside>

For information on returning messages from custom commands, see [Custom resource commands](/fundamentals/custom-resource-commands/).
For information on returning command result payloads from custom commands, see [Custom resource commands](/fundamentals/custom-resource-commands/).

## Settings dialog

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -629,7 +629,7 @@ The `ExecuteCommandResult` type carries the outcome of a command. It exposes the
- **`Success` / `success`** _(boolean)_ — Whether the command completed successfully.
- **`Canceled` / `canceled`** _(boolean)_ — Whether the command was canceled by the user.
- **`Message` / `message`** _(string?)_ — A human-readable message that surfaces in the dashboard [notification center](/dashboard/explore/#notification-center) and CLI output. Used for both success messages and error messages.
- **`Data` / `data`** _(`CommandResultData?`)_ — Optional structured output (plain text, JSON, or Markdown). See [Return structured output from a command](#return-structured-output-from-a-command).
- **`Data` / `data`** _(`CommandResultData?`)_ — Optional structured output (plain text, JSON, or Markdown). See [Return a result from a command](#return-a-result-from-a-command).

<Aside type="note">
A separate `ErrorMessage` property exists for backward compatibility but is deprecated. New code should set `Message` (`message` in TypeScript) — it covers both successful and unsuccessful outcomes.
Expand Down Expand Up @@ -676,7 +676,9 @@ return { success: false, message: err instanceof Error ? err.message : String(er
</TabItem>
</Tabs>

## Return structured output from a command
<span id="return-structured-output-from-a-command"></span>

## Return a result from a command

Resource commands can return a structured payload — plain text, JSON, or Markdown — alongside the success/failure signal. The payload flows automatically through the entire Aspire pipeline:

Expand Down Expand Up @@ -826,6 +828,105 @@ await builder.build().run();
</TabItem>
</Tabs>

### Return Markdown

When a command needs rich output (for example headings, tables, and lists), use the `CommandResults.Success(string, CommandResultData)` overload to return a `CommandResultData` with `Format = CommandResultFormat.Markdown`. This alternative to the three-parameter string overload gives you full control over the result payload:

<Tabs syncKey="aspire-lang">
<TabItem id="csharp" label="C#">

```csharp title="C# — AppHost.cs"
using Aspire.Hosting.ApplicationModel;

var builder = DistributedApplication.CreateBuilder(args);

builder.AddProject<Projects.MyService>("myservice")
.WithCommand(
name: "migrate-database",
displayName: "Migrate Database",
executeCommand: _ =>
{
var markdown = """
# ⚙️ Database Migration Summary

| Table | Result |
|------------|----------------------------|
| Customers | ✅ 1,200 rows |
| Products | ✅ 850 rows |
| Orders | ✅ 3,400 rows |
| OrderItems | ✅ 8,750 rows |
| Categories | ✅ 45 rows |
| Reviews | ❌ FK constraint violation |
| Inventory | ✅ 850 rows |
| Shipping | ✅ 3,400 rows |
| Payments | ❌ Timeout after 30s |
| Coupons | ✅ 120 rows |

**Summary:** 8 of 10 tables migrated successfully. 2 tables failed.
""";

return Task.FromResult(CommandResults.Success(
"Database migrated.",
new CommandResultData
{
Value = markdown,
Format = CommandResultFormat.Markdown,
}));
});

builder.Build().Run();
```

</TabItem>
<TabItem id="typescript" label="TypeScript">

```typescript title="TypeScript — apphost.mts"
import {
createBuilder,
CommandResultFormat,
type ExecuteCommandContext,
type ExecuteCommandResult,
} from './.aspire/modules/aspire.mjs';

const builder = await createBuilder();

await builder
.addNodeApp("myservice", "./myservice", "src/server.ts")
.withCommand("migrate-database", "Migrate Database",
async (_context: ExecuteCommandContext): Promise<ExecuteCommandResult> => {
const markdown = `# ⚙️ Database Migration Summary

| Table | Result |
|------------|----------------------------|
| Customers | ✅ 1,200 rows |
| Products | ✅ 850 rows |
| Orders | ✅ 3,400 rows |
| OrderItems | ✅ 8,750 rows |
| Categories | ✅ 45 rows |
| Reviews | ❌ FK constraint violation |
| Inventory | ✅ 850 rows |
| Shipping | ✅ 3,400 rows |
| Payments | ❌ Timeout after 30s |
| Coupons | ✅ 120 rows |

**Summary:** 8 of 10 tables migrated successfully. 2 tables failed.`;

return {
success: true,
message: "Database migrated.",
data: {
value: markdown,
format: CommandResultFormat.Markdown,
},
};
});

await builder.build().run();
```

</TabItem>
</Tabs>

### Choose a result format

`CommandResultFormat` has three values:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ The preceding code:

## Return the HTTP response body

By default, an HTTP command uses the HTTP status code to determine whether the command succeeded, and it doesn't return the response body as command result data. Set `HttpCommandOptions.ResultMode` to opt in to returning a non-empty response body from the endpoint. The body is surfaced as the command's structured output, just like [structured output from custom resource commands](/fundamentals/custom-resource-commands/#return-structured-output-from-a-command).
By default, an HTTP command uses the HTTP status code to determine whether the command succeeded, and it doesn't return the response body as command result data. Set `HttpCommandOptions.ResultMode` to opt in to returning a non-empty response body from the endpoint. The body is surfaced as the command's structured output, just like [structured output from custom resource commands](/fundamentals/custom-resource-commands/#return-a-result-from-a-command).

The following example configures an HTTP command that returns a JSON response body:

Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/content/docs/whats-new/aspire-13-3.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -521,7 +521,7 @@ await builder.build().run();

### Resource commands return structured results

Custom resource commands can now return an `ExecuteCommandResult` with a structured `Message` payload that the dashboard renders in the notification center. The new `Logger` property on `ExecuteCommandContext` lets command implementations log directly to the resource's log stream.
Custom resource commands can now return an `ExecuteCommandResult` with structured `Data` payloads (`Text`, `Json`, or `Markdown`) that the dashboard renders from the notification center's **View response** action. The `CommandResults.Success(string, CommandResultData)` overload makes it easy to return Markdown tables and other rich output. The new `Logger` property on `ExecuteCommandContext` lets command implementations log directly to the resource's log stream.

<Tabs syncKey='aspire-lang'>
<TabItem id='csharp' label='AppHost.cs'>
Expand Down
Loading