Skip to content

Download an ARC RO-Crate

DataPLANT DataHubs publish a generated ARC RO-Crate metadata file in the project's generic Package Registry. Download it with client.Packages.DownloadROCrateAsync(projectId) (available since version 0.5.0). The method takes the numeric project ID and uses these built-in package coordinates:

Coordinate Value
Package name isa_arc_json
Package version 0.0.1
File name arc-ro-crate-metadata.json

This retrieves the currently available generated main-branch export. It does not generate a new export, and the available file is not guaranteed to represent the latest commit on main.

For a file stored under different package coordinates, use DownloadGenericFileAsync.

Download the metadata

Replace 42 with the ARC's numeric project ID. The example uses anonymous access for a public export. For server-side downloads that require credentials, replace Authentication.Anonymous() with Authentication.PersonalAccessToken("your-datahub-pat"). For browser downloads that require redirects, use OAuth bearer authentication; see redirects and authentication for supported authentication modes and CORS requirements.

This shared JavaScript / TypeScript example retrieves the JSON-LD text in a browser or Node.js. It does not write a file and needs no filesystem import. Frontend npm imports are resolved by your application's bundler, such as Vite.

import { DataHubClient } from "@nfdi4plants/datahub-client";
import { Authentication } from "@nfdi4plants/datahub-client/Http/Authentication.js";

const client = new DataHubClient("https://git.nfdi4plants.org", Authentication.Anonymous());
const projectId = 42;

const metadata = await client.Packages.DownloadROCrateAsync(projectId);

Download and save

The method returns JSON-LD text. The F#, Node.js, and Python examples save that text as UTF-8 to arc-ro-crate-metadata.json in the current working directory, replacing an existing file with that name. The browser example starts a file download through the browser. In an existing Python async application, use await download_ro_crate() inside the running event loop instead of asyncio.run.

#r "nuget: DataHubClient"

open System.IO
open System.Text
open DataHubClient

let client = DataHubClient("https://git.nfdi4plants.org", Authentication.Anonymous())
let projectId = 42

let metadata =
    client.Packages.DownloadROCrateAsync(projectId)
    |> Async.RunSynchronously

File.WriteAllText("arc-ro-crate-metadata.json", metadata, UTF8Encoding(false))
printfn "Saved arc-ro-crate-metadata.json"
import { DataHubClient } from "@nfdi4plants/datahub-client";
import { Authentication } from "@nfdi4plants/datahub-client/Http/Authentication.js";

const client = new DataHubClient("https://git.nfdi4plants.org", Authentication.Anonymous());
const projectId = 42;

const metadata = await client.Packages.DownloadROCrateAsync(projectId);
import { writeFile } from "node:fs/promises";

await writeFile("arc-ro-crate-metadata.json", metadata, "utf8");
console.log("Saved arc-ro-crate-metadata.json");

Save this JavaScript helper as browser.mjs in your application. It uses a Blob and a download link to save the returned text:

/** Download an ARC's JSON-LD metadata using the browser's save-file flow. */
export async function downloadROCrate(client, projectId) {
  const metadata = await client.Packages.DownloadROCrateAsync(projectId);
  const blob = new Blob([metadata], { type: "application/ld+json;charset=utf-8" });
  const url = URL.createObjectURL(blob);
  const link = document.createElement("a");
  link.href = url;
  link.download = "arc-ro-crate-metadata.json";
  link.hidden = true;
  document.body.append(link);
  try {
    link.click();
  } finally {
    link.remove();
    // Give the browser time to start the download before releasing its URL.
    setTimeout(() => URL.revokeObjectURL(url), 1000);
  }
}

For a .ts helper with explicit parameter types, see TypeScript browser downloads.

Call it from a button in your frontend module. These npm imports are resolved by your application's bundler, such as Vite:

import { DataHubClient } from "@nfdi4plants/datahub-client";
import { Authentication } from "@nfdi4plants/datahub-client/Http/Authentication.js";
import { downloadROCrate } from "./browser.mjs";

const client = new DataHubClient("https://git.nfdi4plants.org", Authentication.Anonymous());
const button = document.createElement("button");
button.textContent = "Download RO-Crate";
button.addEventListener("click", () => {
  downloadROCrate(client, 42).catch(console.error);
});
document.body.append(button);
import asyncio
from pathlib import Path
from datahub_client import DataHubClient, Authentication

client = DataHubClient("https://git.nfdi4plants.org", Authentication.Anonymous())
project_id = 42


async def download_ro_crate():
    metadata = await client.Packages.DownloadROCrateAsync(project_id)
    Path("arc-ro-crate-metadata.json").write_text(metadata, encoding="utf-8", newline="")
    print("Saved arc-ro-crate-metadata.json")


asyncio.run(download_ro_crate())

Browser imports and CORS

node:fs/promises is Node.js's filesystem module; it cannot be imported into a browser application. It appears in the Node.js example to write the downloaded text to disk. DataHubClient itself uses fetch and returns a string; use the browser example above to save that string as a file.

When adapting the Node.js save example for a browser, remove its filesystem import as well as the writeFile call. A retained static import can prevent the module from loading even when the imported function is never called. The metadata-only snippet above contains just the client imports and download.

When the DataHub and your application have different origins, the DataHub must allow your application's origin through CORS. Requests include X-DataHubClient-Version, so even anonymous downloads need a successful preflight that allows this header. Authenticated downloads also need the authentication header allowed, such as PRIVATE-TOKEN. A rejected CORS request is a separate problem from an unsupported node:fs/promises import.

Missing or inaccessible exports

A public project does not guarantee an available, anonymously readable export. If the requested export is missing, the server returns HTTP 404, exposed as NotFoundError. HTTP 401 or 403 is exposed as UnauthorizedError and means the request needs valid credentials or additional access. Check the project and package visibility and use an appropriate token when needed.

The download raises an error in these cases; it does not return an empty RO-Crate. Errors expose StatusCode and the server's response Body for diagnostics.

Redirects and authentication

Package downloads can redirect from the DataHub to a signed object-storage URL. The default .NET and Python transports, and the JavaScript transport in Node, follow those redirects while removing Authorization, PRIVATE-TOKEN, and JOB-TOKEN when the origin changes (scheme, hostname, or port). Removed credentials are not restored if a later redirect returns to the DataHub.

Browsers hide intermediate redirect destinations from JavaScript. A browser request using Authentication.PersonalAccessToken(...) or Authentication.JobToken(...) therefore stops with an explanatory error at any redirect, including a same-origin redirect, before sending the token to the destination. Anonymous downloads and OAuth bearer authentication through Authentication.OAuthToken(...) use native redirect handling; browsers remove Authorization when the origin changes. CORS must permit the download too. For private browser downloads that require GitLab's custom token headers, use a server-side client to perform the download.

If you inject your own .NET HttpClient into DotNetHttpClient, configure its handler with AllowAutoRedirect = false so the transport can inspect redirects. Supply authentication through Authentication or individual request headers; credential-bearing HttpClient.DefaultRequestHeaders cause cross-origin redirects to fail, because HttpClient would otherwise add those defaults back. Custom handlers must not add credentials to redirected requests.

See live integration tests for verifying the DataHub's storage configuration and actual download path.

Offline verification

The F#, JavaScript, TypeScript, and Python sample programs replace HTTP access with a fixture, check the exact package endpoint and anonymous headers, and verify the saved UTF-8 content in a temporary working directory. The Python sample uses the real HTTPX client with a mock transport to verify HTTP 302 redirects to an object-storage host. The browser checks exercise the helper above with a local HTTP server and verify the downloaded file. These checks need no DataHub credentials or live export.

See Packages for the remaining Package Registry calls.