Skip to main content


Mutations are a mechanism to run procedures through EQL. For a syntax review please check the EQL Mutations specification.

Using defmutation

To create mutations you can use the defmutation helper, which is similar to the defresolver macro. And similarly to resolvers, it can take a variable number of arguments - none, one ([parameters]), or two ([env parameters]).

Here is a mutation to save a file on disk:

(pco/defmutation save-file [{::keys [file-path file-content] :as file}]
(spit file-path file-content)

Just like defresolver, one difference here is that in mutations there is no input, mutations always use parameters.

This is a subtle difference, but you can see it by inspecting the mutation:

; #com.wsscode.pathom3.connect.operation.Mutation
; {:config #:com.wsscode.pathom3.connect.operation
; {:params [
; :op-name},
; :mutate #object[...]}

Note what would be ::pco/input on a defresolver, it's now ::pco/params in mutation.

Different from inputs, parameters don't have auto-resolution, they always come as-is, but having this information can help on creating extensions (to support auto-resolution if wanted) and help to understand the system (documentation).

To run the mutation you need to use the EQL interface:

(def env (pci/register save-file))

(p.eql/process env
[`(save-file {::file-path "./file.txt" ::file-content "contents here"})])
; => {
; { "./file.txt",
; "contents here"}}

The result of the mutation result comes in the same key as the mutation name.


By default the mutation symbol is the fully qualified var name of the mutation. Note we use the backtick to use the complete name.


Mutations are the first thing the runner executes, this way you know the reads from the query will have update values, in case the mutation affects something related to them.

Mutation joins

You can also make a join in the mutation to specify what you want from the result. For this example we will write a resolver to get the file size, and use it as part of the mutation request:

(pco/defmutation save-file [{::keys [file-path file-content] :as file}]
(spit file-path file-content)

(pco/defresolver file-size [{::keys [file-path]}]
{::file-size (.length (io/file file-path))})

(def env (pci/register [save-file file-size]))

(p.eql/process env
[{`(save-file {::file-path "./file.txt" ::file-content "contents here"})
; => {
; { "./file.txt",
; 13}}

Changing mutation symbol

I recommend sticking to fully qualified names, but in case you need a different name you can use the ::pco/op-name to make it something else:

(pco/defmutation save-file [{::keys [file-path file-content] :as file}]
{::pco/op-name 'io/save-my-file}
(spit file-path file-content)

; then you can use this name to call the mutation
(p.eql/process env
['(io/save-my-file {::file-path "./file.txt" ::file-content "contents here"})])

Resolve mutation params

On Pathom, when you call a mutation, the params flow without any alteration. The default behavior is meant to give the fastest path possible to invoke mutations.

That said, it's common also to want the params to be "resolved", just like Pathom does with inputs on resolvers.

To make that happen, you can use the mutation-resolve-params built-in plugin. When you add this plugin, Pathom will use the ::pco/params as a query and will run that on the user params, and then deliver the resolved data to the mutation.


(pco/defmutation foo [{:keys [b]}]
{:res b})

(-> (pci/register
[(pbir/single-attr-resolver :a :b inc)
(p.plugin/register pbip/mutation-resolve-params))
['(foo {:a 1})])
; => {foo {:res 2}}

When using pco/defmutation, ::pco/params is inferred from the argument destructuring.