Back to Tldraw

Embed shape

apps/docs/content/sdk-features/embed-shape.mdx

5.4.014.2 KB
Original Source

The embed shape displays interactive content from external services within an iframe. When you paste a URL from a supported service onto the canvas, tldraw converts it to an embed with the appropriate dimensions and settings. See EmbedShapeUtil and TLEmbedShape.

Creating embeds

Paste a supported URL onto the canvas, or create an embed shape programmatically:

tsx
editor.createShape({
	type: 'embed',
	x: 100,
	y: 100,
	props: {
		url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
		w: 560,
		h: 315,
	},
})

The embed system recognizes URLs from supported services and converts them to their embeddable equivalents. A YouTube watch URL becomes an embed URL automatically.

Pasting iframe code

You can also paste raw <iframe> HTML directly onto the canvas to create an embed shape, even when the source URL doesn't match a known provider. tldraw extracts the iframe's src attribute and creates an embed pointing at it. For size, it reads the width and height attributes, then pixel values from the style attribute, and otherwise uses 425×350.

This covers services tldraw doesn't know about (OpenStreetMap, SoundCloud, Loom, internal tools) without a custom embed definition. Iframes pasted this way receive a stricter sandbox than the built-in providers (unknownEmbedShapePermissionOverrides: no allow-same-origin, no forms, no popups), since tldraw can't make any safety guarantees about the source.

Supported services

ServiceHostnamesResizableAspect ratio locked
tldrawtldraw.com, beta.tldraw.com, localhost:3000YesNo
Figmafigma.comYesNo
YouTubeyoutube.com, *.youtube.com, youtu.beYesYes
Google Mapsgoogle.*YesNo
Google Calendarcalendar.google.*YesNo
Google Slidesdocs.google.*YesNo
CodeSandboxcodesandbox.ioYesNo
CodePencodepen.ioYesNo
Scratchscratch.mit.eduNoNo
Val Townval.townYesNo
GitHub Gistgist.github.comYesNo
Replitreplit.comYesNo
Feltfelt.comYesNo
Spotifyopen.spotify.comYesNo
Vimeovimeo.com, player.vimeo.comYesYes
Observableobservablehq.comYesNo
Desmosdesmos.comYesNo
Canvacanva.comYesNo

Each service has default dimensions appropriate for its content type. YouTube embeds default to 800×450 (16:9), while Spotify defaults to 720×500. Vimeo starts at 640×360 and corrects itself to the video's real aspect ratio after creation (sizeToContentAspectRatio). Google Maps takes an API key through EmbedShapeUtil.configure({ embedConfig: { google_maps: { apiKey } } }).

Interacting with embeds

Embed shapes behave differently from other shapes because they contain live interactive content. Clicking an embed selects the shape rather than interacting with the content. Double-click it (or press Enter while it's selected) to enter editing mode. In editing mode, pointer events pass through to the iframe so you can scroll, click buttons, and use the embedded application. Click outside the shape or press Escape to exit.

Locking an embed doesn't block this: ShapeUtil#canEditWhileLocked returns true for embeds (unless the definition sets canEditWhileLocked: false), so a locked embed can still enter editing mode and stays put while you use it. Editing also works in readonly mode.

URL transformation

The embed system converts user-facing URLs to embed URLs automatically. When you paste https://www.youtube.com/watch?v=dQw4w9WgXcQ, the embed shape stores the original URL and renders https://www.youtube.com/embed/dQw4w9WgXcQ.

Each embed definition includes two transformation functions:

  • toEmbedUrl: Converts a shareable URL to an embeddable URL
  • fromEmbedUrl: Converts an embed URL back to the original URL

The shape stores the original URL and renders the embed version.

Fallback to bookmarks

When you paste a URL that no embed definition recognizes, the default URL handler creates a bookmark shape instead of an embed. An existing embed shape whose URL doesn't match any definition still renders the URL in an iframe, using the stricter sandbox for unknown sources.

Use getEmbedInfo to check whether a URL is embeddable before creating a shape:

tsx
import { getEmbedInfo, DEFAULT_EMBED_DEFINITIONS } from 'tldraw'

const embedInfo = getEmbedInfo(DEFAULT_EMBED_DEFINITIONS, 'https://youtube.com/watch?v=abc123')

if (embedInfo) {
	// URL is embeddable
	console.log(embedInfo.definition.title) // "YouTube"
	console.log(embedInfo.embedUrl) // "https://www.youtube.com/embed/abc123"
} else {
	// URL is not embeddable, will render as bookmark
}

Iframe security

Embeds run in sandboxed iframes with restricted permissions. The defaults live in embedShapePermissionDefaults:

PermissionDefaultDescription
allow-scriptsYesAllow JavaScript execution
allow-same-originYesAllow access to same-origin storage and APIs
allow-formsYesAllow form submission
allow-popupsYesAllow opening new windows (for linking to source)
allow-popups-to-escape-sandboxNoPopups inherit the sandbox
allow-downloadsNoBlock file downloads
allow-downloads-without-user-activationNoBlock automatic downloads
allow-modalsNoBlock modal dialogs like window.prompt()
allow-orientation-lockNoBlock screen orientation lock
allow-pointer-lockNoBlock pointer lock API
allow-presentationNoBlock the Presentation API
allow-top-navigationNoBlock navigating away from tldraw
allow-top-navigation-by-user-activationNoBlock navigating away, even on user gesture
allow-storage-access-by-user-activationNoBlock access to parent storage

Individual embed definitions override these defaults with overridePermissions. YouTube and Google Maps allow allow-presentation for fullscreen; YouTube, Google Calendar, and Google Slides allow allow-popups-to-escape-sandbox; tldraw embeds allow allow-top-navigation so users can open rooms in new tabs.

GitHub Gist embeds receive special handling: they use srcDoc instead of src to load the gist script, drop allow-same-origin, and restrict gist IDs to hexadecimal characters. This prevents JSONP callback attacks. See the embed permissions example.

Custom embed definitions

Replace or extend the default embed definitions using EmbedShapeUtil.configure(). Type custom definitions as CustomEmbedDefinition and give them an icon so they show up in the Insert embed dialog:

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

const myService: CustomEmbedDefinition = {
	type: 'myservice',
	title: 'My Service',
	hostnames: ['myservice.com'],
	width: 600,
	height: 400,
	doesResize: true,
	icon: 'https://myservice.com/favicon.png',
	toEmbedUrl: (url) => {
		const match = url.match(/myservice\.com\/item\/(\w+)/)
		if (match) {
			return `https://myservice.com/embed/${match[1]}`
		}
		return undefined
	},
	fromEmbedUrl: (url) => {
		const match = url.match(/myservice\.com\/embed\/(\w+)/)
		if (match) {
			return `https://myservice.com/item/${match[1]}`
		}
		return undefined
	},
	embedOnPaste: true,
}

const shapeUtils = [
	EmbedShapeUtil.configure({ embedDefinitions: [...DEFAULT_EMBED_DEFINITIONS, myService] }),
]

export default function App() {
	return (
		<div style={{ position: 'fixed', inset: 0 }}>
			<Tldraw shapeUtils={shapeUtils} />
		</div>
	)
}

Embed definition properties

PropertyTypeRequiredDescription
typestringYesUnique identifier for this embed type
titlestringYesDisplay name shown in the UI
hostnamesstring[]YesURL hostnames to match (supports glob patterns like *.youtube.com)
widthnumberYesDefault width in pixels
heightnumberYesDefault height in pixels
doesResizebooleanYesWhether the shape can be resized
toEmbedUrl(url: string) => string | undefinedYesConvert shareable URL to embed URL
fromEmbedUrl(url: string) => string | undefinedYesConvert embed URL to shareable URL
minWidthnumberNoMinimum width when resizing
minHeightnumberNoMinimum height when resizing
isAspectRatioLockedbooleanNoLock aspect ratio when resizing
canEditWhileLockedbooleanNoAllow interaction when shape is locked (default: true)
overridePermissionsTLEmbedShapePermissionsNoCustom iframe sandbox permissions
sizeToContentAspectRatiobooleanNoCorrect the size to the content's real aspect ratio after creation
iconstringNoIcon URL for the Insert embed dialog (CustomEmbedDefinition only)
backgroundColorstringNoBackground color for the embed container
overrideOutlineRadiusnumberNoCustom border radius (Spotify uses 12px)
embedOnPastebooleanNoWhen true, URLs are auto-converted to embeds on paste
instructionLinkstringNoHelp URL for services requiring setup (like Google Calendar public links)

Embed-on-paste behavior

By default, pasting a URL from a supported service creates an embed shape. Set embedOnPaste: false in the embed definition to create a bookmark instead.

The tldraw embed definition sets embedOnPaste: false, so pasting a tldraw.com URL creates a bookmark. Separately, embed shapes refuse to render a nested tldraw canvas when the page itself is running inside an iframe, which prevents infinite nesting.

Shape properties

PropertyTypeDescription
urlstringThe original URL (converted to embed URL internally); default ''
wnumberWidth of the embed container; default 300
hnumberHeight of the embed container; default 300

SVG export

Embed shapes render as blank rectangles in SVG exports. The iframe content can't be captured directly, so exports show a placeholder with the embed's background color and border radius.