Create Free APK

JavaScript bridge API

Capacitor plugins

The Capacitor plugins the app hosts. A hand-written page reaches them as window.Capacitor.Plugins.<Name>; an AI-built app uses the @/lib/appmintNative wrapper each example names.

Capacitor.ActionSheet Capacitor#

Capacitor.Plugins.ActionSheet β€” showActions({ title, message, options })

Example

Shows a native bottom sheet with a list of choices and tells you which one was tapped (@capacitor/action-sheet 8.1.1).

Returns: Promise { index, canceled } - index is the tapped option; when the sheet is dismissed canceled is true and index is outside the list (-1). Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

Each option is { title, style? } with style 'DEFAULT', 'DESTRUCTIVE' or 'CANCEL'.

var ActionSheet = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.ActionSheet;

async function photoMenu() {
  if (!ActionSheet) return;
  var opts = [
    { title: 'Share', style: 'DEFAULT' },
    { title: 'Edit', style: 'DEFAULT' },
    { title: 'Delete', style: 'DESTRUCTIVE' }
  ];
  var r = await ActionSheet.showActions({ title: 'Photo', message: 'Choose an action', options: opts });
  if (r.canceled || r.index < 0 || r.index >= opts.length) return;   // dismissed
  if (opts[r.index].title === 'Delete') deletePhoto();
}

In an AI-built app the Capacitor-shaped wrapper returns { index: -1 } on dismiss; the short form actionSheet() returns null:

import { ActionSheet, actionSheet } from '@/lib/appmintNative';

const { index } = await ActionSheet.showActions({
  title: 'Photo',
  options: [{ title: 'Share' }, { title: 'Delete', destructive: true }],
});
if (index === 1) deletePhoto();

const i = await actionSheet('Sort by', [{ title: 'Name' }, { title: 'Date' }]);   // number | null

Delete confirmation that also works in a plain browser:

async function confirmDelete(name) {
  const AS = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.ActionSheet;
  if (!AS) return window.confirm('Delete ' + name + '?');
  const r = await AS.showActions({
    title: 'Delete ' + name + '?',
    options: [{ title: 'Delete', style: 'DESTRUCTIVE' }, { title: 'Keep it', style: 'CANCEL' }]
  });
  return !r.canceled && r.index === 0;
}

Notes: Always check the index bound - never treat any index as a real choice. The wrapper's option shape is { title, destructive? }; it maps destructive to the plugin's 'DESTRUCTIVE' style.

Capacitor.App Capacitor#

Capacitor.Plugins.App β€” getInfo(), getState(), exitApp(), minimizeApp(), addListener(...)

Example

App identity and lifecycle: name, package id, version, whether the app is in front, and pause/resume events (@capacitor/app 8.1.0).

Returns: getInfo() β†’ { name, id, build, version }; getState() β†’ { isActive }; getLaunchUrl() β†’ { url } or nothing; getAppLanguage() β†’ { value }; exitApp() / minimizeApp() β†’ void. addListener() β†’ a Promise of a handle with remove(), as in Capacitor’s own API - await it or chain .then(); the returned value also has remove() itself. Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

var App = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.App;
if (App) {
  App.getInfo().then(function (i) {
    document.getElementById('about').textContent = i.name + ' ' + i.version + ' (' + i.build + ')';
  });
}

Pause and resume - save a draft when the user leaves, refresh when they come back:

if (App) {
  App.addListener('appStateChange', function (s) {
    if (!s.isActive) saveDraft();      // went to the background
    else refreshInbox();               // back in front
  });
  App.addListener('pause', function () { console.log('paused'); });
  App.addListener('resume', function () { console.log('resumed'); });
}

In an AI-built app:

import { App } from '@/lib/appmintNative';

const info = await App.getInfo();   // { name, id, build, version }
App.exitApp();                      // closes the app on Android

Notes: The backButton event never fires: the shell keeps the back button (the plugin runs with disableBackButtonHandler). Handle back with AppMint.setBackHandler(fn) or the onBackGesture wrapper. For deep links use WebToApk.getLaunchUrl() / the appmint:deep-link event (or launchUrl() / onDeepLink() in AI-built apps). minimizeApp() is Android-only. Never make an Exit button the only way forward.

Capacitor.AppLauncher Capacitor#

Capacitor.Plugins.AppLauncher β€” canOpenUrl({ url }), openUrl({ url })

Example

Opens another app by link (whatsapp://send?text=…, tel:, geo:, an https link) and asks whether one can handle it (@capacitor/app-launcher 8.0.1).

Returns: canOpenUrl() β†’ Promise { value: boolean }. openUrl() β†’ Promise { completed: boolean } (false when no app took the link). Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

var AL = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppLauncher;

async function chatOnWhatsApp(text) {
  if (!AL) return;
  var url = 'whatsapp://send?text=' + encodeURIComponent(text);
  var can = await AL.canOpenUrl({ url: url });
  if (!can.value) { alert('WhatsApp is not installed'); return; }
  var r = await AL.openUrl({ url: url });
  if (!r.completed) alert('Could not open WhatsApp');
}

In an AI-built app:

import { appLauncher } from '@/lib/appmintNative';

if (await appLauncher.canOpen('tel:+15551234567')) await appLauncher.open('tel:+15551234567');

Open a map link without asking first:

async function openMap(query) {
  const AL = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppLauncher;
  if (!AL) { window.open('https://maps.google.com/?q=' + encodeURIComponent(query)); return; }
  const r = await AL.openUrl({ url: 'geo:0,0?q=' + encodeURIComponent(query) });
  if (!r.completed) alert('No maps app could open this');
}

Notes: On Android 11+ an app can only "see" other apps it declares. Generated apps declare https, geo, google.navigation, tel, mailto, sms, whatsapp, intent and upi links, so canOpenUrl answers false for other schemes (for example spotify:) even when the app is installed. openUrl still works for them - call it and check completed. On Android canOpenUrl also accepts a package name ('com.whatsapp'), with the same visibility limit.

Capacitor.AppUpdate Capacitor#

Capacitor.Plugins.AppUpdate β€” getAppUpdateInfo(), performImmediateUpdate(), startFlexibleUpdate(), completeFlexibleUpdate(), openAppStore()

Example

Checks Google Play for a newer version of the app and starts Play's in-app update. Vendored @capawesome/capacitor-app-update (template/plugins/app-update).

Returns: getAppUpdateInfo() β†’ { currentVersionName, currentVersionCode, availableVersionCode, updateAvailability, updatePriority, immediateUpdateAllowed, flexibleUpdateAllowed, clientVersionStalenessDays?, installStatus } (version codes are strings; updateAvailability 2 = an update is available). performImmediateUpdate() / startFlexibleUpdate() β†’ { code }: 0 OK, 1 cancelled, 2 failed, 3 not available, 4 not allowed, 5 call getAppUpdateInfo() first. openAppStore() β†’ void. Needs: nothing to switch on; the app must be installed from Google Play. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

var AU = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppUpdate;

async function checkForUpdate() {
  if (!AU) return;
  try {
    var info = await AU.getAppUpdateInfo();          // must come first
    if (info.updateAvailability !== 2) return;       // up to date
    if (info.immediateUpdateAllowed) {
      var r = await AU.performImmediateUpdate();     // Play's full-screen update
      if (r.code === 1) console.log('user said not now');
    } else {
      await AU.openAppStore();                       // the Play listing
    }
  } catch (e) {
    console.log('Play update check failed:', e.message);  // e.g. no Play services
  }
}

Flexible update - download in the background, then restart when the user agrees:

async function flexibleUpdate() {
  if (!AU) return;
  var info = await AU.getAppUpdateInfo();
  if (info.updateAvailability !== 2 || !info.flexibleUpdateAllowed) return;
  await AU.addListener('onFlexibleUpdateStateChange', function (s) {
    if (s.installStatus === 2) console.log(s.bytesDownloaded + ' / ' + s.totalBytesToDownload);
    if (s.installStatus === 11) showRestartButton();   // 11 = DOWNLOADED
  });
  await AU.startFlexibleUpdate();
}
function restartNow() { if (AU) AU.completeFlexibleUpdate(); }

In an AI-built app:

import { appUpdate } from '@/lib/appmintNative';

const info = await appUpdate.check();          // null on the web / without Play
if (info?.inPlace) await appUpdate.start();    // Android: immediate update

Notes: Use updateAvailability === 2 as the only "update available" test. The wrapper's check() compares currentVersionName with availableVersionCode, which are never equal on Android, so its updateAvailable is not reliable there - test inPlace instead. start() resolves true even when the user cancels.

Capacitor.AppwrightDocScanner Capacitor#

Capacitor.Plugins.AppwrightDocScanner β€” scan({ pageLimit, pdf, quality }), isSupported()

Example

Opens the Google ML Kit document scanner: the user photographs one or more pages (or imports them), the edges are found and straightened, and the pages come back as JPEG images. Appwright's own plugin (AppwrightDocScannerPlugin.kt).

Returns: scan() β†’ Promise that always RESOLVES: { ok: true, pages: string[] (JPEG data URLs), pdfDataUrl?, pdfPath?, pdfError? } on success, { ok: false, cancelled: true } when the user backs out, { ok: false, error } when the scanner cannot start. isSupported() β†’ { value: boolean } (Google Play services present). Needs: nothing to switch on and no camera permission (the scanner runs inside Play services, which downloads its model on first use).

var Scanner = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightDocScanner;

async function scanReceipt() {
  if (!Scanner) { alert('Scanning is not available here'); return; }
  var s = await Scanner.isSupported();
  if (!s.value) { alert('This phone has no Google Play services'); return; }
  var r = await Scanner.scan({ pageLimit: 1, pdf: false, quality: 0.8 });
  if (r.cancelled) return;
  if (!r.ok) { alert('Scanner error: ' + r.error); return; }
  document.getElementById('preview').src = r.pages[0];
}

pageLimit 0 = no limit. quality is 0.1-1 for the JPEG. With pdf: true the scanner also writes a PDF: pdfDataUrl is it as a data:application/pdf;base64,… URL the page can download, upload or show, and pdfPath is its copy in the app's cache folder. If the PDF could not be read, the pages still return and pdfError says why.

In an AI-built app (null on cancel, error, or the web):

import { docScanner, can } from '@/lib/appmintNative';

if (can('docScanner') !== 'none') {
  const res = await docScanner.scan({ pageLimit: 5, pdf: true });
  if (res) setPages(res.pages);          // res.pdfPath when pdf: true
}

Scan several pages and offer the PDF:

async function scanContract() {
  const Scanner = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightDocScanner;
  if (!Scanner) return;
  const r = await Scanner.scan({ pageLimit: 0, pdf: true, quality: 0.9 });
  if (!r.ok) { if (!r.cancelled) alert('Scanner error: ' + r.error); return; }
  if (r.pdfDataUrl) {
    const a = document.createElement('a');
    a.href = r.pdfDataUrl; a.download = 'contract.pdf'; a.click();
  } else if (r.pdfError) console.warn('PDF not made:', r.pdfError);
  console.log(r.pages.length + ' pages scanned');
}

Notes: Pages are full data URLs and can be large - shrink them before saving many in localStorage. Use pdfDataUrl, not pdfPath - a device file path is not a URL the page can load. A page that cannot be read is skipped; the rest still return.

Capacitor.AppwrightGallery Capacitor#

Capacitor.Plugins.AppwrightGallery β€” save({ source, fileName, album, kind })

Example

Saves a photo or a video into the phone's gallery (Pictures/<album> or Movies/<album>). The app shell's own plugin (AppwrightGalleryPlugin.kt).

Returns: Promise of { uri } of the saved item. Rejects with a code: bad-source, download-failed, permission-denied (Android 7-9, storage refused) or failed. Needs: nothing to switch on; no permission on Android 10+ (Android 7-9 asks for storage the first time).

const Gallery = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightGallery;

async function saveCard(canvas) {
  const dataUrl = canvas.toDataURL('image/png');
  if (!Gallery) { const a = document.createElement('a'); a.href = dataUrl; a.download = 'card.png'; a.click(); return; }
  try {
    await Gallery.save({ source: dataUrl, fileName: 'birthday-card.png', album: 'Cards' });
    toast('Saved to your gallery');
  } catch (e) {
    toast('Could not save: ' + e.message);
  }
}

Save a video from your server into its own album, with a message for each failure code:

const Gallery = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightGallery;

async function saveClip(url) {
  if (!Gallery) { window.open(url, '_blank'); return; }
  try {
    await Gallery.save({ source: url, fileName: 'highlight.mp4', album: 'Highlights', kind: 'video' });
    toast('Video saved to Movies/Highlights');
  } catch (e) {
    if (e.code === 'download-failed') toast('Could not download the video. Check your connection.');
    else if (e.code === 'permission-denied') toast('Allow storage access to save videos.');
    else toast('Could not save: ' + e.message);
  }
}

Save a photo the user edited as a Blob (for example from canvas.toBlob) by turning it into a data: URL first:

const Gallery = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightGallery;

function blobToDataUrl(blob) {
  return new Promise((resolve, reject) => {
    const r = new FileReader();
    r.onload = () => resolve(r.result);
    r.onerror = () => reject(r.error);
    r.readAsDataURL(blob);
  });
}

async function saveEditedPhoto(canvas) {
  if (!Gallery) return;
  const blob = await new Promise((res) => canvas.toBlob(res, 'image/jpeg', 0.92));
  const { uri } = await Gallery.save({ source: await blobToDataUrl(blob), fileName: 'edit-' + Date.now() + '.jpg', album: 'My Edits' });
  console.log('saved as', uri);
}

Notes: source is a data: URL, an http(s) URL or a content:///file:// URI; kind ('image'/'video') is read from the MIME type when omitted. AI-built apps use saveToGallery() from @/lib/appmintNative.

Capacitor.AppwrightHaptics Capacitor#

Capacitor.Plugins.AppwrightHaptics

Example

AppMint's own plugin for rich vibration patterns: a list of beats, each with its own start time, length and strength.

Returns: play({ events }) resolves { ok: true } or { ok: false, error } ('unsupported' = no vibrator, 'empty' = no events, or the system's message). It never rejects. isSupported() resolves { value: true/false }. Needs: nothing to switch on - every app declares the vibration permission, whether or not Vibrate is ticked in Step 4 (Access). window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

Each event is { at, duration, intensity, sharpness }: at = start in ms, duration = length in ms (under 20 becomes a 30 ms tap), intensity = 0-1 (default 0.8). sharpness is accepted but ignored on Android.

var AH = window.Capacitor && window.Capacitor.Plugins ? window.Capacitor.Plugins.AppwrightHaptics : null;

async function heartbeat() {
  if (!AH) return false;
  var r = await AH.play({
    events: [
      { at: 0,   intensity: 0.8 },
      { at: 140, intensity: 0.5 },
      { at: 700, intensity: 0.8 },
      { at: 840, intensity: 0.5 }
    ]
  });
  if (!r.ok) console.log('pattern not played:', r.error);
  return r.ok;
}

document.getElementById('like').addEventListener('click', heartbeat);

Hide the button on phones with no vibrator:

(async function () {
  var btn = document.getElementById('feel-it');
  var ok = false;
  if (AH) { try { ok = (await AH.isSupported()).value; } catch (e) {} }
  btn.hidden = !ok;
})();

Play a quick double tap on a successful action, and report when it cannot play:

async function successBuzz() {
  const AH = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightHaptics;
  if (!AH) return;
  const r = await AH.play({ events: [{ at: 0, duration: 40, intensity: 1 }, { at: 120, duration: 40, intensity: 0.6 }] });
  if (!r.ok && r.error === 'unsupported') console.log('this phone has no vibrator');
}

Notes: Strength works only on phones with amplitude control; other phones keep the timing at one fixed strength. If the system refuses the vibration, play answers { ok: false, error: '<the system message>' }. In AI-built apps use hapticPlay from @/lib/appmintNative, which also has named patterns.

Capacitor.AppwrightLiveProgress Capacitor#

Capacitor.Plugins.AppwrightLiveProgress β€” start / update / end

Example

AppMint's own Android plugin (its JS name is AppwrightLiveProgress) for a sticky, silent, live-updating status notification (progress bar, spinner or running clock) that opens a route when tapped.

Returns: start(opts) / update(opts) β†’ Promise<{ ok: true, id }>, or { ok: false, error } with nothing posted - 'permission' when notifications are not allowed (Android 13+: not granted yet, or switched off for the app), 'channel_disabled' when the user turned off the "Live status" category; end({ id }) β†’ Promise<void>. Needs: the Android app and the notification permission (Android 13+). Not available on the web.

In an AI-built app use the wrapper liveProgress from @/lib/appmintNative:

import { liveProgress } from '@/lib/appmintNative';
await liveProgress.start({ id: 'order', title: 'Order #42', text: 'Being prepared', indeterminate: true });
await liveProgress.update({ id: 'order', title: 'Order #42', text: 'On the way', progress: 0.6, route: '/orders/42' });
await liveProgress.end('order');

In a hand-written page:

async function showDelivery(step, total) {
  const P = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightLiveProgress;
  if (!P) return;                                   // not in the Android app
  if (window.Notification && Notification.permission !== 'granted') {
    if (await Notification.requestPermission() !== 'granted') return;
  }
  const r = await P.update({
    id: 'delivery',
    title: 'Your delivery',
    text: 'Stop ' + step + ' of ' + total,
    progress: step / total,                         // 0–1; or indeterminate: true
    route: '/track'                                 // opened when the card is tapped
  });
  if (!r.ok) console.warn('live card not posted:', r.error);
}

async function deliveryDone() {
  const P = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightLiveProgress;
  if (P) await P.end({ id: 'delivery' });
}

A workout timer: startedAt makes the card count up on its own, so it is posted once and ended when the workout stops:

const Live = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightLiveProgress;
let workoutStart = 0;

async function startWorkout(name) {
  workoutStart = Date.now();
  if (!Live) return;
  const r = await Live.start({ id: 'workout', title: name, text: 'Workout in progress', startedAt: workoutStart, route: '/workout' });
  if (!r.ok && r.error === 'channel_disabled') {
    showHint('Turn on "Live status" in this app\'s notification settings to see the timer.');
  }
}

async function stopWorkout() {
  if (Live) await Live.end({ id: 'workout' });
  saveWorkout({ durationMs: Date.now() - workoutStart });
}

Options: id (default 'live'), title, text, progress (0-1, omit for no bar), indeterminate, startedAt (epoch ms, shows a running clock), route.

Notes: start and update do the same thing (post or replace the card with that id). The card cannot be swiped away - always call end. Ask for the permission first: on Android 13+ without it start answers error: 'permission' and posts nothing.

Capacitor.AppwrightSystemPalette Capacitor#

Capacitor.Plugins.AppwrightSystemPalette β€” get()

Example

Returns the phone's Material You colours (the palette Android 12+ builds from the wallpaper), so the app can match the system apps. AppMint's own plugin (AppwrightSystemPalettePlugin.kt).

Returns: Promise { available: false } below Android 12, else { available: true, accent1, accent2, accent3, neutral1, neutral2 } - each an object from tone to hex colour, with tones '0', '10', '50', '100' … '900', '1000' (e.g. accent1['600'] === '#3F5F90'). Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

var Palette = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightSystemPalette;

async function useWallpaperColours() {
  if (!Palette) return;
  var p = await Palette.get();
  if (!p.available) return;                 // keep the app's own colours
  var root = document.documentElement.style;
  root.setProperty('--accent', p.accent1['600']);
  root.setProperty('--surface', p.neutral1['50']);
}
useWallpaperColours();

In an AI-built app:

import { systemPalette } from '@/lib/appmintNative';

const p = await systemPalette();
if (p.available && p.accent1) document.documentElement.style.setProperty('--accent', p.accent1['600']);

Pick light or dark tones to match the system theme, and read the palette again when the user comes back from changing the wallpaper:

var Palette = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightSystemPalette;
var darkQuery = window.matchMedia('(prefers-color-scheme: dark)');

async function applyPalette() {
  if (!Palette) return;
  var p = await Palette.get();
  if (!p.available) return;
  var dark = darkQuery.matches;
  var s = document.documentElement.style;
  s.setProperty('--accent', p.accent1[dark ? '200' : '600']);
  s.setProperty('--on-accent', p.accent1[dark ? '800' : '0']);
  s.setProperty('--bg', p.neutral1[dark ? '900' : '10']);
  s.setProperty('--text', p.neutral1[dark ? '100' : '900']);
}

darkQuery.addEventListener('change', applyPalette);
document.addEventListener('visibilitychange', function () {
  if (document.visibilityState === 'visible') applyPalette();
});
applyPalette();

Notes: Check text contrast before you apply palette colours - a wallpaper tone is not guaranteed to read well on your background. Browsers expose no wallpaper colours: on the web the wrapper answers { available: false }. The colours change when the user changes the wallpaper; call get() again on resume.

Capacitor.AppwrightWidget Capacitor#

Capacitor.Plugins.AppwrightWidget β€” set(card), clear(), placedCount(), requestPin()

Example

Fills the app's home-screen widget: one card with a title, a big value, up to twelve lines, your colours and a tap route - the same card AppMint.widget.set() takes, every key included. The card is saved on the phone and shown even after a reboot. AppMint's own plugin (AppwrightWidgetPlugin.kt).

Returns: set({ title?, value?, caption?, lines?, route?, bg?, ink?, accent?, textSize?, align?, wrap?, … }) β†’ Promise { ok: true, placed }, rejected with the reason for a bad card (placed = widgets currently on the home screen). clear() β†’ void. placedCount() β†’ { value }. requestPin() β†’ { value: boolean } (true when the launcher's "add widget" sheet was opened). Needs: nothing to switch on - every app declares the widget. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

var Widget = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.AppwrightWidget;

async function updateWidget(tasks) {
  if (!Widget) return;
  await Widget.set({
    title: 'Today',
    value: tasks.length + ' tasks',
    lines: tasks.slice(0, 6).map(function (t) { return t.title; }),
    route: '/today',                 // opened when the widget is tapped
    bg: '#0B0F1A', ink: '#FFFFFF', accent: '#22D3EE'
  });
}

Offer to place it (only when none is placed yet):

async function offerWidget() {
  if (!Widget) return;
  var n = (await Widget.placedCount()).value;
  if (n > 0) return;
  var r = await Widget.requestPin();
  if (!r.value) alert('Long-press the home screen, tap Widgets and pick this app.');
}

In an AI-built app:

import { widget } from '@/lib/appmintNative';

await widget.set({ title: 'Today', value: '3 tasks', lines: ['Gym', 'Call mum'], route: '/today' });
if ((await widget.placedCount()) === 0) await widget.requestPin();

Notes: Colours are #RRGGBB, #AARRGGBB (alpha first) or rgba(); empty ones use a dark card with a cyan value. See AppMint.widget for styled lines, caption, textSize, align, wrap and font (your own .ttf/.otf file, given as a path in your app). The tap opens the app at route as a deep link (read it with WebToApk.getLaunchUrl() or the appmint:deep-link event). Before any set() the widget says "Nothing to show yet"; clear() returns it to that state. In a browser can('widget') is 'none'.

Capacitor.Badge Capacitor#

Capacitor.Plugins.Badge β€” the launcher-icon count

Example

The Badge plugin (capawesome) that sets the number on the app's launcher icon.

Returns: Promises: set({ count }), clear(), increase(), decrease() β†’ void; get() β†’ { count }; isSupported() β†’ { isSupported }. The Android plugin has no permission methods. Needs: nothing on Android. Not available on the web.

In an AI-built app use the wrapper badge from @/lib/appmintNative:

import { badge } from '@/lib/appmintNative';
await badge.set(3);
await badge.clear();

In a hand-written page:

async function setUnread(count) {
  const B = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Badge;
  if (!B) return false;                       // not inside the app
  try {
    const s = await B.isSupported();
    if (!s.isSupported) return false;         // this launcher shows no badges
    if (count > 0) await B.set({ count: count });
    else await B.clear();
    return true;
  } catch (e) {
    return false;
  }
}

window.addEventListener('appmint:push', function () {
  const inbox = JSON.parse(window.WebToApk ? WebToApk.getPushInbox() : '[]');
  setUnread(inbox.filter(function (m) { return !m.read; }).length);
});

Count up and down as items arrive and are read:

const B = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Badge;
async function onNewMessage() { if (B) await B.increase(); }
async function onMessageRead() { if (B) await B.decrease(); }
async function showCurrent() {
  if (!B) return;
  const { count } = await B.get();
  console.log('badge shows', count);
}

Notes: on Android the look depends on the launcher (a number, a dot, or nothing). navigator.setAppBadge(n) in the app writes to the same badge (it exists only on launchers that can show a count).

Capacitor.BarcodeScanner Capacitor#

Capacitor.Plugins.BarcodeScanner β€” scan, startScan, readBarcodesFromImage, isSupported

Example

QR and barcode scanning with the @capacitor-mlkit/barcode-scanning API - the app shell's own plugin (BarcodeScannerPlugin.kt) over the same Google code scanner as AppMint.scanCode(). The older @capacitor-community/barcode-scanner calls (startScan() resolving { hasContent, content }, checkPermission, prepare, hideBackground) share this name and work too.

Returns: scan({ formats?, autoZoom? }) β†’ { barcodes: [{ rawValue, displayValue, format, valueType, bytes?, cornerPoints?, urlBookmark?, wifi?, email?, phone?, sms?, geoPoint?, contactInfo?, calendarEvent?, driverLicense? }] } (rejects "scan canceled." when the user backs out); readBarcodesFromImage({ path, formats? }) β†’ { barcodes }; isSupported() β†’ { supported }; isGoogleBarcodeScannerModuleAvailable() β†’ { available }; installGoogleBarcodeScannerModule() (progress as googleBarcodeScannerModuleInstallProgress); checkPermissions() / requestPermissions() β†’ { camera }; openSettings(). format is 'QR_CODE', 'EAN_13', 'CODE_128'… Needs: nothing to switch on and no camera permission - Google's scanner draws its own screen (Play services downloads it on first use). window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

const Scanner = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.BarcodeScanner;

scanBtn.onclick = async () => {
  if (!Scanner) return;
  try {
    const { barcodes } = await Scanner.scan({ formats: ['QR_CODE'] });
    const code = barcodes[0];
    if (code.valueType === 'URL') location.href = code.urlBookmark.url;
    else resultBox.textContent = code.displayValue;
  } catch (e) {
    if (e.message !== 'scan canceled.') alert(e.message);
  }
};

Price lookup from a product barcode, with the listener style (startScan + barcodesScanned) that ML Kit apps already use:

const Scanner = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.BarcodeScanner;

async function scanProduct() {
  const listener = await Scanner.addListener('barcodesScanned', async ({ barcodes }) => {
    await listener.remove();
    await Scanner.stopScan();
    const ean = barcodes[0].rawValue;
    const res = await fetch('https://api.example.com/products/' + encodeURIComponent(ean));
    priceBox.textContent = res.ok ? (await res.json()).price : 'Not found';
  });
  await Scanner.startScan({ formats: ['EAN_13', 'EAN_8', 'UPC_A', 'UPC_E'] });
}

Read a QR code from a photo the user already has:

const Scanner = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.BarcodeScanner;
const Camera = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Camera;

async function qrFromGallery() {
  const photo = await Camera.getPhoto({ source: 'PHOTOS', resultType: 'uri', quality: 90 });
  const { barcodes } = await Scanner.readBarcodesFromImage({ path: photo.path, formats: ['QR_CODE'] });
  return barcodes.length ? barcodes[0].rawValue : null;
}

Notes: Upstream's startScan() shows a live camera preview behind a see-through page and keeps scanning until stopScan(); here it opens the scanner screen, the first code fires barcodeScanned / barcodesScanned, and the session ends (the call then resolves { hasContent, content, format } for community code; a closed scanner resolves { hasContent: false }). Torch and zoom belong to that preview, so isTorchAvailable() is false and the torch/zoom setters reject - the scanner screen has its own flashlight button. checkPermissions() answers granted when the app has no camera permission at all (the scanner needs none); an app that also uses the camera gets its real state. AI-built apps use scanCode() from @/lib/appmintNative.

Capacitor.Browser Capacitor#

Capacitor.Plugins.Browser β€” open({ url, toolbarColor }), close(), addListener('browserFinished')

Example

Opens a web page in an in-app browser tab (Chrome Custom Tabs), so the user reads terms, docs or a sign-in page and comes back with one gesture (@capacitor/browser 8.0.3).

Returns: open() / close() β†’ Promise void. Events: browserFinished (the user closed the tab) and browserPageLoaded. Needs: nothing - every app hosts the plugin (a browser that supports Custom Tabs, such as Chrome, must be installed). window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

var Browser = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Browser;

async function openTerms() {
  if (!Browser) return;
  var h = await Browser.addListener('browserFinished', function () {
    console.log('user closed the terms');
    h.remove();
  });
  await Browser.open({ url: 'https://example.com/terms', toolbarColor: '#0B0F1A' });
}

In an AI-built app (a new tab on the web):

import { browser } from '@/lib/appmintNative';

const off = browser.onClosed(() => refreshAccount());
await browser.open('https://example.com/terms', { toolbarColor: '#0B0F1A' });
// call off() when you no longer care

Open a link, and close the tab when a sign-in return reaches the page:

var Browser = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Browser;

async function openHelp() {
  if (!Browser) { window.open('https://example.com/help', '_blank'); return; }
  try {
    await Browser.open({ url: 'https://example.com/help' });
  } catch (e) {
    alert('Could not open the page: ' + e.message);
  }
}

window.addEventListener('appmint:deep-link', function () {
  if (Browser) Browser.close().catch(function () {});
});

Notes: url must not be empty. toolbarColor is a hex colour; an invalid one is ignored. close() also works on Android in this plugin version (it finishes the tab's host activity) - useful after a sign-in link returns to the app. The tab is a separate browser: it does not share the app's cookies or localStorage.

Capacitor.Calendar Capacitor#

Capacitor.Plugins.Calendar β€” createEvent, findEvents, deleteEvent, listCalendars, openCalendar

Example

Adds, finds and deletes events in the phone's own calendar (Android CalendarContract), and opens the calendar app at a date. Vendored @capacitor/calendar (template/plugins/calendar).

Returns: createEvent() β†’ { id }; findEvents() β†’ { events: [{ id, title, location?, notes?, startDate, endDate, isAllDay, calendarId, calendarName?, attendees? }] }; listCalendars() β†’ { calendars: [{ id, name, displayName?, isPrimary }] }; deleteEvent(), openCalendar() β†’ void. Dates are epoch milliseconds. Needs: tick Calendar in Step 4 (Access) when you build. For HTML, ZIP and AI-built apps the build also adds the calendar permission by itself when your files contain a Calendar.<method>( call written exactly so (as below) or the wrapper's calendar.createEvent(. The plugin asks the user for calendar access on first use. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

var Calendar = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Calendar;

async function addDentist() {
  if (!Calendar) return;
  var start = new Date('2026-10-02T10:00:00').getTime();
  try {
    var r = await Calendar.createEvent({
      title: 'Dentist', startDate: start, endDate: start + 60 * 60 * 1000,
      location: 'Main St 5', notes: 'Bring the card', isAllDay: false,
      firstReminderMinutes: 30
    });
    console.log('event id', r.id);
  } catch (e) {
    // e.code: OS-PLUG-CLDR-0020 permission denied, -0001 bad arguments
    alert('Could not add the event: ' + e.message);
  }
}

Read this week, delete one, open the calendar:

async function thisWeek() {
  if (!Calendar) return;
  var now = Date.now();
  var found = await Calendar.findEvents({ startDate: now, endDate: now + 7 * 864e5 });
  found.events.forEach(function (ev) { console.log(ev.title, new Date(ev.startDate)); });
  if (found.events.length) await Calendar.deleteEvent({ id: found.events[0].id });
  await Calendar.openCalendar({ date: now });
}

In an AI-built app (on the web createEvent downloads an .ics file; reading returns []):

import { calendar } from '@/lib/appmintNative';

if ((await calendar.requestPermission()) === 'granted') {
  const id = await calendar.createEvent({ title: 'Dentist', start: new Date('2026-10-02T10:00'), end: new Date('2026-10-02T11:00') });
  const week = await calendar.listEvents({ from: Date.now(), to: Date.now() + 7 * 864e5 });
  if (id) await calendar.deleteEvent(id);
  await calendar.openCalendar(new Date());
}

Notes: Without the calendar permission in the app, no dialog appears and every event call rejects (missing permission); a user who says no gets OS-PLUG-CLDR-0020. recurrence: { frequency: 'daily' | 'weekly' | 'monthly' | 'yearly', interval?, count?, endDate? } makes a repeating event. createEventInteractively({ title, startDate, endDate }) opens the calendar app's own editor instead: it resolves { id } of the saved event, and rejects OS-PLUG-CLDR-0006 when the user closes the editor without saving (it needs read as well as write access to tell the two apart). deleteEvent({ id, fromDate }) ends a repeating event before fromDate and keeps the earlier occurrences. The wrapper returns null / [] / false instead of throwing.

Capacitor.Camera Capacitor#

Capacitor.Plugins.Camera

Example

The official Capacitor Camera plugin, built into the app: take a photo with the camera or choose photos from the gallery.

Returns: Promises. getPhoto(opts) β†’ { dataUrl | base64String | webPath, format, saved } (which field depends on resultType), and REJECTS when the user cancels; pickImages(opts) β†’ { photos: [{ webPath, format }] }; checkPermissions() / requestPermissions({ permissions }) β†’ { camera, photos }. Needs: tick Camera in Step 4 (Access). For HTML, ZIP and AI-built apps the build also adds the camera permission by itself when your files contain Camera.getPhoto( written exactly so (as below), getUserMedia with video, or <input capture>. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

Take a photo and show it.

async function takePhoto() {
  var Camera = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Camera;
  if (!Camera) { showMessage('Open the app to use the camera.'); return; }
  try {
    var perm = await Camera.requestPermissions({ permissions: ['camera'] });
    if (perm.camera !== 'granted') { showMessage('Camera permission was refused.'); return; }

    var photo = await Camera.getPhoto({
      source: 'CAMERA',          // 'CAMERA' | 'PHOTOS' | 'PROMPT'
      resultType: 'dataUrl',     // 'dataUrl' | 'base64' | 'uri'
      quality: 80,
      direction: 'REAR',         // 'REAR' | 'FRONT'
      correctOrientation: true,
      saveToGallery: false
    });
    document.getElementById('photo').src = photo.dataUrl;
  } catch (err) {
    // The user closed the camera (or no camera app). Nothing to do.
  }
}

Choose up to 5 photos from the gallery.

async function choosePhotos() {
  var Camera = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Camera;
  if (!Camera) return;
  try {
    var result = await Camera.pickImages({ quality: 80, limit: 5 });
    result.photos.forEach(function (p) {
      var img = document.createElement('img');
      img.src = p.webPath;       // a URL the page can load
      document.getElementById('gallery').appendChild(img);
    });
  } catch (err) { /* cancelled */ }
}

In an AI-built app use the wrapper, which also works on the web (and resolves null instead of throwing on cancel):

import { camera } from '@/lib/appmintNative';
const photo = await camera.take({ source: 'camera' });

Notes: pickImages needs no camera permission. If the camera permission is not in the app (the box was off and the build found no camera call), the camera permission is refused with no prompt. An <input type="file" accept="image/*" capture> also opens the camera. Camera is sensitive: declare it in your Play Data safety form.

Capacitor.CapacitorBarcodeScanner Capacitor#

Capacitor.Plugins.CapacitorBarcodeScanner β€” scanBarcode({ hint })

Example

One-call QR / barcode scanning with the official @capacitor/barcode-scanner API - the app shell's own plugin (CapacitorBarcodeScannerPlugin.kt) over the same Google code scanner as AppMint.scanCode(). Code written for the npm package (CapacitorBarcodeScanner.scanBarcode({ hint: CapacitorBarcodeScannerTypeHint.ALL })) works unchanged.

Returns: scanBarcode({ hint, scanInstructions?, cameraDirection?, scanOrientation? }) β†’ { ScanResult, format }. hint / format are the package's numbers: 0 QR_CODE, 1 AZTEC, 2 CODABAR, 3 CODE_39, 4 CODE_93, 5 CODE_128, 6 DATA_MATRIX, 8 ITF, 9 EAN_13, 10 EAN_8, 11 PDF_417, 14 UPC_A, 15 UPC_E, 17 ALL. Rejects with code OS-PLUG-BARC-0006 when the user closes the scanner. Needs: nothing to switch on and no camera permission - Google's scanner draws its own screen (Play services downloads it on first use). window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

const Scanner = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.CapacitorBarcodeScanner;

ticketBtn.onclick = async () => {
  if (!Scanner) return;
  try {
    const { ScanResult } = await Scanner.scanBarcode({ hint: 0 });   // QR codes only
    const ok = await fetch('/api/tickets/check?code=' + encodeURIComponent(ScanResult)).then((r) => r.ok);
    statusBox.textContent = ok ? 'Valid ticket' : 'Unknown ticket';
  } catch (e) {
    if (e.code !== 'OS-PLUG-BARC-0006') alert(e.message);
  }
};

Accept any code, and tell the user which kind it was:

const Scanner = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.CapacitorBarcodeScanner;
const NAMES = { 0: 'QR code', 5: 'Code 128', 9: 'EAN-13', 10: 'EAN-8', 14: 'UPC-A', 15: 'UPC-E' };

async function scanAnything() {
  const { ScanResult, format } = await Scanner.scanBarcode({ hint: 17 });
  return (NAMES[format] || 'Barcode') + ': ' + ScanResult;
}

Handle a rejection that is not the user closing the scanner:

const Scanner = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.CapacitorBarcodeScanner;

async function scanProduct() {
  if (!Scanner) { alert('Scanning works only inside the app'); return null; }
  try {
    const { ScanResult } = await Scanner.scanBarcode({ hint: 9 });   // EAN-13
    return ScanResult;
  } catch (e) {
    if (e.code === 'OS-PLUG-BARC-0006') return null;                 // user closed the scanner
    alert('Scan failed: ' + e.message);
    return null;
  }
}

Notes: scanInstructions, scanButton, cameraDirection and scanOrientation style upstream's own camera screen and have no effect on Google's scanner (always the back camera). MAXICODE, RSS_14, RSS_EXPANDED and UPC_EAN_EXTENSION are not supported as a hint (rejects OS-PLUG-BARC-0008); 17 (ALL) scans every supported type. AI-built apps use scanCode() from @/lib/appmintNative.

Capacitor.CapacitorCookies Capacitor#

Capacitor.Plugins.CapacitorCookies β€” getCookies / setCookie / deleteCookie / clearCookies / clearAllCookies

Example

Reads and writes the app's cookies, including those of another host your page talks to (Capacitor core's CapacitorCookies).

Returns: getCookies({ url? }) β†’ { [name]: value } - with a url, the cookies that URL (host and path) would send; without one, the page's own document.cookie. It rejects a url that is not http(s). setCookie({ key, value, url?, path?, expires? }), deleteCookie({ key, url? }), clearCookies({ url? }), clearAllCookies() β†’ Promise void; setCookie / deleteCookie reject without a string key (and value) and then write nothing. Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

var Cookies = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.CapacitorCookies;

// Read the session cookie your API host set β€” not the page's own cookies.
async function apiSession() {
  if (!Cookies) return null;          // not inside the app
  var jar = await Cookies.getCookies({ url: 'https://api.example.com/' });
  return jar.session || null;
}

// Remember a choice for the API host, for 30 days, on one path only.
async function rememberRegion(region) {
  if (!Cookies) return;
  var expires = new Date(Date.now() + 30 * 864e5).toUTCString();
  await Cookies.setCookie({ url: 'https://api.example.com', path: '/v2', key: 'region', value: region, expires: expires });
}

Sign out: drop that host's cookies, or every cookie the app holds.

async function signOut(everything) {
  if (!Cookies) return;
  if (everything) await Cookies.clearAllCookies();
  else await Cookies.clearCookies({ url: 'https://api.example.com' });
}

Notes: A cookie set with a path is only returned for a url under that path. Values are URL-decoded when read. Cookies are shared by every page the app shows, so never keep secrets in one a foreign page could read - they belong in Preferences or the app's secure storage.

Capacitor.CapacitorSQLite Capacitor#

Capacitor.Plugins.CapacitorSQLite β€” createConnection, open, execute, query, run, executeSet, close

Example

A real SQLite database on the phone, with the @capacitor-community/sqlite API. Vendored @capacitor-community/sqlite (template/plugins/sqlite), running on Android's own SQLite. Code written for the npm package (new SQLiteConnection(CapacitorSQLite)) works unchanged; a hand-written page calls the plugin object directly, as below.

Returns: createConnection({ database, version, encrypted: false, mode: 'no-encryption', readonly: false }), open({ database }), close({ database }), closeConnection({ database }) β†’ void; execute({ database, statements }) β†’ { changes: { changes } }; run({ database, statement, values }) β†’ { changes: { changes, lastId } }; query({ database, statement, values }) β†’ { values: [rows] }; executeSet({ database, set: [{ statement, values }] }) β†’ { changes }; isDBExists({ database }) / isDBOpen({ database }) β†’ { result }; beginTransaction, commitTransaction, rollbackTransaction, isTransactionActive({ database }); deleteDatabase({ database }); exportToJson / importFromJson for backups. Needs: nothing to switch on and no permission. The database lives in the app's private storage and survives app updates. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

const DB = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.CapacitorSQLite;
const database = 'notes';

async function openNotes() {
  if (!DB) return false;
  await DB.createConnection({ database, version: 1, encrypted: false, mode: 'no-encryption', readonly: false });
  await DB.open({ database, readonly: false });
  await DB.execute({ database, statements:
    'CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY AUTOINCREMENT, text TEXT NOT NULL, at INTEGER);' });
  return true;
}

async function addNote(text) {
  const r = await DB.run({ database, statement: 'INSERT INTO notes (text, at) VALUES (?, ?);', values: [text, Date.now()] });
  return r.changes.lastId;
}

async function listNotes() {
  const r = await DB.query({ database, statement: 'SELECT id, text, at FROM notes ORDER BY at DESC;', values: [] });
  return r.values || [];
}

Several writes that must all land or none - one transaction with executeSet:

const DB = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.CapacitorSQLite;

async function moveMoney(database, fromId, toId, amount) {
  await DB.executeSet({ database, transaction: true, set: [
    { statement: 'UPDATE accounts SET balance = balance - ? WHERE id = ?;', values: [amount, fromId] },
    { statement: 'UPDATE accounts SET balance = balance + ? WHERE id = ?;', values: [amount, toId] },
    { statement: 'INSERT INTO ledger (from_id, to_id, amount, at) VALUES (?, ?, ?, ?);', values: [fromId, toId, amount, Date.now()] },
  ] });
}

Open the database only once per app start, even when two screens ask at the same time:

const DB = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.CapacitorSQLite;
let ready = null;

function db(database) {
  if (!ready) ready = (async () => {
    try {
      await DB.createConnection({ database, version: 1, encrypted: false, mode: 'no-encryption', readonly: false });
    } catch (e) {
      if (!/already exists/i.test(e.message)) throw e;   // a page reload keeps the native connection
    }
    if (!(await DB.isDBOpen({ database, readonly: false })).result) await DB.open({ database, readonly: false });
    return database;
  })();
  return ready;
}

Notes: The app is built without SQLCipher, so databases are not encrypted: encrypted: true, setEncryptionSecret, changeEncryptionSecret, isSecretStored and the other encryption calls reject with "Encryption is not available in this app". isInConfigEncryption() answers false. Everything else (queries, bound values, transactions, versioned upgrades with addUpgradeStatement, JSON import/export) is the plugin's own code. Rejections carry the SQLite error text.

Capacitor.Clipboard Capacitor#

Capacitor.Plugins.Clipboard β€” write(options), read()

Example

Copies text (or a link) to the system clipboard and reads it back (@capacitor/clipboard 8.0.1).

Returns: write({ string?, url?, image?, label? }) β†’ Promise void. read() β†’ Promise { value, type } (type is 'text/plain' for text). Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

async function copyCode(code) {
  var Clipboard = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Clipboard;
  if (!Clipboard) { alert('Copy is not available here'); return; }
  await Clipboard.write({ string: code, label: 'Invite code' });
}

async function pasteCode(input) {
  var Clipboard = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Clipboard;
  if (!Clipboard) return;
  try {
    var r = await Clipboard.read();
    input.value = r.value;
  } catch (e) {
    // An EMPTY clipboard is a rejection: "There is no data on the clipboard".
    if (/no data/i.test(e.message)) input.value = '';
    else alert('Could not read the clipboard: ' + e.message);
  }
}

In an AI-built app the Capacitor-shaped wrapper turns an empty clipboard into '':

import { Clipboard } from '@/lib/appmintNative';

await Clipboard.write({ string: 'ABC-123' });
const { value } = await Clipboard.read();   // '' when the clipboard is empty

Copy a link and confirm it with a tap of feedback:

async function copyLink(url) {
  var P = window.Capacitor && window.Capacitor.Plugins;
  if (!P || !P.Clipboard) { alert('Copy is not available here'); return; }
  await P.Clipboard.write({ url: url });
  if (P.Haptics) P.Haptics.impact({ style: 'LIGHT' });
  copiedToast.hidden = false;
}

Notes: write needs one of string, url or image (a data URL), else it rejects "No data provided". Android 12+ shows its own "pasted from…" notice on read - that is the OS, not the app. The wrapper's write sends only string || url.

Capacitor.Contacts Capacitor#

Capacitor.Plugins.Contacts β€” getContacts, getContact, pickContact, createContact, deleteContact

Example

The phone's address book with the @capacitor-community/contacts API. Vendored @capacitor-community/contacts (template/plugins/contacts). Code written for the npm package works unchanged.

Returns: checkPermissions() / requestPermissions() β†’ { contacts, contactsWrite } (each 'granted' | 'denied' | 'prompt'; contacts is reading, contactsWrite is adding and deleting); getContacts({ projection }) β†’ { contacts: [ContactPayload] }; getContact({ contactId, projection }) and pickContact({ projection }) β†’ { contact }; createContact({ contact }) β†’ { contactId }; deleteContact({ contactId }) β†’ void. projection picks the fields: { name, phones, emails, organization, birthday, note, urls, postalAddresses, image } (each true). Needs: tick Contacts in Step 4 (Access) to read, and Edit Contacts as well to add or delete. HTML, ZIP and AI-Studio builds tick them by themselves when the code calls Contacts.getContacts( / getContact( / pickContact( (reading) or Contacts.createContact( / deleteContact( (both). The plugin asks the user on first use; without the box ticked the call rejects "Permission is required to access contacts." - and in an app with none of Contacts, SMS or Call log ticked the plugin is not in the app at all (Capacitor rejects "not implemented"). window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

const Contacts = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Contacts;

async function friendsWithPhones() {
  if (!Contacts) return [];
  const { contacts } = await Contacts.requestPermissions();
  if (contacts !== 'granted') return [];
  const res = await Contacts.getContacts({ projection: { name: true, phones: true } });
  return res.contacts
    .filter((c) => c.phones && c.phones.length)
    .map((c) => ({ id: c.contactId, name: (c.name && c.name.display) || '', phone: c.phones[0].number }));
}

Let the user choose one person from the system picker:

const Contacts = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Contacts;

inviteBtn.onclick = async () => {
  if (!Contacts) return;
  try {
    const { contact } = await Contacts.pickContact({ projection: { name: true, phones: true, emails: true } });
    inviteField.value = (contact.emails && contact.emails[0] && contact.emails[0].address) || '';
  } catch (e) {
    // The user closed the picker, or refused access.
  }
};

Save a business card from the app into the address book:

const Contacts = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Contacts;

async function saveCard(card) {
  const perm = await Contacts.requestPermissions();
  if (perm.contactsWrite !== 'granted') return null;
  const { contactId } = await Contacts.createContact({ contact: {
    name: { given: card.first, family: card.last },
    organization: { company: card.company, jobTitle: card.title },
    phones: [{ type: 'work', label: 'work', number: card.phone }],
    emails: [{ type: 'work', label: 'work', address: card.email }],
  } });
  return contactId;
}

Notes: pickContact() rejects with code CANCELLED when the user backs out of the picker. Android only fills contactId, name, phones, emails, organization, birthday, note, urls, postalAddresses and image when the projection asks for them - ask only for what you show. Reading contacts is a sensitive permission on Google Play: describe why in the listing's data-safety form. AI-built apps use listContacts(), pickContact() and addContact() from @/lib/appmintNative.

Capacitor.Device Capacitor#

Capacitor.Plugins.Device

Example

The official Capacitor Device plugin, built into the app: phone model, OS version, battery, a stable ID and the language.

Returns: Promises. getInfo() β†’ { model, platform, operatingSystem, osVersion, androidSDKVersion, manufacturer, isVirtual, webViewVersion, … }; getId() β†’ { identifier }; getBatteryInfo() β†’ { batteryLevel (0–1), isCharging }; getLanguageCode() β†’ { value }. Needs: nothing to switch on. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

Show the phone and battery.

async function showDevice() {
  var cap = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Device;
  if (!cap) { document.getElementById('out').textContent = navigator.userAgent; return; }
  try {
    var info = await cap.getInfo();
    var battery = await cap.getBatteryInfo();
    document.getElementById('out').textContent =
      info.manufacturer + ' ' + info.model + ' - ' + info.operatingSystem + ' ' + info.osVersion +
      ' - battery ' + Math.round(battery.batteryLevel * 100) + '%' + (battery.isCharging ? ' (charging)' : '');
  } catch (err) {
    document.getElementById('out').textContent = 'Could not read device info';
  }
}

A stable ID and the language.

async function helloServer() {
  var cap = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Device;
  if (!cap) return;
  var id = (await cap.getId()).identifier;
  var lang = (await cap.getLanguageCode()).value;     // e.g. "en"
  await fetch('https://api.example.com/hello?lang=' + encodeURIComponent(lang), {
    method: 'POST', body: JSON.stringify({ device: id })
  });
}

In an AI-built app use the wrapper, which also works on the web:

import { Device } from '@/lib/appmintNative';
const info = await Device.getInfo();

Notes: For the Android build ID, security patch, screen, memory and network, the shell's WebToApk.getDeviceInfo() gives more detail. Plugin ids are not hardware ids; they can change after a factory reset.

Capacitor.Dialog Capacitor#

Capacitor.Plugins.Dialog β€” alert, confirm, prompt

Example

Shows a real Android system dialog: a message, a yes/no question, or a one-line text question (@capacitor/dialog 8.0.1).

Returns: alert({ title?, message, buttonTitle? }) β†’ Promise void. confirm({ title?, message, okButtonTitle?, cancelButtonTitle? }) β†’ { value: boolean }. prompt({ title?, message, okButtonTitle?, cancelButtonTitle?, inputPlaceholder?, inputText? }) β†’ { value: string, cancelled: boolean }. Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

var Dialog = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Dialog;

async function deleteNote(id) {
  if (!Dialog) return;
  var r = await Dialog.confirm({
    title: 'Delete note?',
    message: 'This cannot be undone.',
    okButtonTitle: 'Delete',
    cancelButtonTitle: 'Keep'
  });
  if (r.value) removeNote(id);
}

Alert and prompt:

async function renameFlow() {
  if (!Dialog) return;
  await Dialog.alert({ title: 'Saved', message: 'Your changes are stored.', buttonTitle: 'OK' });

  var p = await Dialog.prompt({ title: 'Rename', message: 'New name', inputText: 'Groceries', inputPlaceholder: 'List name' });
  if (!p.cancelled && p.value.trim()) renameList(p.value.trim());
}

In an AI-built app the wrapper never rejects: dismissal is { value: false } / { cancelled: true }, and on the web it uses the browser's own dialogs:

import { Dialog } from '@/lib/appmintNative';

const { value } = await Dialog.confirm({ title: 'Delete note?', message: 'This cannot be undone.' });
if (value) removeNote(id);

Notes: message is required - without it the plugin rejects "Please provide a message for the dialog". Dialogs are modal; use them only for decisions that must block.

Capacitor.Filesystem Capacitor#

Capacitor.Plugins.Filesystem

Example

The stock Capacitor Filesystem plugin: read and write files in the app's OWN storage (not a folder the user picked - for that use WebToApkFS).

Returns: every method returns a Promise and rejects on failure (for example reading a file that does not exist). readFile β†’ { data }, writeFile β†’ { uri }, readdir β†’ { files: [{ name, type, size, mtime, uri }] }, mkdir / deleteFile β†’ nothing. Needs: nothing to switch on; 'DATA' and 'CACHE' need no permission.

A page has no Directory / Encoding imports, so pass the plain strings 'DATA' (private app files, kept until uninstall) or 'CACHE' (the system may clear it), and 'utf8' for text:

async function saveAndReadBack() {
  var Fs = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Filesystem;
  if (!Fs) { console.log('Filesystem plugin not available (not inside the app)'); return; }

  try {
    await Fs.mkdir({ path: 'notes', directory: 'DATA', recursive: true });
    await Fs.writeFile({ path: 'notes/today.txt', data: 'Hello', directory: 'DATA', encoding: 'utf8', recursive: true });

    var r = await Fs.readFile({ path: 'notes/today.txt', directory: 'DATA', encoding: 'utf8' });
    console.log('text:', r.data);

    var dir = await Fs.readdir({ path: 'notes', directory: 'DATA' });
    dir.files.forEach(function (f) { console.log(f.type, f.name, f.size); });   // type: 'file' | 'directory'

    await Fs.deleteFile({ path: 'notes/today.txt', directory: 'DATA' });
  } catch (e) {
    console.log('File error:', e.message);   // e.g. the file does not exist
  }
}

Leave out encoding to write or read binary data as base64:

var Fs = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Filesystem;
var base64Data = btoa('raw bytes here');   // any base64 string, e.g. from a canvas or FileReader
if (Fs) {
  Fs.writeFile({ path: 'blobs/data.bin', data: base64Data, directory: 'CACHE', recursive: true })
    .then(function (w) { console.log('written to', w.uri); })
    .catch(function (e) { console.log('write failed:', e.message); });
}

In AI-built apps, the @/lib/appmintNative wrapper that uses this plugin is fileTransfer (it stages downloads and uploads in 'CACHE'):

import { fileTransfer } from '@/lib/appmintNative';

const r = await fileTransfer.download('https://example.com/report.pdf', 'report.pdf', {
  onProgress: (p) => console.log(Math.round(p.fraction * 100) + '%'),
});
if (r.path) console.log('saved in the app at', r.path);   // on the web the browser saves it instead

Notes: window.Capacitor exists only inside the app - always feature-detect. Files in 'DATA' are private to the app and deleted on uninstall; to put files where the user can see them, use WebToApkFS (a folder the user grants) or a download.

Capacitor.FileTransfer Capacitor#

Capacitor.Plugins.FileTransfer

Example

The official Capacitor File Transfer plugin: downloads a URL to a file on the phone, or uploads a file from the phone, natively, with progress events.

Returns: downloadFile({url, path, headers?, progress?}) β†’ Promise<{path}>; uploadFile({url, path, fileKey?, mimeType?, method?, headers?, progress?}) β†’ Promise<{bytesSent, responseCode, response, headers}>. Progress arrives as 'progress' listener events {type, url, bytes, contentLength, lengthComputable}. Failures reject with {message, code}. Needs: nothing to switch on. window.Capacitor exists on pages the app serves itself and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites. Always feature-detect.

Download into the app's cache with progress:

async function download(url, name, onPct) {
  const P = window.Capacitor && window.Capacitor.Plugins;
  if (!P || !P.FileTransfer || !P.Filesystem) return null;          // not in the app
  const target = await P.Filesystem.getUri({ path: name, directory: 'CACHE' });
  const listener = await P.FileTransfer.addListener('progress', function (p) {
    if (p.url === url && p.lengthComputable) onPct(Math.round(p.bytes / p.contentLength * 100));
  });
  try {
    const r = await P.FileTransfer.downloadFile({ url: url, path: target.uri, progress: true });
    return r.path;
  } catch (e) {
    alert('Download failed: ' + e.message);
    return null;
  } finally {
    listener.remove();
  }
}

Upload a file that is already on the phone:

const P = window.Capacitor && window.Capacitor.Plugins;
if (P && P.FileTransfer) {
  P.FileTransfer.uploadFile({
    url: 'https://example.com/api/upload', path: localFileUri,
    fileKey: 'file', mimeType: 'image/jpeg', headers: { Authorization: 'Bearer ' + token }
  }).then(function (r) { console.log('server said', r.responseCode, r.response); })
    .catch(function (e) { alert('Upload failed: ' + e.message); });
}

Report a failed transfer by its code:

async function safeDownload(url, target) {
  const P = window.Capacitor && window.Capacitor.Plugins;
  if (!P || !P.FileTransfer) { alert('Downloads need the app'); return null; }
  try {
    const r = await P.FileTransfer.downloadFile({ url: url, path: target });
    return r.path;
  } catch (e) {
    console.log('transfer failed', e.code, e.message);
    alert('Could not download the file.');
    return null;
  }
}

Notes: The path must be a file path or file:// URI, not a Blob - write a Blob to the cache with Filesystem.writeFile first. On Android 10 and older, a path in public storage asks for storage permission. In AI-built apps prefer fileTransfer from @/lib/appmintNative, which does all of this and also works on the web.

Capacitor.FileViewer Capacitor#

Capacitor.Plugins.FileViewer

Example

The official Capacitor File Viewer plugin: opens a document or media file in the phone's viewer app, from a web address, a local file path, or a file bundled in the app.

Returns: each method returns a Promise<void> that rejects with {message, code} when nothing can open the file. Needs: nothing to switch on. window.Capacitor exists on pages the app serves itself and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites. Always feature-detect.

async function openPdf(url) {
  const FV = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.FileViewer;
  if (!FV) { window.open(url, '_blank'); return; }          // a normal browser: open a tab
  try {
    await FV.openDocumentFromUrl({ url: url });
  } catch (e) {
    alert('No app on this phone can open this file.');
  }
}

A file on the phone, for example one written with Capacitor.Plugins.Filesystem:

async function openSaved(name) {
  const P = window.Capacitor && window.Capacitor.Plugins;
  if (!P || !P.FileViewer || !P.Filesystem) return;
  const f = await P.Filesystem.getUri({ path: name, directory: 'CACHE' });
  try {
    await P.FileViewer.openDocumentFromLocalPath({ path: f.uri.replace(/^file:\/\//, '') });
  } catch (e) {
    alert('Cannot open ' + name + ': ' + e.message);
  }
}

openDocumentFromResources({path}) opens a file packed in the APK's own assets folder. The same three methods also exist as previewMediaContentFromUrl, previewMediaContentFromLocalPath and previewMediaContentFromResources for video and audio.

Download a file and open it when it arrives:

async function downloadAndOpen(url, name) {
  const P = window.Capacitor && window.Capacitor.Plugins;
  if (!P || !P.FileTransfer || !P.Filesystem || !P.FileViewer) return;
  const f = await P.Filesystem.getUri({ path: name, directory: 'CACHE' });
  const r = await P.FileTransfer.downloadFile({ url: url, path: f.uri });
  await P.FileViewer.openDocumentFromLocalPath({ path: r.path.replace(/^file:\/\//, '') });
}

Notes: In AI-built apps prefer fileViewer.open(target) from @/lib/appmintNative, which picks the right method and also works on the web. A local path is a plain path (no file://).

Capacitor.Geolocation Capacitor#

Capacitor.Plugins.Geolocation

Example

The official Capacitor Geolocation plugin, built into the app: the phone's position once, or continuous updates.

Returns: Promises. getCurrentPosition(opts) β†’ { timestamp, coords: { latitude, longitude, accuracy, altitude, speed, heading, … } }; watchPosition(opts, callback) β†’ the watch id (a string); clearWatch({ id }); checkPermissions() / requestPermissions() β†’ { location, coarseLocation } ('granted' | 'denied' | 'prompt' | …). Needs: GPS and/or Location in Step 4 (Access) - for HTML, ZIP and AI-built apps the build also turns them on by itself when your files call getCurrentPosition or watchPosition. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

Position once, asking first.

async function whereAmI() {
  var geo = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Geolocation;
  if (!geo) { showMessage('Open the app to use location.'); return; }
  try {
    var perm = await geo.checkPermissions();
    if (perm.location !== 'granted' && perm.coarseLocation !== 'granted') {
      perm = await geo.requestPermissions();
      if (perm.location !== 'granted' && perm.coarseLocation !== 'granted') {
        showMessage('Location is off for this app.');
        return;
      }
    }
    var pos = await geo.getCurrentPosition({ enableHighAccuracy: true, timeout: 15000 });
    showMessage(pos.coords.latitude.toFixed(5) + ', ' + pos.coords.longitude.toFixed(5));
  } catch (err) {
    showMessage('No location: ' + err.message);   // location off in settings, timeout, …
  }
}

Continuous updates. The callback gets (position, error).

var watchId = null;

async function startTracking() {
  var geo = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Geolocation;
  if (!geo) return;
  watchId = await geo.watchPosition({ enableHighAccuracy: true }, function (pos, err) {
    if (err || !pos) return;
    addPointToMap(pos.coords.latitude, pos.coords.longitude);
  });
}

async function stopTracking() {
  var geo = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Geolocation;
  if (geo && watchId) { await geo.clearWatch({ id: watchId }); watchId = null; }
}

In an AI-built app use the wrapper, which also works on the web:

import { Geolocation } from '@/lib/appmintNative';
const pos = await Geolocation.getCurrentPosition({ enableHighAccuracy: true });

Notes: Plain navigator.geolocation works in the app too. If the location switches were off at build time, the permission is refused with no prompt. On Android 12+ the user may allow only approximate location (coarseLocation granted). Location is personal data: declare it in your Play Data safety form.

Capacitor.Haptics Capacitor#

Capacitor.Plugins.Haptics

Example

The official Capacitor Haptics plugin (@capacitor/haptics), built into the app: light/medium/heavy taps, success/warning/error feedback, selection ticks and plain vibration.

Returns: every method returns a Promise that resolves with nothing. Needs: nothing to switch on - every app declares the vibration permission, whether or not Vibrate is ticked in Step 4 (Access). window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

function haptics() {
  return window.Capacitor && window.Capacitor.Plugins ? window.Capacitor.Plugins.Haptics : null;
}

async function tap(style) {            // 'LIGHT' | 'MEDIUM' | 'HEAVY'
  var h = haptics();
  if (!h) return;
  try { await h.impact({ style: style }); } catch (e) { console.log('haptics failed', e); }
}

async function result(type) {          // 'SUCCESS' | 'WARNING' | 'ERROR'
  var h = haptics();
  if (h) { try { await h.notification({ type: type }); } catch (e) {} }
}

document.getElementById('add').addEventListener('click', function () { tap('LIGHT'); });
document.getElementById('pay').addEventListener('click', function () { result('SUCCESS'); });

Selection ticks for a picker or slider, and a plain vibration:

var h = window.Capacitor && window.Capacitor.Plugins ? window.Capacitor.Plugins.Haptics : null;
var wheel = document.getElementById('wheel');

wheel.addEventListener('pointerdown', function () { if (h) h.selectionStart(); });
wheel.addEventListener('change', function () { if (h) h.selectionChanged(); });
wheel.addEventListener('pointerup', function () { if (h) h.selectionEnd(); });

document.getElementById('buzz').addEventListener('click', function () {
  if (h) h.vibrate({ duration: 300 });   // milliseconds
});

Buzz on an error and pair it with a visible message:

var h = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Haptics;

function rejectInput(field) {
  field.classList.add('invalid');
  if (h) h.notification({ type: 'ERROR' }).catch(function () {});
}

Notes: The strings are upper case ('HEAVY', not 'heavy'). In AI-built apps, import { Haptics } from '@/lib/appmintNative' gives the same names. For custom rhythms with strength per beat, use Capacitor.Plugins.AppwrightHaptics.

Capacitor.InAppReview Capacitor#

Capacitor.Plugins.InAppReview β€” requestReview()

Example

Asks Google Play to show its own "rate this app" sheet inside the app. Vendored @capacitor-community/in-app-review (template/plugins/in-app-review).

Returns: Promise void - it resolves when Play's flow ends, and Play never says whether the sheet was shown or what the user did. It rejects when Play could not start the flow. Needs: nothing to switch on; the app must be installed from Google Play for the sheet to appear. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

var Review = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.InAppReview;

async function afterThirdOrder() {
  if (!Review) return;
  try {
    await Review.requestReview();
  } catch (e) {
    console.log('review flow not available:', e.message);
  }
  // continue the app flow either way β€” you cannot know if the user rated
}

In an AI-built app:

import { requestReview } from '@/lib/appmintNative';

// after a success moment, not on launch
const asked = await requestReview();   // false where nothing could be asked (web)

Ask once, after a success moment, and remember that you asked:

var Review = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.InAppReview;

async function maybeAskForReview() {
  if (!Review || localStorage.getItem('review_asked')) return;
  localStorage.setItem('review_asked', '1');
  try { await Review.requestReview(); } catch (e) { console.log('review not shown:', e.message); }
}

Notes: Play limits how often the sheet appears and does not show it for every call - never tie a reward to it and never ask from a "Rate us" button expecting it to open (open the store listing for that). Ask after a success moment, not at startup. A sideloaded or debug install usually shows nothing.

Capacitor.KeepAwake Capacitor#

Capacitor.Plugins.KeepAwake β€” keepAwake(), allowSleep(), isKeptAwake(), isSupported()

Example

Stops the screen from dimming and locking while something must stay visible - a recipe, a route, a QR code, a timer. Vendored @capacitor-community/keep-awake (template/plugins/keep-awake).

Returns: keepAwake() / allowSleep() β†’ Promise void. isKeptAwake() β†’ { isKeptAwake: boolean }. isSupported() β†’ { isSupported: true }. Needs: nothing - every app hosts the plugin; no permission. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

var KA = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.KeepAwake;

async function startCooking() {
  if (KA) await KA.keepAwake();
  showSteps();
}

async function stopCooking() {
  if (KA) await KA.allowSleep();      // always release when the view closes
}

if (KA) KA.isKeptAwake().then(function (r) { console.log('awake?', r.isKeptAwake); });

In an AI-built app (the Screen Wake Lock API on the web):

import { keepAwake } from '@/lib/appmintNative';

useEffect(() => {
  void keepAwake(true);
  return () => { void keepAwake(false); };
}, []);

Keep the screen on only while a timer runs:

var KA = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.KeepAwake;

async function runTimer(seconds) {
  if (KA) await KA.keepAwake();
  await new Promise(function (done) { setTimeout(done, seconds * 1000); });
  if (KA) await KA.allowSleep();
  alert('Time is up');
}

Notes: The flag belongs to the app window: it only works while the app is in front, and it stays on until allowSleep() - release it on every exit from the screen. keepAwake() returns false when nothing could keep the screen on.

Capacitor.Keyboard Capacitor#

Capacitor.Plugins.Keyboard β€” show(), hide(), addListener('keyboardDidShow' | ...)

Example

Opens or closes the software keyboard and tells the page when it appears and how tall it is (@capacitor/keyboard 8.0.5).

Returns: show() / hide() β†’ Promise void. addListener(event, fn) β†’ a Promise of a handle with remove(), as in Capacitor’s own API - await it or chain .then(); the returned value also has remove() itself; show events pass { keyboardHeight } in CSS pixels. Events: keyboardWillShow, keyboardDidShow, keyboardWillHide, keyboardDidHide. Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

var Keyboard = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Keyboard;

if (Keyboard) {
  Keyboard.addListener('keyboardDidShow', function (info) {
    console.log('keyboard height', info.keyboardHeight);
    document.body.classList.add('typing');       // e.g. hide the bottom tab bar
  });
  Keyboard.addListener('keyboardDidHide', function () {
    document.body.classList.remove('typing');
  });
}

function onSend() {
  if (Keyboard) Keyboard.hide().catch(function () {});   // rejects if no field is focused
}

In an AI-built app use onKeyboard() for layout - it reports only the part of the keyboard the resized page does not already clear - and hideKeyboard() to close it:

import { onKeyboard, hideKeyboard, Keyboard } from '@/lib/appmintNative';

const off = onKeyboard((px) => { composer.style.paddingBottom = `${px}px`; });  // call off() on unmount
hideKeyboard();

const h = Keyboard.addListener('keyboardWillShow', ({ keyboardHeight }) => console.log(keyboardHeight));
h.remove();

Show the keyboard for a field after a button tap:

var Keyboard = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Keyboard;

searchBtn.addEventListener('click', function () {
  searchInput.focus();
  if (Keyboard) Keyboard.show().catch(function () {});
});

Notes: The shell already resizes the page for the keyboard, so do NOT add the full keyboardHeight as padding - a bottom bar is already above the keys. setAccessoryBarVisible, setStyle, setResizeMode and setScroll are not implemented on Android - do not call them. hide() rejects "Can't close keyboard, not currently focused" when nothing has focus.

Capacitor.LocalNotifications Capacitor#

Capacitor.Plugins.LocalNotifications β€” schedule, cancel, getPending, requestPermissions, events

Example

Notifications the app posts itself - now, or at a time - with the official @capacitor/local-notifications API (com.capacitorjs:local-notifications). Code written for the npm package works unchanged.

Returns: requestPermissions() / checkPermissions() β†’ { display: 'granted' | 'denied' | 'prompt' }; schedule({ notifications: [{ id, title, body, schedule?: { at | on | every }, extra?, actionTypeId?, channelId? }] }) β†’ { notifications: [{ id }] }; getPending() β†’ { notifications }; cancel({ notifications: [{ id }] }); registerActionTypes({ types }); createChannel / listChannels / deleteChannel; checkExactNotificationSetting() β†’ { exact_alarm }. Events: localNotificationReceived, localNotificationActionPerformed (actionId is 'tap' for a tap on the notification itself). Needs: tick Notifications in Step 4 (Access) - without it the plugin is not in the app and every call rejects "not implemented". For timed notifications also tick ⏰ Reminders (exact timing and surviving a reboot). HTML, ZIP and AI-Studio builds tick both by themselves when the code imports @capacitor/local-notifications and calls LocalNotifications.schedule(. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

const LN = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.LocalNotifications;

async function remindIn(minutes, text) {
  if (!LN) return false;
  const { display } = await LN.requestPermissions();
  if (display !== 'granted') return false;
  await LN.schedule({ notifications: [{
    id: Date.now() % 2147483647,
    title: 'Reminder',
    body: text,
    schedule: { at: new Date(Date.now() + minutes * 60000), allowWhileIdle: true },
    extra: { screen: 'tasks' },
  }] });
  return true;
}

Open the right screen when the user taps a notification - also when the tap started the app, since the event waits for the listener:

const LN = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.LocalNotifications;

if (LN) {
  LN.addListener('localNotificationActionPerformed', ({ actionId, notification }) => {
    if (actionId === 'tap' && notification.extra && notification.extra.screen) {
      location.hash = '#/' + notification.extra.screen;
    }
  });
}

A daily habit reminder with Done / Snooze buttons:

const LN = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.LocalNotifications;

async function dailyWaterReminder() {
  await LN.registerActionTypes({ types: [{ id: 'water', actions: [
    { id: 'done', title: 'Done' },
    { id: 'snooze', title: 'In 30 min' },
  ] }] });
  await LN.schedule({ notifications: [{
    id: 42, title: 'Drink water', body: 'Time for a glass of water.',
    actionTypeId: 'water', schedule: { on: { hour: 10, minute: 0 }, repeats: true },
  }] });
  LN.addListener('localNotificationActionPerformed', ({ actionId }) => {
    if (actionId === 'snooze') {
      LN.schedule({ notifications: [{ id: 43, title: 'Drink water', body: 'Snoozed reminder.',
        schedule: { at: new Date(Date.now() + 30 * 60000) } }] });
    }
  });
}

Notes: On Android 12+ a timed notification wants the exact-alarm grant: the first schedule() may open the system "Alarms & reminders" page; if the user leaves it off, the notification still fires, a little later (the result carries a warning). The plugin's notifications use their own "Default" channel, separate from the app's other notifications. A sound from the web files is not supported (Android plays the channel's default sound). Taps reach the page through the unlock screen when the app has a PIN. AI-built apps use notify() / LocalNotifications.schedule from @/lib/appmintNative, which posts through the app's own notification stack.

Capacitor.Network Capacitor#

Capacitor.Plugins.Network β€” getStatus(), addListener('networkStatusChange')

Example

Tells the page whether the phone is online and on which kind of connection, and when that changes (@capacitor/network 8.0.1).

Returns: getStatus() β†’ Promise { connected: boolean, connectionType: 'wifi' | 'cellular' | 'none' | 'unknown' }. The networkStatusChange event passes the same shape. Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

var Network = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Network;

function showBanner(s) {
  document.getElementById('offline').hidden = s.connected;
}

if (Network) {
  Network.getStatus().then(showBanner);
  Network.addListener('networkStatusChange', function (s) {
    showBanner(s);
    if (s.connected) retryQueuedWrites();
    if (s.connectionType === 'cellular') console.log('on mobile data');
  });
}

In an AI-built app:

import { Network, networkStatus, onNetworkChange } from '@/lib/appmintNative';

const { connected, connectionType } = await Network.getStatus();
const now = networkStatus();                        // sync: { connected, type }
const off = onNetworkChange((s) => setOnline(s.connected));   // call off() on unmount

Pause a large upload on mobile data:

var Network = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Network;

async function canUploadVideo() {
  if (!Network) return navigator.onLine;
  var s = await Network.getStatus();
  if (!s.connected) return false;
  return s.connectionType === 'wifi' || confirm('Upload over mobile data?');
}

Notes: connected means the phone has a network, not that your server answers - still handle failed requests. The wrapper's Network.getStatus() and networkStatus() read navigator.onLine / navigator.connection, not the plugin, so their type can be 'unknown' or a speed class like '4g'; onNetworkChange() does use the plugin's event in the app.

Capacitor.Preferences Capacitor#

Capacitor.Plugins.Preferences β€” get / set / remove / keys / clear

Example

A small native key-value store (Android SharedPreferences) for strings (@capacitor/preferences 8.0.1).

Returns: set({ key, value }), remove({ key }), clear() β†’ Promise void; get({ key }) β†’ { value } (null when the key is missing); keys() β†’ { keys: string[] }. Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

var Prefs = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Preferences;

async function saveTheme(name) {
  if (!Prefs) return false;          // not inside the app
  await Prefs.set({ key: 'theme', value: name });
  return true;
}

async function loadTheme() {
  if (!Prefs) return null;
  var r = await Prefs.get({ key: 'theme' });
  return r.value;            // null if never saved
}

Objects must be turned into a string first:

async function saveUser() {
  if (!Prefs) return;
  await Prefs.set({ key: 'user', value: JSON.stringify({ id: 7, name: 'Asha' }) });
  var u = JSON.parse((await Prefs.get({ key: 'user' })).value || 'null');
  var all = (await Prefs.keys()).keys;
  await Prefs.remove({ key: 'user' });
}

List and clear the saved keys from a settings screen:

async function resetSettings() {
  if (!Prefs) return;
  var k = (await Prefs.keys()).keys;
  console.log('clearing', k.length, 'saved values');
  await Prefs.clear();
}

Notes: @/lib/appmintNative has NO Preferences wrapper, on purpose: localStorage is already real and persistent in the app, and a second store would split one app's data across two places. AI-built apps should keep using localStorage (or the data runtime). Use Preferences in a hand-written page only if you want all of your settings there, and pick one store per value. configure({ group }) changes the store name (default CapacitorStorage).

Capacitor.PrivacyScreen Capacitor#

Capacitor.Plugins.PrivacyScreen β€” enable(), disable(), isEnabled()

Example

The official @capacitor/privacy-screen plugin: blocks screenshots and screen recording and hides the app's content in the recent-apps view. Baked so imported Capacitor projects that call it work.

Returns: enable({android}) β†’ {success}; disable() β†’ {success}; isEnabled() β†’ {enabled}. Needs: nothing to switch on.

const Privacy = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.PrivacyScreen;

async function openVault() {
  if (Privacy) await Privacy.enable({ android: { privacyModeOnActivityHidden: 'dim' } });
  showVault();
}
async function closeVault() {
  if (Privacy) await Privacy.disable();
}

Check the current state before toggling:

async function toggleProtection() {
  if (!Privacy) return;
  var s = await Privacy.isEnabled();
  if (s.enabled) await Privacy.disable();
  else await Privacy.enable();
}

Protect the screen only while a sensitive view is open:

async function showCardNumber() {
  if (Privacy) await Privacy.enable({ android: { privacyModeOnActivityHidden: 'splash' } });
  cardBox.hidden = false;
}
async function hideCardNumber() {
  cardBox.hidden = true;
  if (Privacy) await Privacy.disable();
}

Notes: privacyModeOnActivityHidden: 'none', 'dim' or 'splash'. Code written for the app itself should use WebToApk.setSecureScreen(true) / privacyScreen.enable() from @/lib/appmintNative, which follows the page (released when the app leaves it); mixing both, the last call wins.

Capacitor.PushNotifications Capacitor#

Capacitor.Plugins.PushNotifications β€” register, requestPermissions, registration, pushNotificationReceived

Example

Push notifications from YOUR OWN server through YOUR Firebase project, with the official @capacitor/push-notifications API (com.capacitorjs:push-notifications, registered by the shell's PushNotificationsPlugin.kt). Code written for the npm package works unchanged: register(), then send to the token from the registration event with the Firebase Admin SDK or the FCM HTTP v1 API.

Returns: requestPermissions() / checkPermissions() β†’ { receive: 'granted' | 'denied' | 'prompt' }; register() / unregister() β†’ void (the token arrives as the registration event { value }, a failure as registrationError { error }); getDeliveredNotifications() β†’ { notifications }; removeDeliveredNotifications({ notifications }), removeAllDeliveredNotifications(); createChannel({ id, name, importance? }), listChannels(), deleteChannel({ id }). Events: pushNotificationReceived (a message while the app is open: { id, title?, body?, data }) and pushNotificationActionPerformed ({ actionId: 'tap', notification: { id, data } }). Needs: build with AppMint Push switched on and your google-services.json uploaded (Notifications step) - that is the Firebase project the token belongs to. Without it register() rejects with code PUSH_NOT_CONFIGURED ("Push is not set up in this app"). HTML, ZIP and AI-Studio builds tick Notifications by themselves when the code calls PushNotifications.register( / requestPermissions( / addListener(. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

const Push = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.PushNotifications;

async function enablePush(userId) {
  if (!Push) return;
  await Push.addListener('registration', ({ value }) => {
    // Your server stores the token and sends to it later.
    fetch('https://api.example.com/push-tokens', {
      method: 'POST', headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ userId, token: value, platform: 'android' }),
    });
  });
  await Push.addListener('registrationError', ({ error }) => console.warn('push:', error));
  const { receive } = await Push.requestPermissions();
  if (receive !== 'granted') return;
  try { await Push.register(); }
  catch (e) { if (e.code === 'PUSH_NOT_CONFIGURED') alert('Push is not set up in this build.'); }
}

React to a message while the app is open, and to a tap on one that arrived while it was closed (the tap event waits for your listener, so a cold start is not lost):

const Push = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.PushNotifications;

if (Push) {
  Push.addListener('pushNotificationReceived', (msg) => {
    alert((msg.title || 'New message') + (msg.body ? ': ' + msg.body : ''));
  });
  Push.addListener('pushNotificationActionPerformed', ({ notification }) => {
    const orderId = notification.data && notification.data.orderId;
    if (orderId) location.hash = '#/orders/' + orderId;
  });
}

A channel for important alerts (Android 8+), used by messages that name it in android.notification.channel_id:

const Push = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.PushNotifications;

async function setupChannels() {
  if (!Push) return;
  await Push.createChannel({ id: 'orders', name: 'Order updates', description: 'Your order status', importance: 4, vibration: true });
}

Send to ONE user, also when the app is closed: save the token under the signed-in user, and have your own server send a data message with AppMint's keys to that user's tokens only (the integration guide's "Send From Your Own Server" steps have the Cloud Function):

const Push = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.PushNotifications;

async function savePushToken(db, role, uid) {
  if (!Push) return;
  const installId = (window.WebToApk && window.WebToApk.getInstallId && window.WebToApk.getInstallId()) || 'web';
  const ref = db.ref('pushTokens/' + role + '/' + uid + '/' + installId);
  await Push.addListener('registration', ({ value }) => ref.set(value));
  const { receive } = await Push.requestPermissions();
  if (receive === 'granted') await Push.register();
  return () => ref.remove();   // call on sign-out
}

// Your server (FCM HTTP v1), to each of that user's tokens:
// { "message": { "token": "<token>", "android": { "priority": "high" },
//   "data": { "amp_type": "show", "amp_id": "order-42", "amp_title": "New order", "amp_body": "2 items", "amp_link": "#/orders/42" } } }

Notes: A message sent with a notification block is shown by Android itself while the app is in the background; its tap opens the app (through the unlock screen when the app has a PIN) and fires pushNotificationActionPerformed. While the app is open, messages arrive as pushNotificationReceived and are not drawn in the tray. A data-only message that carries AppMint's amp_* keys (at least amp_id and amp_title) is drawn by the app itself - with the app's own notification sound, in the in-app inbox (appmint:push), opening amp_link when tapped - also when the app is completely closed; send it with android.priority: "high". It is not counted in the send center's statistics. Other data-only messages reach the page only while it is open, through this plugin. AppMint Push send-center messages keep working side by side - they never reach this plugin. In an app built with OneSignal instead of AppMint Push, messages are delivered by OneSignal and never reach this plugin. AI-built apps receive creator pushes through pushInbox() from @/lib/appmintNative.

Capacitor.ScreenBrightness Capacitor#

Capacitor.Plugins.ScreenBrightness β€” getBrightness(), setBrightness({ brightness })

Example

Sets the app window's screen brightness - full brightness for a boarding pass or QR code, then back. Vendored @capacitor-community/screen-brightness (template/plugins/screen-brightness).

Returns: getBrightness() β†’ Promise { brightness } (0-1, or -1 when the app has not overridden it and the screen follows the system). setBrightness({ brightness }) β†’ Promise void. Needs: nothing - every app hosts the plugin; no permission (it changes only this app's window, not the system setting). window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

var SB = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.ScreenBrightness;

async function showTicket() {
  if (SB) await SB.setBrightness({ brightness: 1 });     // full brightness
  document.getElementById('qr').hidden = false;
}

async function hideTicket() {
  if (SB) await SB.setBrightness({ brightness: -1 });    // -1 = follow the system again
  document.getElementById('qr').hidden = true;
}

In an AI-built app:

import { brightness } from '@/lib/appmintNative';

await brightness.set(1);        // clamped to 0–1; false where not available (web)
// ... on leaving the screen
await brightness.restore();
const level = await brightness.get();   // -1 = following the system / not available

Read the current level first and restore exactly that:

var SB = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.ScreenBrightness;
var before = -1;

async function boost() {
  if (!SB) return;
  before = (await SB.getBrightness()).brightness;
  await SB.setBrightness({ brightness: 1 });
}
async function undo() {
  if (SB) await SB.setBrightness({ brightness: before });
}

Notes: Always pass a number - setBrightness({}) has no value to apply. Restore with -1 when the view closes; the override also ends when the app closes. A web page cannot drive the backlight, so can('brightness') is 'none' in a browser.

Capacitor.ScreenOrientation Capacitor#

Capacitor.Plugins.ScreenOrientation β€” orientation(), lock({ orientation }), unlock()

Example

Reads the screen orientation, pins it for one view (a game, a chart, a signature pad) and releases it (@capacitor/screen-orientation 8.0.1).

Returns: orientation() β†’ Promise { type } (e.g. 'portrait-primary'). lock() / unlock() β†’ Promise void. Event screenOrientationChange passes { type }. Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

orientation values: 'portrait', 'landscape', 'portrait-primary', 'portrait-secondary', 'landscape-primary', 'landscape-secondary', 'natural', 'any'.

var SO = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.ScreenOrientation;

async function openChart() {
  if (SO) await SO.lock({ orientation: 'landscape' });
  showChart();
}

async function closeChart() {
  if (SO) await SO.unlock();          // always release when leaving the view
  hideChart();
}

if (SO) {
  SO.orientation().then(function (o) { console.log('now', o.type); });
  SO.addListener('screenOrientationChange', function (o) { console.log('rotated to', o.type); });
}

In an AI-built app:

import { lockOrientation, ScreenOrientation } from '@/lib/appmintNative';

const ok = await lockOrientation('landscape');   // false when refused (always on desktop)
await lockOrientation('any');                    // release

await ScreenOrientation.lock({ orientation: 'portrait' });
await ScreenOrientation.unlock();

Handle a refused lock and tell the user:

var SO = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.ScreenOrientation;
var Toast = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Toast;

async function tryLandscape() {
  if (!SO) { document.getElementById('rotate-hint').hidden = false; return; }   // ask the user to rotate by hand
  try {
    await SO.lock({ orientation: 'landscape' });
  } catch (e) {
    if (Toast) Toast.show({ text: 'Could not rotate the screen', duration: 'short' });
  }
}

Notes: The wrappers only know portrait / landscape / any: ScreenOrientation.lock({ orientation: 'landscape-secondary' }) locks to plain landscape. Give the user a way out of a locked view.

Capacitor.ScreenReader Capacitor#

Capacitor.Plugins.ScreenReader β€” isEnabled(), speak({ value, language }), addListener('stateChange')

Example

Tells the page whether TalkBack is on, and speaks an announcement (@capacitor/screen-reader 8.0.1).

Returns: isEnabled() β†’ Promise { value: boolean }. speak() β†’ Promise void. The stateChange event passes { value: boolean } when TalkBack is switched on or off. Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

var SR = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.ScreenReader;

function setCalmMode(on) {
  document.documentElement.classList.toggle('no-motion', on);   // shorten animations
}

if (SR) {
  SR.isEnabled().then(function (r) { setCalmMode(r.value); });
  SR.addListener('stateChange', function (s) { setCalmMode(s.value); });
}

async function announceSaved() {
  if (SR) await SR.speak({ value: 'Order placed', language: 'en' });
}

In an AI-built app (speak uses text-to-speech on the web; isEnabled() is false there):

import { screenReader } from '@/lib/appmintNative';

const on = await screenReader.isEnabled();
const off = screenReader.onChange((enabled) => setCalmMode(enabled));
await screenReader.speak('Order placed', 'en');

Feature-detect and keep the page usable without the plugin:

var SR = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.ScreenReader;

async function say(message) {
  if (SR) { await SR.speak({ value: message }); return; }
  document.getElementById('live-region').textContent = message;   // an aria-live="polite" element
}

Notes: language defaults to 'en'. Prefer normal accessible markup (labels, aria-live regions) for most announcements; use speak for short, important state changes. There is no web API to detect a screen reader, so can('screenReader') is 'none' in a browser.

Capacitor.Share Capacitor#

Capacitor.Plugins.Share β€” share(options), canShare()

Example

Opens the Android share sheet with a title, text and/or a link (@capacitor/share 8.0.1).

Returns: share() β†’ Promise { activityType } (the chosen app's component name, may be ''); it REJECTS when the user closes the sheet. canShare() β†’ Promise { value: boolean }. Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

async function shareLink() {
  var Share = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Share;
  if (!Share) { alert('Sharing is not available here'); return; }
  // Only pass the fields you have. A present but empty `url` is rejected ("Unsupported url").
  var opts = { title: 'My list', text: 'Milk, eggs, bread' };
  var link = 'https://example.com/list/42';
  if (link) opts.url = link;
  try {
    var r = await Share.share(opts);
    console.log('shared to', r.activityType);
  } catch (e) {
    console.log('share cancelled or failed:', e.message);   // "Share canceled" on dismiss
  }
}

In an AI-built app the wrapper has the same shape and also rejects on cancel:

import { Share, can } from '@/lib/appmintNative';

if (can('share') !== 'none') {
  try {
    await Share.share({ title: 'My list', text: 'Milk, eggs, bread', url: 'https://example.com/list/42' });
  } catch {
    // the user closed the sheet β€” not an error to show
  }
}

Check canShare() first and hide the button when sharing is not available:

var Share = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Share;
var btn = document.getElementById('share');

btn.hidden = true;
if (Share) {
  Share.canShare().then(function (r) { btn.hidden = !r.value; });
}

Notes: url must be http(s):// or file://. dialogTitle sets the chooser heading. Sharing files needs file:// paths the app owns; from a page, share files with navigator.share({ files }) instead. Only one share can be open at a time ("Can't share while sharing is in progress").

Capacitor.SpeechRecognition Capacitor#

Capacitor.Plugins.SpeechRecognition

Example

Turns the user's speech into text with Android's speech recognizer (the community @capacitor-community/speech-recognition plugin, built into the app). Android WebView has no Web Speech recognition of its own, so this is the way to do dictation.

Returns: Promises. available() β†’ { available }. checkPermissions() / requestPermissions() β†’ { speechRecognition: 'granted' | 'denied' | 'prompt' }. start({ partialResults: false }) β†’ { status: 'success', matches: [...] } after the user stops talking; it rejects on errors such as no speech or no match. Needs: tick Mic in Step 4 (Access) when you build (for HTML, ZIP and AI-built apps the build also adds the microphone permission by itself when your files mention SpeechRecognition), and the user must allow the microphone. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

One-shot dictation (the same way @/lib/appmintNative speech uses it):

var SR = window.Capacitor && window.Capacitor.Plugins ? window.Capacitor.Plugins.SpeechRecognition : null;

async function dictate() {
  var out = document.getElementById('text');
  if (!SR || !(await SR.available()).available) { out.textContent = 'Voice input is not available on this phone.'; return; }

  var p = (await SR.checkPermissions()).speechRecognition;
  if (p !== 'granted') p = (await SR.requestPermissions()).speechRecognition;
  if (p !== 'granted') { out.textContent = 'Microphone permission was not given.'; return; }

  try {
    var r = await SR.start({ language: 'en-US', maxResults: 3, partialResults: false, popup: false });
    out.textContent = r.matches && r.matches.length ? r.matches[0] : '';
  } catch (e) {
    out.textContent = 'Did not catch that. Try again.';
  }
}

document.getElementById('mic').addEventListener('click', dictate);

Live text while the user speaks (partialResults: true), and the listening state:

// Add the listeners ONCE, when the page starts.
if (SR) {
  SR.addListener('partialResults', function (d) {
    // Words as they come, and then the final text, all arrive here.
    if (d.matches && d.matches.length) document.getElementById('text').textContent = d.matches[0];
  });
  SR.addListener('listeningState', function (d) {
    // 'started' = the user began speaking, 'stopped' = the user stopped speaking
    document.body.classList.toggle('listening', d.status === 'started');
  });
}

document.getElementById('mic-live').addEventListener('click', async function () {
  if (!SR) return;
  try {
    await SR.start({ language: 'en-US', partialResults: true, popup: false });   // resolves at once, no matches
  } catch (e) {
    document.getElementById('text').textContent = 'Could not start: ' + e.message;
  }
});

document.getElementById('stop').addEventListener('click', function () { if (SR) SR.stop(); });

Show the mic button only when dictation can work, and stop listening when the page is hidden:

var SR = window.Capacitor && window.Capacitor.Plugins ? window.Capacitor.Plugins.SpeechRecognition : null;
var mic = document.getElementById('mic');

mic.hidden = true;
if (SR) {
  SR.available().then(function (r) { mic.hidden = !r.available; });
}

document.addEventListener('visibilitychange', function () {
  if (document.hidden && SR) SR.stop();
});

Notes:

  • In live mode (partialResults: true), the final text arrives after the 'stopped' state, so do not remove the partialResults listener on 'stopped'. An error after the start (no speech, no match) is not reported in this mode.
  • language is a tag like 'en-US' or 'ta-IN' (default: the phone's language). popup: true shows Google's own listening dialog instead. getSupportedLanguages() β†’ { languages } and isListening() β†’ { listening } also exist.
  • start() rejects with 'Missing permission' if you did not ask first. Listening stops by itself after a pause in speech.

Capacitor.SplashScreen Capacitor#

Capacitor.Plugins.SplashScreen β€” show(), hide()

Example

The @capacitor/splash-screen API over the app's own branded launch splash, so imported Capacitor / AI Studio projects that call SplashScreen.hide() once their first screen is ready work in the Android app.

Returns: hide() β†’ Promise (the splash goes once the creator's minimum splash time has passed); show({showDuration, autoHide}) β†’ Promise (the splash comes back over the page and, with autoHide - the default - goes after showDuration ms, default 3000). Needs: nothing to switch on.

const Splash = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.SplashScreen;

async function boot() {
  await loadFirstScreen();
  if (Splash) await Splash.hide();     // otherwise the shell hides it at first paint
}
boot();

Cover a slow account switch with the splash, kept up until the new data is on screen:

const Splash = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.SplashScreen;

async function switchAccount(accountId) {
  if (Splash) await Splash.show({ autoHide: false });   // stays until hide()
  try {
    await loadAccount(accountId);
    renderHome();
  } finally {
    if (Splash) await Splash.hide();
  }
}

Show the branded splash for a moment after signing out; it goes by itself after showDuration:

const Splash = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.SplashScreen;

async function signOut() {
  await clearSession();
  showSignInScreen();
  if (Splash) {
    try {
      await Splash.show({ showDuration: 1200 });
    } catch (e) {
      console.warn('splash not shown:', e.code);
    }
  }
}

Notes: without a hide() call the splash still goes at the first paint, like every app. AI-built apps use splash.hold() / splash.ready() from @/lib/appmintNative.

Capacitor.StatusBar Capacitor#

Capacitor.Plugins.StatusBar β€” setStyle, setBackgroundColor, show, hide, getInfo

Example

Changes the status bar's icon style, colour and visibility (@capacitor/status-bar 8.0.2).

Returns: every method β†’ Promise void, except getInfo() β†’ { visible, style, color, overlays, height }. Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

style names the BACKGROUND it suits: 'DARK' = light icons (for a dark bar), 'LIGHT' = dark icons, 'DEFAULT' = follow the system.

var StatusBar = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.StatusBar;
if (StatusBar) {
  StatusBar.setStyle({ style: 'DARK' });          // light icons
  StatusBar.hide();                               // e.g. while a video plays
  // later
  StatusBar.show();
}

Colour the bar with the shell bridge, not the plugin. On Android 15+ the plugin's setBackgroundColor does nothing (edge-to-edge ignores it). The shell paints the strip itself and takes the icon style in the same call:

if (window.WebToApk && window.WebToApk.setStatusBarColor) {
  window.WebToApk.setStatusBarColor('#0B0F1A', 'light');   // dark bar, light icons
} else if (StatusBar) {
  StatusBar.setBackgroundColor({ color: '#0B0F1A' });      // hex only, e.g. #RRGGBB
}

In an AI-built app use setStatusBar(color, contentStyle); it prefers the shell bridge and sets colour and icons in one call:

import { setStatusBar, StatusBar } from '@/lib/appmintNative';

setStatusBar('#0B0F1A', 'light');                       // dark bar, light icons
await StatusBar.setBackgroundColor({ color: '#FFFFFF' }); // colour only; icons picked from luminance

Notes: setBackgroundColor rejects a colour that is not a hex string. In fullscreen / kiosk builds the bars are hidden by the shell. In the Android app the wrapper's StatusBar.setStyle() alone changes nothing (the bridge needs a colour to paint) - pass the style to setStatusBar() together with a colour.

Capacitor.TextToSpeech Capacitor#

Capacitor.Plugins.TextToSpeech β€” speak(), stop(), getSupportedLanguages(), getSupportedVoices()

Example

The community @capacitor-community/text-to-speech plugin over the phone's text-to-speech engine. Baked so imported Capacitor projects that call it work.

Returns: speak({text, lang, rate, pitch, volume, voice}) β†’ Promise that resolves when the sentence has been spoken; stop(); getSupportedLanguages() β†’ {languages}; getSupportedVoices() β†’ {voices}; isLanguageSupported({lang}) β†’ {supported}. Needs: nothing to switch on.

const Tts = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.TextToSpeech;

async function readAloud(text) {
  if (!Tts) { speechSynthesis.speak(new SpeechSynthesisUtterance(text)); return; }
  await Tts.speak({ text: text, lang: 'en-US', rate: 1.0, pitch: 1.0, volume: 1.0 });
}
document.getElementById('stop').onclick = () => Tts && Tts.stop();

Check the language before speaking and show a message when it is not installed:

const Tts = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.TextToSpeech;

async function speakIn(lang, text) {
  if (!Tts) return false;
  const { supported } = await Tts.isLanguageSupported({ lang: lang });
  if (!supported) { showMessage('This phone has no ' + lang + ' voice installed.'); return false; }
  await Tts.speak({ text: text, lang: lang });
  return true;
}

Read a paragraph aloud with a stop button, and handle a speak error:

const Tts = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.TextToSpeech;

async function readParagraph(el, btn) {
  if (!Tts) return;
  btn.textContent = 'Stop';
  btn.onclick = () => Tts.stop();
  try {
    await Tts.speak({ text: el.textContent, lang: 'en-US', rate: 0.9 });   // resolves when finished
  } catch (e) {
    console.warn('could not speak:', e.message);
  } finally {
    btn.textContent = 'Listen';
    btn.onclick = () => readParagraph(el, btn);
  }
}

Notes: window.speechSynthesis works in the app too (the shell's own engine) and is what AI-built apps use through speak(); this plugin is the Capacitor-shaped path.

Capacitor.TextZoom Capacitor#

Capacitor.Plugins.TextZoom β€” get(), getPreferred(), set({ value })

Example

Reads and changes the text size of the app's page, for an in-app "text size" control (@capacitor/text-zoom 8.0.1).

Returns: get() β†’ Promise { value } (current zoom, 1 = 100%). getPreferred() β†’ { value } (the size the user picked in Android settings). set({ value }) β†’ Promise void. Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

var TZ = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.TextZoom;

async function biggerText() {
  if (!TZ) return;
  var now = (await TZ.get()).value;
  await TZ.set({ value: Math.min(2, now + 0.1) });
}

async function resetText() {
  if (!TZ) return;
  var pref = (await TZ.getPreferred()).value;   // e.g. 1.15 when the phone uses large text
  await TZ.set({ value: pref });
}

In an AI-built app (on the web it scales the root font size instead):

import { textZoom } from '@/lib/appmintNative';

const size = await textZoom.get();
await textZoom.set(size + 0.1);                     // clamped to 0.5–3
await textZoom.set(await textZoom.getPreferred());  // back to the system size

A text size slider that remembers the choice:

var TZ = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.TextZoom;
var slider = document.getElementById('text-size');   // <input type="range" min="0.8" max="1.6" step="0.1">

if (TZ) {
  var saved = parseFloat(localStorage.getItem('textZoom'));
  if (saved) TZ.set({ value: saved });
  slider.addEventListener('change', function () {
    TZ.set({ value: parseFloat(slider.value) });
    localStorage.setItem('textZoom', slider.value);
  });
} else {
  slider.hidden = true;
}

Notes: The app already follows the system text size; this is for an extra in-app control. Test your layout at large sizes - fixed heights on text controls will clip.

Capacitor.Toast Capacitor#

Capacitor.Plugins.Toast β€” show({ text, duration, position })

Example

Shows a short Android system toast such as "Saved" or "Copied" (@capacitor/toast 8.0.1).

Returns: Promise void. Needs: nothing to switch on - every app hosts the plugin. window.Capacitor exists on pages the app serves itself (HTML, ZIP and AI-built apps) and, in a Website mode app, on your own site (its domain and sub-domains) - never on other sites the app opens. Feature-detect.

var Toast = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Toast;

function saved() {
  if (Toast) Toast.show({ text: 'Saved', duration: 'short', position: 'bottom' });
}

duration is 'short' (about 2 s) or 'long' (about 3.5 s). position is 'top', 'center' or 'bottom'.

In an AI-built app use toast() or the Capacitor-shaped Toast; on the web they draw a snackbar in the app's colours:

import { Toast, toast } from '@/lib/appmintNative';

await Toast.show({ text: 'Added to list', duration: 'long' });
toast('Copied');   // short form, same result

Feature-detect and show an in-page message when the plugin is missing:

function notify(message) {
  var Toast = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Toast;
  if (Toast) { Toast.show({ text: message, duration: 'short' }); return; }
  var el = document.getElementById('snackbar');   // a plain in-page message
  el.textContent = message;
  el.hidden = false;
  setTimeout(function () { el.hidden = true; }, 2000);
}

Notes: text is required ("Must provide text"). The system draws the toast in the phone maker's style, and newer Android versions ignore position for text toasts. A toast is easy to miss - never use it for an error the user must act on; use Dialog.alert for that.

Capacitor.Torch Capacitor#

Capacitor.Plugins.Torch β€” enable(), disable(), toggle(), isEnabled(), isAvailable()

Example

The phone's flashlight, with the @capawesome/capacitor-torch API. The app shell's own plugin (TorchPlugin.kt).

Returns: enable() / disable() / toggle() β†’ Promise (rejects CAMERA_IN_USE while the app's own camera stream is open, UNAVAILABLE without a flash); isEnabled() β†’ {enabled}; isAvailable() β†’ {available}. Needs: nothing to switch on and no permission.

const Torch = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Torch;

async function setupLightButton() {
  if (!Torch || !(await Torch.isAvailable()).available) { lightBtn.hidden = true; return; }
  lightBtn.onclick = async () => {
    try {
      await Torch.toggle();
      lightBtn.classList.toggle('on', (await Torch.isEnabled()).enabled);
    } catch (e) {
      alert(e.message);
    }
  };
}

Flash an SOS signal from a button, and switch the light off if the camera takes the flash midway:

const Torch = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Torch;
const wait = (ms) => new Promise((r) => setTimeout(r, ms));

sosBtn.onclick = async () => {
  if (!Torch || !(await Torch.isAvailable()).available) return;
  const flashes = [200, 200, 200, 600, 600, 600, 200, 200, 200];   // S O S, in ms
  try {
    for (const ms of flashes) {
      await Torch.enable();  await wait(ms);
      await Torch.disable(); await wait(200);
    }
  } catch (e) {
    await Torch.disable().catch(() => {});
    alert(e.code === 'CAMERA_IN_USE' ? 'Close the camera first.' : e.message);
  }
};

Keep a light button in step with the system, since the user can switch the flashlight from the quick-settings tile while the app is in the background:

const Torch = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Torch;

async function syncLightButton() {
  if (!Torch) return;
  const { enabled } = await Torch.isEnabled();
  lightBtn.classList.toggle('on', enabled);
  lightBtn.setAttribute('aria-pressed', String(enabled));
}

document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'visible') syncLightButton();
});
syncLightButton();

Notes: the light follows the system state (turning it off from the quick-settings tile reads back as off) and is switched off when the app closes. While a getUserMedia stream is open, light it through the track: track.applyConstraints({ advanced: [{ torch: true }] }). AI-built apps use torch from @/lib/appmintNative.

Generated from the app runtime and its example files on every docs build. Read it as Markdown Β· All families.

Checked against the shipped bridge on 2026-11-04.