tauri-apps/tauri · error

failed to run on_webview_created hook

Error message

failed to run on_webview_created hook

What it means

During webview attachment on iOS (with the `wry` runtime), Tauri runs the `on_webview_created` hook through `with_webview`. If executing that platform hook fails, the builder panics with this message, aborting webview creation.

Solutions

  1. Ensure all webview creation happens on the iOS main thread (dispatch via `run_on_main_thread` or create from the main event loop).
  2. Update tauri, tauri-build and wry to matching latest versions for your iOS target.
  3. Check custom iOS plugins/hooks (`on_webview_created` consumers) for unsafe code that invalidates the webview or view controller.
  4. Test with a minimal tauri iOS example to isolate whether your app code or the toolchain is at fault.

Example fix

// before
app.run(|app| { let _ = WebviewWindowBuilder::new(app, "main", Default::default()).build(); }); // off-main-thread on iOS
// after
let handle = app.handle().clone();
std::thread::spawn(move || {
  handle.run_on_main_thread(move || {
    let _ = WebviewWindowBuilder::new(&handle, "main", Default::default()).build();
  });
});
Defensive patterns

Strategy: validation

Validate before calling

// Guard: create webviews only on the main thread (iOS)
if !std::thread::current().name().map_or(false, |n| n == "main") {
  app.run_on_main_thread(|| create_window(app))?;
}

Prevention

When it happens

Trigger: Building a webview on iOS where `crate::ios::on_webview_created` cannot run against the native WKWebView/view-controller pointers — e.g. the webview was not created on the main thread or the native pointers are invalid.

Common situations: iOS apps creating webviews from background threads; custom iOS plugin code interfering with the WKWebView lifecycle; tauri/wry version mismatches on iOS.

Related errors


AI-assisted analysis of tauri-apps/tauri@460ec35447 (2026-09-18). Data as JSON: /api/errors/4cf7c906e0bda7d1. Report an issue: GitHub.

Appendix: source

Thrown at crates/tauri/src/manager/webview.rs:677

    // let plugins know that a new webview has been added to the manager
    let manager = webview.manager_owned();
    let webview_ = webview.clone();
    // run on main thread so the plugin store doesn't dead lock with the event loop handler in App
    let _ = webview.run_on_main_thread(move || {
      manager
        .plugins
        .lock()
        .expect("poisoned plugin store")
        .webview_created(webview_);
    });

    #[cfg(all(target_os = "ios", feature = "wry"))]
    {
      webview
        .with_webview(|w| {
          unsafe { crate::ios::on_webview_created(w.inner() as _, w.view_controller() as _) };
        })
        .expect("failed to run on_webview_created hook");
    }

    let event = crate::EventName::from_str("tauri://webview-created");
    let payload = Some(crate::webview::CreatedEvent {
      label: webview.label().into(),
    });

    let _ = webview
      .manager
      .emit(event, EmitPayload::Serialize(&payload));

    webview
  }

  pub fn eval_script_all<S: Into<String>>(&self, script: S) -> crate::Result<()> {
    let script = script.into();
    let webviews = self.webviews_lock().values().cloned().collect::<Vec<_>>();
    webviews

View on GitHub (pinned to 460ec35447)