Skip to main content

Getting started

Experimental / Work in Progress

This binding is unreleased and experimental. Build from source before following this guide. See Overview for the build steps.

Your first program

The example below uses the memory service, which requires no credentials and runs entirely in-process. Open the project in utop or add it to a dune executable that depends on the opendal library.

open Opendal

(* Return the value, or print the error and exit. Every OpenDAL call returns a
[(_, string) result], so this keeps the example flat. *)
let or_fail = function
| Ok v -> v
| Error err ->
prerr_endline err;
exit 1

let () =
(* Create an operator for the in-memory service — no credentials needed. *)
let op = or_fail (Operator.new_operator "memory" []) in

(* Write a file, then read it back (read returns a char array). *)
or_fail (Operator.write op "hello.txt" (Bytes.of_string "Hello, World!"));
let content = or_fail (Operator.read op "hello.txt") in
let text = content |> Array.to_seq |> Bytes.of_seq |> Bytes.to_string in
Printf.printf "read: %s\n" text;

(* Inspect metadata, then delete. *)
let meta = or_fail (Operator.stat op "hello.txt") in
Printf.printf "size = %Ld bytes\n" (Operator.Metadata.content_length meta);
or_fail (Operator.delete op "hello.txt")

Operator.read returns (char array, string) result. Convert to Bytes with Array.to_seq |> Bytes.of_seq and then to string with Bytes.to_string.

Point it at a real backend

Only the scheme and config map change; every operation stays the same:

open Opendal

let () =
let config =
[ ("bucket", "my-bucket")
; ("region", "us-east-1")
; ("access_key_id", "...")
; ("secret_access_key", "...")
]
in
match Operator.new_operator "s3" config with
| Error err -> Printf.eprintf "Failed: %s\n" err
| Ok op ->
ignore (Operator.write op "hello.txt" (Bytes.of_string "Hello from S3!"))

See Services for every backend and its configuration keys.

Error handling

Every fallible function returns ('a, string) result. The idiomatic pattern is match:

match Operator.read op "maybe-missing.txt" with
| Ok content ->
(* use content *)
let text = content |> Array.to_seq |> Bytes.of_seq |> Bytes.to_string in
print_endline text
| Error msg ->
(* msg is a string description of the error *)
Printf.eprintf "Error: %s\n" msg

You can also use Result.get_ok to raise an exception on error (useful in tests and scripts):

let content = Operator.read op "file.txt" |> Result.get_ok in

Streaming reads and writes

For large files, avoid loading everything into memory:

(* Streaming read — read 1 KiB starting at byte offset 0 *)
match Operator.reader op "big.bin" with
| Error err -> Printf.eprintf "Reader failed: %s\n" err
| Ok r ->
let buf = Bytes.create 1024 in
(match Operator.Reader.pread r buf 0L with
| Ok n -> Printf.printf "Read %d bytes\n" n
| Error err -> Printf.eprintf "pread failed: %s\n" err)
(* Streaming write — write in chunks, then close to commit *)
match Operator.writer op "big.bin" with
| Error err -> Printf.eprintf "Writer failed: %s\n" err
| Ok w ->
ignore (Operator.Writer.write w (Bytes.of_string "first chunk\n"));
ignore (Operator.Writer.write w (Bytes.of_string "second chunk\n"));
(match Operator.Writer.close w with
| Ok _meta -> print_endline "Upload complete"
| Error err -> Printf.eprintf "Close failed: %s\n" err)

Writer.close must be called to commit the upload; it returns metadata for the written object.

Listing a directory

list loads all entries into an array; lister streams them one at a time:

(* Batch listing *)
match Operator.list op "data/" with
| Error err -> Printf.eprintf "List failed: %s\n" err
| Ok entries ->
Array.iter
(fun entry -> Printf.printf "%s\n" (Operator.Entry.name entry))
entries
(* Streaming listing — memory-efficient for large directories *)
match Operator.lister op "data/" with
| Error err -> Printf.eprintf "Lister failed: %s\n" err
| Ok lst ->
let rec loop () =
match Operator.Lister.next lst with
| Ok (Some entry) ->
Printf.printf "%s\n" (Operator.Entry.name entry);
loop ()
| Ok None -> () (* end of listing *)
| Error err -> Printf.eprintf "Next failed: %s\n" err
in
loop ()

Checking capabilities

Not every service supports every operation. Query capabilities before calling optional operations like copy or rename:

let info = Operator.info op in
let cap = Operator.OperatorInfo.capability info in
if Operator.Capability.copy cap then
ignore (Operator.copy op "source.txt" "backup.txt")
else
print_endline "copy not supported by this backend"