Docs
  • Solver
  • Models
    • Field Service Routing
    • Employee Shift Scheduling
    • Pick-up and Delivery Routing
    • Task Scheduling
  • Platform
Start Free Trial
  • Timefold Solver SNAPSHOT
  • Deploying to the Timefold Platform
  • Visualization
  • Edit this Page

Timefold Solver SNAPSHOT

    • Introduction
    • Getting started
      • Overview
      • Build as a service
      • Embed as a library
        • Hello World guide
        • Quarkus guide
        • Spring Boot guide
    • Domain modeling
      • Guide
      • Building blocks
      • Common patterns
    • Constraints and score
      • Overview
      • Score calculation
      • Understanding the score
      • Load balancing and fairness
      • Performance tips and tricks
    • Running the Solver
      • Overview
      • As a service
        • REST API
        • Model configuration overrides
        • Model enrichment
        • Demo data
        • Exposing metrics
        • Visualization
        • Service consumer guide
      • As a library
        • Configuring Timefold Solver
        • Constraint weights
        • Quarkus integration
        • Spring Boot integration
        • JPA/JAXB/JSON integration
    • Diagnosing the Solver
      • Benchmarking
      • Solver diagnostics
    • Deploying to the Timefold Platform
      • Overview
      • Guide
      • Platform model metadata
      • Using metrics
      • Visualization
    • Optimization algorithms
      • Overview
      • Construction heuristics
      • Local search
      • Exhaustive search
      • Custom moves
        • Neighborhoods API
        • Move Selector reference
    • Responding to change
      • Continuous planning
      • Real-time planning
      • Non-disruptive replanning
      • Assignment Recommendation API
    • Example use cases
      • Vehicle routing (guide)
      • More examples on GitHub
    • FAQ
    • New and noteworthy
    • Upgrading Timefold Solver
      • Upgrading Timefold Solver: Overview
      • Upgrade Timefold Solver to the latest version
      • Upgrade from Timefold Solver 1.x to 2.x
      • Upgrading from OptaPlanner
      • Backwards compatibility
      • Migration guides
        • Variable Listeners to Custom Shadow Variables
        • Chained planning variable to planning list variable
    • Commercial editions
      • Overview
      • Installation
      • Performance improvements
      • Score analysis
      • Recommendation API
      • Nearby selection
      • Multithreaded solving
      • Partitioned search
      • Constraint profiling
      • Multistage moves
      • Throttling best solution events
      • License management

Visualization

A model’s solution can have a custom visualization UI, rendered inside Timefold Platform as an iframe, instead of consumers only seeing the raw solution data or the generic score analysis view.

Deploying custom models to the platform is in preview.

This is currently only available to a limited set of partners. If you’re interested in joining this preview program, get in touch with the Timefold team to discuss access.

This page documents how visualization works today, based directly on the current platform implementation. The contract described here, including iframe sizing, refresh behavior, asset paths, and page-announcement metadata, is still evolving and may change as the platform’s visualization support matures, possibly without a smooth migration path.

1. Building the UI

The UI itself is the same set of static files described in Visualization, placed under src/main/resources/META-INF/resources. Once deployed, the platform repackages and serves these files under a ui/ prefix, so the entry point the platform loads must be exactly ui/index.html.

Asset references in ui/index.html must be relative (for example ./assets/main.js), not root-relative (/assets/main.js): the UI isn’t served from the domain root, so an absolute path resolves against the platform’s own root instead of the ui/ prefix.

2. How the platform embeds the UI

The platform renders ui/index.html inside an iframe that it resizes to your content’s reported height, as described in Reporting your height.

If your UI never sends a resize message, the iframe keeps the browser’s default height instead of growing to fit your content, so you’re responsible for reporting your height or handling overflow and scrolling yourself.

3. Calling your model’s API from inside the iframe

After the iframe loads, the platform postMessage`s an `init message to it, carrying tenantId, runId, apiUrl, and apiKey:

{
  "source": "timefold-visualization",
  "type": "init",
  "data": {
    "tenantId": "...",
    "runId": "...",
    "apiUrl": "...",
    "apiKey": "..."
  }
}

Listen for it, and reply with an init-response so the platform knows the UI is handling the handshake:

window.addEventListener("message", (event) => {
  if (event.origin !== window.location.origin) return;
  if (event.data?.source !== "timefold-visualization") return;
  if (event.data.type !== "init") return;

  const { tenantId, runId, apiUrl, apiKey } = event.data.data;
  // ...store these for your API calls...

  event.source.postMessage(
    { source: "timefold-visualization", type: "init-response" },
    event.origin,
  );
});

Reply within a task of receiving init — the platform waits only briefly before falling back to the older query-parameter contract, reloading the iframe with onPlatform, runId, tenantId, apiUrl, and apiKey appended to its src URL instead. Support that fallback too if you want your UI to keep working against platform versions that don’t yet send init.

Strip any trailing slash from apiUrl and prepend it to your own API calls, so they’re routed correctly regardless of where the platform proxies from. Append the path your model’s own REST API is served under — the same path you’d hit locally, as described in Calling your REST API from the UI. Only the base changes between running locally and running embedded in the platform.

4. Reporting your height

The platform sizes the iframe from what your UI reports, not from a fixed viewport. Post a resize message whenever your content’s height changes:

window.parent.postMessage(
  { source: "timefold-visualization", type: "resize", height: document.body.scrollHeight },
  window.location.origin,
);

The reported height is clamped between 200px and 50000px; above that ceiling the platform makes the iframe scrollable instead of growing it further.

5. Refreshing while solving

The platform doesn’t push updates into the iframe or refresh it automatically. Your UI needs to poll its own status or solution endpoint on an interval, and stop polling once the dataset’s status leaves the active or solving set.

6. Error reporting

The platform automatically injects a small error-forwarding script into the served HTML. This script turns uncaught JavaScript errors and unhandled promise rejections into a postMessage call, which the platform surfaces to the user as an alert in its own UI.

Don’t rely solely on console.error to signal failure: uncaught errors and unhandled promise rejections are what actually surface to the end user. Anything you only log to the console stays invisible to them.

7. Announcing visualization pages

A model can offer multiple types of visualization, for example a map, a table, and a Gantt chart, and declares each one so the platform knows what to offer users and how to label and icon it. Declare pages through build-time configuration:

timefold.model.visualization.pages[0].key=map
timefold.model.visualization.pages[0].icon=TbMap
timefold.model.visualization.pages[0].label=Map
timefold.model.visualization.pages[1].key=gantt
timefold.model.visualization.pages[1].icon=TbChartGantt
timefold.model.visualization.pages[1].label=Gantt chart

Each declared page has three required fields; omitting any of them fails the build.

  • key: a stable identifier for the page.

  • icon: an icon name from Tabler Icons.

  • label: the human-readable name shown to users.

  • © 2026 Timefold BV
  • Timefold.ai
  • Documentation
  • Changelog
  • Send feedback
  • Privacy
  • Legal
    • Light mode
    • Dark mode
    • System default