Passing Data Between Freedom UI Pages in Creatio (Real Project Case)
Introduction
This article is based on a real Freedom UI use case, not a hypothetical demo.
The requirement comes from an actual configuration scenario:
- A Freedom UI form page (
UsrSmartFilter_FormPage) - A custom Freedom UI component/page (
UsrColumnViewerComponent) - The need to pass runtime values (object name + record Id)
- And then persist user selections back into the database
No sandbox. No assumptions. Only what works in Freedom UI today.
Real Problem Statement
In a Freedom UI implementation, the user:
- Opens UsrSmartFilter_FormPage
- Selects an object (for example:
Account,Contact, etc.) - Clicks a button to configure columns
- A second Freedom UI page/component opens
- That page must:
- Know which object was selected
- Know which UsrSmartFilter record is being edited
- Show all columns of the selected object
- Save selected column names back to
UsrSmartFilter.UsrColumnNames_Values
Classic UI sandbox messaging cannot be used.
Why Sandbox and Page Parameters Cannot Be Used
In Freedom UI:
- Pages are isolated
- There is no exposed sandbox API
- Parameters passed via
crt.OpenPageRequestare not injected into the target page context
This is a documented and observed platform behavior, not a theory.
The Working Mechanism: BroadcastChannel
The solution that works reliably is using the browser-native <strong>BroadcastChannel</strong> API.
This is not a Creatio abstraction. It is a standard Web API that works because:
- Freedom UI pages run in the same browser context
- Messaging happens at the browser level
- No Creatio internals are bypassed
Real Sender: UsrSmartFilter_FormPage Handler
This handler exists on UsrSmartFilter_FormPage and is triggered by a button click.
What It Sends
- Selected object schema name
- Current page record Id (
UsrSmartFilter.Id)
Real Handler Code
{ request: "QNT.GetColumns", handler: async (request, next) => { const objectValue = request.$context.PDS_UsrSelectedObject_n4di2v4; const pageId = request.$context.Id; if (!objectValue || !pageId) { return next?.handle(request); } const channel = new BroadcastChannel("UsrSmartFilter_Channel"); channel.postMessage({ selectedObjectName: objectValue.displayValue, pageId: pageId }); channel.close(); return next?.handle(request); } }
This code is taken directly from a working Freedom UI page.
Real Receiver: UsrColumnViewerComponent
UsrColumnViewerComponent is a custom Freedom UI component responsible for:
- Receiving the object name and page Id
- Loading the selected object schema dynamically
- Allowing the user to select columns
- Persisting the selection
Receiving the Data
this._channelIn = new BroadcastChannel("UsrSmartFilter_Channel"); this._channelIn.onmessage = async (event) => { const { selectedObjectName, pageId } = event.data || {}; this._selectedObjectName = selectedObjectName; this._parentPageId = pageId; await this._loadObjectSchema(); this._render(); };
This logic runs when the component is initialized.
Loading the Object Schema Dynamically
Instead of hardcoding Account, the schema is loaded dynamically using the received object name:
const model = await sdk.Model.create(this._selectedObjectName); const schema = await model.getSchema(); this._columns = Object.values(schema.attributes).map(attr => ({ name: attr.name, caption: attr.caption || attr.name }));
This is standard Freedom UI Model API usage.
Persisting the Selected Columns (Real Persistence)
When the user selects or unselects columns, the component:
- Builds a comma-separated string
- Updates the existing
UsrSmartFilterrecord
const model = await sdk.Model.create("UsrSmartFilter"); await model.update({ Id: this._parentPageId, UsrColumnNames_Values: columnsCsv });
This is not messaging — this is actual data persistence.
Why This Is a Real, Production-Safe Pattern
- Uses only documented Web APIs
- Uses Creatio Freedom UI Model API
- No reliance on undocumented sandbox features
- Works across page reloads once persisted
This pattern has been validated in real projects.
Key Constraints (Real, Not Theoretical)
BroadcastChanneldoes not queue messages- Receiver must be initialized first
- Transient data should always be persisted
Ignoring these leads to real bugs.
Conclusion
This article does not describe a hypothetical demo.
It documents a real Freedom UI implementation pattern for:
- Passing data between pages
- Dynamically loading object metadata
- Persisting user configuration
Until Creatio provides an official sandbox replacement for Freedom UI, this is the correct approach.
Final Takeaway
Freedom UI pages do not share state.
Browser-level messaging + Model API persistence is the only reliable solution today.
This is based on actual working code, not assumptions.
Thanks for this write up. I've been using the native BroadcastChannel API for communication in Freedom UI and works fantastically!
It's important to make sure the receiver also gets disconnected to allow garbage collection. For the sender, it's easy to just call channel.close(); after sending, but the listener is typically initialized in the crt.HandleViewModelInitRequest on the receiving page. For this, I will put a reference to the channel object in an attribute on the receiving page (how to use attributes) and then dispose/close in the crt.HandleViewModelDestroyRequest:
{ request: "crt.HandleViewModelDestroyRequest", handler: async (request, next) => { const channel = await request.$context.MyChannelAttr; if (channel) { channel.close(); request.$context.MyChannelAttr = null; } return next?.handle(request); } }
Ryan