How to programatically update iframe src's in streamlit app (python)?

Summary

So basically I want to build an app that can display power bi reports. So, given that i have a list of power bi report embed urls, I want to programatically update the srcs (i.e. power bi report embed urls) inside the iframe element that i would pass in st.markdown() so that everytime i enter a new embed url, it automatically opens a new power bi report. Any way I can achieve that? Also, can anyone suggest me an alternative way to embed power bi reports if this doesn’t work out?

Here is what i am kinda trying to achieve

Code snippet:

embed_urls=['rep1.powerbi.com','rep2.powerbi.com','rep3.powerbi.com']
for i in embed_urls:
   st.markdown('<iframe src=i> </iframe>')


I know the piece of code above doesn’t make any sense but it is close to what I’m trying to achieve.

Two things:

First, if you want to include html in your app from markdown, you’ll need to include the optional argument unsafe_allow_html=True. Alternatively, you can use the components API to inject html, which comes in with its own iframe.

Second, you’ll need to format the string to inject the variable: st.markdown(f'<iframe src={i}> </iframe>').

So two solutions would be:

for i in embed_urls:
   st.markdown(f'<iframe src={i}> </iframe>', unsafe_allow_html=True)
for i in embed_urls:
   st.components.v1.html(f'<iframe src={i}> </iframe>')
1 Like

Just a follow-up question: Are both solutions -st.markdown including unsafe_allow_html and st.components.v1.html- technically the same resulting in an equivalent outcome? Or would you prefer one over the other under certain circumstances?

They differ in the layers created within the HTML of the app. The components API creates an iframe within which to put whatever HTML or script you are injecting. (So the above example would have both the iframe created by the components API and the typed-out iframe nested within, along with additional layers of divs.)

Also, if you are including some JavaScript, it doesn’t seem to run with the st.markdown solution, but does run with the components API solution.

So the more complicated you are getting with what you are injecting, the more likely you’d need to start working with the components API, maybe even up to the point needing to actually create a custom component.

1 Like

For me, one big difference is:

st.components …updates everytime you open/refresh.

st.markdown(f'<iframe src= …never updates the iframe, no matter how many times you reload.

(tested on Chrome and Firefox)

I’ve discovered something interesting while trying to embed an external Streamlit page inside a local Streamlit page. Apparently, if the page I embed is a local Streamlit page, there’s no problem. But if it’s something I’ve uploaded to Streamlit Share cloud, it won’t let me embed it. I’ll show the code and the difference between both (url doesnt work, but url_local does):

import streamlit as st
import streamlit.components.v1 as components

#example of streamlit share public page
url = 'https://nl2sql.streamlit.app/' #use any external streamlit page
#a local but different streamlit page
url_local = 'http://192.168.1.150:8501/ #use any local streamlit page

#using iframe markdown
st.markdown(f'<iframe src={url} width="100%" height="600" style="position:absolute; top:200px; left:0; overflow:hidden; margin-top:-300px;"></iframe>', unsafe_allow_html=True)
st.markdown(f'<iframe src={url_local} width="100%" height="600" style="position:absolute; top:200px; left:0; overflow:hidden; margin-top:-300px;"></iframe>', unsafe_allow_html=True)

#using components
st.components.v1.iframe(url, width=None, height=600)
st.components.v1.iframe(url_local, width=None, height=600)

Is it just me? or embed an external streamlit page into a local streamlit page doesn’t work?

When embedding a Streamlit Cloud app anywhere (even in another Streamlit app), you need the embedded app to have the right query parameter in its url:

I embedded a Google calendar using st.components.

I have the following process where I expect the Calendar to update at the end. It takes about 5 seconds for Google Calendar API to create a new event. To play it safe I use a sleep of 10 seconds.

Button clicked → script runs → success message displayed → time.sleep(10)st.rerun()

I expect the my iframe to update and show the updated calendar but it does not show. Any suggestions?