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 NotesNew 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-instancevisibility 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.overrideor 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
currentdata 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()andrenderLabels()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()andgetDataSource()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
DOMNodetypes 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)DatabaseServiceandStructrModuleinstances are discovered throughServiceLoader- 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
StorageSyncServicereplaces theDirectoryWatchServiceand drives synchronization through storage providers- Provider-neutral synchronization SPI with outbound vocabulary and sync-direction gating
- Reference implementation on the NIO
WatchServicefor the local filesystem, plus a synchronizer for S3 - External storage keys in the
AbstractFile/StorageProviderdomain allow matching and tracking externally managed files in virtual mounts - Full deployment handler for
StorageConfigurationentities and their entries
Asynchronous Scripting
async/awaitsupport in embedded JavaScript snippets- Async variants of built-in functions that allow it, so several outbound HTTP calls run at once
Promise.raceanswers 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,updateandmirrormodes, so a target that already holds data is no longer overwritten by defaultmirrorremoves the records of an exported type that the archive does not carry, never principals- A
typesmode 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
NodeandDatewith a format string,Nodeacting 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()andsetContent()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
migratemaintenance 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 parametersPdfServletis 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 missingPathoperations and aFileStore - 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
DOMNodetypes, 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
/structrand 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
isMandatoryfor URL routing parameters,afterAcmeChallengelifecycle method
Enhancements
Security
- Password hashing migrated from SHA-512 to Argon2id with OWASP 2023 parameters and transparent migration on login, Argon2id parameters configurable
ConfigServlethardened: 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 configurablePolyglotAccessfor 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
DoSFiltersettings give way to 15httpservice.ratelimiting.*keys on top of Jetty 12.1’sDoSHandler, 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
ZonedDateTimesupport end to end: backend handling,date_format(),toJson()with rawDateandZonedDateTime, 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 throughlog.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
uiandallviews 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
granteessections, 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,
StorageSyncServiceadded 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
RANGEindexes are created again - Unknown property keys raise an explicit error in
get()andset()when unknown keys are not allowed - A type value that names no existing type is refused with 422 instead of failing with a
NullPointerExceptionfrom label maintenance sort()sorts bynameby default and supports descending order,weekDays()uses the correct start of week,toDate()anddate_format()pass null through again- Values are compared with
deepEquals, so writing an unchanged array is no longer a modification dynamicMethodCacheis 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
SetPermissionCommandis fixed
Deployment
- Every
ActionMappingproperty 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
wrapJsInMainis 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
/foois no longer served for/foo/bar - Custom keywords are checked before render-context keywords, and the
responsekeyword 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
uiandallviews 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()anddecrypt()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
Imagelabel, and a repeater’s data key resolves ahead of a same-named function in JavaScript
Files and Storage
- Dynamic files can be edited again,
.foldersand.childrenreturn 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.,pwdshows 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
createZipuses a temporary file,UnarchiveFunctionhandles 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
ShadowPagecreated 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 orcall()- 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,
DELETEoptions moved to the fourth argument, and failures throw instead of returning a status. Run the migration in dry-run mode first, then use themigratemaintenance 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.automigrationis replaced byapplication.migration.mode, a choice ofdry-run,applyoroffthat defaults toapply. An instance that never enabled automatic migration will migrate on its next start. - The two-factor IP allowlist was removed.
security.twofactorauthentication.whitelistedipsmatched an address taken from theX-Forwarded-Forheader, 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
DoSFiltersettings are gone, replaced by 15httpservice.ratelimiting.*keys with a main switch (httpservice.ratelimiting.enabled, defaultfalse), a global limit, a stricter bucket for authentication endpoints, and exclusions by address or path. Every removedhttpservice.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=falseexplicitly. - The internal ui and all views are admin-only, private user-defined functions are no longer callable over REST, and the unused
LogResourcehas 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
restrictToUrlRoutesflag. 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(nowhttpservice.uricompliance.allowedviolations, naming individual violations),log.functions.stacktrace(nowlog.functions.shortenstacktrace, with inverted sense),json.output.dateformat(a date is written in the format of its own property),zoneddatetimeproperty.defaultformat(nowzoneddatetimeproperty.format.override, ISO by default),jsonrestservlet.user.classandwebsocketservlet.class(both decided by the schema now). - See the migration guide for general Structr 6.x to 7.x upgrade information.