---
title: File
description: The file plugin adds Notion's file block, an attachment uploaded through your upload function or linked, shown with its name and size.
icon: paperclip
---

`createFilePlugin()` adds Notion's file block: an empty panel (your `empty` snippet, `createMediaEmptyPlugin({ empty })`) until the block has a file, then the file's name and size, linking to it, with an editable caption. Files are uploaded through the `upload` function you provide, or linked. It is not a default plugin: list it.

```svelte title="Editor.svelte" check
<script lang="ts">
	import { Edytor, createFilePlugin, createUploadsPlugin } from 'edytor';

	// Optional: the view's uploader (an Upload button, and pasted or dropped files).
	const uploads = createUploadsPlugin({
		upload: async (file) => {
			const body = new FormData();
			body.append('file', file);
			const response = await fetch('/api/uploads', { method: 'POST', body });
			return (await response.json()).url;
		}
	});
</script>

<Edytor plugins={[uploads, createFilePlugin()]} />
```

A [slash menu](/docs/plugins/slash-menu) then lists "File" under `Media`. Without the uploads plugin (`createUploadsPlugin({ upload })`), files are linked only.

## Options

`createFilePlugin(options)` takes a `FilePluginOptions`; the empty block is drawn by the view's media empty plugin, its Upload by the uploads plugin.

| Prop | Type | Default | Description |
| - | - | - | - |
| `row?` | `Snippet<[FileRowPayload]>` | - | The row of a file block with a source, replacing Notion's (a paperclip, the name and the size). The first file plugin listed in a view gives it. |

`row` receives a `FileRowPayload`: `block` (`undefined` in a suggestion's preview), `src`, `name`, `size` (bytes, when it was uploaded), `sizeLabel` (that size in the view's words, `''` without one) and `link`, the attachment of your `a`, as a menu's row takes its own: `target="_blank"`, `rel="noopener noreferrer nofollow"`, `draggable="false"` and `download` (the name). Render its `href={file.src}` yourself, so a server-rendered readonly view links too. It is drawn inside the block's void wrapper (`[data-edytor-file]`), above the caption.

```svelte title="Files.svelte" check
<script lang="ts">
	import { Edytor, createFilePlugin, type FileRowPayload } from 'edytor';

	const files = createFilePlugin({ row });
</script>

{#snippet row(file: FileRowPayload)}
	<a class="attachment" href={file.src} {@attach file.link}>
		<span>{file.name}</span>
		{#if file.sizeLabel}<small>{file.sizeLabel}</small>{/if}
	</a>
{/snippet}

<Edytor plugins={[files]} />
```


## The kind

| Kind   | Element  | Data                                          | Preset                                                                |
| ------ | -------- | --------------------------------------------- | --------------------------------------------------------------------- |
| `file` | `figure` | `{ src: string, name: string, size?: number }` | File (📎, group `Media`, keywords `attachment`, `upload`, `pdf`) |

An upload stores the URL `upload` answers with the picked file's `name` and `size` (bytes), as one data write, only when the block is still there, a file block and without a source (a source a collaborator set while the upload ran stays); a link stores its URL with the name read from its last path segment. A source is accepted only when it is an `http:`/`https:` URL with a host or a `blob:` URL of one (`safeMediaSrc`), never `data:`: a file stored inline would be sent in every update. The rendered link has `target="_blank"`, `rel="noopener noreferrer nofollow"` and `download` set to the name.

The block is void like the [image](/docs/plugins/image), and its only text is the caption. The command id is `block.file`. Its role is in `defaultSemantics`. In a readonly view the empty block is a passive placeholder, so a viewer never runs `upload`. With `upload`, pasted and dropped files no other kind takes (a PDF, an archive; an image goes to the image block, a video or audio file to theirs when those kinds upload) become file blocks, shown uploading with a progress bar, one undo step for the paste; see [Pasted and dropped files](/docs/editor/clipboard#pasted-and-dropped-files).

## Clipboard

- Copying exports `<figure data-edytor-file><a href="…">name</a><figcaption>caption</figcaption></figure>`; the plain text is the URL, then the caption.
- Pasting that HTML back imports a file block.

## Styling

| Selector                    | Matches                         |
| --------------------------- | ------------------------------- |
| `[data-edytor-type='file']` | The `figure`                    |
| `[data-edytor-file]`        | The link's wrapper              |
| `[data-edytor-file-name]`   | The name                        |
| `[data-edytor-file-size]`   | The size (`5 B`, `1.2 MB`)      |

The row's look (its shade, the hover, the muted size) is the [Notion theme](/docs/customization/styling#notion-theme)'s. The empty state uses the shared `media` selectors (see [Embed](/docs/plugins/embed#styling)).
