---
title: Shapes
status: published
author: steveruizok
date: 3/22/2023
order: 2
keywords:
  - custom
  - shapes
  - shapeutils
  - utils
---

In tldraw, a shape is something that can exist on the page, like an arrow, an image, or some text.

This article is about shapes: what they are, how they work, and how to create your own shapes. If you'd prefer to see an example, see the tldraw repository's [examples app](https://github.com/tldraw/tldraw/tree/main/apps/examples) for examples of how to create custom shapes in tldraw.

## Types of shape

We make a distinction between three types of shapes: "core", "default", and "custom".

### Core shapes

The editor's core shapes are shapes that are built in and always present. At the moment the only core shape is the [group shape](/reference/tlschema/TLGroupShape).

### Default shapes

The default shapes are all of the shapes that are included by default in the [Tldraw](?) component, such as the [TLArrowShape](?) or [TLDrawShape](?). They are exported from the `tldraw` library as [defaultShapeUtils](?).

### Custom shapes

Custom shapes are shapes that were created by you or someone you love. Find more information about custom shapes [below](#Custom-shapes-1).

## The shape object

Shapes are just records (JSON objects) that sit in the [store](/docs/editor#Store). For example, here's a shape record for a rectangle geo shape:

```ts
{
    "parentId": "page:somePage",
    "id": "shape:someId",
    "typeName": "shape"
    "type": "geo",
    "x": 106,
    "y": 294,
    "rotation": 0,
    "index": "a28",
    "opacity": 1,
    "isLocked": false,
    "props": {
        "w": 200,
        "h": 200,
        "geo": "rectangle",
        "color": "black",
        "labelColor": "black",
        "fill": "none",
        "dash": "draw",
        "size": "m",
        "font": "draw",
        "text": "diagram",
        "align": "middle",
        "verticalAlign": "middle",
        "growY": 0,
        "url": ""
    },
    "meta": {},
}
```

### Base properties

Every shape contains some base information. These include the shape's type, position, rotation, opacity, and more. You can find the full list of base properties [here](/reference/tlschema/TLBaseShape).

### Props

Every shape also contains some shape-specific information, called `props`. Each type of shape can have different props. For example, the `props` of a text shape are much different than the props of an arrow shape.

### Meta

Meta information is information that is not used by tldraw but is instead used by your application. For example, you might want to store the name of the user who created a shape, or the date that the shape was created. You can find more information about meta information [below](#Meta-information).

## The `ShapeUtil` class

While tldraw's shapes themselves are simple JSON objects, we use [ShapeUtil](?) classes to answer questions about shapes. For example, when the editor needs to render a text shape, it will find the [TextShapeUtil](?) and call its [ShapeUtil#component](?) method, passing in the text shape object as an argument.

---

## Custom shapes

You can create your own custom shapes. In the examples below, we will create a custom "card" shape. It'll be a simple rectangle with some text inside.

> For an example of how to create custom shapes, see our [custom shapes example](https://github.com/tldraw/tldraw/tree/main/apps/examples/src/examples/CustomConfigExample).

### Shape type

In tldraw's data model, each shape is represented by a JSON object. Let's first create a type that describes what this object will look like.

```ts
import { TLBaseShape } from 'tldraw'

type CardShape = TLBaseShape<'card', { w: number; h: number }>
```

With the [TLBaseShape](?) helper, we define the shape's `type` property (`card`) and the shape's `props` property (`{ w: number, h: number }`). The type can be any string but the props must be a regular [JSON-serializable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#description) JavaScript object.

The [TLBaseShape](?) helper adds the other base properties of a shape, such as `x`, `y`, `rotation`, and `opacity`.

### Shape Util

While tldraw's shapes themselves are simple JSON objects, we use [ShapeUtil](?) classes to answer questions about shapes.

Let's create a [ShapeUtil](?) class for the shape.

```tsx
import { HTMLContainer, ShapeUtil } from 'tldraw'

class CardShapeUtil extends ShapeUtil<CardShape> {
	static override type = 'card' as const

	getDefaultProps(): CardShape['props'] {
		return {
			w: 100,
			h: 100,
		}
	}

	getGeometry(shape: ICardShape) {
		return new Rectangle2d({
			width: shape.props.w,
			height: shape.props.h,
			isFilled: true,
		})
	}

	component(shape: CardShape) {
		return <HTMLContainer>Hello</HTMLContainer>
	}

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

This is a minimal [ShapeUtil](?). We've given it a static property `type` that matches the type of our shape, we've provided implementations for the abstract methods [ShapeUtil#getDefaultProps](?), [ShapeUtil#getBounds](?), [ShapeUtil#component](?), and [ShapeUtil#indicator](?).

We still have work to do on the `CardShapeUtil` class, but we'll come back to it later. For now, let's put the shape onto the canvas by passing it to the [Tldraw](?) component.

### The `shapeUtils` prop

We pass an array of our shape utils into the [Tldraw](?) component's `shapeUtils` prop.

```tsx
const MyCustomShapes = [MyCardShape]

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

We can create one of our custom card shapes using the [Editor](?) API. We'll do this by setting the `onMount` prop of the [Tldraw](?) component.

```tsx
export default function () {
	return (
		<div style={{ position: 'fixed', inset: 0 }}>
			<Tldraw
				shapeUtils={MyCustomShapes}
				onMount={(editor) => {
					editor.createShapes([{ type: 'card' }])
				}}
			/>
		</div>
	)
}
```

Once the page refreshes, we should now have our custom shape on the canvas.

### Meta information

Shapes also have a `meta` property (see [TLBaseShape#meta](?)) that you can fill with your own data. This should feel like a bit of a hack, however it's intended to be an escape hatch for applications where you want to use tldraw's existing shapes but also want to attach a bit of extra data to the shape.

Note that tldraw's regular shape definitions have an unknown object for the shape's `meta` property. To type your shape's meta, use a union like this:

```ts
type MyShapeWithMeta = TLGeoShape & { meta: { createdBy: string } }

const shape = editor.getShape<MyShapeWithMeta>(myGeoShape.id)
```

You can update a shape's `meta` property in the same way you would update its props, using [Editor#updateShapes](?).

```ts
editor.updateShapes<MyShapeWithMeta>([
	{
		id: myGeoShape.id,
		type: 'geo',
		meta: {
			createdBy: 'Steve',
		},
	},
])
```

Like [TLBaseShape#props](?), the data in a [TLBaseShape#meta](?) object must be JSON serializable.

In addition to setting meta properties this way, you can also set the default meta data for shapes using the Editor's [Editor#getInitialMetaForShape](?) method.

```tsx
editor.getInitialMetaForShape = (shape: TLShape) => {
	if (shape.type === 'text') {
		return { createdBy: currentUser.id, lastModified: Date.now() }
	} else {
		return { createdBy: currentUser.id }
	}
}
```

Whenever new shapes are created using the [Editor#createShapes](?) method, the shape's meta property will be set using the [Editor#getInitialMetaForShape](?) method. By default this method returns an empty object.

### Using starter shapes

You can use "starter" shape utils like [BaseBoxShapeUtil](?) to get regular rectangular shape behavior.

### Flags

You can use flags like [ShapeUtil#hideRotateHandle](?) to hide different parts of the UI when the shape is selected, or else to control different behaviors of the shape.

### Interaction

You can turn on `pointer-events` to allow users to interact inside of the shape.

### Editing

You can make shapes "editable" to help decide when they're interactive or not.

...and more!
