In a previous article I outlined how to embed an IFRAME on a Classic Creatio page. This article will outline how to do this for a Freedom UI page. We’ll accomplish this by creating a simple Freedom UI component, and then adding the component to the page. We’ll be able to use a static URL for the IFRAME source or bind the source to a property on the page. The end result will be as follows:

Creating a Simple Freedom UI IFRAME Component
Our first step is to create a simple Freedom UI component that will create the IFRAME. To create this, add a Module and paste in the following code:
// jshint esversion: 11
define("UsrFrameComponent", ["@creatio-devkit/common"], function (sdk) {
class UsrFrameComponent extends HTMLElement {
constructor() {
super();
this._dom = this.attachShadow({mode: 'open'});
}
get src() {
return this._frameConfig.src;
}
set src(value) {
this.frameConfig = {src: value};
}
get frameConfig() {
return this._frameConfig;
}
set frameConfig(value) {
this._frameConfig = value;
this._frameConfig.src = this.frameConfig.src || "about:blank";
this._frameConfig.height = this.frameConfig.height || "100%";
this._frameConfig.width = this.frameConfig.width || "100%";
this._frameConfig.style = (this._frameConfig.style || "") + "height:" + this.frameConfig.height + ";width:" + this.frameConfig.width + ";";
if (!this.frameConfig.border) {
this._frameConfig.style += "border:none;";
}
if (!this._frameConfig.sandbox) {
this._frameConfig.sandbox = "allow-scripts allow-forms allow-same-origin allow-popups allow-popups-to-escape-sandbox";
}
this._loadFrame();
}
_loadFrame() {
let frame = '<iframe src="' + this._frameConfig.src + '" style="' + this._frameConfig.style + '" sandbox="' + this._frameConfig.sandbox + '" allowfullscreen="" loading="lazy" referrerpolicy="no-referrer-when-downgrade">';
this._dom.innerHTML = frame;
}
}
customElements.define("usr-frame-component", UsrFrameComponent);
sdk.registerViewElement({
type: "usr.FrameComponent",
selector: "usr-frame-component",
inputs: {
frameConfig: {},
src: {}
}
});
return UsrFrameComponent;
});
This component will take a src URL and create an IFRAME with 100% width and height (there’s other options, I’ll discuss those later). The component is called usr.FrameComponent, which is what we’ll use to add it to the page.
Adding the IFRAME Component to the Page
The next step is to add the IFRAME component to the page. To do this, we need to add a reference to the module we created in the top of the page code.
define("UsrMyPage_FormPage", /**SCHEMA_DEPS*/["UsrFrameComponent"]/**SCHEMA_DEPS*/, function/**SCHEMA_ARGS*/()/**SCHEMA_ARGS*/
Now, we need to add the element to the page. If you’d like you can drag a label where you want to put the IFRAME and then edit the source code to modify it from a label to a usr.FrameComponent. The element in the page code, with the changes so it is a IFRAME, would look like this:
{
"operation": "insert",
"name": "FrameComponent",
"values": {
"layoutConfig": {
"column": 1,
"row": 1,
"colSpan": 1,
"rowSpan": 20
},
"type": "usr.FrameComponent",
"src": "https://customerfx.com"
},
"parentName": "GridContainer_xvfw2eb",
"propertyName": "items",
"index": 0
}
The important part here are the following:
- type = “usr.FrameComponent”
- src = some URL
Additionally, I’ve set it to span 20 rows. As I mentioned, the component will fill 100% height and 100% width of the container it is in, so this will allow for a better height of the frame. At this point, that is all that is needed and the result is the image at the beginning of this article.
Binding the URL of the IFRAME to a Value
Instead of using a static URL for the IFRAME, we can also bind it to a value. For example, if we have a Website field on the page, we could display that website in the frame. To do this, we’d simply bind the src to the columns attribute in the viewModelConfigDiff (using a $ to indicate we’re binding to that attribute), like this:
{
"operation": "insert",
"name": "FrameComponent",
"values": {
"layoutConfig": {
"column": 1,
"row": 1,
"colSpan": 1,
"rowSpan": 20
},
"type": "usr.FrameComponent",
"src": "$PDS_UsrWebsite_4644qhj"
},
"parentName": "GridContainer_xvfw2eb",
"propertyName": "items",
"index": 0
}
The result would be the following:

Just keep in mind that for this scenario the change event fires for each keystroke so you’ll see some flicker as the user types the URL. Or we could create our own attribute an form a URL to the lat/long of an address on Google maps:

We can also have multiple IFRAMEs on the same page:

A nice part is that once we’ve added it to the viewModelConfigDiff we can drag it around and resize it in the designer as well:

Other Options
I mentioned there’s a few other options for the component in this article. Rather than include a src property, you can use a frameConfig property and include the properties listed below:
{
"operation": "insert",
"name": "FrameComponent",
"values": {
"layoutConfig": {
"column": 1,
"row": 1,
"colSpan": 1,
"rowSpan": 20
},
"type": "usr.FrameComponent",
"frameConfig": {
"src": "https://customerfx.com",
"style": "border: 1px solid red;",
"height": "200px",
"width": "300px",
"border": true,
"sandbox": "allow-popups allow-popups-to-escape-sandbox"
}
},
"parentName": "GridContainer_xvfw2eb",
"propertyName": "items",
"index": 0
}
Keep in mind, not all website allow themselves to be embedded in an IFRAME. Many websites have a HTTP header for X-Frame-Options, which is a header used to indicate if a browser should be allowed to embed a webpage. If set to “sameorigin” it means the website only allows embedding in a frame on its own website. Something to keep in mind with embedding websites in an IFRAME.




Dear Ryan Farley,
Thank you for the detailed article and instructions provided—it has been immensely helpful. We have followed the steps as described, but we are encountering an issue where the IFRAME does not resize as expected. In the Freedom UI Designer, the IFRAME displays correctly based on the specified rowSpan. However, when we open the page, the height of the IFRAME consistently appears to be around 135 pixels.
Could you please provide any insights or additional guidance on how we might resolve this resizing issue?
Thank you in advance for your assistance!
Best regards,
Vit
It’s likely that the container it is in is rendering that size. The size of things in the designer aren’t always how they will render at runtime You might need to adjust the height of the container.
Ryan
Hi Ryan,
When the iframe is navigating, is messing up creatio browser history.
Any ideas on how to fix it? I been trying different solutions to prevent that with no luck.
Thank you!
That is a common problem with any IFRAME, not just one in Creatio. I don’t know of a good solution, but google does show a lot of results on that topic.
Hey Ryan,
I’ve come upon your tutorial, but I struggle with making it happen. Once I refer to the custom component (module) on my Freedom UI page, the page won’t load anymore. I can see on my page in UI builder that I the custom component is defined, but it just won’t load. Do you have any suggestions why this might be?
Thank you!
I would guess likely missed adding the component module name to the SCHEMA_DEPS of the page, see the second code block in this article. Either way, if you look in the browser console/dev tools it will likely reveal the error or cause of the page not loading.
Hello Ryan,
This post is amazing—it was a huge help in implementing a PDF preview functionality!
Could you provide some guidance on retrieving files from Creatio Machine?
Currently, I have the file detail displayed on the page and am sending to the PDF Source, the OData link to the ‘Data’ column each time a line from this detail is selected. However, I’m struggling to make the link dynamic across different environments.
Have you ever developed something like this?
Thank you!
Hard to say without knowing more of how it’s implemented, however, there’s plenty of ways to determine the URL of the system client side and server side. If this is client-side you could use Terrasoft.loaderBaseUrl or Terrasoft.workspaceBaseUrl for the base url
I recently implemented a custom component in an IFrame based on Ryan’s instructions above. I adapted my version to dynamically display or download content sent via API. In our case, the files are stored on an external site, then transmitted via RestAPI using base64 encoding. I embedded the required HTML in my custom iframe component with all the logic needed to determine whether to download or display in line.
The API call is triggered via handler on the page, which calls a Creatio business process. The response includes the encoded data for the file. The encoded data from the process response is pushed to page parameters for the data fields required along with the custom IFrame component which waits for a “data loaded” trigger.
Key points to note:
1) process is called from page load, which passes input record Id to business process.
2) business process calls API via OOTB web service components.
3) response values set to process parameters.
4) handler pushes process parameters to page parameter values.
5) iFrame document viewer component uses page parameter values to load/display. The key data point on our end is an unlimited length text field that acts as a temporary storage for the full base64 encoded data string.
You may be able to adapt some of this to your use case:
viewConfigDiff: /**SCHEMA_VIEW_CONFIG_DIFF*/[
{
“operation”: “insert”,
“name”: “DocumentViewer”,
“values”: {
“visible”: “$IsDocumentLoaded”,
“type”: “usr.DocumentViewerFrameComponent”,
“base64Data”: “$base64Data”,
“fileType”: “$fileType”,
“fileName”: “$fileName”,
“dataLoaded”: “$IsDocumentLoaded”
},
“parentName”: “DocumentViewFlex”,
“propertyName”: “items”,
“index”: 0
},
………..
handlers: /**SCHEMA_HANDLERS*/[
{
request: “crt.HandleViewModelInitRequest”,
handler: async (request, next) => {
// Allow other handlers to process first
const result = await next?.handle(request);
(async () => {
// Check required context parameters
const [IsDocumentLoaded, Id] = await Promise.all([
request.$context.IsDocumentLoaded,
request.$context.Id
]);
if (!IsDocumentLoaded && Id) {
try {
const handlerChain = sdk.HandlerChainService.instance;
// Run the process with input and output parameters
const processResult = await handlerChain.process({
type: “crt.RunBusinessProcessRequest”,
processName: “UsrViewDocument”,
processParameters: {
RecordId: Id
},
resultParameterNames: [“fileName”, “base64Data”, “fileType”], // Specify output parameters
$context: request.$context
});
// Check if the process executed successfully
if (processResult.success) {
request.$context.fileName = processResult.resultParameterValues[“fileName”];
request.$context.base64Data = processResult.resultParameterValues[“base64Data”];
request.$context.fileType = processResult.resultParameterValues[“fileType”];
request.$context.IsDocumentLoaded = true;
} else {
console.error(“Process execution failed:”, processResult.errorMessage || “Unknown error”);
}
} catch (error) {
console.error(“Error processing the document handler:”, error);
}
}
})();
return result;
}
}
Matt, that is fantastic. Especially nice touch to use a process for the web service call. Thanks for sharing.
Hi Ryan,
Thank you for the article. I have been trying to implement the same approach, with the only difference being that I am unable to use a dynamic source within frameConfig.
Here is my configuration:
{
“operation”: “insert”,
“name”: “Frame”,
“values”: {
“type”: “usr.FrameComponent”,
“frameConfig”: {
“src”: “$PDS_UsrTrackingLink_g0raa39”,
“style”: “border: 1px solid red;”,
“height”: “900px”,
“width”: “1000px”,
“border”: true,
“sandbox”: “allow-popups allow-popups-to-escape-sandbox”
}
},
“parentName”: “TabContainer_6wrqf5a”,
“propertyName”: “items”,
“index”: 0
},
While this setup works with a static source, using a dynamic source within frameConfig results in a 404 – File or directory not found error on the page.
Could you please provide any insights or suggestions on how to resolve this issue?
Hello Pranshu,
It does work with a dynamic source for the frame for me (at least last I tested this and wrote this article, not sure if anything has changed in the core Creatio since). I would suggest to use dev tools to see that the source URL is looking like once rendered.
Ryan
Also, it could be that in your control you’re using the properties nested within frameConfig. I would try using the exposed src property to see if the binding works for your case.
Hi Ryan,
I appreciate your work on the module! I noticed that the frame.style can sometimes be undefined, which causes the iframe height to appear as undefinedheight:100%.
A simple update to the code should resolve this issue:
this._frameConfig.style = (this._frameConfig.style || “”) + “height:” + this.frameConfig.height + “;width:” + this.frameConfig.width + “;”;
This ensures that style is always initialized properly before appending height and width values.
Just wanted to bring this to your attention. Let me know what you think!
Good catch Pranshu, I’ve updated the code in the article with that change.
Ryan
Hi Ryan,
Can you please help with the way to avoid CORS Policy error?
CORS policy (and whether or not a page allows itself to be embedded in an IFRAME) are things controlled by the resources you’re trying to consume and enforced by the browser. Most common ways to avoid is to create/use a server side proxy for the client side calls.
Ryan,
I am looking to have a solid Maps add-on to visualise accounts lists in a map, so as I change filter view the pins change. I am looking to have the navigate to features including traffic data and all the normal capabilities including automatic geocoding of addresses in any table.
So thew advice is this. You have an iframe solution. Do you feel this is a workaround or a solid step for a real solution ? I note you have not created a product (that I can find) so how should I think about this component need ? Thanks in advance for your thoughts…
Hi Brett,
Not sure an IFRAME would help with this since you can’t pass the markers to a Google map url. That is typically done with the maps js library and you load the map into a DOM element (like a div somewhere on the page). Also, a challenge that you’d run into for this is that the list only loads 30 records at a time. The list might have 100s or 1000s of records, but you’re only going to get pages of 30 records at a time. For each one of those, you’d need to either pre-geocode the records so you have their lat/long available or get the address lat/long at runtime for each. Either way, that is going to result in a lot of Google API requests, however, if it’s controlled how/where it’s used, it could be reasonable.
Rather than an IFRAME, I’d probably just add a flex container to the page and leave it empty, then use the google maps library to load the map into that container. You’d have to pre-include the lat/long into the list datasource https://customerfx.com/article/including-additional-column-values-in-a-page-datasource-load-on-a-creatio-page/ to avoid the need to query for each lat/long for each record (assuming you’ve pre-geocoded each record before hand), then create the markers for each on the already loaded map in the div.
All in all, not a super straightforward or easy task.
Ryan
Any idea how to use the Images uploaded against a Module from the Module’s code? I’m trying to put a placeholder image in when there is no value provided to the custom component (in my case for an image display) but can’t see how I would access the Images I’ve set up in the Module.
Hi Harvey,
Unless I am misunderstanding what you’re after, that hasn’t changed for Freedom UI. For example, if you added an png image to the Accounts_FormPage and named the image MyImage, you’d access that using the URL:
https://creatiourl/0/conf/content/img/Accounts_FormPage-MyImage.png
Same if you added it to the module except the URL would use the module name. Alternatively, you could add the image to the SysImage table (easy way is to add it as the logo to an account, then remove it and it will leave the image in the SysImage table but remove it from the account). For those you’d use the URL (where “230bb6ca-4cb6-9a45-45be-52a02df9e896” is the Id from the SysImage table):
https://creatiourl/0/img/entity/hash/SysImage/Data/230bb6ca-4cb6-9a45-45be-52a02df9e896
Ryan
Hi Ryan,
Thanks for the article.
What would be the best/easiest way to pass a value back from the iframe to update a field on the parent page?
There are a lot of conditions and security concerns that could prevent this from working. Often you need some headers added to both the parent page and the page in the IFRAME to allow that. I’ve not tried any of this from Creatio, but typically that is done using postMessage. See https://stackoverflow.com/questions/69784876/how-to-pass-data-from-ifram-to-parent
This would require code on the page in the IFRAME to call postMessage, then code in the parent page (the FrameComponent) to receive the message. However, I don’t know if any of this would work in the context of Creatio (would depend on if Creatio has the necessary headers that allow or at least not prevent this.
I have tried this by adding module and added the Iframe to the page. Still not able to achieve the requirement. the below is of the page where I am adding the iframe.
I have tried this by adding module and added the Iframe to the page. Still not able to achieve the requirement. the below is of the page where I am adding the iframe.
define(“UsrTestSection_FormPage”, /**SCHEMA_DEPS*/[“UsrFrameComponent”]/**SCHEMA_DEPS*/, function/**SCHEMA_ARGS*/(UsrFrameComponent)/**SCHEMA_ARGS*/ {
return {
viewConfigDiff: /**SCHEMA_VIEW_CONFIG_DIFF*/[
{
“operation”: “insert”,
“name”: “FrameComponent”,
“values”: {
“layoutConfig”: {
“column”: 1,
“row”: 1,
“colSpan”: 1,
“rowSpan”: 20
},
“type”: “usr.FrameComponent”,
“src”: “https://customerfx.com”
},
If you’re using 8.3.1, Creatio does not enforce content security policies which could block the iframe content from loading. In previous versions, the content security policy was set to log violations only. It seems that in 8.3.1 it is now set to block unless it is a trusted url.
You have two options:
Option 1: Add the URL you’re loading in the IFRAME to the content security policy trusted list. To do this, click gear icon, go to Content Security Policy, add URL on trusted sources tab, then log out/in.
Option 2: Change the content security policy off or back to warn only. To do this, go to system settings, locate the setting for “Content security mode” (code CspMode) and change to either “Log violations of content security policies” or “Disable content security”, then log out/in.
However, if that is not your issue, you’ll need to provide more details of what exactly is going wrong.
I have tried both the suggested options, but I am getting error : Uncaught Error: Script error for “UsrFrameComponent”, needed by: UsrTestSection_FormPage in the console of the browser and also the page is not able to load for the view.
Hi Ryan Farley,
Thank you
All the errors are resolved now.
Will this work with the new Freedom UI Mobile Pages? There is no *code_below in mobile source code.
define(“PageName”, /**SCHEMA_DEPS*/[]/**SCHEMA_DEPS*/, function/**SCHEMA_ARGS*/()/**SCHEMA_ARGS*/ {
return {
Possibly – not sure? I’ve not tried or looked at what you can do with custom components in mobile.