Skip to main content

File attachments

files registers named inputs for Observable's FileAttachment builtin. Local files are read during Notebook construction and sent to the browser as data URLs.

from pathlib import Path

import observablejs as obs

data_dir = Path("data")
data_dir.mkdir(exist_ok=True)
(data_dir / "rows.csv").write_text(
"date,value\n2026-07-09,12\n2026-07-10,18\n",
encoding="utf-8",
)

notebook = obs.Notebook(
obs.ojs(
'rows = FileAttachment("rows.csv").csv({typed: true})',
key="rows",
),
files={"rows.csv": "rows.csv"},
base_path=data_dir,
)

The mapping key is the name used by FileAttachment. In the example, typed: true asks the CSV reader to convert values such as numbers and dates.

Accepted files values

obs.Notebook(..., files={name: value}, base_path=None)

Each value can be one of these forms:

ValueResulting attachment record
Local str or pathlib.PathA data URL plus inferred mimeType and byte size.
URL string such as https:, data:, or blob:The URL plus a mimeType inferred from the mapping key.
MappingA normalized record with url and optional metadata.

FileAttachment records use camelCase field names.

notebook = obs.Notebook(
obs.ojs('countries = FileAttachment("countries.json").json()'),
files={
"countries.json": {
"url": "https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json",
"mimeType": "application/json",
}
},
)

A mapping used by FileAttachment requires a string url. mimeType, lastModified, and size are optional. Python keeps these browser attachment fields in the synchronized record.

Browser access

FileAttachment(name) returns Notebook Kit's browser attachment object. Its href, name, mimeType, lastModified, and size properties are always present. lastModified and size are undefined when the registered record omits them.

DataMethods
URL.href, .url()
Raw response.blob(), .arrayBuffer(), .stream(), .text()
JSON and delimited text.json(), .csv(), .tsv(), .dsv()
Documents and images.image(), .xml(), .html()
Arrow, Parquet, and workbooks.arrow(), .arquero(), .parquet(), .zip(), .xlsx()

.href contains the attachment URL. Notebook Kit's deprecated .url() method returns the same value asynchronously. Use .href in new cells.

The widget also adds .sqlite(), which returns a SQLiteDatabaseClient with query, queryRow, sql, and schema-inspection methods. Before calling it, set globalThis.observablejsSqlite to an object with initSqlJs and an optional locateFile, or assign the loader directly to globalThis.initSqlJs.

Path and base resolution

Local paths are expanded with Path.expanduser() and resolved to absolute paths during construction.

base_path supplies a resolution base. It is not a filesystem boundary, so a relative path containing .. can resolve outside that directory.

CallBase for a relative files value
Notebook(..., base_path=path)path
Notebook(..., base_path=None)Current working directory at construction time
Notebook.from_html(..., base_path=path)path

The local file bytes are captured when the notebook is created. The registered data URL remains that construction-time snapshot across later file and current working directory changes.

An explicit local path raises FileNotFoundError when it is missing. File read and metadata failures raise the corresponding OSError subclass.

Attachment records

notebook.attachments is the attachment mapping from the latest detached, recursively read-only NotebookState snapshot. For a local file, the record has this shape:

{
"rows.csv": {
"url": "data:text/csv;base64,...",
"mimeType": "text/csv",
"size": 39,
}
}

The MIME type comes from the attachment name. .arrow and .parquet use Apache Arrow and Apache Parquet media types. Unknown suffixes use application/octet-stream.

Names absent from notebook.attachments resolve against the browser document base URI.

Frameworks that listen to traitlets can observe notebook.state for changes. Attachments are fixed at construction, so create a new notebook to change them.

Source HTML discovery

Notebook.from_html can discover local attachment calls in Notebook Kit HTML:

notebook = obs.Notebook.from_html(
source,
base_path="notebooks/report",
embed_file_attachments=True,
)

embed_file_attachments=True registers existing local files referenced by literal FileAttachment("name") calls inside JavaScript notebook cells. Static template literals such as FileAttachment(`rows.csv`) and imported aliases from observablehq:stdlib are also recognized. Discovery ignores calls in comments, string literals, regular-expression literals, Markdown cells, and scripts outside the <notebook> element.

Discovery populates notebook.attachments with data URLs and keeps each FileAttachment call in the imported document definition. Explicit files entries take precedence when they use the same name. Each discovered name whose resolved path is an existing file becomes a record. Remaining names use browser URL resolution.

base_path is required when embed_file_attachments=True. Pass the directory that should resolve relative attachment names. See Source constructors for the source constructor contract and relative JavaScript import rewriting.

Serialized HTML boundary

notebook.to_notebook_html() returns the Notebook Kit document text, not its attachment records. When that text is consumed independently, its environment must provide the URLs used by FileAttachment, or the source must use URLs that remain reachable there.