Release Notes

What changed, release by release.

New features, enhancements, bugfixes and upgrade notes for every Structr release.

Current stable release
Structr 7.0.0September 22, 2026
Major releaseReleased September 22, 2026

Structr 7.0.0

Structr 7.0.0 is a major release. It introduces the Structr Process Engine with a BPMN editor and runtime, rebuilds the widget system around data-driven components, moves the platform onto the Java Module System and Java 25, and carries the largest security hardening effort in Structr’s history. The scripting layer gains async/await, leveled logging and a consistent error contract, the outbound HTTP functions get a uniform 7.0 API with an assisted migration, and deployment learns explicit data import modes. New in this release are the export diff module, storage synchronization, an embedded Neo4j driver, scratchpads, and URL routing that can serve pages exclusively through their defined routes.

Because this is a major release, several API contracts changed. Read the Upgrade Notes before upgrading a production instance.

Read the Upgrade Notes

New Features

Structr Process Engine (BPMN)

A new module for modelling and running business processes:

  • BPMN process editor on a canvas, with an Element / Process tab strip so process-level settings are always one click away
  • Processes as first-class schema types (BpmnProcess), with vendor-specific pluggable BPMN importers
  • Task listeners with a 1:1 lifecycle between listener and handler method, one method per event and phase (on / after)
  • Engine-managed runtime nodes with permissions granted to initiator and assignee, and permission propagation across the BPMN model
  • JWT-based access tokens for sessionless process step access
  • Automatic process page creation, process-bound widgets whose data source is derived from the BPMN user task’s subject type
  • Event action mapping integration: process start with initial parameters, has-active-instance visibility mapping, process and user-task field types
  • $.processInstanceUrl() builds an absolute URL to a process instance’s page from the Site’s host and port, application.baseurl.override or the render context
  • Deployment export and import with identity-preserving upsert that keeps definitions used by running instances
  • Process Types filter in the schema and data areas, new play/pause/stop icons, documentation articles for the whole engine

New Widget System with Data-Driven Components

The frontend construction kit has been rebuilt around data-driven components:

  • A component binds to a data adapter instead of carrying its own query. Schema, query, script, folder and current data sources are available, and a process-bound source derives its type from the BPMN user task’s subject
  • Pagination, filtering and sorting come with the component, with no code to write, and a partial reload that keeps the focus where the user left it
  • Render templates are Widgets that are expanded on demand. renderEach(), renderFields() and renderLabels() no longer take a data adapter parameter, they use the enclosing component. A script condition selects the edit or the render template per field
  • A field editor in the admin UI configures which fields a component shows, with an Expert Settings mode that supports enum fields, and smart selects in the widget dialog
  • The theme system was refactored for Tailwind CSS 4, with getTheme() and getDataSource() available from scripting
  • Components declare dimensions, which drive a compatibility check when a widget is inserted; an unset dimension means no constraint. Dimensions are part of the deployment export
  • Widgets can be inserted, replaced and wrapped around existing elements, the widget importer creates event action mappings, and the widget library moved to its own repository and is versioned independently
  • Shared-component property sync moved into core for all DOMNode types and is aware of the sync mode

Java Module System (JPMS)

Structr now runs as proper Java modules on Java 25:

  • structr-base, the driver API and all drivers transitioned to JPMS modules, sub-modules likewise (file-access and messaging-engine remain on the classpath because of third-party dependencies)
  • DatabaseService and StructrModule instances are discovered through ServiceLoader
  • New build tooling sorts jars onto the module path from a seed file with overrides, plus a pre-flight check that detects dependency problems before the JVM starts
  • A dependency management document describes the system
  • The plugins folder for user libraries is auto-created in all distribution paths

Export Diff Module

  • Compares two deployment exports and reports what changed between them
  • Reports whether two exports are versions of one application or two different ones, and describes each from its schema and pages
  • compareExports() scripting function as the single producer behind both the admin UI and server-side callers
  • Deltas carry the old and the new value, so a reader learns what a change became and not only which field it touched

Storage Synchronization

  • StorageSyncService replaces the DirectoryWatchService and drives synchronization through storage providers
  • Provider-neutral synchronization SPI with outbound vocabulary and sync-direction gating
  • Reference implementation on the NIO WatchService for the local filesystem, plus a synchronizer for S3
  • External storage keys in the AbstractFile / StorageProvider domain allow matching and tracking externally managed files in virtual mounts
  • Full deployment handler for StorageConfiguration entities and their entries

Asynchronous Scripting

  • async/await support in embedded JavaScript snippets
  • Async variants of built-in functions that allow it, so several outbound HTTP calls run at once
  • Promise.race answers the call that finished first, settled from the host
  • A rejected promise reports what it was rejected with

Embedded Neo4j Driver

  • New embedded Neo4j database driver (Neo4j 2026.08.1), selectable in the database setup wizard together with the in-memory driver
  • The CI pipeline runs the full test suite once per database driver

Scratchpads

  • Scratchpads are first-class entities, with multiple scratchpads per user, names, and REST-based updates so output can be streamed while a script runs

Data Deployment Modes

  • seed, append, update and mirror modes, so a target that already holds data is no longer overwritten by default
  • mirror removes the records of an exported type that the archive does not carry, never principals
  • A types mode reports which types hold records instead of making the caller derive the list
  • A data import is refused on an instance that calls itself production, overridable with force
  • A deployment import answers its caller with a report of what it did and what it dropped, including the outcome of each deployment config script and the line a failing script failed on

URL Routing

  • Pages can be made reachable exclusively through their defined URL routes, not by name, UUID or position
  • Parameter types Node and Date with a format string, Node acting as a type filter the way $.find() does
  • Required parameters, default values processed through the type converter, path priority and default values exposed in the UI, granular URI compliance modes for Jetty
  • Path parameters are exported and imported with the deployment

Outbound HTTP API 7.0

  • FETCH() performs a request with an arbitrary HTTP verb
  • A uniform options object across all outbound HTTP functions, with one response shape
  • Streaming support in GET() and setContent() for binary data, without the 2 GB download limit, and binary request bodies so a File can be uploaded as its content
  • A migration mode with a dry run, a migrate maintenance command and a report on calls that still use the pre-7.0 signature

PDF Rendering In-Process

  • PDFs are rendered with openhtmltopdf inside the JVM instead of the external wkhtmltopdf binary: pdf() needs nothing installed, renders in the caller’s context and returns a File
  • pdf() takes a page path with the id of the object the page renders plus an object of request parameters
  • PdfServlet is offered in the servlet choice set

Virtual Filesystem as a java.nio Filesystem

  • The virtual filesystem is addressable as structr:/// through a java.nio registration layer, with the missing Path operations and a FileStore
  • Deployment export and import use java.nio calls instead of Path.toFile(), so they work on non-default filesystems

Other New Features

  • Abstract and interface types are enforced and marked in the schema UI
  • Shared-component property sync moved into core for all DOMNode types, aware of the sync mode
  • Device trust for two-factor authentication, so a trusted browser can skip 2FA on the next login, disableable per user and with a secret that can be rotated
  • Modules can mount servlets outside /structr and register their own UI resources through a new servlet
  • New scripting functions: getLabels(), getRequestHeaderNames(), println(), $.log.debug/info/warn/error(), getDirectAccessEntries(), getEffectiveAccessEntries(), notify()
  • An optional filter for the server log on the dashboard and in the serverlog() function
  • A whitelist setting for the registration and password-reset rate limits, with CIDR ranges
  • isMandatory for URL routing parameters, afterAcmeChallenge lifecycle method

Enhancements

Security

  • Password hashing migrated from SHA-512 to Argon2id with OWASP 2023 parameters and transparent migration on login, Argon2id parameters configurable
  • ConfigServlet hardened: timing-safe password comparison, brute-force lockout per client IP, session fixation prevention, CSRF origin checks, distinct field identifiers for password managers
  • CSRF origin checks on the login and upload servlets and on the WebSocket upgrade handshake, upload redirect URLs validated against open redirects
  • SSRF protection extracted into HttpHelper.validateUrl() and applied to the proxy, deployment and data feed paths, configurable and enabled by default, protocol-relative URLs blocked
  • JWT: configurable audience binding, enforced issuer claim, entropy check on the secret
  • HMAC-signed opaque confirmation and 2FA tokens replace the previous predictable format
  • Session id rotation on login, session tokens bound to a real session, cookie-only tracking, Secure cookies by default
  • Failed logins no longer disclose whether an account exists, and cost the same either way, with per-user synchronization of the attempt counter
  • Only whitelisted POST keys enter the template context, so user-controlled values cannot override internal variables such as link
  • OAuth state is kept in a size-capped, time-limited cache instead of an unbounded map
  • CORS misconfiguration warning when credentials are enabled with a wildcard origin, relaxed-TLS outbound calls are logged, hostname verification stays on
  • Nesting depth limit for fromXml() when XML parser security is enabled, statement-count limit and configurable PolyglotAccess for GraalVM scripting contexts
  • Credentials are redacted in logs: no FTP passwords, no credential function arguments, no OAuth tokens in debug output
  • A warning for admin accounts that still use the default password, at startup, at login and in the dashboard security warnings
  • Application secret and JWT secret are auto-generated when unconfigured
  • Apache SSHD upgraded from 2.15.0 to 2.17.1, SSH session isolation fixed and console commands restricted to admins

Rate Limiting and Endpoint Protection

  • Rate limiting was rewritten: the 211 per-servlet DoSFilter settings give way to 15 httpservice.ratelimiting.* keys on top of Jetty 12.1’s DoSHandler, with a leaking-bucket tracker, a stricter bucket for authentication endpoints, exclusions by address or path, and refusals logged by Structr itself including the remote address
  • The health check, metrics and histogram whitelists accept address ranges in CIDR notation such as 10.0.0.0/24, and report an entry they cannot apply instead of refusing to start
  • The e-mail rate limit whitelist is read the same way, so an address range means the same thing everywhere
  • A reusable log throttle, bounded per key and in total per window, applied to the denied resource access message

Scripting

  • find(), search(), findPrivileged(), create(), get(), set(), the predicate functions, typeInfo(), propertyInfo(), enumInfo(), functionInfo(), getRelationshipTypes(), ancestorTypes(), inheritingTypes(), base64decode() and the advanced mail functions raise real errors instead of returning error messages, wherever the scripting engine can handle them
  • Function names and parameter types appear in exception messages
  • ZonedDateTime support end to end: backend handling, date_format(), toJson() with raw Date and ZonedDateTime, a validated format override setting, js-joda in the frontend, OpenAPI info
  • Autocomplete for $.predicate., node types and keywords in the JavaScript console and the Monaco editor
  • The log() function prepends its call origin, disableable through log.logfunction.printcaller
  • Polyglot symbol resolution prefers stored values and constants over functions, closer to StructrScript
  • StructrScript supports inline comments
  • Boundary-aware deep clone: cloneNode(deep, stopAt) copies a page’s common frame without its page-specific content

User Interface

  • Files area: native drag and drop hints, an Upload button, Ctrl/Meta-click and Shift-click selection, syntax highlighting for HTML files in the editor
  • Schema editor: Clone View, uniqueness validation for property and view names, warnings that the ui and all views are internal and admin-only, reserved-word validation for data keys, backend validation against conflicts with internal attributes, layout restoration that keeps newly created types visible
  • Pages area: a Sites section, Sites on the General tab, double-click to expand or collapse subtrees, correct linkable updates, DOMPurify sanitizing HTML that could break the layout
  • Global search covers Sites, Files, Localizations and Mail Templates with navigation to the result, offers Ignore Case where the database supports it, includes action and parameter mappings, and skips media files for performance
  • Event action mapping: native file upload to forms through the upload servlet, configurable text and CSS class for inline notifications, Show/hide page section(s) follow-up actions with URL-bound partial loading and repeater scoping
  • Dialogs unified in size and height, back navigation between linked nodes, a confirmation dialog for deleting schema nodes, a reset button after choices
  • Documentation in the UI: process engine articles, a REST API method parameters article, URL routing docs moved to markdown, permission and grantees sections, mail and ACME documentation
  • Configuration UI: no help text in the login form that could be confused with admin credentials, session fixation protection optional, active section surviving logout and login

Platform and Build

  • Jetty upgraded from 12.0.23 to 12.1.11, GraalVM to 25.0.3, Neo4j to 2026.08.1, a shared version property for all Bouncy Castle modules
  • Configurable Jetty stop timeout, module-path pinning for structr-* modules, filtered build warnings, code style applied across the codebase with checkstyle definitions and an automatic code review with scoring
  • Test suite: up to two retries globally, one pipeline run per database driver, UTF-8 test JVMs, per-test log attribution, cleanup of old SNAPSHOT packages in the package registry
  • MemgraphDB driver removed, StorageSyncService added to the default services

Bugfixes

Core and Scripting

  • Function properties with a type hint now use the input converter for null values, range predicates work on function properties, and RANGE indexes are created again
  • Unknown property keys raise an explicit error in get() and set() when unknown keys are not allowed
  • A type value that names no existing type is refused with 422 instead of failing with a NullPointerException from label maintenance
  • sort() sorts by name by default and supports descending order, weekDays() uses the correct start of week, toDate() and date_format() pass null through again
  • Values are compared with deepEquals, so writing an unchanged array is no longer a modification
  • dynamicMethodCache is invalidated so schema method edits take effect, a method whose source was never set no longer throws
  • Framework exceptions from the $-functions propagate into scripts instead of being swallowed, server-side scripting and data-source failures are logged at ERROR
  • Index names are enclosed in backticks, so property names containing dashes work
  • Bulk graph commands skip missing or conflicting keys instead of aborting, permission propagation stops silently extending permissions, and recursive permission explosion in SetPermissionCommand is fixed

Deployment

  • Every ActionMapping property is exported and keeps its value, including the four notification properties, covered by a test
  • Pages with identical names survive a round trip, and a Site linked to pages with non-unique names resolves deterministically
  • File and folder names are normalized to Unicode NFC, so an export written on macOS still matches itself after a git round-trip
  • wrapJsInMain is part of the export, URL routes are excluded from the clone blacklist, component dimensions are exported and an unset dimension means no constraint
  • A detached-node check runs in its own transaction and cannot break the export, DOM nodes belonging to no page are reported and can be repaired
  • Module data is not deleted when the archive carries no modules folder, and a flow name no longer breaks commits or imports on an installation without the flow module
  • Script bodies are no longer parsed as markup, and unclosed void elements no longer invent elements and text nodes that were never in the export

Sites, Pages and URL Routing

  • Pages without a Site are served on every host no Site claims, the port is part of the Site match, and a Site configured with a port alone claims nothing
  • A page’s Sites are read as superuser, so a Site the visitor may not see still routes its pages
  • URL routes must consume all parts of a request, so /foo is no longer served for /foo/bar
  • Custom keywords are checked before render-context keywords, and the response keyword is restricted to page context
  • Sites are respected for error pages and when URL routing is used
  • Whitespace-only script expressions render again, and printed Content output keeps its place and order

Authentication and Sessions

  • HTTP Basic Auth works for passwords containing colons (RFC 7617)
  • Two-factor authentication issues fixed, login() denies a login when 2FA is enabled, and the login servlet supports 2FA redirects
  • Failed password attempts are counted in their own thread, so header authentication can count them too
  • Failed logins lock an account for minutes instead of forever, and a password reset no longer blocks an account that is in use
  • A missing session from getSession() is treated as no session with a silent fallback to the cached id
  • The OAuth callback is bound to the browser that started the flow, with PKCE and nonce on every authorization request

Minor Security Fixes

  • The internal ui and all views no longer hand out session ids, refresh tokens, 2FA tokens or confirmation keys, and credential properties are read-only
  • Schema JSON and the runtime event log are admin-only, the resolver answers in the caller’s context, and private user-defined functions are not callable over REST
  • Missing permission checks, path traversal, command injection and shared request state in servlets
  • Health, histogram and metrics endpoints refuse requests whose address belongs to a proxy rather than to the caller, and the spoofable Forwarded header is ignored
  • encrypt() and decrypt() require a key derivation scheme, and a key set at runtime no longer leaks into other requests
  • CSV export neutralizes leading formula characters, XMPP requires TLS, and CR/LF are removed from mail headers and attachment names
  • Uploaded images get the Image label, and a repeater’s data key resolves ahead of a same-named function in JavaScript

Files and Storage

  • Dynamic files can be edited again, .folders and .children return complete results during dynamic file rendering
  • IOExceptions during move and delete operations are no longer swallowed, non-existent S3 buckets are handled, and S3 providers report errors properly
  • SFTP path resolution handles .. and ., pwd shows the root directory correctly, and redundant SFTP event listener transactions are removed
  • A commit-only post-process queue defers the file metadata update, so a rolled-back transaction no longer discards it
  • createZip uses a temporary file, UnarchiveFunction handles a parent folder, custom thumbnail properties work, and mounted folders migrate properly

User Interface

  • The Monaco suggestion popup is no longer hidden behind the function bar, the schema graph repaints once the UI font has loaded, and unescaped characters no longer break parts of the UI
  • Bulk edit saves method changes correctly and shows the change status per tab, the ACL dialog no longer breaks when opening a popup, and access control escapes user names
  • Pager and pagination logic is fixed for all page sizes and result counts, including results below the soft limit, and paging controls remain usable when the soft limit hides the total
  • The preview slideout reloads for the correct page, the Recycle Bin slideout closes after emptying, and widget dialogs, context menus and the shared components area behave when collapsed or newly created
  • The ShadowPage created while the backend is open no longer appears in the pages tree twice
  • The console no longer renders raw HTML, no longer prints a result for a rolled-back transaction, and survives a function returning null

Other

  • functionInfo() works for user-defined methods called through cron or call()
  • The schema type resource returns a single entity instead of a one-element array, and the soft limit is ignored for schema queries
  • Scheduled jobs can no longer run twice thanks to a shared lock
  • A diagnostic-only migration check no longer aborts startup, the dry run no longer halts an empty instance, and migration runs at startup by default with a non-zero exit status for the dry run
  • Types inheriting a trait are cleaned up when the trait is deleted, and nested flows can be created for package paths that partly exist
  • Static resources are shipped in the distribution again, fixing unavailable documentation servlet resources

Upgrade Notes

  • Java 25 is required. Structr runs as Java modules on the module path; user libraries belong in the auto-created plugins folder.
  • Password hashes migrate to Argon2id transparently on the next successful login. No action is needed, but plan for the first login of each user to be slightly more expensive.
  • The outbound HTTP functions follow a new contract: all optional arguments moved into an options object, responses share one shape, DELETE options moved to the fourth argument, and failures throw instead of returning a status. Run the migration in dry-run mode first, then use the migrate maintenance command, and check the report for calls that need manual work.
  • encrypt() and decrypt() now require a key derivation scheme as their first argument (encrypt(scheme, value [, key])).
  • Schema migration now runs at startup by default. application.schema.automigration is replaced by application.migration.mode, a choice of dry-run, apply or off that defaults to apply. An instance that never enabled automatic migration will migrate on its next start.
  • The two-factor IP allowlist was removed. security.twofactorauthentication.whitelistedips matched an address taken from the X-Forwarded-For header, which a client can set itself. Clients from those addresses now provide the second factor.
  • enumInfo() no longer wraps values in GraphObjects. This only affects uses outside repeaters, which wrap automatically.
  • The schema type resource returns an entity, not a one-element array. Review code that indexes into that result.
  • Rate limiting is new, and it is off until you switch it on. The per-servlet Jetty DoSFilter settings are gone, replaced by 15 httpservice.ratelimiting.* keys with a main switch (httpservice.ratelimiting.enabled, default false), a global limit, a stricter bucket for authentication endpoints, and exclusions by address or path. Every removed httpservice.dosfilter.* key is recognised at startup and names its replacement.
  • Cookies are Secure by default and the session id is rotated on login. An instance reached over plain HTTP must set httpservice.cookies.secure=false explicitly.
  • The internal ui and all views are admin-only, private user-defined functions are no longer callable over REST, and the unused LogResource has been removed.
  • login() denies a login when two-factor authentication is enabled for that user.
  • The MemgraphDB driver has been removed. Instances using it must move to Neo4j (server or embedded) or the in-memory driver.
  • Health, histogram and metrics endpoints evaluate the peer address, not the Forwarded header. Whitelists accept CIDR ranges now, and an entry that cannot be applied is logged and ignored instead of preventing startup.
  • Pages can be restricted to their URL routes through the restrictToUrlRoutes flag. Existing pages keep their name, UUID and position access unless the flag is set.
  • PDF rendering no longer needs wkhtmltopdf. Remove the binary from deployment images if nothing else uses it.
  • find(), search(), create() and the info functions throw errors where they previously returned error messages. Update code that inspected return values for error strings.
  • Deployment exports normalize file and folder names to Unicode NFC. Re-export once so a stored export matches what a new export produces.
  • Further settings were removed or replaced, each with a startup hint naming its successor: httpservice.uricompliance (now httpservice.uricompliance.allowedviolations, naming individual violations), log.functions.stacktrace (now log.functions.shortenstacktrace, with inverted sense), json.output.dateformat (a date is written in the format of its own property), zoneddatetimeproperty.defaultformat (now zoneddatetimeproperty.format.override, ISO by default), jsonrestservlet.user.class and websocketservlet.class (both decided by the schema now).
  • See the migration guide for general Structr 6.x to 7.x upgrade information.