JavaScript: Files
Load files — whether static or generated dynamically by a data loader — using the built-in FileAttachment
function. This is available by default in Markdown, but you can import it explicitly like so:
import {FileAttachment} from "npm:@observablehq/stdlib";
The FileAttachment
function takes a path and returns a file handle. This handle exposes the file’s name and MIME type.
FileAttachment("volcano.json")
Like a local import, the path is relative to the calling code’s source file: either the page’s Markdown file or the imported local JavaScript module. (To load a remote file, use fetch
, or use a data loader to download the file at build time.)
Calling FileAttachment
doesn’t actually load the file; the contents are only loaded when you invoke a file contents method. For example, to load a JSON file:
const volcano = FileAttachment("volcano.json").json();
The value of volcano
above is a promise. In other code blocks, the promise is resolved implicitly and hence you can refer to the resolved value directly.
volcano
Static analysis
The FileAttachment
function can only be passed a static string literal; constructing a dynamic path such as FileAttachment("my" + "file.csv")
is invalid syntax. Static analysis is used to invoke data loaders at build time, and ensures that only referenced files are included in the generated output during build. In the future #260, it will also allow content hashes for cache breaking during deploy.
If you have multiple files, you can enumerate them explicitly like so:
const frames = [
FileAttachment("frame1.png"),
FileAttachment("frame2.png"),
FileAttachment("frame3.png"),
FileAttachment("frame4.png"),
FileAttachment("frame5.png"),
FileAttachment("frame6.png"),
FileAttachment("frame7.png"),
FileAttachment("frame8.png"),
FileAttachment("frame9.png")
];
None of the files in frames
above are loaded until a content method is invoked, for example by saying frames[0].image()
.
Supported formats
FileAttachment
supports a variety of methods for loading file contents:
The contents often dictate the appropriate method — for example, an Apache Arrow file is almost always read with file.arrow
. When multiple methods are valid, choose based on your needs. For example, you can load a CSV file using file.text
to implement parsing yourself instead of using D3.
In addition to the above, you can get the resolved relative path to the file using file.url
. This returns a promise to a string:
FileAttachment("volcano.json").url()
See file-based routing for additional details.
Basic formats
The following common basic formats are supported natively.
Text
To load a humble text file, use file.text
:
const hello = FileAttachment("hello.txt").text();
hello
By default, file.text
expects the file to be encoded in UTF-8. To use a different encoding, pass the desired encoding name to file.text
.
const pryvit = FileAttachment("pryvit.txt").text("utf-16be");
pryvit
JSON
To load a JSON (JavaScript Object Notation) file, use file.json
FileAttachment("volcano.json").json()
A common gotcha with JSON is that it has no built-in date type; dates are therefore typically represented as ISO 8601 strings, or as a number of milliseconds or seconds since UNIX epoch.
Media
To display an image, you can use a static image in Markdown such as <img src="horse.jpg">
or 
. Likewise, you can use a video
or audio
element. Per file-based routing, static references to these files are automatically detected and therefore these files will be included in the built output.
<video src="horse.mp4" autoplay muted loop controls>
If you want to manipulate an image in JavaScript, use file.image
. For example, below we load an image and invert the RGB channel values.
const canvas = document.querySelector("#horse-canvas");
const context = canvas.getContext("2d");
const horse = await FileAttachment("horse.jpg").image();
context.drawImage(horse, 0, 0, canvas.width, canvas.height);
const data = context.getImageData(0, 0, canvas.width, canvas.height);
for (let j = 0, k = 0; j < canvas.height; ++j) {
for (let i = 0; i < canvas.width; ++i, k += 4) {
data.data[k + 0] = 255 - data.data[k + 0];
data.data[k + 1] = 255 - data.data[k + 1];
data.data[k + 2] = 255 - data.data[k + 2];
}
}
context.putImageData(data, 0, 0);
(The images above are from Eadweard Muybridge’s studies of animal locomotion.)
Markup
The file.xml
method reads an XML file and returns a promise to a Document
; it takes a single argument with the file’s MIME-type, which defaults to "application/xml"
. The file.html
method similarly reads an HTML file; it is equivalent to file.xml("text/html")
.
Binary formats
Load binary data using file.blob
to get a Blob
, or file.arrayBuffer
to get an ArrayBuffer
. For example, to read Exif image metadata with ExifReader:
import ExifReader from "npm:exifreader";
const buffer = await FileAttachment("horse.jpg").arrayBuffer();
const tags = ExifReader.load(buffer);
display(tags);
To read a file incrementally, get a ReadableStream
with file.stream
. For example, to count the number of bytes in a file:
const stream = await FileAttachment("horse.jpg").stream();
const reader = stream.getReader();
let total = 0;
while (true) {
const {done, value} = await reader.read();
if (done) break;
total += value.length;
}
display(total);