# Upgrade Notes
This page explains how to upgrade the platform, and which changes to watch for when upgrading to 1.0.
# Upgrade Steps
For a platform running with Docker Deployment or Production Deployment, upgrade as follows:
Back up the database and attachments (mandatory in production):
docker compose exec -T database pg_dump -U postgres -Fc application > backup_$(date +%F).dump tar czf attachments_$(date +%F).tar.gz runtime/attachments1
2Read the change notes later on this page and check whether any of them affect your existing business.
Pull the new configuration and images and restart. In the
platformdirectory, run:git pull && docker compose pull && docker compose up -d1If you have changed files such as
docker-compose.ymlorruntime/proxy/conf.d/default.conflocally,git pullmay conflict. You can rungit stashfirst, thengit stash popafter pulling and check the differences.Check the version and status:
docker compose images # the TAG column is the running version docker compose ps # startup is complete when server shows healthy1
2
When the backend starts, it automatically imports the platform seed data and the application seed data under codes/data/csv; no extra steps are needed.
For upgrading a Dokku deployment, see Deploying the Backend on Dokku.
# Upgrading to 1.0.0-beta18: Anonymous Calls to Dynamic Services
# What Changed
Dynamic services (Development > Integration > Services) have a switch that allows anonymous calls, enableAnonymous (labeled Enable Anonymous in the UI), which defaults to false. Before 1.0.0-beta18 this switch had no effect: requests without a login could call any dynamic service (grails-platform#768).
Starting with 1.0.0-beta18, services whose enableAnonymous is false or not set reject requests without a login. In testing, a call without a token returns:
POST /api/service/Test%20Echo
HTTP 500
{"msg":"ErrorCode: 11004, ErrorMsg: AnonymousInvocation","errorCode":11004}
2
3
WARNING
The HTTP status code the platform defines for this error is 403, but the current version actually returns 500. Callers should check errorCode 11004 in the response body and not rely on the HTTP status code.
Impact: every service that has been called anonymously without having enableAnonymous turned on will fail after the upgrade. Typical cases are webhook callbacks, device reporting endpoints, and endpoints called from public pages.
# Checking Before the Upgrade
Run the following SQL against the database of the old version, before upgrading, to list the services that have been called anonymously and will be rejected after the upgrade:
-- The anonymous identity is serialized as "Anonymous" (AnonymousAuthenticationProvider.serializeAuthentication)
select s.name, s.enable_anonymous, count(*) as anonymous_calls, max(r.start_time) as last_call
from dynamic_service_exec_record r
join dynamic_service s on s.id = r.provider_id
where coalesce(s.enable_anonymous, false) = false
and (r.exec_auth is null or r.exec_auth::text like '%Anonymous%')
group by 1, 2 order by 3 desc;
2
3
4
5
6
7
How to run it:
docker compose exec database psql -U postgres -d application
Example output (when there are anonymous call records):
name | enable_anonymous | anonymous_calls | last_call
-----------+------------------+-----------------+----------------------------
Test Echo | f | 1 | 2026-09-23 13:28:02.959255
2
3
This SQL does not find everything
The platform only writes call records for services that have logging (enable_log) turned on. A service without logging will not show up in this SQL even if it has always been called anonymously. Use the SQL below to list every service that will reject anonymous calls, and confirm one by one whether its callers send a login token:
select name, active, enable_log
from dynamic_service
where coalesce(enable_anonymous, false) = false
order by name;
2
3
4
# What to Do
For services that really need anonymous access, set enableAnonymous to true:
In the UI: go to
Development > Integration > Services, edit the service, and turn onEnable Anonymous;Or set the
enableAnonymouscolumn in the seed CSV, for example:name(*),active,logic.name,enableAnonymous Device Report,T,Device Report Core Logic,true1
2
Callers of the other services must log in first: get an access_token from POST /api/auth/login, then call the service with the Authorization: Bearer <access_token> request header.
For services with anonymous access turned on, validate the caller in the service logic yourself (for example by checking a signature or an agreed secret), and do not expose create, update or delete operations directly.
# Features Removed Since 1.0 and Their Replacements
Version 1.0 (Grails 7 / Java 25) removed a number of old features. When upgrading from 0.x, or when configuring by following old documentation, adjust according to the list below.
- Reports and document printing (Jasper reports,
DynamicReport, print actions)- Replacement: use dashboards for statistics; when you need to generate files, generate them yourself in dynamic logic or a plugin
- Reference: Dashboard, Plugin Development
- Wizards (Wizard,
DynamicFormWizardStep)- Replacement: collect input with an object action that has a parameter form
- Reference: Object Actions
- Gantt chart forms
- Replacement: no built-in replacement for now; you can develop a custom frontend component
- Reference: Plugin Development
- Dynamic field definitions / instances (
DynamicFieldDefinition,DynamicFieldInstance,(#)columns in CSV)- Replacement: to add fields to an object, use model fields of a dynamic domain model; for action parameters, use the fields of an
ACTIONtype form - Reference: Dynamic Domain Model, Object Actions
- Replacement: to add fields to an object, use model fields of a dynamic domain model; for action parameters, use the fields of an
- Field hooks (Field Hook: field default values, validation, linkage, search)
- Replacement: form hooks (Form Hook / Data Hook)
- Reference: Form Customization
- Display widget configuration (
DomainColumnClientSideTypeConfig)- Replacement: set the widget type in the form field's
displayType - Reference: Advanced Field Controls
- Replacement: set the widget type in the form field's
RequestMapaccess control- Replacement: CRUD permissions of domain models and access requirements of forms/actions/menus are all configured with
RoleRequirement - Reference: Object Permission Control
- Replacement: CRUD permissions of domain models and access requirements of forms/actions/menus are all configured with
- Dynamic permissions via the object hook
Update/delete ability(UPDATE_DELETE)- Replacement: the hook type can still be selected but is no longer executed; use custom logic in
RoleRequirementinstead - Reference: Object Permission Control
- Replacement: the hook type can still be selected but is no longer executed; use custom logic in
- Organizations (
Organization,Organization.csv,$ROOT_ORG$)- Replacement: multi-tenancy is distinguished by tenant (
TENANT_ID) only - Reference: Docker Deployment
- Replacement: multi-tenancy is distinguished by tenant (
- System integration
DynamicIntegration: incoming integrations- Replacement: Webhook (
Development > Integration > Webhook) - Reference: System Integration
- Replacement: Webhook (
- System integration
DynamicIntegration: outgoing integrations- Replacement: send HTTP requests yourself in object hooks such as
After creating - Reference: Migrating from Outgoing Integrations
- Replacement: send HTTP requests yourself in object hooks such as
- Service consumers of dynamic services, user tokens (
UserToken, theX-MY-Tokenrequest header), andjvm://service discovery- Replacement: external calls use the standard login token (
/api/auth/login); calls within the platform use dynamic RPC - Reference: Dynamic Service
- Replacement: external calls use the standard login token (
- The
LLM_ENGINElogic engine, the smart assistant, and AI configuration such as OpenAI / Anthropic- Replacement: no built-in replacement; call the LLM's external HTTP API directly from the Groovy code of dynamic logic
- Email sending (SMTP configuration, email templates, email sending records)
- Replacement: no built-in replacement; call an email service's external HTTP API directly from the Groovy code of dynamic logic; for in-app messages use
ๆถๆฏ > ๆถๆฏไธญๅฟ(this menu is shown in Chinese in the English UI)
- Replacement: no built-in replacement; call an email service's external HTTP API directly from the Groovy code of dynamic logic; for in-app messages use
- Customizing API response data (the
Object renderhook)- Replacement: form Data Hook
- Reference: Form Customization
- Enable logic of scheduled tasks (
enableLogic)- Replacement: decide whether to run at the beginning of the task's core logic yourself
- Reference: Scheduled Tasks
- The "Run at Startup" (
RUN_AT_STARTUP) type of scheduled tasks- Replacement: put logic that must run at system startup in the
onLoad()method of a plugin class that implementsMuyanPlatformComponent(it also runs every time the plugin is reloaded, so the logic must be safe to run repeatedly); change such tasks before upgrading, otherwise this startup logic will no longer run after the upgrade - Reference: Scheduled Tasks, Plugin Development ยท Lifecycle Callbacks
- Replacement: put logic that must run at system startup in the
- Seed data actions
Reload Seed DataandImport Seed Data Package, and the gradle taskpackageSeedData- Replacement: restart the backend to re-import the application seed data under
codes/data/csv, or import data through a plugin - Reference: Data Import
- Replacement: restart the backend to re-import the application seed data under
- Heroku deployment and the online trial environment
- Replacement: deploy with Docker Compose on your own machine or a server
- Reference: Docker Deployment, Production Deployment
- The all-in-one installer
muyantech/installer(Docker Swarm + GlusterFS, JDK 11)- Replacement: deploy to a server with Docker Compose
- Reference: Production Deployment
The legacy frontend (0.x) is still deployed together with Docker Compose at http://localhost:9080/legacy/, for transitional use only.