Playgrounds

JavaScript Playground

Write, run, and share JavaScript in your browser. Code runs in a sandboxed frame and never leaves the page.

Loading editor...

Output

Nothing yet. Press Run to execute the code above.

What is a JavaScript playground?

A JavaScript playground is an editor and a run button in the same page. You type code, press run, and read what it printed, without a project, a build step, or a file on disk. The browser console does the same job and is one keystroke away, which is why the interesting question is what a playground adds: room to write more than one line comfortably, output that stays put while you edit, and something you can hand to another person.

This one runs a real editor rather than a textarea. Monaco is the editor from VS Code, so bracket matching, multiple cursors, find and replace, and completion for the built-in browser APIs all behave the way they do in the thing you already use. Your code executes inside a sandboxed frame, output lands in the panel below with the console level it came from, and the Share button puts the whole program into a link. Nothing is stored on a server, because there is no server.

When to use it

The most common reason is a small question with a fast answer. You have read the docs for an API you have not used before, and you want to see what Intl.RelativeTimeFormat returns before you wire it into anything. You want to know whether structuredClone handles the shape you are about to throw at it. The console would do, except that the moment the expression needs three lines and a helper function, editing it in a single-line prompt becomes the hard part.

The second is narrowing a bug. Something in a large codebase misbehaves and you suspect one function. Paste the function here on its own, feed it the input that broke it, and you find out in a few seconds whether the fault is the function or everything around it. A reduction that runs in isolation is also the thing a bug report needs, and this gives you one you can attach as a link.

Then there is explaining something to someone else. A link that opens with the code already in it, ready to run and to edit, lands differently from a snippet pasted into chat. That covers teaching, code review, and the answer to a question in a team channel. If the code arrived from someone else and is hard to read, the Format button tidies it in place; the JS Prettifier does the same job with the options exposed when you want control over the result. Output that is JSON is worth taking to the JSON Formatter, which lays it out properly rather than as one console line.

How this tool works

The editor is Monaco, pinned to version 0.52.2 and pulled off a CDN on your first visit to the page. That is a few megabytes, cached by the browser afterwards, and it is why the toolbar says the editor is loading before it says it is ready. Version 0.52.2 rather than the newest release for a specific reason: from 0.53 onwards Monaco ships its language workers under filenames that change with every version, and those workers are what provide completion and the red underlines. Pinning to the last release with stable worker paths keeps them working from a CDN.

Your code runs inside an iframe carrying sandbox="allow-scripts", and the permission that is missing matters more than the one that is there. Without allow-same-origin the frame gets an opaque origin, which means code running in it cannot read cookies, cannot reach localStorage, and cannot see anything belonging to this site. Try it: reading document.cookie or localStorage from the playground throws a SecurityError, and location.origin reads null. That is the property that makes it safe to open a link somebody sent you. The frame is also thrown away and rebuilt on every run, so a stray setInterval from the last attempt cannot bleed into the next one.

Console output does not go to your browser console. The frame replaces console.log, info, warn, error, and debug with versions that post their arguments to this page, which is what fills the panel. Values are inspected rather than stringified, so an object prints its keys, a Map prints its pairs, and a circular reference prints as [Circular] rather than throwing. Uncaught errors and unhandled promise rejections are reported through the same channel, with the stack when the browser provides one. Your code is wrapped in an async function before it runs, so await works at the top level the way it does in a devtools console.

Execution is capped at five seconds. When the limit is reached the frame is destroyed and the panel says so. That covers anything that waits: a request that never answers, a promise that never settles, a timer chain that keeps rescheduling itself. It does not save you from a tight synchronous loop with no await in it, because a frame in the same process shares a thread with the page that owns it, and a loop that never yields never gives the timer a chance to fire. If you write while (true) {} with nothing inside it, close the tab.

The network is available from inside the frame, with the usual rule. fetch works against any endpoint that sends permissive CORS headers and fails against any that does not, exactly as it would from any other page. Dynamic import from a CDN works on the same terms, so await import("https://esm.sh/nanoid") is a real option and npm install is not. There is nothing on this page proxying requests for you.

Share puts your code in the URL and nowhere else. The Share button base64url-encodes the editor contents into a code query parameter and copies the resulting link; opening that link decodes it back into the editor. No account, no paste service, no row in a database, and nothing to expire or be deleted out from under you. The trade is length, since the program travels in the link: a few dozen lines makes a long URL, and some chat clients will wrap or truncate it in display even when the link itself is intact.

Examples

  • Retrying something flaky, with top-level await

    Input
    const wait = ms => new Promise(r => setTimeout(r, ms));
    
    async function retry(fn, attempts = 3) {
      for (let i = 1; i <= attempts; i++) {
        try {
          return await fn(i);
        } catch (e) {
          console.warn(`Attempt ${i} failed: ${e.message}`);
          await wait(10);
        }
      }
      throw new Error('All attempts failed');
    }
    
    let calls = 0;
    const result = await retry(async n => {
      calls++;
      if (n < 3) throw new Error('flaky');
      return 'ok on attempt ' + n;
    });
    
    console.log(result, '- calls:', calls);
    Output
    Warning: Attempt 1 failed: flaky
    Warning: Attempt 2 failed: flaky
    ok on attempt 3 - calls: 3

    The await on the last block sits at the top level, which works because your code is wrapped in an async function before it runs. Note that the two warnings arrive with a Warning: prefix and a different colour, and the log line does not. Level is carried by the text as well as the colour, so the difference survives a screenshot in greyscale.

  • How values are printed

    Input
    console.log(new Map([['a', 1], ['b', 2]]));
    console.log(new Set(['x', 'y']));
    console.log({ nested: { list: [1, 'two', null, undefined] } });
    
    const cyclic = { name: 'root' };
    cyclic.self = cyclic;
    console.log(cyclic);
    Output
    Map(2) { a => 1, b => 2 }
    Set(2) { x, y }
    { nested: { list: [1, two, null, undefined] } }
    { name: root, self: [Circular] }

    Objects are inspected rather than passed through JSON.stringify, which would have thrown on the circular reference and silently dropped the undefined. Maps and Sets keep their size and contents instead of printing as empty braces.

  • What the sandbox refuses

    Input
    console.log('origin:', location.origin);
    
    try {
      localStorage.setItem('x', '1');
    } catch (e) {
      console.log('localStorage:', e.name);
    }
    
    try {
      console.log('cookie:', document.cookie);
    } catch (e) {
      console.log('cookie:', e.name);
    }
    Output
    origin: null
    localStorage: SecurityError
    cookie: SecurityError

    This is the sandbox working. An opaque origin has no cookie jar and no storage area of its own, and it is not this site, so nothing belonging to this site is reachable from inside. Anyone can run this in the playground and confirm it rather than take our word for it.

Frequently asked questions

  • Which languages can I run here?

    JavaScript. The editor is in JavaScript mode and the frame runs it as-is, so TypeScript syntax will parse as a syntax error rather than being compiled. Modern JavaScript is fine: optional chaining, nullish assignment, class fields, and top-level await all work, since whatever your browser supports is what runs. TypeScript, Python, and HTML playgrounds are separate tools on this site rather than a language dropdown on this one, so each gets its own page and its own toolchain.

  • Can I import npm packages?

    Not from npm, but the useful half of that works. There is no install step and no bundler, so a bare specifier like import "lodash" has nothing to resolve against. A URL does resolve: await import("https://esm.sh/nanoid") pulls an ES module straight from a CDN and works whenever that CDN sends permissive CORS headers, which esm.sh, jsDelivr, and Skypack all do. That covers most of what you would reach for a package to do in a scratch file.

  • Where does my code go when I share it?

    Into the link, and nowhere else. Share encodes the editor contents as base64url and puts them in the URL as a code parameter, so the whole program travels in the link itself. Nothing here stores it: no paste ID, no row in a table, and no record that you pressed the button. The practical limits follow from that: the link is as long as your code, it cannot be revoked once sent because there is nothing to delete, and anyone holding it can read the code.

  • Is it safe to run a playground link somebody sent me?

    Safer than most places you could paste the same code, and not a reason to stop reading it. The frame runs with an opaque origin and no allow-same-origin, so code in it cannot touch your cookies, your storage, or anything on this site, and there is no session here for it to steal in the first place. What it can still do is what any web page can: make network requests to endpoints that allow them, and burn CPU. Open the link, read the code in the editor, then press Run. The code is visible before it executes, which is the part that matters.

  • Why does my infinite loop freeze the page?

    Because a frame in the same process shares a thread with the page around it, and a loop with no await in it never yields that thread. The five-second cap is a timer, and a timer cannot fire while something else is refusing to stop, so the cap catches every kind of hang except that one. A request that never answers or a promise that never settles is caught and reported. while (true) {} is not, and the fix is to close the tab.

  • Can you build something like this for our team?

    Yes, and the interesting versions are rarely a generic playground. What teams usually want is this shape pointed at their own thing: a page where support can run a query against a sanitised copy of production data, or where a customer can try an API without signing up first, or where onboarding walks someone through a live example instead of a screenshot. The sandboxing, the editor, and the share-by-link are the easy parts. Zinc Online Solutions builds the rest. Tell us what you would want people to be able to run, and who they are.