Skip to main content

How To Define a Portal API

One of the biggest benefits of including Ionic Portals in an application is the ability to easily communicate between web and native code using the PortalsPlugin. However, to make the integration between a Portal and your native application more seamless, creating your own Plugins may be necessary. By creating a Capacitor Plugin, you can create your own API to communicate between web and native code.

In this example, we will create a plugin that performs a fade-in animation when a Portal has finished loading.

Creating the API Definition

We strongly recommend using TypeScript to create a type defintion file which can be used to define your API. This way, there is a central source of truth for the API across Android and iOS as well as having type defintions for the web code.

export interface PortalLoadedPlugin {
portalLoaded(): Promise<void>

On the Android side, the PortalLoadedPlugin class will have to implement a method that matches this type signature.

Implementing the API

Create the Capacitor plugin class.

@CapacitorPlugin(name = "PortalLoaded")
class PortalLoadedPlugin(val onPortalLoaded: () -> Unit): Plugin() {

fun portalLoaded(call: PluginCall) {

Using the PortalLoadedPlugin

To use the PortalLoadedPlugin class, we need to create an instance of it and add it to the portal to be rendered. When creating a Portal via the PortalManager, call the addPluginInstance() function in order to add the Plugin to that Portal instance.

class MyFragment : Fragment() {

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {

val portalLoadedPlugin = PortalLoadedPlugin {
// Perform native action when the web content is finished loading here
Log.d("MyPortalsApp", "Portal is loaded!")


_binding = FragmentFirstBinding.inflate(inflater, container, false)
return binding.root

// ...


Calling the Plugin Code from the Web

Once the Plugin has been defined, implemented, and initialized in the native code, it will also need to be registered on the web.

import { registerPlugin } from '@capacitor/core';
import { PortalLoadedPlugin } from '../definitions';
const PortalLoaded = registerPlugin<PortalLoadedPlugin>('PortalLoaded', {
// We create a no-op on the web for development purposes. This no-op plugin will only be loaded when running the plugin
// in a browser during development like Chrome or Safari.
web: { portalLoaded: () -> {} },
export const portalLoaded = () => {
const onPageLoad = async () => {
// Check if the page has already loaded
if (document.readyState === 'complete') {
} else {
window.addEventListener('load', onPageLoad);
export { portalLoaded } from './PortalLoadedPlugin';
export * from './definitions';

This code is not directly exposing the initialized PortalLoaded class outside of the module. In this scenario, we don't want the method triggered for any reason other than when the page is loaded, so we guard that method behind a pure web implementation. If the web code was not needed, we would have just exported this plugin instance directly.

Then, call the method at the start of the web application:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { portalLoaded } from './PortalLoadedPlugin'


For more information on how to create Capacitor Plugins, check our guide for creating Capacitor Plugins.