Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/tldraw/tldraw/llms.txt

Use this file to discover all available pages before exploring further.

Tldraw supports embedding external content directly on the canvas, including videos, websites, and custom embeds.

Built-in embed support

Tldraw comes with built-in support for popular embedding platforms:
  • YouTube
  • Vimeo
  • Figma
  • Google Maps
  • Twitter/X
  • GitHub Gists
  • CodePen
  • And more…
Users can paste URLs directly onto the canvas and tldraw will automatically convert them to embeds.

Adding custom embeds

CustomEmbedExample.tsx
import {
  CustomEmbedDefinition,
  DEFAULT_EMBED_DEFINITIONS,
  DefaultEmbedDefinitionType,
  Tldraw,
} from 'tldraw'
import 'tldraw/tldraw.css'

// [1] Filter default embeds to keep only specific ones
const defaultEmbedTypesToKeep: DefaultEmbedDefinitionType[] = ['tldraw', 'youtube']
const defaultEmbedsToKeep = DEFAULT_EMBED_DEFINITIONS.filter((embed) =>
  defaultEmbedTypesToKeep.includes(embed.type)
)

// [2] Define a custom embed
const customEmbed: CustomEmbedDefinition = {
  type: 'jsfiddle',
  title: 'JSFiddle',
  hostnames: ['jsfiddle.net'],
  minWidth: 300,
  minHeight: 300,
  width: 720,
  height: 500,
  doesResize: true,
  toEmbedUrl: (url) => {
    const urlObj = new URL(url)
    const matches = urlObj.pathname.match(/\/([^/]+)\/([^/]+)\/(\d+)\/embedded/)
    if (matches) {
      return `https://jsfiddle.net/${matches[1]}/${matches[2]}/embedded/`
    }
    return
  },
  fromEmbedUrl: (url) => {
    const urlObj = new URL(url)
    const matches = urlObj.pathname.match(/\/([^/]+)\/([^/]+)\/(\d+)\/embedded/)
    if (matches) {
      return `https://jsfiddle.net/${matches[1]}/${matches[2]}/`
    }
    return
  },
  icon: 'https://jsfiddle.net/img/favicon.png',
}

// [3] Combine default and custom embeds
const embeds = [...defaultEmbedsToKeep, customEmbed]

export default function CustomEmbedExample() {
  return (
    <div className="tldraw__editor">
      <Tldraw embeds={embeds} />
    </div>
  )
}

Custom embed definition

A custom embed definition has the following structure:
interface CustomEmbedDefinition {
  type: string              // Unique identifier
  title: string            // Display name
  hostnames: string[]      // Supported domains
  minWidth: number         // Minimum width in pixels
  minHeight: number        // Minimum height in pixels  
  width: number           // Default width
  height: number          // Default height
  doesResize: boolean     // Whether embed supports resizing
  toEmbedUrl: (url: string) => string | undefined    // Convert URL to embed URL
  fromEmbedUrl: (url: string) => string | undefined  // Convert embed URL back to regular URL
  icon: string            // Icon URL for the embed dialog
}

URL transformation

The toEmbedUrl and fromEmbedUrl functions transform URLs:
// Convert user-provided URL to embeddable iframe URL
toEmbedUrl: (url) => {
  const videoId = extractVideoId(url)
  return `https://www.youtube.com/embed/${videoId}`
}

Programmatically creating embeds

You can create embed shapes programmatically:
import { Tldraw, useEditor } from 'tldraw'
import 'tldraw/tldraw.css'

function EmbedButtons() {
  const editor = useEditor()

  const addYouTubeVideo = () => {
    editor.createShape({
      type: 'embed',
      x: 100,
      y: 100,
      props: {
        url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
        w: 720,
        h: 405,
      },
    })
  }

  const addWebsite = () => {
    editor.createShape({
      type: 'embed',
      x: 100,
      y: 100,
      props: {
        url: 'https://example.com',
        w: 800,
        h: 600,
      },
    })
  }

  return (
    <div style={{ position: 'absolute', top: 10, left: 10, zIndex: 1000 }}>
      <button onClick={addYouTubeVideo} style={{ marginRight: 8 }}>
        Add YouTube Video
      </button>
      <button onClick={addWebsite}>
        Add Website
      </button>
    </div>
  )
}

export default function Example() {
  return (
    <div className="tldraw__editor">
      <Tldraw>
        <EmbedButtons />
      </Tldraw>
    </div>
  )
}

Interactive embeds

Embeds in tldraw are fully interactive. Users can:
  • Click and interact with embedded content
  • Resize embeds
  • Rotate embeds
  • Move embeds around the canvas
  • Copy and paste embeds

Security considerations

Embeds use iframes which can pose security risks. Only embed content from trusted sources.
Tldraw automatically:
  • Validates URLs against the defined hostnames
  • Sanitizes embed URLs
  • Uses iframe sandboxing for security
For additional security:
const customEmbed: CustomEmbedDefinition = {
  // ... other properties
  hostnames: ['trusted-domain.com'], // Restrict to specific domains
  toEmbedUrl: (url) => {
    // Add additional URL validation
    if (!isValidUrl(url)) return undefined
    return sanitizeUrl(url)
  },
}

Custom embed components

For full control over embed rendering, you can create a custom embed shape:
import {
  HTMLContainer,
  ShapeUtil,
  TLShape,
  T,
  Tldraw,
} from 'tldraw'
import 'tldraw/tldraw.css'

type IframeShape = TLShape<'iframe', { url: string; w: number; h: number }>

class IframeShapeUtil extends ShapeUtil<IframeShape> {
  static override type = 'iframe'
  static override props = {
    url: T.string,
    w: T.number,
    h: T.number,
  }

  getDefaultProps(): IframeShape['props'] {
    return {
      url: '',
      w: 720,
      h: 500,
    }
  }

  component(shape: IframeShape) {
    return (
      <HTMLContainer id={shape.id}>
        <iframe
          src={shape.props.url}
          width="100%"
          height="100%"
          frameBorder="0"
          allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
          allowFullScreen
        />
      </HTMLContainer>
    )
  }

  indicator(shape: IframeShape) {
    return <rect width={shape.props.w} height={shape.props.h} />
  }
}

export default function CustomIframeExample() {
  return (
    <div className="tldraw__editor">
      <Tldraw shapeUtils={[IframeShapeUtil]} />
    </div>
  )
}

Supported default embeds

The DEFAULT_EMBED_DEFINITIONS includes:
  • youtube - YouTube videos
  • vimeo - Vimeo videos
  • figma - Figma files
  • google_maps - Google Maps
  • twitter - Twitter/X posts
  • github_gist - GitHub Gists
  • codepen - CodePen embeds
  • tldraw - Shared tldraw boards
  • And more…

Filtering default embeds

You can limit which embeds are available:
import { DEFAULT_EMBED_DEFINITIONS, Tldraw } from 'tldraw'

// Only allow YouTube and Vimeo
const allowedEmbeds = DEFAULT_EMBED_DEFINITIONS.filter(
  embed => ['youtube', 'vimeo'].includes(embed.type)
)

export default function Example() {
  return (
    <div className="tldraw__editor">
      <Tldraw embeds={allowedEmbeds} />
    </div>
  )
}