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

    • Overview
    • Quartz 4 Quick Start
    • Tutorial
      • Using Quartz
      • Jobs And Triggers
      • More About Jobs & JobDetails
      • Job Data
      • More About Triggers
      • Querying Jobs and Triggers
      • Simple Triggers
      • Cron Triggers
      • RecurrenceTrigger
      • Time and TimeProvider
      • Trigger and Job Listeners
      • Scheduler Listeners
      • Job Execution Middleware
      • Job Stores
      • Configuration, Resource Usage and Building a Scheduler
      • Building a Scheduler Without a Host
      • Clustering
      • Execution Groups
      • Node Affinity (Preferred Node)
      • Testing
      • Compile-Time Checks
      • Declaring Jobs with Attributes
      • Delegate Jobs
    • Configuration Reference
    • JSON Configuration
    • Cron Expression Reference
    • Multi-Tenancy
    • Comparison
    • Frequently Asked Questions
    • Best Practices
    • Before You Go Live
    • Operating a Cluster
    • Log Events
    • Tenancy Patterns
    • Database Schema
    • Database Schema Changes
    • Migration Guide
    • Troubleshooting
    • API Documentation
  • How To's
    • One-Off Job
    • Rescheduling Jobs
    • Backfill
    • Retrying Failed Jobs
    • Pausing with a Reason
    • Job Continuations
    • Overlap Policy
    • Progress and Execution Logs
    • Job Outcomes
    • Multiple Triggers
    • Job Template
    • Running Quartz under Aspire
    • Quartz.NET with Wolverine
    • Coming from Hangfire
    • Coming from TickerQ
    • Embedding Quartz in a Library
    • Running under an External Leader Election
    • Publishing Trimmed and Native AOT
    • Extending Quartz: what is open, what is closed, and how to ask
    • A Job Store of Your Own
    • A Driver Delegate for a New Database
    • Persisting a Custom Trigger Type
    • A Lock Handler of Your Own
  • Packages

    • Quartz Core Additions

      • Jobs
      • Serialization (System.Text.Json)
      • JSON Serialization
      • Plugins
    • Integrations

      • Aspire Integration
      • ASP.NET Core Integration
      • HTTP API
      • HTTP Client
      • Dashboard
      • Dashboard Agent
      • Hosted Services Integration
      • Microsoft DI Integration
      • Multiple Schedulers with Microsoft DI
      • Observability
      • Redis Lock Handler
      • TimeZoneConverter Integration
      • Weasel Schema Management
    • 3rd Party Plugins for Quartz
  • Quartz 3.x

    • Getting Started

      • Quartz 3 Quick Start
      • Tutorial
        • Using Quartz
        • Library Overview
        • Jobs And Triggers
        • More About Jobs
        • More About Triggers
        • Execution Groups
        • Node Affinity (Preferred Node)
        • 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
      • Multi-Tenancy
      • Frequently Asked Questions
      • Best Practices
      • Tenancy Patterns
      • Troubleshooting
      • API Documentation
      • Database Schema
      • Database Schema Changes
      • 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
  • 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.NET 3.x

These are the documents for the 3.x line, which is maintained. Quartz.NET 4.x is the current release; the migration guide says what changed and what to do about it.

Quartz.Dashboard is a Blazor-based dashboard for Quartz.NET. It 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

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();
});

The dashboard UI is at /quartz by default.

Hosting under a custom path

Tips

The self-contained custom path 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 use 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), set a custom base path:

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

Everything is then served under DashboardPath: the pages, navigation links and SignalR hub, the interactive circuit ({DashboardPath}/_blazor), the framework script ({DashboardPath}/_framework/blazor.web.js) and the dashboard static assets ({DashboardPath}/_content/Quartz.Dashboard/*). The dashboard shell emits a <base href> rooted at the dashboard.

This works behind a reverse proxy that forwards only a path prefix, without setting a path base:

  • Set DashboardPath to the externally visible path, for example /my-api/quartz when the proxy forwards /my-api/* verbatim.
  • Forward WebSocket connections for {DashboardPath}/_blazor and {DashboardPath}/hub (the live-views hub).
  • The server-side circuit connects to the live-events hub through the same external URL the browser uses, so the application must reach its own public address for the Live Logs view to work.

If instead the whole application is rebased with UsePathBase() (or the proxy sets the request path base), DashboardPath is relative to the path base, and the default /quartz works as-is under the prefix. With minimal hosting (WebApplication), call app.UseRouting() explicitly after app.UsePathBase(...); otherwise the implicit routing step matches 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. Update reverse-proxy rules scoped to /_blazor, such as a WebSocket-upgrade location.

Warning

A custom DashboardPath is not supported with MapQuartzDashboard(blazor) in an existing Blazor application. The 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 the views that use it, enable the 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();
});

With AuthorizationPolicy set, the policy applies to the dashboard pages, the SignalR hub, the Blazor circuit (/_blazor) and the dashboard static asset endpoint. 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) allow anonymous access, so they keep working under a fail-closed FallbackPolicy. They are public package content.
  • The dashboard pages and the SignalR hub have no authorization metadata, so your host's policies govern them. Under a fail-closed FallbackPolicy, an unauthenticated request to /quartz is redirected to login, and authenticated users get the full dashboard. To open the dashboard to unauthenticated users, do not enforce a fail-closed FallbackPolicy over the dashboard paths, or set an AuthorizationPolicy they 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 come from host/framework-owned endpoints that Quartz cannot annotate. A fail-closed FallbackPolicy blocks them for unauthenticated users whatever the dashboard configuration. To make them reachable before authentication (for example to style the login page), use 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 does not apply to the dashboard itself: the framework script and 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

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: dashboard actions are scheduler operations and can affect the cluster. Restrict write access to trusted operator roles.
  • Many local schedulers in one host: the 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 your hosting stack requires them. The Blazor circuit connects to /_blazor (or {DashboardPath}/_blazor with a custom DashboardPath).
  • Split operator experiences: run a read-only dashboard (ReadOnly = true) for observers and a separate write-enabled one for operators.
  • Operational retention: dashboard history comes from the plugins and is operational. For long-term analytics, configure the plugins plus external retention or reporting.

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, not lossless event storage.
  • No persistence UI for historical analytics; plugin-backed history is operational and log-oriented.
  • Management features are limited on purpose: typed editors cover cron calendars/triggers and operational overrides.
  • The UX targets Quartz APIs and scheduler operations, not workflow or business process visualization.

Integrating with an existing Blazor Server app

If your host already uses Blazor Server (it calls MapRazorComponents<App>().AddInteractiveServerRenderMode()), use the MapQuartzDashboard overload that takes the existing RazorComponentsEndpointConventionBuilder. It 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);

Add the dashboard assembly to the AdditionalAssemblies of the <Router> in your Routes.razor, so the host's interactive router resolves the dashboard pages:

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

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

The dashboard pages, layout, CSS and JavaScript interop are registered into the host's Blazor endpoint routing via AddAdditionalAssemblies. App.razor needs no extra <link> or <script> tags.

Warning

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

Tips

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

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 runs on .NET 10 or later, add this to the project file:

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

It makes the .NET SDK include the Blazor framework scripts (_framework/blazor.web.js, blazor.server.js) in the app's static web assets. Without it, /_framework/blazor.web.js returns HTTP 404: since .NET 10 these files are served as static web assets, no longer embedded in the ASP.NET Core assemblies.

Also enable static files 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 need no extra configuration.

Help us by improving this page!
Last Updated: 10/18/26, 9:21 AM
Contributors: Marko Lahma, Claude Opus 5.5
Next
Jobs