livecodes/sdk-embedding

SkillWeb & browsing

Create and configure embedded playgrounds using createPlayground(), EmbedOptions, container setup, loading modes (eager/lazy/click), and appUrl for self-hosted instances. Load this skill when embedding LiveCodes in web pages, configuring playground containers, or setting up SDK integration.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the livecodes/sdk-embedding skill

What this skill tells your AI

The instructions your AI receives, as published by live-codes/livecodes in .agents/skills/livecodes/sdk-embedding/SKILL.md and read by ahel’s review.

LiveCodes is a client-side code playground that runs in the browser. Use createPlayground to embed interactive code editors in any web page.

Setup

import { createPlayground } from 'livecodes';

// Minimal embed with template
createPlayground('#container', {
  template: 'react',
});

// Embed with custom code
createPlayground('#container', {
  config: {
    markup: { language: 'html', content: '<h1>Hello World</h1>' },
    style: { language: 'css', content: 'h1 { color: blue; }' },
    script: { language: 'javascript', content: 'console.log("Hello");' },
  },
});

// CDN usage (no bundler)
import { createPlayground } from 'https://cdn.jsdelivr.net/npm/livecodes';
createPlayground('#container', { template: 'vue' });

Core Patterns

Load playground lazily

Playgrounds load when they approach the viewport by default. Use loading: 'eager' for immediate load or loading: 'click' for click-to-load.

createPlayground('#container', {
  template: 'react',
  loading: 'click', // Shows "Click to load" screen
});

Configure via query params

For simple cases, use URL-style params instead of full config objects.

createPlayground('#container', {
  params: {
    html: '<h1>Hello</h1>',
    css: 'h1 { color: blue; }',
    js: 'console.log("Hello")',
    console: 'open',
  },
});

Use self-hosted instance

Point to your own LiveCodes deployment.

createPlayground('#container', {
  appUrl: 'https://playground.mywebsite.com',
  template: 'react',
});

Multiple sources priority

When providing multiple config sources, they override in order: template < import < config < params.

createPlayground('#container', {
  template: 'react', // Lowest priority
  import: 'https://gist.github.com/...', // Overrides template
  config: {
    /* ... */
  }, // Overrides import
  params: { js: '...' }, // Highest priority
});

Generate shareable URL

Create a link to the standalone app without embedding.

import { getPlaygroundUrl } from 'livecodes';

const url = getPlaygroundUrl({
  config: {
    markup: { language: 'markdown', content: '# Hello World' },
  },
});
// url = "https://livecodes.io/#config/..."

Compress config for sharing

Compress a stringified config object for use in URL hashes or compact storage.

import { compress } from 'livecodes';

const config = {
  markup: { language: 'html', content: '<h1>Hello World</h1>' },
};
const compressed = compress(JSON.stringify(config));

Decompress config

Decompress a string that was compressed with compress. Returns null if decompression fails.

import { decompress } from 'livecodes';

const decompressed = decompress(compressedString);
if (decompressed) {
  const config = JSON.parse(decompressed);
}

Common Mistakes

HIGH Container element not found throws error

Wrong:

createPlayground('#nonexistent-container', { template: 'react' });
// Throws: "Cannot find element: \"#nonexistent-container\""

Correct:

// Ensure container exists before calling
const container = document.querySelector('#container');
if (container) {
  createPlayground('#container', { template: 'react' });
}

// Or use headless mode (container optional)
createPlayground({
  view: 'headless',
  config: {
    /* ... */
  },
});

createPlayground throws if the container selector matches no elements. In headless mode (headless: true), the container parameter is optional.

Source: src/sdk/index.ts — throws "Cannot find element" for invalid container

HIGH Calling SDK methods after destroy() throws error

Wrong:

const playground = await createPlayground('#container', options);
await playground.destroy();
await playground.run(); // Throws: "Cannot call API methods after calling `destroy()`."

Correct:

const playground = await createPlayground('#container', options);
await playground.run();
// Use SDK methods while playground exists...
await playground.destroy(); // Last call — no more methods after this

Once destroy() is called, all subsequent SDK method calls throw with the message "Cannot call API methods after calling destroy()."

Source: src/sdk/index.ts — alreadyDestroyedMessage constant

MEDIUM Invalid appUrl throws error

Wrong:

createPlayground('#container', {
  appUrl: 'my-playground', // Not a valid URL
});
// Throws: "my-playground is not a valid URL."

Correct:

createPlayground('#container', {
  appUrl: 'https://playground.example.com',
  template: 'react',
});

The appUrl must be a parseable URL string. Use the full URL including protocol.

Source: src/sdk/index.ts — URL parsing in getPlaygroundUrl

MEDIUM SDK method timeout after 60 seconds

SDK calls timeout after 60 seconds if the playground doesn't respond.

// This can timeout if playground has infinite loop or slow WASM load
await playground.run(); // Times out after 60s

Handle with try/catch for long-running operations:

try {
  await playground.run();
} catch (error) {
  if (error.message.includes('timed out')) {
    console.error('Playground timed out');
  }
}

Source: src/sdk/index.ts — API_TIMEOUT = 60_000 (60 seconds)

Signals

GitHub stars
1k
Forks
266
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
livecodes-sdk-embedding
Source
github.com/live-codes/livecodes