Marketplace

Building Modules

Modules extend LingoCon — add studio tools, interactive widgets on public pages, sound-change packs, themes, and more. They're hosted by the platform, so anyone can add yours with one click; nobody downloads anything.

Three tiers

Every module runs in one of three tiers, chosen by how much power it needs:

  • Declarative
    Pure data — no code runs. A sound-change rule pack or a theme palette. Zero risk, instant. The platform's trusted engine does the work.
  • Client sandbox
    Your JavaScript runs inside a locked-down iframe (no network, cookies, storage, or DOM access to the host). It talks to LingoCon only through the host SDK. This is what powers reader widgets, visualizers, and studio panels.
  • Server
    Heavy/trusted compute via WASM or edge isolates. Planned for a later phase.

Types & where they apply

Pick a type when you create a module. It determines where the module shows up:

Studio panel
Client sandbox (JS)

Studio sidebar · Studio module panel

Reader widget
Client sandbox (JS)

Studio sidebar · Studio module panel · Public language page (Tools)

Content block
Client sandbox (JS)

Studio module panel

Transformer
Declarative (data only)

Studio sidebar · Studio module panel · Dictionary transform in studio

Generator
Client sandbox (JS)

Studio sidebar · Studio module panel

Exporter
Client sandbox (JS)

Studio sidebar · Studio module panel

Importer
Client sandbox (JS)

Studio sidebar · Studio module panel

Visualizer
Client sandbox (JS)

Studio sidebar · Studio module panel · Public language page (Tools)

Validator
Client sandbox (JS)

Studio sidebar · Studio module panel

Theme & font
Declarative (data only)

Studio sidebar · Studio module panel · Public language page (theme)

The host SDK

A global host object is injected into every client-sandbox module. Render your UI into the #app element and use these methods:

host.ready()                       // call once, when your script has loaded
host.onInit(function (ctx) { ... }) // ctx = { languageSlug, languageId, permissions, theme }
host.request(method, params)        // returns a Promise of data (see below)
host.reportHeight()                 // re-measure after you change the DOM
host.context()                      // the init context, or null before init

The iframe auto-resizes to your content, but call host.reportHeight() after async renders to be safe.

Data methods & permissions

Read a language's data with host.request(). Each method requires a permission that the installer must grant; declare them when you publish.

getLanguage{ name, slug, description }
getDictionaryread:dictionary{ entries: [{ lemma, gloss, ipa, partOfSpeech }] }
getPhonologyread:phonology{ symbols: [{ symbol, ipa, latin, name }] }
getParadigmsread:paradigms{ paradigms: [{ id, name, slots, words }] }

Requesting a method you didn't declare (or that wasn't granted) returns an error.

Security model

Client-sandbox code runs in a sandbox="allow-scripts" iframe with a null origin: it cannot read cookies, localStorage, or the host DOM.

A strict Content-Security-Policy (default-src 'none') blocks all network egress — no fetch, XHR, WebSocket, or beacons. The only channel out is postMessage to the host.

At publish time bundles are statically scanned (size limit + denylist for document.cookie, storage, eval, dynamic import, etc.). All language data is brokered by the host against granted permissions.

Full example

A complete reader widget that lists the 20 most recent words:

host.onInit(async function (ctx) {
  const root = document.getElementById("app");
  root.textContent = "Loading…";
  try {
    const { entries } = await host.request("getDictionary");
    root.innerHTML = "";
    const h = document.createElement("h3");
    h.textContent = ctx.languageSlug + " · " + entries.length + " words";
    root.appendChild(h);
    const ul = document.createElement("ul");
    entries.slice(0, 20).forEach(function (e) {
      const li = document.createElement("li");
      li.textContent = e.lemma + (e.ipa ? " /" + e.ipa + "/" : "") + " — " + (e.gloss || "");
      ul.appendChild(li);
    });
    root.appendChild(ul);
  } catch (err) {
    root.textContent = "Could not load data.";
  }
  host.reportHeight();
});
host.ready();

Paste this into the playground, grant read:dictionary, and press Run.

Publishing

  1. Create a module and choose a type and tier.
  2. Prototype and test it in the playground until it works against your data.
  3. On the module's edit page, paste your widget code (client-sandbox) or declarative JSON, tick the permissions you use, and publish a version.
  4. Add it to a language (account-wide or per-language). For reader widgets and themes, everyone viewing that language's public page sees it.