Skip to documentation
Heimdall Docs
Heimdall Docs

Configuration

Heimdall has two configuration surfaces: server registration settings and the browser runtime object exposed as window.Heimdall.

Use this page when you need the exact defaults, the mutable runtime properties, and the timing rules for when a setting must be applied.

1. Configuration Surfaces

Most application configuration happens on the server. Browser behavior that is intentionally runtime-tunable lives under Heimdall.config after the JavaScript bundle loads.

JavaScript
Server:
builder.Services.AddHeimdall(options => { ... });
app.UseHeimdall();

Browser:
window.Heimdall
window.Heimdall.config

2. Server Settings

AddHeimdall accepts HeimdallServiceSettings. These settings affect server endpoint behavior, error detail, and Bifrost topic authorization.

Text
builder.Services.AddHeimdall(options =>
{
    options.EnableDetailedErrors = builder.Environment.IsDevelopment();
    options.BifrostTopicPolicy = "BifrostTopic";
    options.AuthorizeBifrostTopic = (ctx, topic) =>
        ValueTask.FromResult(topic.StartsWith("user:"));
    options.BifrostHeartbeatInterval = TimeSpan.FromSeconds(15);
});

3. Server Defaults

These are the framework defaults from HeimdallServiceSettings.

Text
EnableDetailedErrors:
false

BifrostTopicPolicy:
null

AuthorizeBifrostTopic:
null

BifrostHeartbeatInterval:
15 seconds

4. Browser Runtime Object

After the bundle loads, Heimdall assigns a global runtime object to window.Heimdall. Its methods are the supported browser-side API surface.

Text
Heimdall.apiVersion
Heimdall.pluginApiVersion

Heimdall.invoke(invocationId, payload, options)
Heimdall.boot(root)
Heimdall.dispose(root)
Heimdall.use(plugin, options)
Heimdall.plugins.list()
Heimdall.plugins.remove(id)
Heimdall.onReady(callback)
Heimdall.clearCsrfToken()

Heimdall.sse.connect(topic, options)
Heimdall.sse.disconnect(element)
Heimdall.sse.disconnectAll()

Heimdall.config
Heimdall._observer

OOB Default

Out-of-band response directive processing is enabled by default. The runtime default is Heimdall.config.oobEnabled = true. Set it to false only when the application wants to ignore <invocation> directives returned by invocations or SSE payloads.

5. Set Runtime Config

The runtime creates Heimdall.config when the script loads. Put configuration immediately after the runtime script, and put early-boot options before DOMContentLoaded or before any manual boot call. The hosted Heimdall templates include wwwroot/js/heimdall.config.js and load it after the runtime bundle, giving application-wide defaults one obvious place to live. The static-site generator does not include the browser runtime, so it does not need this hook.

HTML
<!-- Shared hosted layout or Components/App.razor -->
<script src="/_content/HeimdallFramework.Web/heimdall-bundle.min.js"></script>
<script src="/js/heimdall.config.js"></script>

// wwwroot/js/heimdall.config.js
(function () {
  "use strict";

  if (!window.Heimdall)
    return;

  // Keep the starter file quiet by default.
  // Heimdall.config.debug = true;
  // Heimdall.config.requestTimeoutMs = 10000;
  // Heimdall.config.requestSync = "queue-latest";
  // Heimdall.config.clientInfo = true;
})();

6. Client Information Storage Limits

The browser configuration controls which localStorage and sessionStorage keys are collected. The server settings apply the same limits while binding the untrusted X-Heimdall-Client-Info header. Keep both sides bounded; the browser skips over-limit values and the server rejects an invalid header.

Text
builder.Services.AddHeimdall(options =>
{
    options.ClientInfoLimits.MaxStorageValueLength = 1024;
    options.ClientInfoLimits.MaxStorageChars = 2048;
});

Heimdall.config.clientInfo = {
  enabled: true,
  storage: {
    local: ["ui.theme"],
    session: ["checkout.state"]
  }
};

7. Runtime Config Defaults

These are the current browser runtime defaults from startup.js.

Text
Heimdall.config = {
  basePath: "/__heimdall",
  apiVersion: 1,

  endpoints: {
    contentInvocations: "/__heimdall/v1/content/invocations",
    csrf: "/__heimdall/v1/csrf",
    bifrostToken: "/__heimdall/v1/bifrost/token",
    bifrost: "/__heimdall/v1/bifrost"
  },

  observeDom: true,
  debug: false,
  authReturnUrlParameter: "ReturnUrl",
  antiforgery: true,
  requestHeaders: null,
  clientInfo: false,
  clientInfoMaxAgeMs: 60000,
  clientInfoLimits: {
    maxKeysPerArea: 16,
    maxKeyLength: 128,
    maxValueLength: 1024,
    maxStorageChars: 2048,
    maxExtensionEntries: 16,
    maxExtensionChars: 1024,
    maxExtensionDepth: 4
  },

  inputDebounceMs: 250,
  hoverDelayMs: 150,
  scrollThresholdPx: 120,
  scrollMinIntervalMs: 250,

  visibleRootMargin: "0px",
  visibleThreshold: 0,

  oobEnabled: true,
  requestSync: "parallel",
  requestTimeoutMs: 0,

  sseDefaultSwap: "none",
  sseEventName: "heimdall",
  sseReconnectDelayMs: 250,
  sseReconnectMaxDelayMs: 10000,
  sseReconnectBackoffFactor: 2,
  sseSweepIntervalMs: 5000,
  ssePauseWhenHidden: false
};

8. Endpoint and Auth Redirect Configuration

Endpoint values are read when the runtime makes a request. The default paths match app.UseHeimdall(). Only change them when the server has been mapped behind a matching path or proxy. A separate frontend can use absolute backend URLs, provided the backend exposes the endpoints and the browser is configured for its CORS and authentication policy.

Text
Heimdall.config.endpoints.contentInvocations = "/internal/heimdall/invocations";
Heimdall.config.endpoints.csrf = "/internal/heimdall/csrf";
Heimdall.config.endpoints.bifrostToken = "/internal/heimdall/bifrost/token";
Heimdall.config.endpoints.bifrost = "/internal/heimdall/bifrost";
Text
// Separate frontend and HTML backend.
Heimdall.config.endpoints.contentInvocations =
  "https://html.example.com/__heimdall/v1/content/invocations";
Heimdall.config.endpoints.csrf =
  "https://html.example.com/__heimdall/v1/csrf";
Heimdall.config.endpoints.bifrostToken =
  "https://html.example.com/__heimdall/v1/bifrost/token";
Heimdall.config.endpoints.bifrost =
  "https://html.example.com/__heimdall/v1/bifrost";

Content invocations send POST requests with the invocation id in the query string and X-Heimdall-Content-Invocation header. CSRF and Bifrost token requests are GET requests; they are same-origin only when the configured endpoints are relative.

When cookie authentication redirects an invocation or Bifrost subscribe-token request to sign-in, Heimdall rewrites the configured return URL query parameter to the current page. The default is ReturnUrl, matching ASP.NET Core cookie auth. Matching is case-insensitive, so returnUrl and ReturnUrl are both recognized, and Heimdall preserves the casing used by the server redirect URL.

Text
// ASP.NET Core default:
options.ReturnUrlParameter = "ReturnUrl";

// Heimdall default:
Heimdall.config.authReturnUrlParameter = "ReturnUrl";

// If your app uses a custom name:
options.ReturnUrlParameter = "next";
Heimdall.config.authReturnUrlParameter = "next";

9. Trigger Timing Defaults

These config values supply runtime defaults. Per-element attributes and programmatic invocation options still win when they are supplied.

Text
Heimdall.config.inputDebounceMs
-> default for heimdall-content-input
-> overridden by heimdall-debounce

Heimdall.config.hoverDelayMs
-> default for heimdall-content-hover
-> overridden by heimdall-hover-delay

Heimdall.config.scrollThresholdPx
-> near-end scroll distance
-> overridden by heimdall-scroll-threshold

Heimdall.config.scrollMinIntervalMs
-> minimum time between scroll-triggered invocations

Heimdall.config.requestSync
-> default overlap strategy: parallel
-> overridden by heimdall-sync or invoke({ sync })

Heimdall.config.requestTimeoutMs
-> client request timeout: 0 means no timeout
-> overridden by invoke({ timeoutMs })

10. Boot and DOM Observation

The runtime boots document boundaries on ready. observeDom installs a MutationObserver so newly inserted Heimdall markup is activated automatically.

JavaScript
Heimdall.config.observeDom = true;

// Manual boot is useful when observeDom is false,
// or when an external renderer inserts markup.
Heimdall.boot(document.querySelector("#new-region"));

11. Visible Trigger Timing

visibleRootMargin and visibleThreshold are read once when the IntersectionObserver is first created. Set them before visible elements are booted.

HTML
<script src="/_content/HeimdallFramework.Web/heimdall-bundle.min.js"></script>
<script>
  Heimdall.config.visibleRootMargin = "200px 0px";
  Heimdall.config.visibleThreshold = 0.1;
</script>

12. OOB Configuration

OOB processing controls whether returned invocation directives can update other DOM regions outside the main target. Redirect and abort directives are still response directives, but invocation processing is gated by oobEnabled.

Text
// Default: true
Heimdall.config.oobEnabled = true;

// Disable invocation directive processing.
Heimdall.config.oobEnabled = false;

13. SSE Configuration

Bifrost SSE uses global defaults for event name, default swap mode, reconnect behavior, sweep timing, and hidden-tab pause behavior. Subscriber attributes can override event, target, swap, and disable behavior per element.

Text
Heimdall.config.sseDefaultSwap = "none";
Heimdall.config.sseEventName = "heimdall";
Heimdall.config.sseReconnectDelayMs = 250;
Heimdall.config.sseReconnectMaxDelayMs = 10000;
Heimdall.config.sseReconnectBackoffFactor = 2;
Heimdall.config.sseSweepIntervalMs = 5000;
Heimdall.config.ssePauseWhenHidden = false;

Per-subscriber HTML attributes:

Text
heimdall-sse="orders"
heimdall-sse-topic="orders"
heimdall-sse-target="#orders"
heimdall-sse-swap="beforeend"
heimdall-sse-event="order.updated"
heimdall-sse-disable="true"

14. Programmatic Invocation

Use Heimdall.invoke when a browser script needs to run the same content-invocation request pipeline as a DOM trigger. It supports the same synchronization coordinator, an optional client timeout, and an external AbortSignal.

JavaScript
const controller = new AbortController();

const result = await Heimdall.invoke(
  "orders.refresh",
  { page: 2 },
  {
    target: "#orders",
    swap: "inner",
    headers: { "X-Request-Source": "toolbar" },
    sync: "replace",
    syncGroup: "orders",
    timeoutMs: 5000,
    signal: controller.signal,
    onSuccess: result => console.log(result.ms),
    onError: result => console.warn(result.status)
  });

// Expected cancellation resolves normally.
if (result.cancelled) {
  console.log(result.cancelReason);
}

15. Programmatic SSE

Use Heimdall.sse.connect when a script needs to subscribe without a declarative heimdall-sse attribute. The element anchors lifecycle validation for that subscription.

JavaScript
const subscription = Heimdall.sse.connect("orders", {
  element: document.querySelector("#orders-panel"),
  target: "#orders",
  swap: "beforeend",
  event: "order.updated"
});

subscription.close();

Heimdall.sse.disconnect("#orders-panel");
Heimdall.sse.disconnectAll();

16. Request, Swap, and Runtime Events

Heimdall exposes a dependency-free integration surface through bubbling DOM CustomEvents. Request configuration is mutable, request-before and swap-before are cancellable with preventDefault(), and finally always fires. Declarative invocations dispatch from the triggering element; programmatic and SSE work remains observable on document.

JavaScript
Heimdall.config.debug = true;

document.addEventListener("heimdall:request-config", event => {
  event.detail.headers["X-Correlation-ID"] = crypto.randomUUID();
  event.detail.sync = "replace";
  event.detail.syncGroup = "search";
});

document.addEventListener("heimdall:request-before", event => {
  // event.preventDefault() cancels before fetch.
});

document.addEventListener("heimdall:request-after", event => {});
document.addEventListener("heimdall:request-finally", event => {});
document.addEventListener("heimdall:request-cancel", event => {});
document.addEventListener("heimdall:request-timeout", event => {});

document.addEventListener("heimdall:swap-before", event => {
  // Mutable: target, fragment, and swap.
  // event.preventDefault() skips this swap.
});
document.addEventListener("heimdall:swap-after", event => {});

// Existing invocation/directive events remain supported.
document.addEventListener("heimdall:before", e => {});
document.addEventListener("heimdall:after", e => {});
document.addEventListener("heimdall:error", e => {});
document.addEventListener("heimdall:abort", e => {});
document.addEventListener("heimdall:redirect", e => {});
document.addEventListener("heimdall:javascript-error", e => {});

// SSE connection lifecycle events remain supported.
document.addEventListener("heimdall:sse-open", e => {});
document.addEventListener("heimdall:sse-close", e => {});
document.addEventListener("heimdall:sse-message", e => {});
document.addEventListener("heimdall:sse-error", e => {});
document.addEventListener("heimdall:sse-pause", e => {});
document.addEventListener("heimdall:sse-resume", e => {});
document.addEventListener("heimdall:sse-reconnect-scheduled", e => {});
document.addEventListener("heimdall:sse-abort", e => {});
document.addEventListener("heimdall:sse-redirect", e => {});

17. When Changes Take Effect

Most values are read at the moment of use. A few bootstrapping values need to be set early because observers and sweep timers are created once.

Text
Read on each request:
endpoints, debug, oobEnabled, antiforgery,
requestHeaders, clientInfo, clientInfoMaxAgeMs,
requestSync, requestTimeoutMs

Read from each trigger event:
inputDebounceMs, hoverDelayMs,
scrollThresholdPx, scrollMinIntervalMs

Set before first visible boot:
visibleRootMargin, visibleThreshold

Set before ready for automatic DOM observing:
observeDom

Set before SSE starts for initial sweeper interval:
sseSweepIntervalMs

Read during SSE reconnect/pause behavior:
sseReconnectDelayMs, sseReconnectMaxDelayMs,
sseReconnectBackoffFactor, ssePauseWhenHidden

18. Practical Defaults

For most apps, keep the default endpoints, keep observeDom on, keep OOB on, and tune only the user-experience timings that your UI actually needs.

Text
Common application-level tweaks:

Heimdall.config.debug = builderEnvironmentIsDevelopment;
Heimdall.config.inputDebounceMs = 300;
Heimdall.config.hoverDelayMs = 250;
Heimdall.config.requestSync = "parallel";
Heimdall.config.requestTimeoutMs = 0;
Heimdall.config.ssePauseWhenHidden = true;

19. Antiforgery and Client Context

Antiforgery is enabled by default on the server and client. A global server opt-out removes Heimdall's need for AddAntiforgery and UseAntiforgery; keep them if another endpoint still uses ASP.NET Core antiforgery. A per-invocation RequireAntiforgeryToken(false) exception is narrower: keep the shared infrastructure whenever any other Heimdall invocation or Bifrost still validates tokens. Keep the browser setting aligned and set it before the first invocation or SSE subscription. Client information is a separate opt-in and remains untrusted presentation context.

C#
// Global opt-out: every Heimdall invocation and Bifrost.
builder.Services.AddHeimdall(options =>
    options.EnableAntiforgery = false);

var app = builder.Build();
app.UseHeimdall();
// No AddAntiforgery() or UseAntiforgery()
// is required by Heimdall in this mode.

// Matching browser opt-out, before first use.
Heimdall.config.antiforgery = false;

// Optional browser context
Heimdall.config.clientInfo = true;
Heimdall.config.clientInfoMaxAgeMs = 60_000;

20. Asynchronous Headers

requestHeaders can return an object, header pairs, Headers, null, or a promise of any of those. It runs for content invocations and Heimdall security-token requests. If it throws or rejects, Heimdall fails the request before fetch.

JavaScript
Heimdall.config.requestHeaders = async context => ({
  Authorization: `Bearer ${await auth.getToken({
    signal: context.signal
  })}`
});

document.addEventListener("heimdall:unauthorized", event => {
  event.preventDefault();
  auth.openSignIn();
});

21. Content-Invocation Rate Limiting

Heimdall content invocations support ASP.NET Core's native rate-limiter policies. Register the built-in limiter services and middleware, then apply EnableRateLimiting or DisableRateLimiting to an invocation method or declaring type. Method metadata overrides type metadata.

C#
using Microsoft.AspNetCore.RateLimiting;

builder.Services.AddRateLimiter(options =>
{
    options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
    options.AddFixedWindowLimiter("content-invocations", limiter =>
    {
        limiter.PermitLimit = 60;
        limiter.Window = TimeSpan.FromMinutes(1);
        limiter.QueueLimit = 0;
    });
});

app.UseRateLimiter();
app.UseHeimdall();

[EnableRateLimiting("content-invocations")]
[ContentInvocation("profile.save")]
public static IHtmlContent Save(ProfilePayload payload)
    => Profile.Render(payload);

// A narrow opt-out:
[DisableRateLimiting]
[ContentInvocation("health.ping")]
public static IHtmlContent Ping()
    => Html.Span("ok");

22. Focused References

The configuration surface is intentionally summarized here. Use the focused pages for the full browser-context schema, cache invalidation, request-header precedence, unauthorized behavior, security boundaries, and telemetry setup.

Text
/client-requests  client info + request hooks
/security         antiforgery + CORS + authentication
/observability    traces + metrics

Plugin Installation and Execution Bounds

A browser plugin has its own installation options and explicit loading order. Those options belong to the plugin and are separate from Heimdall.config and server options.

JavaScript
Heimdall.pluginApiVersion
Heimdall.use(editorPlugin, { commitKey: "Enter" });
Heimdall.plugins.list();
Heimdall.plugins.remove("company.editor");
Heimdall.dispose(root);

Heimdall.config.pluginMountTimeoutMs = 10000;
Heimdall.config.pluginCommandTimeoutMs = 10000;
Heimdall.config.pluginCommandMaxArgsChars = 65536;
Heimdall.config.pluginCommandMaxCount = 256;
Heimdall.config.pluginSseQueueLimit = 128;

Zero disables a mount/command timeout. SSE queue overflow reports sse-command-queue-full and closes the subscription with plugin-command-queue-full. NuGet or server assembly registration does not load browser assets. See the plugin guide for compatibility, startup ordering, async readiness, cancellation, and diagnostics.