Get Started

Datomic is a general purpose database system based on immutable database values, an indelible log, and time-aware entity-attribute-value tuples. This design provides first-class “as-of” queries and auditability and flexible representation and query of data regardless of shape. Let’s try it out.

Choose a Datomic edition to use for this tutorial:

Hello, Datomic Local

Install sample database

We will use the streets database in datomic-samples for Datomic Local.

  • Mac/Linux install script:

    curl -fsSL https://www.datomic.com/install-datomic-samples.sh | sh
  • Windows install script (PowerShell):

    irm https://www.datomic.com/install-datomic-samples.ps1 | iex
  • Manually:

    1. download,

    2. unzip,

    3. create ~/.datomic/local.edn: {:storage-dir "/absolute/path/unzipped"}

Launch a project REPL

Datomic Local is a JAR available from Maven. Install it by creating a hello-datomic project directory with a deps.edn file containing the following:

{:deps {com.datomic/local {:mvn/version "1.0.301"}}}

Install Clojure if you haven’t already. Then launch a REPL (from the command line or in your IDE) and require the Client API:

(require '[datomic.client.api :as d])
(ns hello-datomic
  (:require [datomic.client.api :as d]))

Define a client pointing to the samples you installed.

(def client
  (d/client {:server-type :datomic-local
             :system "datomic-samples"}))

Connect to the pre-loaded streets database from datomic-samples and get a database value to read from:

(def conn (d/connect client {:db-name "streets"}))

(def db (d/db conn))

Querying

Let’s retrieve all the facts about Joe.

(d/pull db '[*] [:person/name "Joe"])
;; => {:db/id ...
;;     :person/name "Joe"
;;     :person/street "Broadway"}

That function pulls the current state of the entity (collection of facts) identified by the lookup ref [:person/name "Joe"]. (A lookup ref uses domain attributes to name a unique entity id.)

We’re not limited to the present, though. Past facts about Joe are still in the database. Passing (d/history db) to a query opens up the complete record of assertions and retractions:

(d/q '[:find ?street-name
       :in $ ?name
       :where
       [?e :person/name ?name]
       [?e :person/street ?street-name]]
     (d/history db) "Joe")
;; => [["1st"] ["Broadway"]]

This Datalog query returns every value the database has ever had for “street Joe lives on”. Joe’s old street name appears because the history database includes retracted facts as well as current ones.

We’re not limited to the audit trail of one entity. Datomic provides consistent whole-world snapshots of all past states:

;; Who lived on which street, before Joe moved out of 1st street?
(d/q '[:find ?name ?street
       :where
       [?e :person/name ?name]
       [?e :person/street ?street]]
     (d/as-of db #inst "1982-12-31"))
;; => [["John" "Main"] ["Mary" "Elm"] ["Joe" "1st"]]

Note that once we acquired db from conn, none of these queries required a database connection. Datomic queries are functional: they run locally against stable database values.

Changes

Say Joe moves to Park Ave, and Mary takes over his old apartment on Broadway. We add those facts to Datomic by asserting them in a transaction:

(def tx-result
  (d/transact conn
              {:tx-data [{:person/name "Joe",
                          :person/street "Park Ave"}
                         {:person/name "Mary",
                          :person/street "Broadway"}
                         {:db/id "datomic.tx"
                          :db/doc "Key hand-off confirmed by Alice"}]}))

(Notice we record :db/doc information on the transaction entity itself by using the tempid datomic.tx. Entities model the “what” of our domain; transactions are a good place to model “when”, “who”, “where”, and “why” with arbitrary attributes.)

d/transact returns a map with keys :db-before, :db-after, :tx-data, and :tempids.

  • :db-before and :db-after are immutable database values capturing the world before and after the transaction

  • :tx-data is a data structure of facts Datomic added and retracted

  • :tempids maps any temporary entity IDs to their permanent ones

Working with immutable database values

Because the db value we’ve been using was created before this transaction, querying it again reflects state of that time — then, now, and always.

(d/pull db '[*] [:person/name "Joe"])
;; => {:db/id ...
;;     :person/name "Joe"
;;     :person/street "Broadway"}

This may feel strange at first, but stable database values are useful and form the foundation of a simple architecture. New state is easily seen by using the database from tx-result’s :db-after or a fresh call to (d/db conn):

(d/pull (:db-after tx-result) '[*] [:person/name "Joe"])
;; => {:db/id ...
;;     :person/name "Joe"
;;     :person/street "Park Ave"}
(d/pull (d/db conn) '[*] [:person/name "Mary"])
;; => {:db/id ...
;;     :person/name "Mary"
;;     :person/street "Broadway"}

Datoms

Inspect :tx-data and you’ll notice it doesn’t assert our facts in the key/value shape we submitted:

(:tx-data tx-result)
;; => (#datom[<entity A> 50 #inst "2026-04-16T10:38:23.382-00:00" <tx-id> true]
;;     #datom[<entity B> 74 "Park Ave"                            <tx-id> true]
;;     #datom[<entity B> 74 "Broadway"                            <tx-id> false]
;;     #datom[<entity C> 74 "Broadway"                            <tx-id> true]
;;     #datom[<entity C> 74 "2nd"                                 <tx-id> false]
;;     #datom[<entity A> 63 "Key hand-off confirmed by Alice"     <tx-id> true])

A fact in Datomic is represented as a datom consisting of entity, attribute, value, transaction, and a boolean for whether the fact is being asserted or retracted. (One can think of it like an subject-predicate-object triple with a temporal component.) The keys and values we transacted are in the shorthand map form for transaction data.

More on transaction semantics

Datomic doesn’t update values in place. That means changing an attribute to a new value involves an assertion datom for the new value and a retraction datom for the old value. That’s why you see two datoms each for entity B and entity C in the tx-data.

Note also that Datomic transactions consist of the addition of a set of datoms. While other database systems may represent a transaction as an ordered series of smaller updates, a Datomic transaction is a singular declaration with no intra-transaction sequence. There is no concept of ordering within a transaction. Datomic transactions remain ACID and inter-transaction semantics guarantee strong serializability.

Querying changes

Because Datomic transactions are first-class and a part of each datom, transactions are queryable like any other entity. We can grab the ID of the transaction that asserted Joe’s move to Park Ave (?tx inside the query), then use it to pull both the wall-clock time and the :db/doc we stored on that transaction:

(def tx-id
  (ffirst (d/q '[:find ?tx
                 :in $ ?name ?street
                 :where
                 [?e :person/name ?name]
                 [?e :person/street ?street ?tx true]]
               (d/history (d/db conn))
               "Joe"
               "Park Ave")))

(d/pull (d/db conn) '[:db/txInstant :db/doc] tx-id)
;; => {:db/txInstant #inst "2026-04-20T..."
;;     :db/doc "Key hand-off confirmed by Alice"}

Conclusion

That’s enough for a first walkthrough. You used Datomic Local to query the present and past, transact datoms, query transaction entities, and work with immutable database values. What’s next?

The Rationale surveys the design decisions behind Datomic: perception without coordination, immutable database values, and an information model based on accreting atomic facts.

For more walkthroughs and feature explanations, check out our Guides.

Or dive into the reference Documentation.

Hello, Datomic Pro

Install Datomic Pro

Datomic Pro is distributed as a zip file. Download the latest version or run this curl command:

curl https://datomic-pro-downloads.s3.amazonaws.com/1.0.7705/datomic-pro-1.0.7705.zip -O

Unzip that to a central location. You will run scripts that live in its bin directory.

Run a transactor

Datomic Pro requires a running transactor to perform writes to a storage service. We’ll use dev storage: a JDBC server embedded in the transactor, storing to local disk, designed for interactive development. (In production your storage service is a separate process running SQL, DynamoDB, or Cassandra.)

Start your transactor by running this command from inside the Datomic Pro directory you unzipped:

bin/transactor config/samples/dev-transactor-template.properties

(In a permanent project you would make a copy of that sample transactor config file in your project directory. For our purposes here, running directly off the sample is sufficient.)

Install the streets sample database

Download datomic-samples-backups, which contains sample datasets suitable for Datomic Pro. Unzip it and restore the streets database:

/path/to/datomic-pro/bin/datomic -Xmx4g -Xms4g restore-db \
  file:/path/to/datomic-samples-backups/streets \
  datomic:dev://localhost:4334/streets

Launch a project REPL

Create a hello-datomic project directory with a deps.edn file containing the following:

{:deps {com.datomic/peer {:mvn/version "1.0.7705"}}}

Install Clojure if you haven’t already. Then launch a REPL (from the command line or in your IDE) and require the Peer API:

(require '[datomic.api :as d])
(ns hello-datomic
  (:require [datomic.api :as d]))

Connect to the streets sample database and get a database value to read from:

(def uri "datomic:dev://localhost:4334/streets")

(def conn (d/connect uri))

(def db (d/db conn))

Querying

Let’s retrieve all the facts about Joe.

(d/pull db '[*] [:person/name "Joe"])
;; => {:db/id ...
;;     :person/name "Joe"
;;     :person/street "Broadway"}

That function pulls the current state of the entity (collection of facts) identified by the lookup ref [:person/name "Joe"]. (A lookup ref uses domain attributes to name a unique entity id.)

We’re not limited to the present, though. Past facts about Joe are still in the database. Passing (d/history db) to a query opens up the complete record of assertions and retractions:

(d/q '[:find ?street-name
       :in $ ?name
       :where
       [?e :person/name ?name]
       [?e :person/street ?street-name]]
     (d/history db) "Joe")
;; => #{["1st"] ["Broadway"]}

This Datalog query returns every value the database has ever had for “street Joe lives on”. Joe’s old street name appears because the history database includes retracted facts as well as current ones.

We’re not limited to the audit trail of one entity. Datomic provides consistent whole-world snapshots of all past states:

;; Who lived on which street, before Joe moved out of 1st street?
(d/q '[:find ?name ?street
       :where
       [?e :person/name ?name]
       [?e :person/street ?street]]
     (d/as-of db #inst "1982-12-31"))
;; => #{["John" "Main"] ["Mary" "Elm"] ["Joe" "1st"]}

Note that once we acquired db from conn, none of these queries required a database connection. Datomic queries are functional: they run locally against stable database values.

Changes

Say Joe moves to Park Ave, and Mary takes over his old apartment on Broadway. We add those facts to Datomic by asserting them in a transaction:

(def tx-result
  @(d/transact conn
               [{:person/name "Joe",
                 :person/street "Park Ave"}
                {:person/name "Mary",
                 :person/street "Broadway"}
                {:db/id "datomic.tx",
                 :db/doc "Key hand-off confirmed by Alice"}]))

(Notice we record :db/doc information on the transaction entity itself by using the tempid datomic.tx. Entities model the “what” of our domain; transactions are a good place to model “when”, “who”, “where”, and “why” with arbitrary attributes.)

d/transact returns a complete future containing a map with keys :db-before, :db-after, :tx-data, and :tempids.

  • :db-before and :db-after are immutable database values capturing the world before and after the transaction

  • :tx-data is a data structure of facts Datomic added and retracted

  • :tempids maps temporary entity IDs to their permanent ones

Working with immutable database values

Because the db value we’ve been using was created before this transaction, querying it again reflects state of that time — then, now, and always.

(d/pull db '[*] [:person/name "Joe"])
;; => {:db/id ...
;;     :person/name "Joe"
;;     :person/street "Broadway"}

This may feel strange at first, but stable database values are useful and form the foundation of a simple architecture. New state is easily seen by using the database from tx-result’s :db-after or a fresh call to (d/db conn):

(d/pull (:db-after tx-result) '[*] [:person/name "Joe"])
;; => {:db/id ...
;;     :person/name "Joe"
;;     :person/street "Park Ave"}
(d/pull (d/db conn) '[*] [:person/name "Mary"])
;; => {:db/id ...
;;     :person/name "Mary"
;;     :person/street "Broadway"}

Datoms

Inspect :tx-data and you’ll notice it doesn’t assert our facts in the key/value shape we submitted:

(:tx-data tx-result)
;; => (#datom[<entity A> 50 #inst "2026-04-16T10:38:23.382-00:00" <tx-id> true]
;;     #datom[<entity B> 73 "Park Ave"                            <tx-id> true]
;;     #datom[<entity B> 73 "Broadway"                            <tx-id> false]
;;     #datom[<entity C> 73 "Broadway"                            <tx-id> true]
;;     #datom[<entity C> 73 "2nd"                                 <tx-id> false]
;;     #datom[<entity A> 62 "Key hand-off confirmed by Alice"     <tx-id> true])

A fact in Datomic is represented as a datom consisting of entity, attribute, value, transaction, and a boolean for whether the fact is being asserted or retracted. (One can think of it like an subject-predicate-object triple with a temporal component.) The keys and values we transacted are in the shorthand map form for transaction data.

More on transaction semantics

Datomic doesn’t update values in place. That means changing an attribute to a new value involves an assertion datom for the new value and a retraction datom for the old value. That’s why you see two datoms each for entity B and entity C in the tx-data.

Note also that Datomic transactions consist of the addition of a set of datoms. While other database systems may represent a transaction as an ordered series of smaller updates, a Datomic transaction is a singular declaration with no intra-transaction sequence. There is no concept of ordering within a transaction. Datomic transactions remain ACID and inter-transaction semantics guarantee strong serializability.

Querying changes

Because Datomic transactions are first-class and a part of each datom, transactions are queryable like any other entity. We can grab the ID of the transaction that asserted Joe’s move to Park Ave (?tx inside the query), then use it to pull both the wall-clock time and the :db/doc we stored on that transaction:

(def tx-id
  (ffirst (d/q '[:find ?tx
                 :in $ ?name ?street
                 :where
                 [?e :person/name ?name]
                 [?e :person/street ?street ?tx true]]
               (d/history (d/db conn))
               "Joe"
               "Park Ave")))

(d/pull (d/db conn) '[:db/txInstant :db/doc] tx-id)
;; => {:db/txInstant #inst "2026-04-20T..."
;;     :db/doc "Key hand-off confirmed by Alice"}

Conclusion

That’s enough for a first walkthrough. You used Datomic Pro to query the present and past, transact datoms, query transaction entities, and work with immutable database values. What’s next?

The Rationale surveys the design decisions behind Datomic: perception without coordination, immutable database values, and an information model based on accreting atomic facts.

For more walkthroughs and feature explanations, check out our Guides.

Or dive into the reference Documentation.

Hello, Datomic Cloud

Launch Datomic Cloud

Datomic Cloud runs on AWS and is launched via CloudFormation.

  1. Make sure your AWS account has an EC2 key pair. See AWS account setup for details.

  2. Create a CloudFormation stack for Datomic storage

    • Use the most recent Storage Template Amazon S3 URL:

      https://s3.amazonaws.com/datomic-cloud-1/cft/1254/storage-template-9433-1254.json
    • Note the stack name for the next step.

  3. Create a CloudFormation stack for Datomic compute

    • Use the most recent Compute Template Amazon S3 URL:

      https://s3.amazonaws.com/datomic-cloud-1/cft/1254/compute-template-9433-1254.json
    • Once deployed, go to Outputs and note ClientApiGatewayEndpoint for your client config later.

Launch a project REPL

Create a hello-datomic project directory with a deps.edn file declaring a dependency on the Client API:

{:deps {com.datomic/client-cloud {:mvn/version "1.0.137"}}}

Install Clojure if you haven’t already. Then launch a REPL (from the command line or in your IDE) and require the Client API:

(require '[datomic.client.api :as d])
(ns hello-datomic
  (:require [datomic.client.api :as d]))

Define a client pointing to your Cloud system. Substitute the values from the AWS region, storage stack, and compute stack you created above:

(def client
  (d/client {:server-type :cloud
             :region "<your AWS Region>" ; e.g. us-east-1
             :system "<system name>" ; from storage stack you created
             :endpoint "<your endpoint>" ; from compute stack you created -> Outputs -> ClientApiGatewayEndpoint
             :creds-profile "<your_aws_profile_if_not_using_the_default>"}))

Create a database

Make a database named streets and connect to it.

(d/create-database client {:db-name "streets"})

(def conn (d/connect client {:db-name "streets"}))

Load the schema and sample data

Submit these transactions containing the streets schema and facts about some sample events.

(run! (fn [txd] (d/transact conn {:tx-data txd}))
      [;; Schema: people have names and streets
       [{:db/ident         :person/name
         :db/unique        :db.unique/identity
         :db/valueType     :db.type/string
         :db/cardinality   :db.cardinality/one}
        {:db/ident         :person/street
         :db/valueType     :db.type/string
         :db/cardinality   :db.cardinality/one}
        {:db/id "datomic.tx" ; back-date each transaction to get a queryable timeline
         :db/txInstant #inst "1970"}]
       ;; Some people and their streets
       [{:person/name "John"
         :person/street "Main"}
        {:person/name "Joe"
         :person/street "1st"}
        {:person/name "Mary"
         :person/street "Elm"}
        {:db/id "datomic.tx"
         :db/txInstant #inst "1980"}]
       ;; Mary and Joe both moved at the same time
       [{:db/id [:person/name "Mary"]
         :person/street "2nd"}
        {:db/id [:person/name "Joe"]
         :person/street "Broadway"}
        {:db/id "datomic.tx"
         :db/txInstant #inst "1983"}]
       ;; John moved to Ash st.
       [{:db/id [:person/name "John"]
         :person/street "Ash"}
        {:db/id "datomic.tx"
         :db/txInstant #inst "1986"}]
       ;; John moves a lot
       [{:db/id [:person/name "John"]
         :person/street "3rd"}
        {:db/id "datomic.tx"
         :db/txInstant #inst "1989"}]])

Now we can grab a database value to read from.

(def db (d/db conn))

Querying

Let’s retrieve all the facts about Joe.

(d/pull db '[*] [:person/name "Joe"])
;; => {:db/id ...
;;     :person/name "Joe"
;;     :person/street "Broadway"}

That function pulls the current state of the entity (collection of facts) identified by the lookup ref [:person/name "Joe"]. (A lookup ref uses domain attributes to name a unique entity id.)

We’re not limited to the present, though. Past facts about Joe are still in the database. Passing (d/history db) to a query opens up the complete record of assertions and retractions:

(d/q '[:find ?street-name
       :in $ ?name
       :where
       [?e :person/name ?name]
       [?e :person/street ?street-name]]
     (d/history db) "Joe")
;; => [["1st"] ["Broadway"]]

This Datalog query returns every value the database has ever had for “street Joe lives on”. Joe’s old street name appears because the history database includes retracted facts as well as current ones.

We’re not limited to the audit trail of one entity. Datomic provides consistent whole-world snapshots of all past states:

;; Who lived on which street, before Joe moved out of 1st street?
(d/q '[:find ?name ?street
       :where
       [?e :person/name ?name]
       [?e :person/street ?street]]
     (d/as-of db #inst "1982-12-31"))
;; => [["John" "Main"] ["Mary" "Elm"] ["Joe" "1st"]]

Note that once we acquired db from conn, none of these queries required a database connection. Datomic queries are functional: they run locally against stable database values.

Changes

Say Joe moves to Park Ave, and Mary takes over his old apartment on Broadway. We add those facts to Datomic by asserting them in a transaction:

(def tx-result
  (d/transact conn
              {:tx-data [{:person/name "Joe",
                          :person/street "Park Ave"}
                         {:person/name "Mary",
                          :person/street "Broadway"}
                         {:db/id "datomic.tx"
                          :db/doc "Key hand-off confirmed by Alice"}]}))

(Notice we record :db/doc information on the transaction entity itself by using the tempid datomic.tx. Entities model the “what” of our domain; transactions are a good place to model “when”, “who”, “where”, and “why” with arbitrary attributes.)

d/transact returns a map with keys :db-before, :db-after, :tx-data, and :tempids.

  • :db-before and :db-after are immutable database values capturing the world before and after the transaction

  • :tx-data is a data structure of facts Datomic added and retracted

  • :tempids maps any temporary entity IDs to their permanent ones

Working with immutable database values

Because the db value we’ve been using was created before this transaction, querying it again reflects state of that time — then, now, and always.

(d/pull db '[*] [:person/name "Joe"])
;; => {:db/id ...
;;     :person/name "Joe"
;;     :person/street "Broadway"}

This may feel strange at first, but stable database values are useful and form the foundation of a simple architecture. New state is easily seen by using the database from tx-result’s :db-after or a fresh call to (d/db conn):

(d/pull (:db-after tx-result) '[*] [:person/name "Joe"])
;; => {:db/id ...
;;     :person/name "Joe"
;;     :person/street "Park Ave"}
(d/pull (d/db conn) '[*] [:person/name "Mary"])
;; => {:db/id ...
;;     :person/name "Mary"
;;     :person/street "Broadway"}

Datoms

Inspect :tx-data and you’ll notice it doesn’t assert our facts in the key/value shape we submitted:

(:tx-data tx-result)
;; => (#datom[<entity A> 50 #inst "2026-04-16T10:38:23.382-00:00" <tx-id> true]
;;     #datom[<entity B> 74 "Park Ave"                            <tx-id> true]
;;     #datom[<entity B> 74 "Broadway"                            <tx-id> false]
;;     #datom[<entity C> 74 "Broadway"                            <tx-id> true]
;;     #datom[<entity C> 74 "2nd"                                 <tx-id> false]
;;     #datom[<entity A> 63 "Key hand-off confirmed by Alice"     <tx-id> true])

A fact in Datomic is represented as a datom consisting of entity, attribute, value, transaction, and a boolean for whether the fact is being asserted or retracted. (One can think of it like an subject-predicate-object triple with a temporal component.) The keys and values we transacted are in the shorthand map form for transaction data.

More on transaction semantics

Datomic doesn’t update values in place. That means changing an attribute to a new value involves an assertion datom for the new value and a retraction datom for the old value. That’s why you see two datoms each for entity B and entity C in the tx-data.

Note also that Datomic transactions consist of the addition of a set of datoms. While other database systems may represent a transaction as an ordered series of smaller updates, a Datomic transaction is a singular declaration with no intra-transaction sequence. There is no concept of ordering within a transaction. Datomic transactions remain ACID and inter-transaction semantics guarantee strong serializability.

Querying changes

Because Datomic transactions are first-class and a part of each datom, transactions are queryable like any other entity. We can grab the ID of the transaction that asserted Joe’s move to Park Ave (?tx inside the query), then use it to pull both the wall-clock time and the :db/doc we stored on that transaction:

(def tx-id
  (ffirst (d/q '[:find ?tx
                 :in $ ?name ?street
                 :where
                 [?e :person/name ?name]
                 [?e :person/street ?street ?tx true]]
               (d/history (d/db conn))
               "Joe"
               "Park Ave")))

(d/pull (d/db conn) '[:db/txInstant :db/doc] tx-id)
;; => {:db/txInstant #inst "2026-04-20T..."
;;     :db/doc "Key hand-off confirmed by Alice"}

Conclusion

That’s enough for a first walkthrough. You used Datomic Cloud to query the present and past, transact datoms, query transaction entities, and work with immutable database values. What’s next?

The Rationale surveys the design decisions behind Datomic: perception without coordination, immutable database values, and an information model based on accreting atomic facts.

For more walkthroughs and feature explanations, check out our Guides.

Or dive into the reference Documentation.