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:
| Value | Resulting attachment record |
|---|---|
Local str or pathlib.Path | A 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. |
| Mapping | A 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.
| Data | Methods |
|---|---|
| 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.
| Call | Base 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.