TinyMCE MultiCloud Plugin - v0.1.1
    Preparing search index...

    TinyMCE MultiCloud Plugin - v0.1.1

    TinyMCE MultiCloud Plugin

    npm npm downloads License: MIT Demo API Docs TypeScript

    A TinyMCE plugin that lets users browse/pick files from multiple cloud providers and insert them into editor content as links, images, or embeds.

    Built-in provider adapters:

    • Google Drive
    • OneDrive
    • Dropbox
    • BayernCloud

    No open-source TinyMCE plugin existed that lets users pick files directly from their own Google Drive, OneDrive, or Dropbox accounts without routing files through a paid third-party service. The commercial alternatives (Filestack, Uploadcare) work by copying the selected file to their own CDN — which adds per-month costs, creates a vendor dependency, and moves file ownership away from the user.

    This plugin was built to fill that gap: files stay in the user's own cloud account, the plugin just brokers the picker and returns a URL. No CDN, no per-upload quota, no vendor lock-in.

    TinyMCE MultiCloud Plugin Filestack Uploadcare
    License / cost MIT, free $69–$379+/month $0–$119+/month
    File storage User's own cloud account Filestack CDN (vendor) Uploadcare CDN (vendor)
    Cloud sources Google Drive, OneDrive, Dropbox, Nextcloud/BayernCloud Drive, Dropbox, OneDrive, Box, + more Drive, Dropbox, OneDrive, + more
    Embed support ✅ link / image / iframe / audio+video ✅ via CDN URL ✅ via CDN URL
    Files stay in user's account ✅ Yes ❌ Copied to vendor CDN ❌ Copied to vendor CDN
    Bandwidth quota None (provider handles delivery) 75–400 GB/month (plan limit) Varies by plan
    Self-hostable ✅ Fully (drop a JS file) ❌ SaaS only ❌ SaaS only
    Open source ✅ MIT

    The trade-off: because files remain in the user's cloud storage, they must be publicly accessible for embedded content to be viewable by readers. The plugin handles this automatically — it creates the public share link via the provider's API so the user does not need to configure sharing manually. A warning is shown in the upload dialog to make clear that the file will be publicly accessible to anyone with the link.


    Different cloud providers have different OAuth flows, SDKs, and picker UX constraints. The plugin uses a provider adapter contract and popup bridge protocol, so each provider can implement its own picker while TinyMCE integration stays consistent.

    This plugin uses XDBC for Design-by-Contract (DBC) validation of all plugin options and provider configurations. Instead of scattered if/throw guards, every precondition is expressed as a typed contract that fires before any plugin logic runs.

    • Plugin options object — must be a plain object (not null, not array)
    • providers map — must be a plain object if provided
    • defaultProvider — must be a string and must exist in the providers map
    • defaultInsertMode — must be "link", "image", or "embed" if provided
    • popupTimeoutMs — must be a positive number if provided
    • dialogTitle / defaultProvider — must be non-empty strings if provided
    • Per-provider SDK credentials — validated when SDK mode is active (i.e. enabled: true and no pickerUrl override):
      • Google Drive: clientId and apiKey must be defined, string, and match the API-key pattern
      • OneDrive: clientId must be defined, string, and match the API-key pattern
      • Dropbox: appKey must be defined, string, and match the API-key pattern
      • BayernCloud: baseUrl (valid URL), username, and either password or bearerToken (at least one non-empty)
    • Fail-fast with actionable messages — errors are surfaced at plugin init with a clear hint (e.g. "Did you set Google Drive clientId?"), not as opaque SDK failures deep in an OAuth flow.
    • Contracts as documentation — the decorator stack on each validator class is a machine-readable, always-up-to-date specification of what the configuration must look like.
    • Uniform error shape — all validation failures throw DBC.Infringement (subclass of Error), making them easy to catch and distinguish from runtime errors.
    • Configurable behaviour — two independent named DBC instances allow each layer to be configured separately (see Soft logging mode below):
      • globalThis.MultiCloud.Validation.Config — governs configuration contract checks
      • globalThis.MultiCloud.Validation.Boundary — governs Zod boundary schema checks on provider API data
    • Single error type — all validation failures, whether from DBC contracts or Zod schema checks, throw DBC.Infringement (XDBC's ZOD.tsCheck routes through DBC.reportTsCheckInfringement internally).

    In addition to XDBC DBC contracts on configuration, the plugin validates all data that crosses provider API boundaries at runtime using XDBC's Zod integration (ZOD.tsCheck from xdbc/src/DBC/ZOD). Zod schemas are defined with the zod library but validation is always run through XDBC — keeping the error shape and behaviour consistent with the rest of the contract layer.

    Where DBC validates input configuration before any logic runs, XDBC's Zod implementation validates what providers return before that data is trusted and used.

    Boundary Schema Validates
    Any provider result pickerResultSchema item.id, item.name, item.url non-empty; all URL fields are valid URLs; mode is a known enum value
    Google Picker callback googleDocSchema id required and non-empty; url, thumbnailLink are valid URLs when present
    OneDrive navigable picker oneDriveFileSchema name required; webUrl, @microsoft.graph.downloadUrl are valid URLs when present; file.mimeType is a string when present
    Dropbox Chooser callback dropboxFileSchema link required and a valid URL; thumbnailLink is a valid URL when present
    BayernCloud WebDAV node webDavNodeSchema id, name, url, webdavPath non-empty; url is a valid URL; isDirectory is a boolean

    Because validation runs through ZOD.tsCheck which calls DBC.reportTsCheckInfringement on failure, both DBC contract violations and Zod schema failures throw DBC.Infringement. The boundary layer uses its own DBC instance (globalThis.MultiCloud.Validation.Boundary) so it can be configured independently from config-layer checks.

    import { DBC } from "xdbc";

    try {
    tinymce.init({ plugins: "multicloud", multicloud_providers: myConfig });
    } catch (e) {
    if (e instanceof DBC.Infringement) {
    // either a configuration contract or a provider boundary schema was violated
    }
    }
    npm install
    npm run build
    TinyMCE version Status
    6.x ✅ Tested and supported
    7.x ✅ Tested and supported
    npm install
    npm run dev # watches src/ and rebuilds dist/ on change

    For local development with real cloud provider SDKs:

    1. Copy demo/multicloud.config.example.js to demo/multicloud.config.js.
    2. Fill in your real credentials (this file is gitignored and will never be committed).
    3. Open demo/tinymce-demo.html in a browser (via a local HTTP server, not file://).
    1. Copy demo/multicloud.config.example.js to demo/multicloud.config.js.
    2. Fill in your real cloud app IDs/keys and BayernCloud endpoint values.
    3. Follow the provider console checklists in docs/PRODUCTION_SETUP.md.

    Note: demo/multicloud.config.js is gitignored — never commit real credentials to the repository.

    <script src="https://cdn.tiny.cloud/1/no-api-key/tinymce/7/tinymce.min.js"></script>
    <script src="./dist/index.global.js"></script>

    <textarea id="editor"></textarea>
    <script>
    tinymce.init({
    selector: "#editor",
    plugins: "link image media multicloud",
    toolbar: "undo redo | bold italic | link image media | multicloud multicloud_upload",

    multicloud_providers: {
    googleDrive: {
    enabled: true,
    clientId: "GOOGLE_OAUTH_CLIENT_ID",
    apiKey: "GOOGLE_BROWSER_API_KEY",
    scopes: ["https://www.googleapis.com/auth/drive.file"]
    },
    oneDrive: {
    enabled: true,
    clientId: "ONEDRIVE_CLIENT_ID",
    action: "query"
    },
    dropbox: {
    enabled: true,
    appKey: "DROPBOX_APP_KEY",
    linkType: "preview"
    },
    bayerncloud: {
    enabled: true,
    // Option 1: Use interactive picker (prompts user for credentials)
    pickerUrl: "./pickers/bayerncloud.html"

    // Option 2: Use WebDAV with pre-configured credentials
    // mode: "nextcloud-webdav",
    // baseUrl: "https://your-nextcloud.example.com",
    // username: "your-username",
    // password: "app-password", // Use app-specific password
    // webdavPath: "", // Optional subfolder
    // createPublicShare: true // Creates public share links
    }
    },

    multicloud_default_provider: "googleDrive",
    multicloud_default_insert_mode: "link",
    multicloud_dialog_title: "Insert From Cloud",
    multicloud_popup_timeout_ms: 120000
    });
    </script>

    The plugin provides two toolbar buttons:

    • multicloud: Opens a file picker to browse and select files from cloud providers
    • multicloud_upload: Opens a dialog to upload local files to cloud providers

    Some providers support uploading local files directly to the cloud:

    Google Drive: ✅ Full upload support

    • Uploads files to user's Drive
    • Creates public sharing link
    • Embeds PDFs and Office documents

    Nextcloud/BayernCloud: ✅ Full upload support (both modes)

    • Picker mode (pickerUrl): Opens upload UI in picker, uses OAuth authentication
    • WebDAV mode: Uses pre-configured credentials for direct upload
    • Uploads via WebDAV PUT request
    • Creates public share links (if createPublicShare: true)
    • Embeds PDFs and Office documents
    • Requires CORS proxy for browser deployments (see docs)

    OneDrive: ✅ Full upload support

    • Uploads files to the user's OneDrive via the Microsoft Graph API
    • Creates a public sharing link
    • Embeds PDFs and Office documents

    Dropbox: ✅ Full upload support

    • Uploads files to the user's Dropbox via the Dropbox API
    • Uses OAuth implicit grant flow; token is cached in localStorage with expiry tracking
    • Creates a shared link for embedding

    To use upload, add multicloud_upload to your toolbar:

    toolbar: "undo redo | bold italic | link image media | multicloud multicloud_upload"
    

    Each provider picker page should call window.opener.postMessage with this payload:

    {
    source: "tinymce-multicloud-plugin",
    type: "picked", // or "cancelled"
    providerId: "googleDrive",
    payload: {
    item: {
    id: "file-id",
    name: "filename.png",
    url: "https://...",
    embedUrl: "https://..." // optional
    },
    mode: "image" // "link" | "image" | "embed"
    }
    }

    A mock bridge page is available at demo/picker-bridge-example.html.

    • Uses Google Identity Services for OAuth token retrieval.
    • Uses gapi client initialization and Google Picker SDK for file selection.
    • Required config: clientId, apiKey.
    • Uses Microsoft OneDrive JavaScript picker SDK (OneDrive.open).
    • Required config: clientId.
    • Uses Dropbox Chooser SDK (Dropbox.choose).
    • Required config: appKey.
    • ⚠️ Requires CORS Proxy: Nextcloud APIs have CORS restrictions - browser deployments require a proxy (see below)
    • Supports two modes:
      1. Interactive Picker (pickerUrl): Opens a popup with Nextcloud Login Flow v2 (OAuth-like), browses files via WebDAV, creates public share links
      2. Pre-configured WebDAV (mode: "nextcloud-webdav"): Uses pre-configured credentials for programmatic file access
    • Uses Nextcloud Login Flow v2 for secure authentication
    • Uses WebDAV PROPFIND to list files
    • Optional OCS share creation for public links
    • Works with any Nextcloud instance (BayernCloud, private Nextcloud servers, etc.)
    • CORS Proxy Setup: See docs/CLOUDFLARE_WORKER_SETUP.md for free Cloudflare Worker proxy (100k requests/day free)
    • Alternative: Deploy plugin on same domain as Nextcloud (no proxy needed)

    Nextcloud instances block browser requests from different domains. To enable browser-based access:

    Option 1: Cloudflare Worker (Recommended - Free)

    1. Follow docs/CLOUDFLARE_WORKER_SETUP.md
    2. Deploy the worker from cloudflare-worker/nextcloud-proxy.js
    3. Update picker config with worker URL
    4. ✅ Works from GitHub Pages, any domain

    Option 2: Same-Origin Deployment

    • Host plugin on same domain as Nextcloud
    • No proxy needed (same-origin = no CORS)

    Option 3: Server-Side Integration

    • Use Nextcloud provider in backend/Node.js
    • No CORS issues in server-to-server requests

    Every built-in provider supports pickerUrl. If pickerUrl is set, the plugin opens that custom picker page and uses the bridge contract instead of the built-in SDK flow.

    • Configure OAuth consent screen and allowed JS origins in Google Cloud.

    ⚠️ Google Drive embeds require the viewer to be signed in to Google

    All Google Drive embeds (drive.google.com/file/d/.../preview) require Google cookies inside the iframe. Modern browsers block third-party cookies by default, so any viewer who is not already signed in to Google in their browser will see a login prompt instead of the file.

    Uploaded files — the plugin automatically sets sharing to "Anyone with the link" after upload, so the file itself is accessible, but the embed still requires Google cookies.

    Picked files — sharing is not changed by the plugin. Files remain at whatever sharing setting they had in Drive (usually "Restricted" / private). Viewers who are not the file owner will not be able to see them at all.

    Recommendations:

    • For content that must be publicly visible to all readers, use the upload button (↑) rather than the picker (↓), or manually set the file to "Anyone with the link" in Google Drive first.
    • To set a file to "Anyone with the link" in Google Drive: right-click the file → Share → click "Restricted" dropdown → select "Anyone with the link" → click Done.
    • For truly public embeds with no sign-in requirement, prefer Dropbox or OneDrive — their embed mechanisms do not depend on the viewer having a Google account.
    • Configure redirect URI and tenant restrictions in Entra/Microsoft app registration.
    • Ensure Chooser domain allowlist matches your deployment domain.
    • For production, prefer bearer tokens or backend proxy endpoints over raw credentials in browser config.
    • Do not embed long-lived secrets in frontend plugin config.
    • Prefer backend endpoints for OAuth code exchange and token management.
    • Explicitly inform users before changing sharing permissions (public link/embed).
    • Validate inserted URLs server-side if your application later renders them in high-trust contexts.

    The plugin uses two independent DBC instances — one for configuration checks and one for Zod boundary schema checks — so each layer can be configured separately. By default both layers throw DBC.Infringement on violations.

    Use configureMultiCloudValidation with config and/or boundary sub-keys to change the behaviour of either layer:

    import { configureMultiCloudValidation } from 'tinymce-multicloud-plugin';

    // Soft-log config violations only (useful for hardened production deployments).
    // Boundary checks remain strict — unexpected API shapes still throw.
    configureMultiCloudValidation({
    config: { throwOnInfringement: false, logToConsole: true },
    });

    // Both layers in soft logging mode:
    configureMultiCloudValidation({
    config: { throwOnInfringement: false, logToConsole: true },
    boundary: { throwOnInfringement: false, logToConsole: true },
    });

    // Then initialize TinyMCE as usual
    tinymce.init({ ... });

    Or using the global bundle:

    <script src="./dist/index.global.js"></script>
    <script>
    TinyMceMultiCloudPlugin.configureMultiCloudValidation({
    config: { throwOnInfringement: false, logToConsole: true },
    });

    tinymce.init({ ... });
    </script>
    Layer DBC path What it covers
    config globalThis.MultiCloud.Validation.Config Plugin options, provider credential shape, defaultProvider contract
    boundary globalThis.MultiCloud.Validation.Boundary Provider API response shapes (Zod schemas)

    Note: configureMultiCloudValidation must be called before tinymce.init(). Contract checks run at plugin initialization time — once the plugin is registered and your options have been validated, changing these settings has no retroactive effect.

    Recommendation: Keep both layers at their default throwOnInfringement: true during development. Soft logging mode for config is intended for hardened production deployments where all configuration has been verified. The boundary layer should generally stay strict — unexpected shapes in provider API responses indicate a real integration problem.

    Open demo/tinymce-demo.html after building. The demo includes mock pickers under demo/pickers/ for all providers.

    The demo auto-loads demo/multicloud.config.js if present; otherwise it falls back to local mock picker pages.

    How each provider handles different file types. Insert modes: image = <img>, embed = <iframe>, audio = <audio>, video = <video>, link = <a>.

    File type Extensions Insert mode Notes
    Images png, jpg, jpeg, gif, webp, bmp, svg, tiff image Direct <img> via Drive preview URL
    Audio mp3, wav, ogg, aac, m4a, flac, opus audio <iframe> preview (Drive transcodes audio)
    Video mp4, webm, mov, avi, mkv, m4v, wmv, flv embed <iframe> preview (Drive transcodes video)
    PDF pdf embed <iframe> via Drive preview URL
    Office (OOXML) docx, xlsx, pptx embed <iframe> via Drive preview URL
    Office (legacy) doc, xls, ppt embed <iframe> via Drive preview URL
    OpenDocument odt, ods, odp embed <iframe> via Drive preview URL
    Archives zip, rar, 7z, tar, gz, bz2, xz link Download link only
    Other anything else link Download link
    File type Extensions Insert mode Notes
    Images png, jpg, jpeg, gif, svg, webp, bmp image Raw CDN URL via dl.dropboxusercontent.com
    Audio mp3, wav, ogg, aac, m4a, flac, opus, oga, weba audio <audio> with raw CDN URL
    Video mp4, webm, ogg, mov, m4v, avi, wmv, flv, mkv embed <video> with raw CDN URL
    PDF pdf embed <iframe> via Google Docs Viewer
    Office (OOXML) docx, xlsx, pptx, doc, xls, ppt embed <iframe> via Microsoft Office Online viewer
    OpenDocument odt, ods, odp link Google Docs Viewer cannot reliably load ODF
    Archives zip, rar, 7z, tar, gz, bz2, xz link Download link only
    Other anything else link Download link
    File type Extensions Insert mode Notes
    Images png, jpg, jpeg, gif, svg, webp, bmp, tiff, apng, avif image Direct embed URL from OneDrive
    Audio mp3, wav, ogg, aac, m4a, flac, opus audio <audio> or <iframe> depending on download URL availability
    Video mp4, webm, ogg, mov, m4v, avi, wmv, flv, mkv embed <video> or <iframe>
    PDF pdf embed <iframe>
    Office (OOXML) docx, xlsx, pptx, doc, xls, ppt embed <iframe>
    OpenDocument odt, ods, odp embed <iframe>
    Archives zip, rar, 7z, tar, gz, bz2, xz link Download link only
    Other anything else link Download link
    File type Extensions Insert mode Notes
    Images png, jpg, jpeg, gif, webp, bmp, tiff, apng, avif image <img> via public share URL
    SVG svg link Cross-origin SVG cannot be embedded reliably
    Audio mp3, wav, ogg, aac, m4a, flac, opus link Cross-origin streaming unreliable
    Video mp4, webm, ogg, mov, avi, wmv, flv, mkv link Cross-origin streaming unreliable
    PDF pdf embed <iframe> via Google Docs Viewer (requires public share)
    Office (OOXML) docx, xlsx, pptx embed <iframe> via Google Docs Viewer (requires public share)
    Office (legacy) doc, xls, ppt link Viewer support unreliable for legacy formats
    OpenDocument odt, ods, odp link Viewer support unreliable for ODF
    Archives zip, rar, 7z, tar, gz, bz2, xz link Download link only
    Other anything else link Download link

    Note on Nextcloud embedding: Google Docs Viewer fetches files from its own servers, so the Nextcloud share link must be publicly accessible (not password-protected or on a private network). Embedding may fail intermittently due to Google's rate limiting on the viewer service.

    This repository gives you:

    • production-ready TinyMCE plugin shell
    • multi-provider adapter model
    • real Google Drive integration (GIS + Picker)
    • real OneDrive and Dropbox picker integrations
    • BayernCloud Nextcloud/WebDAV adapter
    • popup bridge fallback protocol

    What you still need per provider:

    • production credential/token strategy (server-backed where possible)
    • tenant/security policy integration