Expedition. Free, virtual, Nov 3–6.

Technical tracks for practitioners, outcomes for leaders.

An AI-generated snowboarding game running inside a sandboxed iframe

Instead of answering with a wall of text, AI can now answer with the interface itself: a chart, a dashboard, a form, a widget, a whole mini app.

Here's the part nobody demos: running it without putting your app at risk.

Getting a model to generate an interface is easy. Running that code inside your app is not. Whether it's a chart from a question, a report from a prompt, a UI prototype, an email template, or a SQL query, the model hands you code, and your app runs it.

That's when the real questions show up. Does it even run? Is it safe to run? What happens when it pulls a script from a CDN you've never heard of, calls an API that doesn't exist, or crashes before rendering a thing?

I hit every one of these building a small side project: an arcade where you describe a game in one sentence and a model writes a playable 3D game. And yes, every game stars me, snowboard included. Games are a great stress test. They're big, interactive, full of JavaScript, and they break in very visible ways.

Dash's Midnight Haunt Shred, an AI-generated game running in a sandboxed iframe

But none of the lessons were about games. They apply to any app that turns AI output into something people run or click on. Here's what broke, why it broke, and the patterns that fixed it.

Why runtime AI output needs guardrails

First, a distinction. When you use a coding agent, you're in the loop. You see the diffs, approve the actions, and run the tests before anything ships. That review step is the guardrail, and good agents build it in.

Runtime generation is different. Your app asks a model for code and runs it immediately, for an end user, with no human in between. That's the case this post is about. Review at dev time, sandbox at runtime.

Here's the mental model I use. Code generated at runtime is a pull request that merges itself. Great when it's right. But nobody reviewed it.

The model isn't malicious. And it's often great. But without a reviewer, it can be:

  • Confidently wrong. It calls APIs that don't exist in your library version, or uses a variable it never declared.
  • Eager to fetch things. It adds a <script src> to a CDN, an import map, a web font, or a package it remembers from training data.
  • Unaware of your runtime. It assumes a normal browser page with cookies, storage, and network access. Or a server with every library installed.
  • Steered by its input. If any part of the prompt comes from a user, that user is writing code through the model.

So you have two separate problems to solve:

  1. Quality: make the generated code more likely to work.
  2. Containment: when it doesn't work, or does something unexpected, nothing outside the box should get hurt.

Most tutorials stop at the first one. Let's do both.

Improve output quality with a generate-then-review prompt pattern

One prompt can produce code that looks right. A second review pass can catch some of what's wrong with it, but the result still needs testing and runtime containment.

The trick? Ask the same model to review its own output, with a different persona and a narrower job. In my example app, the review pass caught bugs such as undefined variables, wrong library APIs, missing restart logic, and objects rendering off screen. It doesn't catch everything, so the output still needs the containment below.

PassPersonaOutput
Draft"Expert developer" for your domainRaw code
Review"Senior engineer doing a code review"JSON: title, critique, html

The separation is what matters: the draft prompt is free to be creative, and the review prompt only checks and fixes against a fixed list of APIs, returning structured output. Swap the personas for your use case: "data viz engineer" for charts, "SQL performance reviewer" for queries, "accessibility reviewer" for UI.

Note: You might expect a higher temperature on the draft and a lower one on the review. That's a reasonable split for models that honor sampling parameters, but Google's documentation says Gemini 4 Argon ignores custom temperature, top_p, and top_k values, so the examples below leave them out. Check your model's documentation before relying on them.

Run the AI pipeline where the data lives

In my example, each generated artifact is a row in a table. Each pass is a single UPDATE that calls the model with AI_COMPLETE.

Here's why I like this shape: in my example, the prompt, the model output, and the status all live in one row, so I didn't need a separate job queue. The same pattern should fit a chart spec or a generated report, but check queueing and concurrency against your own app's needs.

My example uses gemini-4-argon, announced in public preview for Snowflake Cortex AI Functions via cross-region inference. Public preview features aren't suitable for production workloads, and availability and limits can vary, so check Models and regional availability (or run SHOW CORTEX BASE MODELS) for what your account can use, and set max_tokens within that model's limit.

The draft pass:

UPDATE ARGON_DEMO.ARCADE.GAMES
   SET DRAFT_HTML = AI_COMPLETE('gemini-4-argon', ?, {'max_tokens': 32000}),
       STATUS     = 'REVIEWING',
       UPDATED_AT = CURRENT_TIMESTAMP()
 WHERE GAME_ID = ?;

The review pass reads DRAFT_HTML straight from the row and asks for structured output with a JSON schema. That means no regex parsing on the way out:

UPDATE ARGON_DEMO.ARCADE.GAMES g
   SET HTML = r.j:html::STRING, TITLE = r.j:title::STRING, CRITIQUE = r.j:critique::STRING
  FROM (
    SELECT GAME_ID, AI_COMPLETE(
      model            => 'gemini-4-argon',
      prompt           => ? || '\n\nCode to review:\n' || DRAFT_HTML,
      model_parameters => {'max_tokens': 32768},
      response_format  => {'type': 'json', 'schema': {'type': 'object',
        'properties': {'title': {'type': 'string'}, 'critique': {'type': 'string'}, 'html': {'type': 'string'}},
        'required': ['title', 'critique', 'html']}}
    ) AS j
    FROM ARGON_DEMO.ARCADE.GAMES WHERE GAME_ID = ?
  ) r
 WHERE g.GAME_ID = r.GAME_ID;

Four details that matter for any use case:

  • Bind the user's text. Never concatenate it into SQL. The user's request goes in as a ? bind parameter, after the fixed system prompt.
  • Make the review pass best-effort. If it fails, keep the draft and record why. A slightly rough result beats an error page.
  • Store the critique. Showing people what the reviewer found and fixed builds trust. In my app, toggling between draft and final turned out to be one of the coolest features.
  • Keep the model name a parameter. It's just a string argument, so comparing models on the same prompts is a query, not a migration.

Handle long-running generation: return early and poll

In my example, generation can take long enough that I don't want to hold an HTTP request open.

So the POST handler inserts a row with STATUS = 'DRAFTING', returns 202 Accepted with the ID, and schedules the pipeline to run after the response is sent. In Next.js, that's after(). That work is still bound by the route's maximum duration on your platform, and serverless deployments may cut it off, so check your runtime's execution limits. My app runs on a long-lived container, where that's fine. For longer or durable work, use a job queue or background-work service instead:

const id = crypto.randomUUID()
await querySnowflake(
  `INSERT INTO ARGON_DEMO.ARCADE.GAMES (GAME_ID, THEME, STATUS) VALUES (?, ?, 'DRAFTING')`,
  { binds: [id, theme] },
)
after(() => runPipeline(id, theme))
return Response.json({ id, status: "DRAFTING" }, { status: 202 })

The client polls the row every few seconds while the status is DRAFTING or REVIEWING, and stops at READY or FAILED. The status column doubles as your progress bar. Same pattern for report generation, batch summaries, or anything else that outlives a request.

Note: One of my generations failed right after the draft finished, with an empty error message. The identical retry worked. Long AI calls fail transiently. Record the error on the row, mark it FAILED, and make retrying cheap.

Contain generative UI with a sandboxed iframe and a Content Security Policy

Now the containment half.

If your app renders AI-generated HTML or JavaScript, whether that's a chart, a widget, a report, or a full app, you want to limit what it can reach: the host app, the user's session, and the network. No single control does that, so I use three layers, as defense in depth.

Layer 1: a sandboxed iframe with an opaque origin

Serve the generated content from its own route and always load it in an iframe with sandbox="allow-scripts", and without allow-same-origin:

<iframe src="/api/games/{id}/play" sandbox="allow-scripts"></iframe>

That combination is the main layer. Scripts can run, but without allow-same-origin the frame gets an opaque origin, which restricts same-origin access to the parent, such as its cookies, its DOM, and its storage. Leaving out allow-top-navigation keeps it from navigating the top window. It's a strong layer, not a replacement for authorization on your own APIs. Your endpoints should still check who's calling.

Note: Never combine allow-scripts with allow-same-origin for generated content served from your own origin. Together they let the framed script remove its own sandbox.

Layer 2: a strict CSP on the response

Send the sandbox and a restrictive policy as an HTTP header too. That way the sandbox still applies when someone opens the URL directly instead of through the iframe, as long as your deployment actually serves the header (check the real response, not just your code):

Content-Security-Policy: sandbox allow-scripts; default-src 'none';
  script-src 'unsafe-inline' data: blob:; style-src 'unsafe-inline';
  img-src data: blob:; media-src data: blob:; font-src data:;
  worker-src blob:; connect-src data: blob:;
  form-action 'none'; base-uri 'none'

Yes, 'unsafe-inline' looks scary. But that's the point. The generated code is inline script, and the sandbox is what contains it.

The real win is default-src 'none' with no https: anywhere. The only sources allowed are inline code and data:/blob: URLs, so the policy restricts the resource types it lists, including script-initiated connections, to nothing remote.

Watch out: default-src doesn't cover every directive. form-action and base-uri don't fall back to it, so set them explicitly, as above. (Leaving allow-forms out of the sandbox blocks form submission too, but it's worth having both.) And no CSP directive stops a script from navigating its own frame to an outside URL, which can carry data in the query string. So treat CSP as one containment layer, not a complete data-exfiltration control. Validate the deployed policy in the browsers you support, and separately secure your endpoints and any channels like postMessage.

Layer 3: rewrite the document before serving it

Models love adding things you didn't ask for. So before serving, strip anything that tries to pull in outside code, then inject what the code is allowed to use:

html = html
  .replace(/<script[^>]*type=["']importmap["'][^>]*>[\s\S]*?<\/script>/gi, "")
  .replace(/<script[^>]*\bsrc=["'][^"']*["'][^>]*>\s*<\/script>/gi, "")
  .replace(/<link[^>]*rel=["']stylesheet["'][^>]*>/gi, "")
  .replace(/<meta[^>]*http-equiv=["']Content-Security-Policy["'][^>]*>/gi, "")

Regex isn't a security boundary. Treat this step as a compatibility measure, not a security control: it removes common external dependency tags, so the code is more likely to use the library you provide instead of silently failing to load one from a CDN. The sandbox and CSP are separate layers, and both still need testing in your deployment.

Pin your dependencies instead of letting the model choose

Tell the model exactly which libraries it can import, and nothing else. In my example, the prompt says to write import * as THREE from 'three'. The server injects an import map that resolves three to a vendored copy of Three.js, inlined as a data: URL:

const inject =
  `<meta http-equiv="Content-Security-Policy" content="${csp}">` +
  `<script type="importmap">${JSON.stringify({ imports: { three: getThreeDataUrl() } })}</script>`

The same trick works for a charting library, a UI kit, or anything else your generated code needs. You get a known version, so the review prompt can say "check against these APIs". You also get no network dependency and no CSP exception for a CDN.

Fix the localStorage SecurityError in sandboxed iframes

This one cost me the most time. By far.

Generated code loves localStorage. Saved settings, cached state, high scores. In an opaque-origin sandbox, just touching window.localStorage throws a SecurityError. The code dies before it renders a single thing.

Telling the model "don't use localStorage" helps. It doesn't fix it. So the host injects a tiny in-memory shim before the generated scripts run.

My first attempt redefined localStorage as a value on window. It didn't work. The browser's own accessor lives on the prototype chain, and it kept winning.

What worked in my testing: defining a getter on window, on its prototype, and on Window.prototype. Browser behavior here varies, so test it in the browsers and runtime you target. And it's session-only by design. If your generated code genuinely needs persistence, pick an explicit storage strategy (like passing state back to the host with postMessage) instead:

(function () {
  function mk() {
    var d = {};
    return {
      getItem: function (k) { return k in d ? d[k] : null },
      setItem: function (k, v) { d[k] = String(v) },
      removeItem: function (k) { delete d[k] },
      clear: function () { d = {} },
      key: function (i) { return Object.keys(d)[i] || null },
      get length() { return Object.keys(d).length },
    };
  }
  ["localStorage", "sessionStorage"].forEach(function (n) {
    try { window[n].length } catch (e) {
      var v = mk();
      [window, Object.getPrototypeOf(window), Window.prototype].forEach(function (o) {
        try { Object.defineProperty(o, n, { get: function () { return v }, configurable: true }) } catch (_) {}
      });
    }
  });
})();

Like the rewrite step, this is a compatibility measure, not a security control. State only lasts for the session, which was a fine trade in my app for code that actually starts.

Other ways to run AI-generated code, and their tradeoffs

The iframe-plus-CSP approach fit my use case. It's not the only option:

  • Serve generated content from a separate origin, like a dedicated sandbox subdomain. That's how many code playgrounds work. Stronger isolation, but now you're running another domain and handling CORS.
  • Run code in a Web Worker. No DOM access, so it's a no-go for rendering UI. Great for running generated logic or data transforms.
  • Execute server-side in a container or VM. This is the right answer for generated backend code like Python or shell, at the cost of real infrastructure.
  • Run static analysis before execution. Lint or parse the code and reject known-bad patterns. Useful as an extra layer, but you can't analyze your way to safety with arbitrary JavaScript.
  • For generated SQL, lean on the database. Run it with a least-privilege role scoped to exactly what the feature should read.

Whatever you pick, the principle doesn't change. Assume the code will do something you didn't expect, and decide up front what it's allowed to reach.

Curate AI-generated results before showing them to users

Once people can generate freely, your gallery, history, or results list fills up fast. Near-duplicates. Failed runs. The same idea, generated over and over.

So separate making from browsing. In my example app:

  • /describe is for creating, watching progress, and seeing every build, failures included.
  • / is for browsing, showing only finished results with one entry per title.

The dedupe is intentionally simple. The API returns results newest first, and the browse page keeps the first READY entry for each normalized title:

function uniqueReady(list: GameSummary[]) {
  const seen = new Set<string>()
  return list.filter((g) => {
    if (g.status !== "READY") return false
    const key = (g.title || g.theme).trim().toLowerCase().replace(/\s+/g, " ")
    if (seen.has(key)) return false
    seen.add(key)
    return true
  })
}

Conclusion And Resources

The model writing the code gets the headline. The real work is everything around it:

  • A review pass to raise quality.
  • Structured output so results are parseable.
  • An async status column so long calls don't block requests.
  • Layers of containment to limit what bad output can reach.

The biggest insight for me? None of these problems were about the model. They're the same guardrails you'd want for any code that ships without review: validate it, contain it, and plan for failure. Coding agents give you that review step at dev time. At runtime, your app has to provide it.

Shipping generative UI, or rendering AI-generated code anywhere in your app? Plan the containment layers first, then test them in your own deployment. Better prompts make the code better. Containment limits the damage when it isn't.

What generative UI are you shipping? And how are you keeping it in the box?

Let's learn together!

Related Resources

Updated Oct 30, 2026

This content is provided as is, and is not maintained on an ongoing basis. It may be out of date with current Snowflake instances

`\n```\n\nThe same trick works for a charting library, a UI kit, or anything else your generated code needs. You get a known version, so the review prompt can say \"check against these APIs\". You also get no network dependency and no CSP exception for a CDN.\n\n\n## Fix the localStorage SecurityError in sandboxed iframes\n\nThis one cost me the most time. By far.\n\nGenerated code loves `localStorage`. Saved settings, cached state, high scores. In an opaque-origin sandbox, just touching `window.localStorage` throws a `SecurityError`. The code dies before it renders a single thing.\n\nTelling the model \"don't use localStorage\" helps. It doesn't fix it. So the host injects a tiny in-memory shim before the generated scripts run.\n\nMy first attempt redefined `localStorage` as a value on `window`. It didn't work. The browser's own accessor lives on the prototype chain, and it kept winning.\n\nWhat worked in my testing: defining a getter on `window`, on its prototype, and on `Window.prototype`. Browser behavior here varies, so test it in the browsers and runtime you target. And it's session-only by design. If your generated code genuinely needs persistence, pick an explicit storage strategy (like passing state back to the host with `postMessage`) instead:\n\n```js\n(function () {\n function mk() {\n var d = {};\n return {\n getItem: function (k) { return k in d ? d[k] : null },\n setItem: function (k, v) { d[k] = String(v) },\n removeItem: function (k) { delete d[k] },\n clear: function () { d = {} },\n key: function (i) { return Object.keys(d)[i] || null },\n get length() { return Object.keys(d).length },\n };\n }\n [\"localStorage\", \"sessionStorage\"].forEach(function (n) {\n try { window[n].length } catch (e) {\n var v = mk();\n [window, Object.getPrototypeOf(window), Window.prototype].forEach(function (o) {\n try { Object.defineProperty(o, n, { get: function () { return v }, configurable: true }) } catch (_) {}\n });\n }\n });\n})();\n```\n\nLike the rewrite step, this is a compatibility measure, not a security control. State only lasts for the session, which was a fine trade in my app for code that actually starts.\n\n\n## Other ways to run AI-generated code, and their tradeoffs\n\nThe iframe-plus-CSP approach fit my use case. It's not the only option:\n\n- Serve generated content from a separate origin, like a dedicated sandbox subdomain. That's how many code playgrounds work. Stronger isolation, but now you're running another domain and handling CORS.\n- Run code in a Web Worker. No DOM access, so it's a no-go for rendering UI. Great for running generated logic or data transforms.\n- Execute server-side in a container or VM. This is the right answer for generated backend code like Python or shell, at the cost of real infrastructure.\n- Run static analysis before execution. Lint or parse the code and reject known-bad patterns. Useful as an extra layer, but you can't analyze your way to safety with arbitrary JavaScript.\n- For generated SQL, lean on the database. Run it with a least-privilege role scoped to exactly what the feature should read.\n\nWhatever you pick, the principle doesn't change. Assume the code will do something you didn't expect, and decide up front what it's allowed to reach.\n\n\n## Curate AI-generated results before showing them to users\n\nOnce people can generate freely, your gallery, history, or results list fills up fast. Near-duplicates. Failed runs. The same idea, generated over and over.\n\nSo separate making from browsing. In my example app:\n\n- `/describe` is for creating, watching progress, and seeing every build, failures included.\n- `/` is for browsing, showing only finished results with one entry per title.\n\nThe dedupe is intentionally simple. The API returns results newest first, and the browse page keeps the first `READY` entry for each normalized title:\n\n```ts\nfunction uniqueReady(list: GameSummary[]) {\n const seen = new Set()\n return list.filter((g) => {\n if (g.status !== \"READY\") return false\n const key = (g.title || g.theme).trim().toLowerCase().replace(/\\s+/g, \" \")\n if (seen.has(key)) return false\n seen.add(key)\n return true\n })\n}\n```\n\n\n## Conclusion And Resources\n\nThe model writing the code gets the headline. The real work is everything around it:\n\n- A review pass to raise quality.\n- Structured output so results are parseable.\n- An async status column so long calls don't block requests.\n- Layers of containment to limit what bad output can reach.\n\nThe biggest insight for me? None of these problems were about the model. They're the same guardrails you'd want for any code that ships without review: validate it, contain it, and plan for failure. Coding agents give you that review step at dev time. At runtime, your app has to provide it.\n\nShipping generative UI, or rendering AI-generated code anywhere in your app? Plan the containment layers first, then test them in your own deployment. Better prompts make the code better. Containment limits the damage when it isn't.\n\nWhat generative UI are you shipping? And how are you keeping it in the box?\n\nLet's learn together!\n\n### Related Resources\n- [AI_COMPLETE documentation](https://docs.snowflake.com/en/sql-reference/functions/ai_complete)\n- [Models and regional availability for Cortex AI Functions](https://docs.snowflake.com/en/user-guide/snowflake-cortex/aisql-regional-availability)\n- [MDN: iframe sandbox attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/iframe#sandbox)\n- [MDN: Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP)\n- [Snowflake Developers](https://www.snowflake.com/en/developers/)\n","multiValue":false,":type":"text/x-markdown"},"quickstartArticleLogoImage":{"dataType":"string","title":"Quickstart Article Logo Image","multiValue":false,":type":"text/plain"}},"elementsOrder":["quickstartArticleBody","quickstartArticleLogoImage"],"isDeveloperGuidesPage":true,"model":"snowflake-site/models/quickstart-article"},"flexible_column_cont":{"id":"flexible-column-container-96692e770f","type":"2-column-75-25","alignColumns":"top","containerMaxWidth":"extra-large","topPadding":"none","bottomPadding":"none","spaceBetween":"none","reverseOnMobile":false,"carouselOnMobile":false,"backgroundImageOption":"none","flexible_column_content_container_1":{"layout":"SIMPLE","id":"container-46cad22f69",":type":"snowflake-site/components/flexible-column-container/flexible-column-content-container",":items":{"quickstart_last_modi":{"id":"quickstart-last-modified-d9529acab5","icon":{"id":"icon","icon":"calendar",":type":"snowflake-site/components/icon","appliedCssClassNames":"snowflake-icon-blue"},"lastModifiedDatePrefix":"Updated","lastModifiedDate":"2026-10-30",":type":"snowflake-site/components/quickstart/quickstart-last-modified","appliedCssClassNames":"snowflake-responsive-component-top-padding-small"},"text":{"id":"text-a54e899d25","additionalClasses":"qs-disclaimer-text","text":"

This content is provided as is, and is not maintained on an ongoing basis. It may be out of date with current Snowflake instances

\n","richText":true,":type":"snowflake-site/components/text","appliedCssClassNames":"snowflake-responsive-component-top-padding-small"}},":itemsOrder":["quickstart_last_modi","text"]},"flexible_column_content_container_2":{"layout":"SIMPLE","id":"container-4795b02b38",":type":"snowflake-site/components/flexible-column-container/flexible-column-content-container",":items":{},":itemsOrder":[]},":type":"snowflake-site/components/flexible-column-container","isBlogPage":false,"isActiveTOC":false}},":itemsOrder":["quickstart_hero","contentfragment","flexible_column_cont"]},"flexible_column_content_container_3":{"layout":"SIMPLE","id":"container-a9c98277e8",":type":"snowflake-site/components/flexible-column-container/flexible-column-content-container",":items":{"quickstart_table_of_":{"layout":"SIMPLE","id":"container-cf949a2095","isDeveloperGuidesPage":true,":type":"snowflake-site/components/quickstart/quickstart-table-of-content/quickstart-table-of-content-container",":items":{"quickstart_table_of_":{"id":"quickstart-table-of-content-3b7092a8d9","headings":["

Why runtime AI output needs guardrails

","

Improve output quality with a generate-then-review prompt pattern

","

Contain generative UI with a sandboxed iframe and a Content Security Policy

","

Fix the localStorage SecurityError in sandboxed iframes

","

Other ways to run AI-generated code, and their tradeoffs

","

Curate AI-generated results before showing them to users

","

Conclusion And Resources

"],":type":"snowflake-site/components/quickstart/quickstart-table-of-content","fragmentPath":"/content/dam/snowflake-site/en/content-fragments/developers-blog/generative-ui-safely"},"quickstart_button":{"id":"quickstart-button-dc142d9ef3",":type":"snowflake-site/components/quickstart/quickstart-button","fragmentPath":"/content/dam/snowflake-site/en/content-fragments/developers-blog/generative-ui-safely","appliedCssClassNames":"snowflake-responsive-component-top-padding-none"}},":itemsOrder":["quickstart_table_of_","quickstart_button"]}},":itemsOrder":["quickstart_table_of_"]},":type":"snowflake-site/components/flexible-column-container","isBlogPage":false,"isActiveTOC":false},"markup_editor":{"id":"markup-editor-9bd56e13e5","title":"Page CSS","cssUrl":"/content/snowflake-site/global/en/developers/blog/generative-ui-safely/_jcr_content/root/responsivegrid/markup_editor.25fde650378f6363.css",":type":"snowflake-site/components/markup-editor","isGSAPEnabled":false}},":itemsOrder":["flexible_column_cont","markup_editor"],":type":"wcm/foundation/components/responsivegrid","id":"responsive-grid-main-content"},"modal_container":{"layout":"SIMPLE","id":"container-fab1d5f4ef",":type":"snowflake-site/components/modal/modal-container",":items":{},":itemsOrder":[]},"experiencefragment-footer":{"id":"experiencefragment-0fa02ef6fa","localizedFragmentVariationPath":"/content/experience-fragments/snowflake-site/language-masters/en/site/footer/master/jcr:content","configured":true,":type":"snowflake-site/components/experiencefragment",":items":{"root":{"additionalClasses":"sf-footer","layout":"SIMPLE","id":"container-485a8785d7",":type":"snowflake-site/components/container",":items":{"container_copy":{"additionalClasses":"sf-footer__inner","layout":"RESPONSIVE_GRID","columnCount":12,"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","columnClassNames":{"flexible_column_cont":"aem-GridColumn aem-GridColumn--default--12"},"id":"container-a9294c3917",":type":"snowflake-site/components/container",":items":{"flexible_column_cont":{"id":"flexible-column-container-813b926e10","type":"1-column","alignColumns":"top","containerMaxWidth":"extra-large","topPadding":"medium","bottomPadding":"extra-small","spaceBetween":"small","reverseOnMobile":false,"carouselOnMobile":false,"propertiesCSSClasses":"sf-footer-grid","backgroundImageOption":"none","flexible_column_content_container_1":{"layout":"SIMPLE","id":"container-c893eccc27",":type":"snowflake-site/components/flexible-column-container/flexible-column-content-container",":items":{"container":{"additionalClasses":"sf-footer-grid__inner","layout":"RESPONSIVE_GRID","columnCount":12,"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","columnClassNames":{"container":"aem-GridColumn aem-GridColumn--default--12","container_1622723482":"aem-GridColumn aem-GridColumn--default--12","container_copy_copy_":"aem-GridColumn aem-GridColumn--default--12","container_copy_copy":"aem-GridColumn aem-GridColumn--default--12","container_copy":"aem-GridColumn aem-GridColumn--default--12"},"id":"container-5ae7fd062e",":type":"snowflake-site/components/container",":items":{"container_1622723482":{"additionalClasses":"sf-footer__column","layout":"RESPONSIVE_GRID","columnCount":12,"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","columnClassNames":{"container":"aem-GridColumn aem-GridColumn--default--12"},"id":"container-6461e14717",":type":"snowflake-site/components/container",":items":{"container":{"additionalClasses":"sf-footer__newsletter-group","layout":"RESPONSIVE_GRID","columnCount":12,"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","columnClassNames":{"text":"aem-GridColumn aem-GridColumn--default--12","marketo_v2":"aem-GridColumn aem-GridColumn--default--12"},"id":"container-782e6b3fee",":type":"snowflake-site/components/container",":items":{"text":{"id":"text-f01ca0579b","additionalClasses":"sf-footer__newsletter-title","text":"

Subscribe to our monthly newsletter

\n

Stay up to date on Snowflake’s latest products, expert insights and resources—right in your inbox!

\n","richText":true,":type":"snowflake-site/components/text","appliedCssClassNames":"text-size-regular text-color-text-04"},"marketo_v2":{"id":"marketo-v2-c9bcba63eb","marketoForm":{"hidden":null,"formId":"45871","edit":false,"successUrl":null,"script":null,"values":null},"munchkinId":"252-RFO-227","serverInstance":"252-RFO-227.mktoweb.com","partnerCampaignPage":false,"marketoConfigured":true,"formConfigured":true,":type":"snowflake-site/components/form/marketo-v2"}},":itemsOrder":["text","marketo_v2"],"appliedCssClassNames":"snowflake-responsive-container-inner-padding-small"}},":itemsOrder":["container"],"appliedCssClassNames":"snowflake-responsive-container-inner-padding-small"},"container":{"layout":"RESPONSIVE_GRID","columnCount":12,"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","columnClassNames":{"text_copy":"aem-GridColumn aem-GridColumn--default--12","text":"aem-GridColumn aem-GridColumn--default--12"},"id":"container-f9b2763647",":type":"snowflake-site/components/container",":items":{"text":{"id":"text-10889d63eb","additionalClasses":"sf-footer__link-group","text":"\n\n","richText":true,":type":"snowflake-site/components/text","appliedCssClassNames":"text-size-small text-color-text-04"},"text_copy":{"id":"text-be0f1ca944","additionalClasses":"sf-footer__link-group","text":"\n\n","richText":true,":type":"snowflake-site/components/text","appliedCssClassNames":"text-size-small text-color-text-04"}},":itemsOrder":["text","text_copy"],"appliedCssClassNames":"snowflake-responsive-container-inner-padding-medium"},"container_copy_copy":{"layout":"RESPONSIVE_GRID","columnCount":12,"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","columnClassNames":{"text":"aem-GridColumn aem-GridColumn--default--12"},"id":"container-9e1d97d28a",":type":"snowflake-site/components/container",":items":{"text":{"id":"text-7ce131a1cd","additionalClasses":"sf-footer__link-group","text":"\n\n","richText":true,":type":"snowflake-site/components/text","appliedCssClassNames":"text-size-small text-color-text-04"}},":itemsOrder":["text"],"appliedCssClassNames":"snowflake-responsive-container-inner-padding-small"},"container_copy":{"layout":"RESPONSIVE_GRID","columnCount":12,"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","columnClassNames":{"text":"aem-GridColumn aem-GridColumn--default--12"},"id":"container-2d686220dc",":type":"snowflake-site/components/container",":items":{"text":{"id":"text-cabce5699e","additionalClasses":"sf-footer__link-group","text":"\n\n","richText":true,":type":"snowflake-site/components/text","appliedCssClassNames":"text-size-small text-color-text-04"}},":itemsOrder":["text"],"appliedCssClassNames":"snowflake-responsive-container-inner-padding-small"},"container_copy_copy_":{"layout":"RESPONSIVE_GRID","columnCount":12,"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","columnClassNames":{"text":"aem-GridColumn aem-GridColumn--default--12"},"id":"container-7ccd348945",":type":"snowflake-site/components/container",":items":{"text":{"id":"text-4d966819d3","additionalClasses":"sf-footer__link-group","text":"\n\n","richText":true,":type":"snowflake-site/components/text","appliedCssClassNames":"text-size-small text-color-text-04"}},":itemsOrder":["text"],"appliedCssClassNames":"snowflake-responsive-container-inner-padding-small"}},":itemsOrder":["container_1622723482","container","container_copy_copy","container_copy","container_copy_copy_"],"appliedCssClassNames":"snowflake-responsive-container-inner-padding-small"}},":itemsOrder":["container"]},":type":"snowflake-site/components/flexible-column-container","isBlogPage":false,"isActiveTOC":false}},":itemsOrder":["flexible_column_cont"],"appliedCssClassNames":"snowflake-container snowflake-responsive-container-inner-padding-small"},"container_573483281_":{"additionalClasses":"sf-footer__bottom","layout":"RESPONSIVE_GRID","columnCount":12,"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","columnClassNames":{"container_112062425":"aem-GridColumn aem-GridColumn--default--12"},"id":"container-facc01b311",":type":"snowflake-site/components/container",":items":{"container_112062425":{"layout":"RESPONSIVE_GRID","columnCount":12,"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","columnClassNames":{"flexible_column_cont":"aem-GridColumn aem-GridColumn--default--12"},"id":"container-2e68c07aa6",":type":"snowflake-site/components/container",":items":{"flexible_column_cont":{"id":"flexible-column-container-5109fc062e","type":"1-column","alignColumns":"top","containerMaxWidth":"extra-large","topPadding":"none","bottomPadding":"none","spaceBetween":"small","reverseOnMobile":false,"carouselOnMobile":false,"backgroundImageOption":"none","flexible_column_content_container_1":{"layout":"SIMPLE","id":"container-7f49b690b4",":type":"snowflake-site/components/flexible-column-container/flexible-column-content-container",":items":{"container":{"additionalClasses":"sf-footer__legal-container","layout":"RESPONSIVE_GRID","columnCount":12,"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","columnClassNames":{"container":"aem-GridColumn aem-GridColumn--default--12","text_copy_copy_16360":"aem-GridColumn aem-GridColumn--default--12","markup_editor":"aem-GridColumn aem-GridColumn--default--12"},"id":"container-944899ff83",":type":"snowflake-site/components/container",":items":{"container":{"layout":"RESPONSIVE_GRID","columnCount":12,"gridClassNames":"aem-Grid aem-Grid--12 aem-Grid--default--12","columnClassNames":{"image":"aem-GridColumn aem-GridColumn--default--12"},"id":"container-b73139cb38",":type":"snowflake-site/components/container",":items":{"image":{"id":"image-2b4bd195f6","additionalClasses":"sf-footer__logo","height":"64","src":"/content/experience-fragments/snowflake-site/language-masters/en/site/footer/master/_jcr_content/root/container_573483281_/container_112062425/flexible_column_cont/flexible_column_content_container_1/container/container/image.coreimg.svg/1747882370694/nav-icon-snowflake-bug.svg","lazyEnabled":true,"imageLink":{"valid":true,"url":"/en/"},"alt":"Snowflake logo","isLcpImage":false,"width":"64","htmlAttributes":{"alt":"Snowflake logo"},":type":"snowflake-site/components/image"}},":itemsOrder":["image"],"appliedCssClassNames":"snowflake-responsive-container-inner-padding-extra-small"},"text_copy_copy_16360":{"id":"text-bf8d54d296","additionalClasses":"sf-footer__legal-links","text":"\n","richText":true,":type":"snowflake-site/components/text","appliedCssClassNames":"text-size-small text-color-text-04"},"markup_editor":{"id":"markup-editor-c90c54fe42","title":" ","htmlContent":"",":type":"snowflake-site/components/markup-editor","isGSAPEnabled":false}},":itemsOrder":["container","text_copy_copy_16360","markup_editor"],"appliedCssClassNames":"snowflake-responsive-container-inner-padding-none"}},":itemsOrder":["container"]},":type":"snowflake-site/components/flexible-column-container","isBlogPage":false,"isActiveTOC":false}},":itemsOrder":["flexible_column_cont"],"appliedCssClassNames":"snowflake-container snowflake-responsive-container-inner-padding-small"}},":itemsOrder":["container_112062425"],"appliedCssClassNames":"snowflake-responsive-container-inner-padding-none"},"markup_editor_copy":{"id":"markup-editor-46828b6e34","title":"New css",":type":"snowflake-site/components/markup-editor","isGSAPEnabled":false}},":itemsOrder":["container_copy","container_573483281_","markup_editor_copy"]}},":itemsOrder":["root"],"classNames":"aem-xf"},"markup_editor":{"id":"markup-editor-ba3b692c5e","title":"Quickstarts Overrides","cssUrl":"/conf/snowflake-site/settings/wcm/templates/developer-guides-page-template/structure/_jcr_content/root/markup_editor.45e3b816710d8d5a.css",":type":"snowflake-site/components/markup-editor","isGSAPEnabled":false}},":itemsOrder":["experiencefragment-banner","experiencefragment-header","markup_editor_1950346551","responsivegrid","modal_container","experiencefragment-footer","markup_editor"],":type":"wcm/foundation/components/responsivegrid"}},":itemsOrder":["root"],":hierarchyType":"page","analyticsEnabled":true,"coveoConfig":{"apiKey":"xx335921a6-2a0a-40f2-a167-e390b4766c3d","organizationId":"snowflakecomputingproduction8neljofn","searchHub":"snowflake.com","pipeline":"snowflake.com"},"analyticsDebugMode":false,"analyticsData":{"excludeFromAnalytics":false,"subCategory":"blog","pageType":"blog","templateName":"developer-guides-page-template","siteName":"snowflake","pageUrl":"/en/developers/blog/generative-ui-safely/","language":"en","category":"developers","pageName":"Generative UI: Risk-Reduction Patterns for Running AI-Generated Interfaces in Your App","contentTags":["snowflake-site:taxonomy/product/ai"]},"publicationThemeClass":"",":path":"/content/snowflake-site/global/en/developers/blog/generative-ui-safely","isPasswordProtected":false,"analyticsContentTags":["snowflake-site:taxonomy/product/ai"],"locale":"en"}},"analyticsEnabled":true,"coveoConfig":{"apiKey":"xx335921a6-2a0a-40f2-a167-e390b4766c3d","organizationId":"snowflakecomputingproduction8neljofn","searchHub":"snowflake.com","pipeline":"snowflake.com"},"analyticsDebugMode":false,"analyticsData":{"excludeFromAnalytics":false,"subCategory":"","pageType":"homepage","templateName":"root-page-template","siteName":"snowflake","pageUrl":"/","language":"en","category":"home","pageName":"Global","contentTags":[]},"publicationThemeClass":"",":path":"/content/snowflake-site/global","isPasswordProtected":false,"analyticsContentTags":[],"locale":"en"}}