Quartz.NETQuartz.NET
Home
Features
Discussions
NuGet
GitHub
Home
Features
Discussions
NuGet
GitHub
  • Getting Started

    • Quartz 3 Quick Start
    • Tutorial
      • Using Quartz
      • Library Overview
      • Jobs And Triggers
      • More About Jobs
      • More About Triggers
      • Simple Triggers
      • Cron Triggers
      • RecurrenceTrigger
      • Trigger and Job Listeners
      • Scheduler Listeners
      • Job Stores
      • Tuning the Scheduler
      • Configuration, Resource Usage and SchedulerFactory
      • Advanced (Enterprise) Features
    • Configuration Reference
    • JSON Configuration
    • Frequently Asked Questions
    • Best Practices
    • Troubleshooting
    • API Documentation
    • Database Schema
    • Migration Guide
    • Miscellaneous Features
  • How To's

    • One-Off Job
    • Multiple Triggers
    • Job Template
    • Using the CronTrigger
    • Rescheduling Jobs
  • Packages

    • Quartz Core Additions

      • Dashboard
      • Jobs
      • Serialization (System.Text.Json)
      • Serialization (Newtonsoft Json.NET)
      • Plugins
    • Integrations

      • ASP.NET Core Integration
      • Hosted Services Integration
      • Microsoft DI Integration
      • Multiple Schedulers with Microsoft DI
      • OpenTelemetry Integration
      • OpenTracing Integration
      • Redis Lock Handler
      • TimeZoneConverter Integration
    • 3rd Party Plugins for Quartz
  • Unreleased Releases

    • Quartz 4.x
      • Quartz 4 Quick Start
      • Tutorial
        • Using Quartz
        • Jobs And Triggers
        • More About Jobs & JobDetails
        • More About Triggers
        • Simple Triggers
        • Cron Triggers
        • RecurrenceTrigger
        • Trigger and Job Listeners
        • Scheduler Listeners
        • Job Stores
        • Configuration, Resource Usage and SchedulerFactory
        • Advanced (Enterprise) Features
        • Miscellaneous Features
        • CronTrigger Tutorial
      • Configuration Reference
      • JSON Configuration
      • Migration Guide
      • Troubleshooting
      • API Documentation
      • How To's

        • One-Off Job
        • Multiple Triggers
        • Job Template
        • Using the CronTrigger
      • Packages

        • Quartz Core Additions

          • Dashboard
          • Jobs
          • JSON Serialization
          • Plugins
        • Integrations

          • ASP.NET Core Integration
          • HTTP API
          • Hosted Services Integration
          • Microsoft DI Integration
          • Multiple Schedulers with Microsoft DI
          • OpenTelemetry Integration
          • OpenTracing Integration
          • Redis Lock Handler
          • TimeZoneConverter Integration
        • 3rd Party Plugins for Quartz
  • Old Releases

    • Quartz 2.x
      • Quartz 2 Quick Start
      • Tutorial
        • Lesson 1: Using Quartz
        • Lesson 2: Jobs And Triggers
        • Lesson 3: More About Jobs & JobDetails
        • Lesson 4: More About Triggers
        • Lesson 5: SimpleTrigger
        • Lesson 6: CronTrigger
        • Lesson 7: TriggerListeners and JobListeners
        • Lesson 8: SchedulerListeners
        • Lesson 9: JobStores
        • Lesson 10: Configuration, Resource Usage and SchedulerFactory
        • Lesson 11: Advanced (Enterprise) Features
        • Lesson 12: Miscellaneous Features of Quartz
        • CronTrigger Tutorial
      • Configuration Reference
      • Migration Guide
      • API Documentation
    • Quartz 1.x
      • Tutorial
        • Lesson 1: Using Quartz
        • Lesson 2: Jobs And Triggers
        • Lesson 3: More About Jobs & JobDetails
        • Lesson 4: More About Triggers
        • Lesson 5: SimpleTrigger
        • Lesson 6: CronTrigger
        • Lesson 7: TriggerListeners and JobListeners
        • Lesson 8: SchedulerListeners
        • Lesson 9: JobStores
        • Lesson 10: Configuration, Resource Usage and SchedulerFactory
        • Lesson 11: Advanced (Enterprise) Features
        • Lesson 12: Miscellaneous Features of Quartz
      • API Documentation
  • License

Quartz.Dashboard is a Blazor-based dashboard for Quartz.NET that runs inside your ASP.NET Core app and accesses the scheduler in-process.

Warning

Quartz Dashboard is currently a work in progress. The dashboard API surface may change between releases. Supported target frameworks are .NET 8 and newer.

Tips

Quartz 3.16 or later required.

Installation

Add package reference:

Install-Package Quartz.Dashboard

Basic setup

Configure Quartz and add the dashboard services.

services.AddQuartz(q =>
{
    // configure jobs and triggers
});

services.AddQuartzDashboard();
services.AddQuartzHostedService(options => options.WaitForJobsToComplete = true);

Map endpoints:

app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseAntiforgery();

app.UseEndpoints(endpoints =>
{
    endpoints.MapQuartzDashboard();
});

By default, dashboard UI is available at /quartz.

Hosting under a custom path

Tips

The self-contained custom-path behavior described below requires a Quartz.Dashboard release newer than 3.18.2 (#3134). On 3.18.2 and earlier only the dashboard pages, links and hub honor the custom path; the Blazor framework script, the circuit and the static assets stay at the application root, so a prefix-forwarding reverse proxy needs UsePathBase (or equivalent) instead.

When the dashboard hosts its own Blazor root (the parameterless MapQuartzDashboard() overload), the dashboard can be served from a custom base path:

services.AddQuartzDashboard(options =>
{
    options.DashboardPath = "/my-api/quartz";
});

With a custom DashboardPath the dashboard is fully self-contained under the configured path. The pages, navigation links and SignalR hub as well as the Blazor plumbing — the interactive circuit ({DashboardPath}/_blazor), the framework script ({DashboardPath}/_framework/blazor.web.js) and the dashboard static assets ({DashboardPath}/_content/Quartz.Dashboard/*) — are all served under it, and the dashboard shell emits a <base href> rooted at the dashboard itself.

This makes the dashboard work behind a reverse proxy that forwards only a path prefix to the application without setting a path base: configure DashboardPath with the externally visible path (for example /my-api/quartz when the proxy forwards /my-api/* verbatim) and make sure the proxy forwards WebSocket connections for {DashboardPath}/_blazor and {DashboardPath}/hub (the live-views hub). Note that the dashboard's server-side circuit connects to the live-events hub through the same externally visible URL the browser uses, so the application must be able to reach its own public address for the Live Logs view to work.

Alternatively, when the whole application is rebased with UsePathBase() (or the proxy sets the request path base), the configured DashboardPath is interpreted relative to the path base — the default /quartz then works as-is under the prefix. With minimal hosting (WebApplication), call app.UseRouting() explicitly after app.UsePathBase(...) — otherwise the implicit routing step matches against the un-stripped path and every dashboard route returns 404:

app.UsePathBase("/my-api");
app.UseRouting();

Upgrading existing custom-path deployments

In earlier releases the Blazor circuit stayed at the site root; with a custom DashboardPath it now connects at {DashboardPath}/_blazor. Reverse-proxy rules scoped to /_blazor (for example a WebSocket-upgrade location) must be updated accordingly.

Warning

A custom DashboardPath is not supported when integrating into an existing Blazor application with MapQuartzDashboard(blazor); the dashboard page routes are fixed at /quartz in that mode and startup fails with a descriptive exception if a custom path is configured.

Enabling history plugin

To populate execution history and make related views useful, enable Quartz history plugins in QuartzOptions:

services.Configure<QuartzOptions>(options =>
{
    options["quartz.plugin.jobHistory.type"] = "Quartz.Plugin.History.LoggingJobHistoryPlugin, Quartz.Plugins";
    options["quartz.plugin.triggerHistory.type"] = "Quartz.Plugin.History.LoggingTriggerHistoryPlugin, Quartz.Plugins";
});

Production hardening

Policy and role-based authorization

Use an explicit policy for dashboard access:

services.AddAuthorization(options =>
{
    options.AddPolicy("QuartzDashboardOps", policy =>
    {
        policy.RequireAuthenticatedUser();
        policy.RequireRole("Operations", "SchedulerAdmin");
    });
});

services.AddQuartzDashboard(options =>
{
    options.AuthorizationPolicy = "QuartzDashboardOps";
});
app.UseEndpoints(endpoints =>
{
    endpoints.MapQuartzDashboard();
});

When AuthorizationPolicy is set, the policy is applied to the dashboard pages, the SignalR hub, the Blazor circuit (/_blazor) and the dashboard static asset endpoint, so the whole dashboard is gated consistently — including under a fail-closed FallbackPolicy.

Without a policy the dashboard adds no authorization of its own:

  • The static asset endpoint (_content/Quartz.Dashboard/*) and the Blazor circuit (/_blazor) explicitly allow anonymous access so the dashboard's plumbing keeps working under a fail-closed FallbackPolicy — these are public package content.
  • The dashboard pages and the SignalR hub carry no authorization metadata of their own, so they remain governed by your host's policies. Under a fail-closed FallbackPolicy, an unauthenticated request to /quartz is redirected to login (by design) while authenticated users get the full dashboard. To expose the dashboard to unauthenticated users, either don't enforce a fail-closed FallbackPolicy over the dashboard paths, or set an AuthorizationPolicy your users satisfy.

Fail-closed FallbackPolicy with MapStaticAssets()

Assets served by the host's app.MapStaticAssets() (the .NET 9/10 default) and the framework script _framework/blazor.web.js are served by host/framework-owned endpoints that Quartz cannot annotate, so a fail-closed FallbackPolicy blocks them for unauthenticated users regardless of the dashboard configuration. If you need them reachable before authentication (for example so the login page is styled), mark your static assets anonymous with app.MapStaticAssets().AllowAnonymous(); — static web assets are public content. The classic app.UseStaticFiles() middleware runs before authorization and is not subject to the FallbackPolicy. See API-only projects for the related RequiresAspNetWebAssets setting.

With a custom DashboardPath this caveat does not apply to the dashboard itself: the framework script and the dashboard static assets are then served under the dashboard path by dashboard-owned endpoints that carry the dashboard's authorization metadata.

API key or custom authorization checks

For dashboard access control, prefer ASP.NET Core policy/handler-based authorization so the dashboard UI and hub are enforced consistently.

Deployment guidance for multi-scheduler and clustered setups

  • Clustered ADO.NET job stores: actions in dashboard are scheduler operations and can affect cluster behavior; restrict write access to trusted operator roles.
  • Many local schedulers in one host: dashboard scheduler selector supports multiple registered schedulers; use clear scheduler names and environment-specific grouping.
  • Reverse proxy and Blazor Server: enable WebSocket/SignalR forwarding and sticky sessions where required by your hosting stack. The Blazor circuit connects to /_blazor (or {DashboardPath}/_blazor when a custom DashboardPath is configured).
  • Split operator experiences: expose a read-only dashboard instance (ReadOnly = true) for observers, and a separate write-enabled dashboard for operators.
  • Operational retention: dashboard history is plugin-fed operational history; configure plugin + external retention/reporting if you need long-term analytics.

Features

  • Scheduler overview and summary cards
  • Jobs and triggers listing with search and pagination
  • Job details and trigger details pages
  • Currently executing jobs view
  • Live event/log stream for scheduler activity
  • Pause, resume, trigger-now, and unschedule/delete actions (when not in read-only mode)
  • Trigger detail cron reschedule and job detail trigger-with-overrides actions
  • Calendar create/replace (cron calendar), details, and delete actions
  • Multi-scheduler selection
  • Read-only mode support via dashboard options

Current limitations

  • Live views are near-real-time polling/streaming and are not guaranteed to be lossless event storage
  • No built-in persistence UI for historical analytics; plugin-backed history is operational/log oriented
  • Advanced management remains intentionally scoped; rich typed editors are currently focused on cron calendars/triggers and operational overrides
  • UX is optimized for Quartz APIs and scheduler operations, not full workflow/business process visualization

Integrating with an existing Blazor Server app

If your host application already uses Blazor Server (i.e., it calls MapRazorComponents<App>().AddInteractiveServerRenderMode()), you must use the MapQuartzDashboard overload that accepts the existing RazorComponentsEndpointConventionBuilder. This avoids registering a second /_blazor SignalR endpoint, which would cause routing conflicts.

services.AddRazorComponents().AddInteractiveServerComponents();
services.AddQuartzDashboard();
app.UseRouting();
app.UseAntiforgery();

var blazor = app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode();

app.MapQuartzDashboard(blazor);

In addition, the host application's interactive router must be able to resolve the dashboard pages. Add the dashboard assembly to the AdditionalAssemblies of the <Router> in your Routes.razor:

<Router AppAssembly="typeof(App).Assembly"
        AdditionalAssemblies="new[] { typeof(Quartz.Dashboard.Components.QuartzDashboardApp).Assembly }">
    ...
</Router>

Without this, the dashboard renders server-side on the initial request, but the interactive router cannot match the /quartz routes once the circuit starts — the dashboard flashes briefly and is then replaced by the application's not-found page.

The dashboard pages, layout, CSS, and JavaScript interop are automatically registered into the host's Blazor endpoint routing via AddAdditionalAssemblies. No additional <link> or <script> tags are needed in your App.razor.

Warning

Do not call the parameterless MapQuartzDashboard() alongside your own MapRazorComponents — this registers two /_blazor endpoints and causes the dashboard's interactive pages to fail.

Tips

The dashboard pages do not declare a render mode of their own, so the host application needs to use global interactive server rendering (for example <Routes @rendermode="InteractiveServer" /> in App.razor). With per-page/component interactivity the dashboard pages render as static SSR and their actions are not functional.

API-only projects (no .razor files)

If your host project has no .razor files of its own (e.g., a pure API project hosting Quartz), and you are running on .NET 10 or later, you must add the following to your project file:

<PropertyGroup>
  <RequiresAspNetWebAssets>true</RequiresAspNetWebAssets>
</PropertyGroup>

This property tells the .NET SDK to include the Blazor framework scripts (_framework/blazor.web.js, blazor.server.js) in the app's static web assets. Without it, requests to /_framework/blazor.web.js return HTTP 404 because in .NET 10+, these files are no longer embedded in the ASP.NET Core assemblies — they are served as static web assets instead.

Additionally, static files must be enabled in the request pipeline:

app.UseRouting();
app.Antiforgery();
app.UseStaticFiles();

On .NET 8 and .NET 9, the framework scripts are served via endpoint routing and no extra configuration is needed.

Help us by improving this page!
Last Updated: 7/14/26, 6:41 AM
Contributors: Marko Lahma, Paul Stuart, Claude Fable 5
Next
Jobs