npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

openeditor-text

v2.1.1

Published

Open Editor — the rich text editor, delivered at runtime. Installing this puts only a small loader in node_modules; the editor engine is fetched, integrity-verified and mounted at page load.

Readme

Open Editor

A rich text editor for the web. npm install puts a small loader in your node_modules — the editor itself is downloaded and mounted when your page loads, so buying a licence upgrades your users on their next refresh with no reinstall and no redeploy.

Live demo · Docs · Pricing

Installation

npm install openeditor-text

Usage

import { createEditor } from 'openeditor-text';

const editor = await createEditor('#editor', {
  endpoint: 'https://your-delivery-host.com',
});

That is the whole setup. endpoint is the one required option — the address the editor is downloaded from, given to you when you sign up (or your own server if you self-host). Everything else is optional.

React

import { useState } from 'react';
import { OpenEditor } from 'openeditor-text/react';

function App() {
  const [content, setContent] = useState('<p>Hello world</p>');

  return (
    <OpenEditor
      endpoint="https://your-delivery-host.com"
      value={content}
      onChange={setContent}
    />
  );
}

Vue

<script setup>
import { ref } from 'vue';
import { OpenEditor } from 'openeditor-text/vue';

const content = ref('<p>Hello world</p>');
</script>

<template>
  <OpenEditor endpoint="https://your-delivery-host.com" v-model="content" />
</template>

Angular

import { OpenEditorComponent } from 'openeditor-text/angular';

@Component({
  standalone: true,
  imports: [OpenEditorComponent, FormsModule],
  template: `
    <open-editor
      endpoint="https://your-delivery-host.com"
      [(ngModel)]="content"
    />
  `,
})
export class AppComponent {
  content = '<p>Hello world</p>';
}

React, Vue and Angular are optional peer dependencies — you only need the one you use.

Licence keys

The free tier needs no key — no signup, no account, no card.

When you buy a plan, pass the key you were emailed:

const editor = await createEditor('#editor', {
  endpoint: 'https://your-delivery-host.com',
  licenceKey: 'eyJhbGciOiJFUzI1NiIs…',
});

The same licenceKey prop works in React, Vue and Angular. (licenseKey is accepted too, so either spelling is fine.)

Keys are checked by the server on every page load and are tied to the domains you registered. If a key is expired, revoked, or used on a domain you did not register, the editor still loads on the free tier — your users get a working editor, never a blank page.

Upgrading a live editor

applyLicence re-verifies a key in place:

import { applyLicence } from 'openeditor-text';

await applyLicence(editor, newKey, { endpoint });

If the new key only changes which features are granted, it applies instantly — content, cursor and undo history all survive.

If it changes the plan, a reload is needed: the free build contains no premium code, so there is nothing to switch on. Rather than re-mounting the editor under someone who is mid-sentence, the loader shows a small dismissible prompt and lets them pick the moment:

Premium unlocked — reload to activate it. [ Reload ] ×

Pass prompt: false to suppress it and handle the reload yourself. Downgrades never prompt — the premium bundle keeps running until the next natural page load.

Buying premium from inside the editor

Customers can upgrade without pasting anything. Show them their editor ID:

import { showInstallId } from 'openeditor-text';

// Put this behind your own "Upgrade" button — it is not rendered automatically.
showInstallId(document.querySelector('#upgrade-panel'));

They give that ID at checkout, and the next load of that same browser comes back premium — the key is delivered automatically and remembered across reloads. getInstallId() returns the raw value if you prefer your own markup.

The handover happens exactly once and then expires, so a leaked log line cannot be replayed to steal the licence. If the browser blocks site storage (private mode, sandboxed iframe) there is no ID, and showInstallId() says so plainly — those customers paste the emailed key instead.

Image uploads

Give the editor somewhere to POST files and the toolbar button, drag-and-drop and paste-from-clipboard all start working:

const editor = await createEditor('#editor', {
  endpoint: 'https://your-delivery-host.com',
  imageUploadUrl: '/api/uploads',
});

Your endpoint receives the file as multipart field file and replies with JSON:

{ "url": "https://cdn.example.com/img/abc.jpg" }

That is the whole contract. For authenticated backends add imageUploadHeaders, imageUploadWithCredentials, imageUploadFieldName or imageUploadData; for S3, R2 or Cloudinary pre-signed flows, imageUploadHandler lets you take over the upload entirely. Uploads are capped at 10 MB by default.

Full reference: image upload docs.

Options

| Option | Type | Default | Description | | --- | --- | --- | --- | | endpoint | string | — | Required. Where the editor is downloaded from. | | licenceKey | string | — | Unlocks paid features. Omit for the free tier. | | imageUploadUrl | string | — | Where uploaded images are POSTed. | | plugins | 'all' \| array | 'all' | Which plugins to install after mount. | | version | string | — | Pin a specific engine version. | | cache | boolean | true | Cache the engine in IndexedDB between visits. |

Any other option is passed straight to the editor — placeholder, minHeight, theme, toolbar, and the rest. See the configuration docs.

Content security policy

The engine runs from a blob: URL, so your CSP must allow it:

script-src 'self' blob:;
connect-src 'self' https://your-delivery-host.com;

Without blob: the editor will not start, and says so in the console.

Good to know

If the network fails, the loader drops a plain <textarea> into your container with the same name and id a form expects — so your users keep writing and the form still submits.

Returning visitors download nothing. The engine is cached in IndexedDB after the first visit; entitlements are still re-checked on every page load. Call clearCache() to clear it.

Privacy: a random install id is stored per browser so anonymous traffic can be rate-limited. It is never derived from your device, IP or user agent, and identifies an install rather than a person. Your document content never leaves the browser.

Licence

MIT.