Repository: onflow/flow-cli
Pull request #95 (2021-03-01 21:26:08)

Config compose
## Description

Use a different version of config in each environment (emulator, testnet, mainnet)

I think this use case is best solved with the following:

`flow project deploy -f flow.json -f flow.mainnet.json`

Configuration would be merged from left to right. In the example above, flow.json is the base, and then flow.mainnet.json can add or override specific values. This is inspired by the Docker Compose CLI, which I quite like.

______

For contributor use:

- [ ] Targeted PR against `master` branch
- [ ] Linked to Github issue with discussion and accepted design OR link to spec that describes this work
- [ ] Code follows the [standards mentioned here](https://github.com/onflow/flow-cli/blob/master/CONTRIBUTING.md#styleguides)
- [ ] Updated relevant documentation
- [ ] Re-reviewed `Files changed` in the Github PR explorer
- [ ] Added appropriate labels



Patch:
diff --git a/NOTICE b/NOTICE
index f5adb5d1..b1ddef21 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,4 +1,4 @@
 Flow CLI
-Copyright 2019-2020 Dapper Labs, Inc.
+Copyright 2019-2021 Dapper Labs, Inc.
 
 This product includes software developed at Dapper Labs, Inc. (https://www.dapperlabs.com/).
diff --git a/build/build.go b/build/build.go
index f38d6fa5..c3cf09c4 100644
--- a/build/build.go
+++ b/build/build.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/cmd/flow/main.go b/cmd/flow/main.go
index f97a2090..f2ed3259 100644
--- a/cmd/flow/main.go
+++ b/cmd/flow/main.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -55,7 +55,7 @@ func init() {
 	cmd.AddCommand(scripts.Cmd)
 	cmd.AddCommand(transactions.Cmd)
 	cmd.AddCommand(version.Cmd)
-	cmd.PersistentFlags().StringVarP(&cli.ConfigPath, "config-path", "f", cli.ConfigPath, "Path to flow configuration file")
+	cmd.PersistentFlags().StringSliceVarP(&cli.ConfigPath, "config-path", "f", cli.ConfigPath, "Path to flow configuration file")
 }
 
 func main() {
diff --git a/docs/deploy-project-contracts.md b/docs/deploy-project-contracts.md
index 1cf3cc06..84edb924 100644
--- a/docs/deploy-project-contracts.md
+++ b/docs/deploy-project-contracts.md
@@ -23,8 +23,7 @@ Before using this command, read about how to
 > flow project deploy --network=testnet
 
 NonFungibleToken -> 0xf8d6e0586b0a20c7
-FungibleToken -> 0xf8d6e0586b0a20c7
-Kibble -> 0xf8d6e0586b0a20c7
+KittyItems -> 0xf8d6e0586b0a20c7
 
 ✅ All contracts deployed successfully
 ```
@@ -34,12 +33,12 @@ In the example above, your `flow.json` file might look something like this:
 ```json
 {
   "contracts": {
-    "Foo": "./cadence/contracts/FooContract.cdc",
-    "Bar": "./cadence/contracts/BarContract.cdc"
+    "NonFungibleToken": "./cadence/contracts/NonFungibleToken.cdc",
+    "KittyItems": "./cadence/contracts/KittyItems.cdc"
   },
   "deployments": {
     "testnet": {
-      "my-testnet-account": ["Foo", "Bar"]
+      "my-testnet-account": ["KittyItems", "NonFungibleToken"]
     }
   }
 }
@@ -47,20 +46,87 @@ In the example above, your `flow.json` file might look something like this:
 
 Here's a sketch of the contract source files:
 
-```cadence:title=Foo.cdc
-pub contract Foo { 
+```cadence:title=NonFungibleToken.cdc
+pub contract NonFungibleToken { 
   // ...
 }
 ```
 
-```cadence:title=Bar.cdc
-import Foo from Foo.cdc
+```cadence:title=KittyItems.cdc
+import NonFungibleToken from "./NonFungibleToken.cdc"
 
-pub contract Bar { 
+pub contract KittyItems { 
   // ...
 }
 ```
 
+## Security
+
+⚠️ Warning: Please be careful when using private keys in configuration files. We suggest you 
+to separate private keys in another configuration file, put that file in `.gitignore` and then 
+reference that account in configuration with `fromeFile` property. 
+
+### Private Account Configuration File
+`flow.json` Main configuration file example:
+```json
+{
+  "contracts": {
+    "NonFungibleToken": "./cadence/contracts/NonFungibleToken.cdc",
+    "KittyItems": "./cadence/contracts/KittyItems.cdc"
+  },
+  "deployments": {
+    "testnet": {
+      "my-testnet-account": ["KittyItems", "NonFungibleToken"]
+    }
+  },
+  "accounts": {
+    "my-testnet-account": { "fromFile": "./flow.testnet.json" }
+  }
+}
+```
+
+`flow.testnet.json` Private configuration file. **Put this file in `.gitignore`**
+```json
+{
+  "accounts": {
+    "my-testnet-account": {
+      "address": "3ae53cb6e3f42a79",
+      "keys": "334232967f52bd75234ae9037dd4694c1f00baad63a10c35172bf65fbb8ad1111"
+    }
+  }
+}
+```
+
+### Private Account Configuration Environment Variable
+
+Use environment variable for any values that should be kept private (private keys, addresses...). 
+See example bellow:
+
+`flow.json` Main configuration file. Set environment variable when running flow cli like so:
+```shell
+PRIVATE_KEY=key flow project deploy
+```
+```json
+{
+  ...
+  "accounts": {
+    "my-testnet-account": {
+      "address": "3ae53cb6e3f42a79",
+      "keys": "$PRIVATE_KEY"
+    }
+  }
+  ...
+}
+```
+
+### Composing Multiple Configuration Files
+You can use composition of configuration files like so:
+```shell
+flow project deploy -f main.json -f private.json
+```
+
+This way you can keep your private accounts in the `private.json` file and add that file to `.gitignore`.
+
 ## Dependency Resolution
 
 The `deploy` command attempts to resolve the import statements in all contracts being deployed.
diff --git a/flow/account.go b/flow/account.go
index daf71519..1c3d8be4 100644
--- a/flow/account.go
+++ b/flow/account.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/accounts/accounts.go b/flow/accounts/accounts.go
index 417f4ad5..505abebb 100644
--- a/flow/accounts/accounts.go
+++ b/flow/accounts/accounts.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/accounts/add-contract/add.go b/flow/accounts/add-contract/add.go
index 3bbe5baf..1f9dac85 100644
--- a/flow/accounts/add-contract/add.go
+++ b/flow/accounts/add-contract/add.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -30,9 +30,9 @@ import (
 )
 
 type Config struct {
-	Signer   string   `default:"service" flag:"signer,s"`
-	Host     string   `flag:"host" info:"Flow Access API host address"`
-	Results  bool     `default:"false" flag:"results" info:"Display the results of the transaction"`
+	Signer  string `default:"service" flag:"signer,s"`
+	Host    string `flag:"host" info:"Flow Access API host address"`
+	Results bool   `default:"false" flag:"results" info:"Display the results of the transaction"`
 }
 
 var conf Config
@@ -57,7 +57,7 @@ var Cmd = &cobra.Command{
 		tx := templates.AddAccountContract(
 			signerAccount.Address,
 			templates.Contract{
-				Name: contractName,
+				Name:   contractName,
 				Source: string(contractSource),
 			},
 		)
diff --git a/flow/accounts/create/create.go b/flow/accounts/create/create.go
index 6ed3f7fc..78f31c72 100644
--- a/flow/accounts/create/create.go
+++ b/flow/accounts/create/create.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/accounts/staking-info/get.go b/flow/accounts/staking-info/get.go
index 84e4682b..f506a214 100644
--- a/flow/accounts/staking-info/get.go
+++ b/flow/accounts/staking-info/get.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/accounts/update-contract/update.go b/flow/accounts/update-contract/update.go
index 625c35c3..2c117530 100644
--- a/flow/accounts/update-contract/update.go
+++ b/flow/accounts/update-contract/update.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -30,9 +30,9 @@ import (
 )
 
 type Config struct {
-	Signer   string   `default:"service" flag:"signer,s"`
-	Host     string   `flag:"host" info:"Flow Access API host address"`
-	Results  bool     `default:"false" flag:"results" info:"Display the results of the transaction"`
+	Signer  string `default:"service" flag:"signer,s"`
+	Host    string `flag:"host" info:"Flow Access API host address"`
+	Results bool   `default:"false" flag:"results" info:"Display the results of the transaction"`
 }
 
 var conf Config
@@ -57,7 +57,7 @@ var Cmd = &cobra.Command{
 		tx := templates.UpdateAccountContract(
 			signerAccount.Address,
 			templates.Contract{
-				Name: contractName,
+				Name:   contractName,
 				Source: string(contractSource),
 			},
 		)
diff --git a/flow/arguments.go b/flow/arguments.go
index 88634519..cb95e396 100644
--- a/flow/arguments.go
+++ b/flow/arguments.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/blocks.go b/flow/blocks.go
index 1c2f1813..5b35c84b 100644
--- a/flow/blocks.go
+++ b/flow/blocks.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/blocks/blocks.go b/flow/blocks/blocks.go
index c1cd9a20..5c2ee0ce 100644
--- a/flow/blocks/blocks.go
+++ b/flow/blocks/blocks.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/blocks/get/get.go b/flow/blocks/get/get.go
index ba02fef3..d07ce14f 100644
--- a/flow/blocks/get/get.go
+++ b/flow/blocks/get/get.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/cadence/cadence.go b/flow/cadence/cadence.go
index 299fbe41..11a37a97 100644
--- a/flow/cadence/cadence.go
+++ b/flow/cadence/cadence.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/cadence/languageserver/languageserver.go b/flow/cadence/languageserver/languageserver.go
index 09849a79..14eb84d7 100644
--- a/flow/cadence/languageserver/languageserver.go
+++ b/flow/cadence/languageserver/languageserver.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/cadence/vscode/gen.go b/flow/cadence/vscode/gen.go
index 272c002c..4789965d 100644
--- a/flow/cadence/vscode/gen.go
+++ b/flow/cadence/vscode/gen.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/cadence/vscode/vscode.go b/flow/cadence/vscode/vscode.go
index cb33c3ec..a87a0c65 100644
--- a/flow/cadence/vscode/vscode.go
+++ b/flow/cadence/vscode/vscode.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@
 package vscode
 
 import (
-    "fmt"
+	"fmt"
 	"io/ioutil"
 	"os"
 	"os/exec"
diff --git a/flow/cli.go b/flow/cli.go
index fdb189e8..e859005b 100644
--- a/flow/cli.go
+++ b/flow/cli.go
@@ -38,7 +38,7 @@ const (
 	Indent              = "  "
 )
 
-var ConfigPath = "flow.json"
+var ConfigPath = []string{"flow.json"}
 
 func Exit(code int, msg string) {
 	fmt.Println(msg)
diff --git a/flow/collections.go b/flow/collections.go
index f5b30f7e..7476ab1f 100644
--- a/flow/collections.go
+++ b/flow/collections.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/collections/collections.go b/flow/collections/collections.go
index 6f8b26ee..8c9cfbde 100644
--- a/flow/collections/collections.go
+++ b/flow/collections/collections.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/collections/get/get.go b/flow/collections/get/get.go
index 028706d3..1ffcdd5f 100644
--- a/flow/collections/get/get.go
+++ b/flow/collections/get/get.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/config.go b/flow/config.go
index f888a704..7ea21994 100644
--- a/flow/config.go
+++ b/flow/config.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -218,7 +218,7 @@ func SaveConfig(conf *Config) error {
 	if err != nil {
 		return err
 	}
-	err = ioutil.WriteFile(ConfigPath, data, 0777)
+	err = ioutil.WriteFile(ConfigPath[0], data, 0777)
 	if err != nil {
 		return err
 	}
@@ -233,7 +233,7 @@ func MustSaveConfig(conf *Config) {
 }
 
 func LoadConfig() *Config {
-	f, err := os.Open(ConfigPath)
+	f, err := os.Open(ConfigPath[0])
 	if err != nil {
 		if os.IsNotExist(err) {
 			fmt.Printf("Project config file %s does not exist. Please initialize first\n", ConfigPath)
@@ -262,7 +262,7 @@ func LoadConfig() *Config {
 }
 
 func ConfigExists() bool {
-	info, err := os.Stat(ConfigPath)
+	info, err := os.Stat(ConfigPath[0])
 	if os.IsNotExist(err) {
 		return false
 	}
diff --git a/flow/emulator/emulator.go b/flow/emulator/emulator.go
index ff37758b..296d4a34 100644
--- a/flow/emulator/emulator.go
+++ b/flow/emulator/emulator.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/env.go b/flow/env.go
index 992a910d..b8c02dd3 100644
--- a/flow/env.go
+++ b/flow/env.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/events/events.go b/flow/events/events.go
index 2a038ca8..156eaf89 100644
--- a/flow/events/events.go
+++ b/flow/events/events.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/events/get/get.go b/flow/events/get/get.go
index bf7b44d1..dcd053ae 100644
--- a/flow/events/get/get.go
+++ b/flow/events/get/get.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/initialize/init.go b/flow/initialize/init.go
index 8a29e4a5..28d6dfc7 100644
--- a/flow/initialize/init.go
+++ b/flow/initialize/init.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/keys/generate/generate.go b/flow/keys/generate/generate.go
index b2950269..ec26c5b7 100644
--- a/flow/keys/generate/generate.go
+++ b/flow/keys/generate/generate.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/keys/keys.go b/flow/keys/keys.go
index 0dc91858..705c146b 100644
--- a/flow/keys/keys.go
+++ b/flow/keys/keys.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/keys/save/hex/hex.go b/flow/keys/save/hex/hex.go
index eaf4c9b3..a02e8673 100644
--- a/flow/keys/save/hex/hex.go
+++ b/flow/keys/save/hex/hex.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/keys/save/kms/kms.go b/flow/keys/save/kms/kms.go
index cd0812b6..12ee0450 100644
--- a/flow/keys/save/kms/kms.go
+++ b/flow/keys/save/kms/kms.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/keys/save/save.go b/flow/keys/save/save.go
index 8ee1f82e..9c5a7757 100644
--- a/flow/keys/save/save.go
+++ b/flow/keys/save/save.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/project/cli/cli.go b/flow/project/cli/cli.go
index acce0b30..f9c9e8b8 100644
--- a/flow/project/cli/cli.go
+++ b/flow/project/cli/cli.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/project/cli/config/config.go b/flow/project/cli/config/config.go
index 2fbe8ccb..a648fc8f 100644
--- a/flow/project/cli/config/config.go
+++ b/flow/project/cli/config/config.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -118,6 +118,8 @@ func (c *Contracts) GetByNameAndNetwork(name string, network string) Contract {
 	return contracts[0]
 }
 
+// TODO: this filtering can cause error if not found, better to refactor to returning
+
 // GetByName get contract by name
 func (c *Contracts) GetByName(name string) Contract {
 	return funk.Filter([]Contract(*c), func(c Contract) bool {
@@ -127,50 +129,146 @@ func (c *Contracts) GetByName(name string) Contract {
 
 // GetByNetwork returns all contracts for specific network
 func (c *Contracts) GetByNetwork(network string) Contracts {
-	return funk.Filter([]Contract(*c), func(c Contract) bool {
-		return c.Network == network || c.Network == "" // if network is not defined return for all set value
-	}).([]Contract)
+	var contracts []Contract
+
+	for _, contract := range *c {
+		if contract.Network == network || contract.Network == "" {
+			contracts = append(contracts, contract)
+		}
+	}
+
+	return contracts
+}
+
+// AddOrUpdate add new or update if already present
+func (c *Contracts) AddOrUpdate(name string, contract Contract) {
+	for i, existingContract := range *c {
+		if existingContract.Name == name {
+			(*c)[i] = contract
+			return
+		}
+	}
+
+	*c = append(*c, contract)
 }
 
 // GetAccountByName get account by name
-func (a *Accounts) GetByName(name string) Account {
-	return funk.Filter([]Account(*a), func(a Account) bool {
-		return a.Name == name
-	}).([]Account)[0]
+func (a *Accounts) GetByName(name string) *Account {
+	for _, account := range *a {
+		if account.Name == name {
+			return &account
+		}
+	}
+
+	return nil
 }
 
 // GetByAddress get account by address
-func (a *Accounts) GetByAddress(address string) Account {
-	return funk.Filter([]Account(*a), func(a Account) bool {
-		return a.Address.String() == address
-	}).([]Account)[0]
+func (a *Accounts) GetByAddress(address string) *Account {
+	for _, account := range *a {
+		if account.Address.String() == address {
+			return &account
+		}
+	}
+
+	return nil
+}
+
+// AddOrUpdate add new or update if already present
+func (a *Accounts) AddOrUpdate(name string, account Account) {
+	for i, existingAccount := range *a {
+		if existingAccount.Name == name {
+			(*a)[i] = account
+			return
+		}
+	}
+
+	*a = append(*a, account)
 }
 
 // GetByNetwork get all deployments by network
 func (d *Deployments) GetByNetwork(network string) Deployments {
-	return funk.Filter([]Deploy(*d), func(d Deploy) bool {
-		return d.Network == network
-	}).([]Deploy)
+	var deployments []Deploy
+
+	for _, deploy := range *d {
+		if deploy.Network == network {
+			deployments = append(deployments, deploy)
+		}
+	}
+
+	return deployments
 }
 
 // GetByAccountAndNetwork get deploy by account and network
 func (d *Deployments) GetByAccountAndNetwork(account string, network string) []Deploy {
-	return funk.Filter([]Deploy(*d), func(d Deploy) bool {
-		return d.Account == account && d.Network == network
-	}).([]Deploy)
+	var deployments []Deploy
+
+	for _, deploy := range *d {
+		if deploy.Network == network && deploy.Account == account {
+			deployments = append(deployments, deploy)
+		}
+	}
+
+	return deployments
+}
+
+// AddOrUpdate add new or update if already present
+func (d *Deployments) AddOrUpdate(deployment Deploy) {
+	for i, existingDeployment := range *d {
+		if existingDeployment.Account == deployment.Account {
+			(*d)[i] = deployment
+			return
+		}
+	}
+
+	*d = append(*d, deployment)
 }
 
 // GetByName get network by name
-func (n *Networks) GetByName(name string) Network {
-	return funk.Filter([]Network(*n), func(n Network) bool {
-		return n.Name == name
-	}).([]Network)[0]
+func (n *Networks) GetByName(name string) *Network {
+	for _, network := range *n {
+		if network.Name == name {
+			return &network
+		}
+	}
+
+	return nil
 }
 
+// AddOrUpdate add new network or update if already present
+func (n *Networks) AddOrUpdate(name string, network Network) {
+	for i, existingNetwork := range *n {
+		if existingNetwork.Name == name {
+			(*n)[i] = network
+			return
+		}
+	}
+
+	*n = append(*n, network)
+}
+
+// DefaultEmulatorConfigName
 const DefaultEmulatorConfigName = "default"
 
-func (e *Emulators) GetDefault() Emulator {
-	return funk.Filter([]Emulator(*e), func(e Emulator) bool {
-		return e.Name == DefaultEmulatorConfigName
-	}).([]Emulator)[0]
+// GetDefault gets default emulator
+func (e *Emulators) GetDefault() *Emulator {
+	for _, emulator := range *e {
+		if emulator.Name == DefaultEmulatorConfigName {
+			return &emulator
+		}
+	}
+
+	return nil
+}
+
+// AddOrUpdate add new or update if already present
+func (e *Emulators) AddOrUpdate(name string, emulator Emulator) {
+	for i, existingEmulator := range *e {
+		if existingEmulator.Name == name {
+			(*e)[i] = emulator
+			return
+		}
+	}
+
+	*e = append(*e, emulator)
 }
diff --git a/flow/project/cli/config/json/account.go b/flow/project/cli/config/json/account.go
index 75f65850..eed4cf67 100644
--- a/flow/project/cli/config/json/account.go
+++ b/flow/project/cli/config/json/account.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/project/cli/config/json/config.go b/flow/project/cli/config/json/config.go
index 8634cd15..7c3acc6e 100644
--- a/flow/project/cli/config/json/config.go
+++ b/flow/project/cli/config/json/config.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,15 +20,11 @@ package json
 
 import (
 	"encoding/json"
-	"errors"
-	"fmt"
-	"io/ioutil"
-	"os"
-
-	"github.com/a8m/envsubst"
 	"github.com/onflow/flow-cli/flow/project/cli/config"
 )
 
+// TODO: implement interface for all specific config implementation
+
 type jsonConfig struct {
 	Emulators   jsonEmulators   `json:"emulators"`
 	Contracts   jsonContracts   `json:"contracts"`
@@ -37,7 +33,7 @@ type jsonConfig struct {
 	Deployments jsonDeployments `json:"deployments"`
 }
 
-func (j jsonConfig) transformToConfig() *config.Config {
+func (j *jsonConfig) transformToConfig() *config.Config {
 	return &config.Config{
 		Emulators:   j.Emulators.transformToConfig(),
 		Contracts:   j.Contracts.transformToConfig(),
@@ -57,51 +53,39 @@ func transformConfigToJSON(config *config.Config) jsonConfig {
 	}
 }
 
-// Save saves the configuration to the specified path file in JSON format.
-func Save(conf *config.Config, path string) error {
+// Parsers for configuration
+type Parser struct{}
+
+// NewParser returns a parser
+func NewParser() *Parser {
+	return &Parser{}
+}
+
+// Serialize configuration to raw
+func (p *Parser) Serialize(conf *config.Config) ([]byte, error) {
 	jsonConf := transformConfigToJSON(conf)
 
 	data, err := json.MarshalIndent(jsonConf, "", "\t")
 	if err != nil {
-		return err
-	}
-
-	err = ioutil.WriteFile(path, data, 0777)
-	if err != nil {
-		return err
+		return nil, err
 	}
 
-	return nil
+	return data, nil
 }
 
-// ErrDoesNotExist is error to be returned when config file does not exists
-var ErrDoesNotExist = errors.New("project config file does not exist")
+// Deserialize configuration to config structure
+func (p *Parser) Deserialize(raw []byte) (*config.Config, error) {
+	var jsonConf jsonConfig
+	err := json.Unmarshal(raw, &jsonConf)
 
-func Load(path string) (*config.Config, error) {
-	f, err := envsubst.ReadFile(path)
 	if err != nil {
-		if os.IsNotExist(err) {
-			return nil, ErrDoesNotExist
-		}
-
 		return nil, err
 	}
-	conf := new(jsonConfig)
-	err = json.Unmarshal(f, &conf)
 
-	if err != nil {
-		fmt.Printf("%s contains invalid json: %s\n", path, err.Error())
-		os.Exit(1)
-	}
-
-	return conf.transformToConfig(), nil
+	return jsonConf.transformToConfig(), nil
 }
 
-// Exists checks if file exists on the specified path
-func Exists(path string) bool {
-	info, err := os.Stat(path)
-	if os.IsNotExist(err) {
-		return false
-	}
-	return !info.IsDir()
+// SupportsFormat check if the file format is supported
+func (p *Parser) SupportsFormat(extension string) bool {
+	return extension == ".json"
 }
diff --git a/flow/project/cli/config/json/contract.go b/flow/project/cli/config/json/contract.go
index 586c0b66..bccd3017 100644
--- a/flow/project/cli/config/json/contract.go
+++ b/flow/project/cli/config/json/contract.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/project/cli/config/json/deploy.go b/flow/project/cli/config/json/deploy.go
index e3c02379..31a2429e 100644
--- a/flow/project/cli/config/json/deploy.go
+++ b/flow/project/cli/config/json/deploy.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/project/cli/config/json/emulator.go b/flow/project/cli/config/json/emulator.go
index 7a581ea0..4f7901a8 100644
--- a/flow/project/cli/config/json/emulator.go
+++ b/flow/project/cli/config/json/emulator.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/project/cli/config/json/network.go b/flow/project/cli/config/json/network.go
index f45dfc02..ea897f28 100644
--- a/flow/project/cli/config/json/network.go
+++ b/flow/project/cli/config/json/network.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/project/cli/config/loader.go b/flow/project/cli/config/loader.go
new file mode 100644
index 00000000..129186f7
--- /dev/null
+++ b/flow/project/cli/config/loader.go
@@ -0,0 +1,220 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package config
+
+import (
+	"errors"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/spf13/afero"
+)
+
+// ErrDoesNotExist is error to be returned when config file does not exists
+var ErrDoesNotExist = errors.New("project config file does not exist")
+
+// Exists checks if file exists on the specified path
+func Exists(path string) bool {
+	info, err := os.Stat(path)
+	if os.IsNotExist(err) {
+		return false
+	}
+	return !info.IsDir()
+}
+
+// Parser is interface for any configuration format parser to implement
+type Parser interface {
+	Serialize(*Config) ([]byte, error)
+	Deserialize([]byte) (*Config, error)
+	SupportsFormat(string) bool
+}
+
+// ConfigParsers lsit of all configuration parsers
+type ConfigParsers []Parser
+
+// FindForFormat finds a parser that can parse a specific format based on extension
+func (c *ConfigParsers) FindForFormat(extension string) Parser {
+	for _, parser := range *c {
+		if parser.SupportsFormat(extension) {
+			return parser
+		}
+	}
+
+	return nil
+}
+
+// Loader contains actions for composing and modification of configuration
+type Loader struct {
+	af               *afero.Afero
+	configParsers    ConfigParsers
+	composedMultiple bool
+	composedFromFile map[string]string
+}
+
+// NewLoader creates a new loader
+func NewLoader(filesystem afero.Fs) *Loader {
+	af := &afero.Afero{Fs: filesystem}
+	return &Loader{
+		af:               af,
+		composedFromFile: map[string]string{},
+	}
+}
+
+// AddConfigParser adds new parssers for configuration
+func (l *Loader) AddConfigParser(format Parser) {
+	l.configParsers = append(l.configParsers, format)
+}
+
+// Save save configuration to a path with correct serializer
+func (l *Loader) Save(conf *Config, path string) error {
+	if l.composedMultiple || l.composedFromFile != nil {
+		return errors.New("Saving configuration to multiple files currently not supported")
+	}
+
+	configFormat := l.configParsers.FindForFormat(
+		filepath.Ext(path),
+	)
+
+	data, err := configFormat.Serialize(conf)
+	if err != nil {
+		return err
+	}
+
+	err = l.af.WriteFile(path, data, 0644)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Load and compose multiple configurations
+func (l *Loader) Load(paths []string) (*Config, error) {
+	var baseConf *Config
+
+	for _, path := range paths {
+		raw, err := l.loadFile(path)
+		if err != nil {
+			return nil, err
+		}
+
+		preProcessed := l.preprocess(raw)
+		configParser := l.configParsers.FindForFormat(filepath.Ext(path))
+		if configParser == nil {
+			return nil, fmt.Errorf("Parser not found for config: %s", path)
+		}
+
+		conf, err := configParser.Deserialize(preProcessed)
+		if err != nil {
+			return nil, err
+		}
+
+		// if first conf just assign as baseConf
+		if baseConf == nil {
+			baseConf = conf
+			continue
+		}
+
+		l.composeConfig(baseConf, conf)
+	}
+
+	baseConf, err := l.postprocess(baseConf)
+	if err != nil {
+		return nil, err
+	}
+
+	return baseConf, nil
+}
+
+// preprocess configuration - all manipulations to the raw configuration format happens here
+func (l *Loader) preprocess(raw []byte) []byte {
+	raw, accountsFromFile := ProcessorRun(raw)
+
+	// add all imports from files preprocessor detected for later processing
+	l.composedFromFile = accountsFromFile
+
+	return raw
+}
+
+// postprocess configuration - do all stateful changes to configuration structures here after it is parsed
+func (l *Loader) postprocess(baseConf *Config) (*Config, error) {
+	for name, path := range l.composedFromFile {
+		raw, err := l.loadFile(path)
+		if err != nil {
+			return nil, err
+		}
+
+		configParser := l.configParsers.FindForFormat(filepath.Ext(path))
+		if configParser == nil {
+			return nil, fmt.Errorf("Parser not found for config: %s", path)
+		}
+
+		conf, err := configParser.Deserialize(raw)
+		if err != nil {
+			return nil, err
+		}
+
+		// create an empty config with single account so we don't include all accounts in file
+		accountConf := &Config{
+			Accounts: []Account{*conf.Accounts.GetByName(name)},
+		}
+
+		l.composeConfig(baseConf, accountConf)
+	}
+
+	return baseConf, nil
+}
+
+// composeConfig - here we merge multiple configuration files from right to left
+func (l *Loader) composeConfig(baseConf *Config, conf *Config) {
+	// flag for saving
+	l.composedMultiple = true
+
+	// if not first overwrite first with this one
+	for _, account := range conf.Accounts {
+		baseConf.Accounts.AddOrUpdate(account.Name, account)
+	}
+	for _, network := range conf.Networks {
+		baseConf.Networks.AddOrUpdate(network.Name, network)
+	}
+	for _, contract := range conf.Contracts {
+		baseConf.Contracts.AddOrUpdate(contract.Name, contract)
+	}
+	for _, deployment := range conf.Deployments {
+		baseConf.Deployments.AddOrUpdate(deployment)
+	}
+}
+
+// simple file loader
+func (l *Loader) loadFile(path string) ([]byte, error) {
+	raw, err := l.af.ReadFile(path)
+
+	// TODO: better handle
+	if err != nil {
+		if os.IsNotExist(err) {
+			return nil, err
+			//return nil, ErrDoesNotExist
+		}
+
+		return nil, err
+	}
+
+	return raw, nil
+}
diff --git a/flow/project/cli/config/processor.go b/flow/project/cli/config/processor.go
new file mode 100644
index 00000000..1241391c
--- /dev/null
+++ b/flow/project/cli/config/processor.go
@@ -0,0 +1,67 @@
+/*
+ * Flow CLI
+ *
+ * Copyright 2019-2021 Dapper Labs, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package config
+
+import (
+	"github.com/a8m/envsubst"
+	"regexp"
+	"strings"
+)
+
+var (
+	fileRegex     *regexp.Regexp = regexp.MustCompile(`"([^"]*)"\s*:\s*{\s*"fromFile"\s*:\s*"([^"]*)"\s*},?`)
+	trailingComma *regexp.Regexp = regexp.MustCompile(`\,\s*}`)
+)
+
+// ProcessorRun all pre-processors
+func ProcessorRun(raw []byte) ([]byte, map[string]string) {
+	rawString := string(raw)
+	rawString = processEnv(rawString)
+	rawString, accountFromFiles := processFile(rawString)
+
+	return []byte(rawString), accountFromFiles
+}
+
+// processEnv finds env variables and insert env values
+func processEnv(raw string) string {
+	raw, _ = envsubst.String(raw)
+	return raw
+}
+
+// processFile finds file variables and insert content
+func processFile(raw string) (string, map[string]string) {
+	fileMatches := fileRegex.FindAllStringSubmatch(raw, -1)
+	accountFromFiles := map[string]string{}
+
+	for _, match := range fileMatches {
+		if len(match) < 3 {
+			continue
+		}
+
+		accountFromFiles[match[1]] = match[2]
+
+		// remove fromFile from config after we add that to composer
+		raw = strings.ReplaceAll(raw, match[0], "")
+
+		// remove possible trailing comma
+		raw = trailingComma.ReplaceAllString(raw, "}")
+	}
+
+	return raw, accountFromFiles
+}
diff --git a/flow/project/cli/keys/keys.go b/flow/project/cli/keys/keys.go
index 8f0d99d9..05604800 100644
--- a/flow/project/cli/keys/keys.go
+++ b/flow/project/cli/keys/keys.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/project/cli/project.go b/flow/project/cli/project.go
index 817f6130..351af402 100644
--- a/flow/project/cli/project.go
+++ b/flow/project/cli/project.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,44 +20,49 @@ package cli
 
 import (
 	"errors"
+	"github.com/onflow/flow-cli/flow/project/cli/config/json"
 	"path"
 	"strings"
 
 	"github.com/onflow/flow-go-sdk"
 	"github.com/onflow/flow-go-sdk/crypto"
+	"github.com/spf13/afero"
 	"github.com/thoas/go-funk"
 
 	"github.com/onflow/flow-cli/flow/project/cli/config"
-	"github.com/onflow/flow-cli/flow/project/cli/config/json"
 	"github.com/onflow/flow-cli/flow/project/cli/keys"
 )
 
+// Project has all the funcionality to manage project
 type Project struct {
+	composer *config.Loader
 	conf     *config.Config
 	accounts []*Account
 }
 
-//REF: move this to json config
-const DefaultConfigPath = "flow.json"
+// LoadProject loads configuration and setup the project
+func LoadProject(configFilePath []string) *Project {
+	composer := config.NewLoader(afero.NewOsFs())
+
+	// here we add all available parsers (more to add yaml etc...)
+	composer.AddConfigParser(json.NewParser())
+	conf, err := composer.Load(configFilePath)
 
-func LoadProject() *Project {
-	//REF: this should interact with config and not json directly - currently problem with CD
-	conf, err := json.Load(DefaultConfigPath)
 	if err != nil {
-		if errors.Is(err, json.ErrDoesNotExist) {
+		if errors.Is(err, config.ErrDoesNotExist) {
 			Exitf(
 				1,
 				"Project config file %s does not exist. Please initialize first\n",
-				DefaultConfigPath,
+				configFilePath,
 			)
 		}
 
-		Exitf(1, "Failed to open project configuration in %s", DefaultConfigPath)
+		Exitf(1, "Failed to open project configuration in %s", configFilePath)
 
 		return nil
 	}
 
-	proj, err := newProject(conf)
+	proj, err := newProject(conf, composer)
 	if err != nil {
 		// TODO: replace with a more detailed error message
 		Exitf(1, "Invalid project configuration: %s", err)
@@ -66,14 +71,17 @@ func LoadProject() *Project {
 	return proj
 }
 
-func ProjectExists() bool {
-	return json.Exists(DefaultConfigPath)
+// ProjectExists checks if project exists
+func ProjectExists(path string) bool {
+	return config.Exists(path)
 }
 
+// InitProject initializes the project
 func InitProject() *Project {
 	emulatorServiceAccount := generateEmulatorServiceAccount()
 
 	return &Project{
+		composer: config.NewLoader(afero.NewOsFs()),
 		conf:     defaultConfig(emulatorServiceAccount),
 		accounts: []*Account{emulatorServiceAccount},
 	}
@@ -123,13 +131,14 @@ func generateEmulatorServiceAccount() *Account {
 	}
 }
 
-func newProject(conf *config.Config) (*Project, error) {
+func newProject(conf *config.Config, composer *config.Loader) (*Project, error) {
 	accounts, err := accountsFromConfig(conf)
 	if err != nil {
 		return nil, err
 	}
 
 	return &Project{
+		composer: composer,
 		conf:     conf,
 		accounts: accounts,
 	}, nil
@@ -157,7 +166,7 @@ func (p *Project) Host(network string) string {
 	return p.conf.Networks.GetByName(network).Host
 }
 
-func (p *Project) EmulatorServiceAccount() config.Account {
+func (p *Project) EmulatorServiceAccount() *config.Account {
 	emulator := p.conf.Emulators.GetDefault()
 	return p.conf.Accounts.GetByName(emulator.ServiceAccount)
 }
@@ -219,12 +228,12 @@ func (p *Project) GetAliases(network string) map[string]string {
 	return aliases
 }
 
-func (p *Project) Save() {
+func (p *Project) Save(path string) {
 	p.conf.Accounts = accountsToConfig(p.accounts)
+	err := p.composer.Save(p.conf, path)
 
-	err := json.Save(p.conf, DefaultConfigPath)
 	if err != nil {
-		Exitf(1, "Failed to save project configuration to \"%s\"", DefaultConfigPath)
+		Exitf(1, "Failed to save project configuration to \"%s\"", path)
 	}
 }
 
diff --git a/flow/project/cli/spinner.go b/flow/project/cli/spinner.go
index 1894cd36..d6910eaa 100644
--- a/flow/project/cli/spinner.go
+++ b/flow/project/cli/spinner.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/project/cli/txsender/txsender.go b/flow/project/cli/txsender/txsender.go
index d5d7cb88..1a90f7ee 100644
--- a/flow/project/cli/txsender/txsender.go
+++ b/flow/project/cli/txsender/txsender.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/project/commands/deploy_contracts/deploy.go b/flow/project/commands/deploy_contracts/deploy.go
index 2df2d1a7..959d2e44 100644
--- a/flow/project/commands/deploy_contracts/deploy.go
+++ b/flow/project/commands/deploy_contracts/deploy.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@ import (
 	"github.com/spf13/cobra"
 	"google.golang.org/grpc"
 
+	c "github.com/onflow/flow-cli/flow"
 	"github.com/onflow/flow-cli/flow/project/cli"
 	"github.com/onflow/flow-cli/flow/project/cli/txsender"
 	"github.com/onflow/flow-cli/flow/project/contracts"
@@ -47,7 +48,7 @@ var Cmd = &cobra.Command{
 	Use:   "deploy",
 	Short: "Deploy Cadence contracts",
 	Run: func(cmd *cobra.Command, args []string) {
-		project := cli.LoadProject()
+		project := cli.LoadProject(c.ConfigPath)
 
 		host := project.Host(conf.Network)
 
@@ -148,6 +149,7 @@ var Cmd = &cobra.Command{
 		if len(errs) == 0 {
 			fmt.Println("\n✅ All contracts deployed successfully")
 		} else {
+			// REF: better output when errors
 			fmt.Println("\n❌ Failed to deploy all contracts")
 		}
 	},
diff --git a/flow/project/commands/initialize/init.go b/flow/project/commands/initialize/init.go
index e5322f7a..41103620 100644
--- a/flow/project/commands/initialize/init.go
+++ b/flow/project/commands/initialize/init.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import (
 
 	"github.com/spf13/cobra"
 
+	c "github.com/onflow/flow-cli/flow"
 	"github.com/onflow/flow-cli/flow/project/cli"
 )
 
@@ -30,12 +31,12 @@ var Cmd = &cobra.Command{
 	Use:   "init",
 	Short: "Initialize a new Flow project",
 	Run: func(cmd *cobra.Command, args []string) {
-		if !cli.ProjectExists() {
+		if !cli.ProjectExists(c.ConfigPath[0]) {
 			proj := cli.InitProject()
-			proj.Save()
+			proj.Save(c.ConfigPath[0])
 
 			fmt.Print(
-				cli.Green(fmt.Sprintf("Initialized a new Flow project in %s\n\n", cli.DefaultConfigPath)),
+				cli.Green(fmt.Sprintf("Initialized a new Flow project in %s\n\n", c.ConfigPath)),
 			)
 			fmt.Printf(
 				"Start the Flow Emulator by running: %s\n",
@@ -44,7 +45,7 @@ var Cmd = &cobra.Command{
 		} else {
 			fmt.Printf(
 				"%s\n\nStart the Flow Emulator by running: %s\n",
-				cli.Red(fmt.Sprintf("A Flow project already exists in %s", cli.DefaultConfigPath)),
+				cli.Red(fmt.Sprintf("A Flow project already exists in %s", c.ConfigPath)),
 				cli.Bold("flow project start-emulator"),
 			)
 		}
diff --git a/flow/project/commands/start_emulator/emulator.go b/flow/project/commands/start_emulator/emulator.go
index 30f2c21d..b97ad71f 100644
--- a/flow/project/commands/start_emulator/emulator.go
+++ b/flow/project/commands/start_emulator/emulator.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import (
 	"github.com/onflow/flow-go-sdk/crypto"
 	"github.com/spf13/cobra"
 
+	c "github.com/onflow/flow-cli/flow"
 	"github.com/onflow/flow-cli/flow/project/cli"
 	"github.com/onflow/flow-cli/flow/project/cli/config"
 )
@@ -38,10 +39,10 @@ func configuredServiceKey(
 	crypto.SignatureAlgorithm,
 	crypto.HashAlgorithm,
 ) {
-	proj := cli.LoadProject()
+	proj := cli.LoadProject(c.ConfigPath)
 
 	serviceAccount := proj.EmulatorServiceAccount()
-
+	// TODO: this shouldn't be accessed like this, make a getter
 	serviceKey := serviceAccount.Keys[0]
 
 	privateKey, err := crypto.DecodePrivateKeyHex(serviceKey.SigAlgo, serviceKey.Context["privateKey"])
diff --git a/flow/project/contracts/contracts.go b/flow/project/contracts/contracts.go
index 2bfca105..588d8e3a 100644
--- a/flow/project/contracts/contracts.go
+++ b/flow/project/contracts/contracts.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/project/project.go b/flow/project/project.go
index 03aaa5c5..0a78bce9 100644
--- a/flow/project/project.go
+++ b/flow/project/project.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/script.go b/flow/script.go
index 80f24686..11521fd7 100644
--- a/flow/script.go
+++ b/flow/script.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/scripts/execute/execute.go b/flow/scripts/execute/execute.go
index 586009d7..7d0b5fd4 100644
--- a/flow/scripts/execute/execute.go
+++ b/flow/scripts/execute/execute.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/scripts/scripts.go b/flow/scripts/scripts.go
index 044e2263..83c7d9d4 100644
--- a/flow/scripts/scripts.go
+++ b/flow/scripts/scripts.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/send.go b/flow/send.go
index 0327cca7..3a86601c 100644
--- a/flow/send.go
+++ b/flow/send.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/transactions/send/send.go b/flow/transactions/send/send.go
index 0a85eae8..d6606dd2 100644
--- a/flow/transactions/send/send.go
+++ b/flow/transactions/send/send.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/transactions/status/status.go b/flow/transactions/status/status.go
index 18c985c5..166778d0 100644
--- a/flow/transactions/status/status.go
+++ b/flow/transactions/status/status.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/transactions/transactions.go b/flow/transactions/transactions.go
index 11c11b6c..c0ff5c95 100644
--- a/flow/transactions/transactions.go
+++ b/flow/transactions/transactions.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/version/version.go b/flow/version/version.go
index 72523adb..f5ad2d5a 100644
--- a/flow/version/version.go
+++ b/flow/version/version.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/go.mod b/go.mod
index c0229350..553413f7 100644
--- a/go.mod
+++ b/go.mod
@@ -13,6 +13,7 @@ require (
 	github.com/onflow/flow-emulator v0.15.0
 	github.com/onflow/flow-go-sdk v0.15.0
 	github.com/psiemens/sconfig v0.0.0-20190623041652-6e01eb1354fc
+	github.com/spf13/afero v1.1.2
 	github.com/spf13/cobra v0.0.7
 	github.com/stretchr/testify v1.7.0
 	github.com/thoas/go-funk v0.7.0


Test patch:
diff --git a/flow/project/cli/config/config_test.go b/flow/project/cli/config/config_test.go
index b8159023..8bec3c51 100644
--- a/flow/project/cli/config/config_test.go
+++ b/flow/project/cli/config/config_test.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,9 +15,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package config
+package config_test
 
 import (
+	"github.com/onflow/flow-cli/flow/project/cli/config"
 	"testing"
 
 	"github.com/onflow/flow-go-sdk"
@@ -25,14 +26,14 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func generateComplexConfig() Config {
-	return Config{
-		Emulators: Emulators{{
+func generateComplexConfig() config.Config {
+	return config.Config{
+		Emulators: config.Emulators{{
 			Name:           "default",
 			Port:           9000,
 			ServiceAccount: "emulator-account",
 		}},
-		Contracts: Contracts{{
+		Contracts: config.Contracts{{
 			Name:    "NonFungibleToken",
 			Source:  "../hungry-kitties/cadence/contracts/NonFungibleToken.cdc",
 			Network: "emulator",
@@ -57,7 +58,7 @@ func generateComplexConfig() Config {
 			Source:  "0x123123123",
 			Network: "testnet",
 		}},
-		Deployments: Deployments{{
+		Deployments: config.Deployments{{
 			Network:   "emulator",
 			Account:   "emulator-account",
 			Contracts: []string{"KittyItems", "KittyItemsMarket"},
@@ -70,12 +71,12 @@ func generateComplexConfig() Config {
 			Account:   "account-2",
 			Contracts: []string{"FungibleToken", "NonFungibleToken", "Kibble", "KittyItems"},
 		}},
-		Accounts: Accounts{{
+		Accounts: config.Accounts{{
 			Name:    "emulator-account",
 			Address: flow.ServiceAddress(flow.Emulator),
 			ChainID: flow.Emulator,
-			Keys: []AccountKey{{
-				Type:     KeyTypeHex,
+			Keys: []config.AccountKey{{
+				Type:     config.KeyTypeHex,
 				Index:    0,
 				SigAlgo:  crypto.ECDSA_P256,
 				HashAlgo: crypto.SHA3_256,
@@ -87,8 +88,8 @@ func generateComplexConfig() Config {
 			Name:    "account-2",
 			Address: flow.HexToAddress("2c1162386b0a245f"),
 			ChainID: flow.Emulator,
-			Keys: []AccountKey{{
-				Type:     KeyTypeHex,
+			Keys: []config.AccountKey{{
+				Type:     config.KeyTypeHex,
 				Index:    0,
 				SigAlgo:  crypto.ECDSA_P256,
 				HashAlgo: crypto.SHA3_256,
@@ -100,8 +101,8 @@ func generateComplexConfig() Config {
 			Name:    "account-4",
 			Address: flow.HexToAddress("f8d6e0586b0a20c1"),
 			ChainID: flow.Emulator,
-			Keys: []AccountKey{{
-				Type:     KeyTypeHex,
+			Keys: []config.AccountKey{{
+				Type:     config.KeyTypeHex,
 				Index:    0,
 				SigAlgo:  crypto.ECDSA_P256,
 				HashAlgo: crypto.SHA3_256,
@@ -110,7 +111,7 @@ func generateComplexConfig() Config {
 				},
 			}},
 		}},
-		Networks: Networks{{
+		Networks: config.Networks{{
 			Name:    "emulator",
 			Host:    "127.0.0.1.3569",
 			ChainID: flow.Emulator,
diff --git a/flow/project/cli/config/json/account_test.go b/flow/project/cli/config/json/account_test.go
index a4954b18..c663f1e1 100644
--- a/flow/project/cli/config/json/account_test.go
+++ b/flow/project/cli/config/json/account_test.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/project/cli/config/json/config_test.go b/flow/project/cli/config/json/config_test.go
new file mode 100644
index 00000000..be2ef806
--- /dev/null
+++ b/flow/project/cli/config/json/config_test.go
@@ -0,0 +1,60 @@
+/*
+* Flow CLI
+*
+* Copyright 2019-2021 Dapper Labs, Inc.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*   http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+ */
+package json
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func Test_SimpleJSONConfig(t *testing.T) {
+	b := []byte(`{
+		"emulators": {
+			"default": {
+				"port": 3569,
+				"serviceAccount": "emulator-account"
+			}
+		},
+		"contracts": {},
+		"networks": {
+			"emulator": {
+				"host": "127.0.0.1:3569",
+				"chain": "flow-emulator"
+			}
+		},
+		"accounts": {
+			"emulator-account": {
+				"address": "f8d6e0586b0a20c7",
+				"keys": "11c5dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7",
+				"chain": "flow-emulator"
+			}
+		},
+		"deploys": {}
+	}`)
+
+	parser := NewParser()
+	conf, err := parser.Deserialize(b)
+
+	require.NoError(t, err)
+	assert.Equal(t, 1, len(conf.Accounts))
+	assert.Equal(t, "emulator-account", conf.Accounts[0].Name)
+	assert.Equal(t, "11c5dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7", conf.Accounts[0].Keys[0].Context["privateKey"])
+	assert.Equal(t, "127.0.0.1:3569", conf.Networks.GetByName("emulator").Host)
+}
diff --git a/flow/project/cli/config/json/contract_test.go b/flow/project/cli/config/json/contract_test.go
index 5a21a296..69c770f2 100644
--- a/flow/project/cli/config/json/contract_test.go
+++ b/flow/project/cli/config/json/contract_test.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/project/cli/config/json/deploy_test.go b/flow/project/cli/config/json/deploy_test.go
index 32ceb6d5..4be92d86 100644
--- a/flow/project/cli/config/json/deploy_test.go
+++ b/flow/project/cli/config/json/deploy_test.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -47,8 +47,8 @@ func Test_ConfigDeploymentsSimple(t *testing.T) {
 	assert.Equal(t, []string{"FungibleToken", "NonFungibleToken", "Kibble", "KittyItems"}, deployments.GetByNetwork("testnet")[0].Contracts)
 
 	assert.Equal(t, 2, len(deployments.GetByNetwork("emulator")))
-	assert.Equal(t, "account-3", deployments.GetByNetwork("emulator")[0].Account)
-	assert.Equal(t, "account-4", deployments.GetByNetwork("emulator")[1].Account)
+	assert.Equal(t, "account-3", deployments.GetByAccountAndNetwork("account-3", "emulator")[0].Account)
+	assert.Equal(t, "account-4", deployments.GetByAccountAndNetwork("account-4", "emulator")[0].Account)
 	assert.Equal(t, []string{"KittyItems", "KittyItemsMarket"}, deployments.GetByNetwork("emulator")[0].Contracts)
 	assert.Equal(t, []string{"FungibleToken", "NonFungibleToken", "Kibble", "KittyItems", "KittyItemsMarket"}, deployments.GetByNetwork("emulator")[1].Contracts)
 }
diff --git a/flow/project/cli/config/json/emulator_test.go b/flow/project/cli/config/json/emulator_test.go
index bd368954..33686338 100644
--- a/flow/project/cli/config/json/emulator_test.go
+++ b/flow/project/cli/config/json/emulator_test.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/project/cli/config/json/network_test.go b/flow/project/cli/config/json/network_test.go
index a58defaa..3f2cb439 100644
--- a/flow/project/cli/config/json/network_test.go
+++ b/flow/project/cli/config/json/network_test.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/flow/project/cli/config/loader_test.go b/flow/project/cli/config/loader_test.go
new file mode 100644
index 00000000..57642ed3
--- /dev/null
+++ b/flow/project/cli/config/loader_test.go
@@ -0,0 +1,281 @@
+/*
+* Flow CLI
+*
+* Copyright 2019-2021 Dapper Labs, Inc.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*   http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+ */
+package config_test
+
+import (
+	"github.com/onflow/flow-cli/flow/project/cli/config"
+	"github.com/onflow/flow-cli/flow/project/cli/config/json"
+	"github.com/spf13/afero"
+	"github.com/stretchr/testify/require"
+	"os"
+
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func Test_JSONSimple(t *testing.T) {
+	b := []byte(`{
+		"emulators": {
+			"default": {
+				"port": 3569,
+				"serviceAccount": "emulator-account"
+			}
+		},
+		"contracts": {},
+		"networks": {
+			"emulator": {
+				"host": "127.0.0.1:3569",
+				"chain": "flow-emulator"
+			}
+		},
+		"accounts": {
+			"emulator-account": {
+				"address": "f8d6e0586b0a20c7",
+				"keys": "21c5dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7",
+				"chain": "flow-emulator"
+			}
+		},
+		"deployments": {}
+	}`)
+
+	mockFS := afero.NewMemMapFs()
+	err := afero.WriteFile(mockFS, "test2-flow.json", b, 0644)
+
+	require.NoError(t, err)
+
+	composer := config.NewLoader(mockFS)
+	composer.AddConfigParser(json.NewParser())
+	conf, loadErr := composer.Load([]string{"test2-flow.json"})
+
+	require.NoError(t, loadErr)
+	assert.Equal(t, 1, len(conf.Accounts))
+	assert.Equal(t, "21c5dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7", conf.Accounts[0].Keys[0].Context["privateKey"])
+}
+
+func Test_ComposeJSON(t *testing.T) {
+	b := []byte(`{
+		"accounts": {
+			"emulator-account": {
+				"address": "f8d6e0586b0a20c7",
+				"keys": "21c5dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7"
+			}
+		}
+	}`)
+
+	b2 := []byte(`{
+		 "accounts":{
+				"admin-account":{
+					 "address":"f1d6e0586b0a20c7",
+					 "keys":"3335dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7"
+				}
+		 }
+	}`)
+
+	mockFS := afero.NewMemMapFs()
+	err := afero.WriteFile(mockFS, "flow.json", b, 0644)
+	err2 := afero.WriteFile(mockFS, "flow-testnet.json", b2, 0644)
+
+	require.NoError(t, err)
+	require.NoError(t, err2)
+
+	composer := config.NewLoader(mockFS)
+	composer.AddConfigParser(json.NewParser())
+
+	conf, loadErr := composer.Load([]string{"flow.json", "flow-testnet.json"})
+
+	require.NoError(t, loadErr)
+	assert.Equal(t, 2, len(conf.Accounts))
+	assert.Equal(t, "21c5dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7",
+		conf.Accounts.GetByName("emulator-account").Keys[0].Context["privateKey"],
+	)
+	assert.NotNil(t, conf.Accounts.GetByName("admin-account"))
+	assert.Equal(t, "3335dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7",
+		conf.Accounts.GetByName("admin-account").Keys[0].Context["privateKey"],
+	)
+}
+
+func Test_ComposeJSONOverwrite(t *testing.T) {
+	b := []byte(`{
+		"accounts": {
+			"admin-account": {
+				"address": "f8d6e0586b0a20c7",
+				"keys": "21c5dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7"
+			}
+		}
+	}`)
+
+	b2 := []byte(`{
+		 "accounts":{
+				"admin-account":{
+					 "address":"f1d6e0586b0a20c7",
+					 "keys":"3335dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7"
+				}
+		 }
+	}`)
+
+	mockFS := afero.NewMemMapFs()
+	err := afero.WriteFile(mockFS, "flow.json", b, 0644)
+	err2 := afero.WriteFile(mockFS, "flow-testnet.json", b2, 0644)
+
+	require.NoError(t, err)
+	require.NoError(t, err2)
+
+	composer := config.NewLoader(mockFS)
+	composer.AddConfigParser(json.NewParser())
+
+	conf, loadErr := composer.Load([]string{"flow.json", "flow-testnet.json"})
+
+	require.NoError(t, loadErr)
+	assert.Equal(t, 1, len(conf.Accounts))
+	assert.NotNil(t, conf.Accounts.GetByName("admin-account"))
+	assert.Equal(t, "3335dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7",
+		conf.Accounts.GetByName("admin-account").Keys[0].Context["privateKey"],
+	)
+}
+
+func Test_FromFileAccountSimple(t *testing.T) {
+	b := []byte(`{
+		"accounts": {
+			"service-account": {
+				"address": "f8d6e0586b0a20c7",
+				"keys": "21c5dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7"
+			},
+			"admin-account": { "fromFile": "private.json" }
+		}
+	}`)
+
+	b2 := []byte(`{
+		 "accounts":{
+				"admin-account":{
+					 "address":"f1d6e0586b0a20c7",
+					 "keys":"3335dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7"
+				}
+		 }
+	}`)
+
+	mockFS := afero.NewMemMapFs()
+	err := afero.WriteFile(mockFS, "flow.json", b, 0644)
+	err2 := afero.WriteFile(mockFS, "private.json", b2, 0644)
+
+	require.NoError(t, err)
+	require.NoError(t, err2)
+
+	composer := config.NewLoader(mockFS)
+	composer.AddConfigParser(json.NewParser())
+
+	conf, loadErr := composer.Load([]string{"flow.json", "private.json"})
+
+	require.NoError(t, loadErr)
+	assert.Equal(t, 2, len(conf.Accounts))
+	assert.NotNil(t, conf.Accounts.GetByName("admin-account"))
+	assert.Equal(t, conf.Accounts.GetByName("admin-account").Address.String(), "f1d6e0586b0a20c7")
+	assert.Equal(t, conf.Accounts.GetByName("service-account").Address.String(), "f8d6e0586b0a20c7")
+}
+
+func Test_FromFileAccountComplex(t *testing.T) {
+	b := []byte(`{
+		"accounts": {
+			"service-account": {
+				"address": "f8d6e0586b0a20c7",
+				"keys": "21c5dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7"
+			},
+			"admin-account-1": { "fromFile": "private.json" },
+			"admin-account-3": { "fromFile": "private.json" },
+			"admin-account-5": { "fromFile": "private.testnet.json" }
+		}
+	}`)
+
+	b2 := []byte(`{
+		 "accounts":{
+				"admin-account-1":{
+					 "address":"f1d6e0586b0a20c7",
+					 "keys":"3335dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7"
+				},
+				"admin-account-2":{
+					 "address":"f2d6e0586b0a20c7",
+					 "keys":"3335dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7"
+				},
+				"admin-account-3":{
+					 "address":"f3d6e0586b0a20c7",
+					 "keys":"3335dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7"
+				}
+		 }
+	}`)
+
+	b3 := []byte(`{
+		 "accounts":{
+				"admin-account-5":{
+					 "address":"f5d6e0586b0a20c7",
+					 "keys":"3335dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7"
+				}
+		 }
+	}`)
+
+	mockFS := afero.NewMemMapFs()
+	err := afero.WriteFile(mockFS, "flow.json", b, 0644)
+	err2 := afero.WriteFile(mockFS, "private.json", b2, 0644)
+	err3 := afero.WriteFile(mockFS, "private.testnet.json", b3, 0644)
+
+	require.NoError(t, err)
+	require.NoError(t, err2)
+	require.NoError(t, err3)
+
+	composer := config.NewLoader(mockFS)
+	composer.AddConfigParser(json.NewParser())
+
+	conf, loadErr := composer.Load([]string{"flow.json"})
+
+	require.NoError(t, loadErr)
+	assert.Equal(t, 4, len(conf.Accounts))
+	assert.NotNil(t, conf.Accounts.GetByName("service-account"))
+	assert.NotNil(t, conf.Accounts.GetByName("admin-account-1"))
+	assert.NotNil(t, conf.Accounts.GetByName("admin-account-3"))
+	assert.NotNil(t, conf.Accounts.GetByName("admin-account-5"))
+	assert.Equal(t, conf.Accounts.GetByName("service-account").Address.String(), "f8d6e0586b0a20c7")
+	assert.Equal(t, conf.Accounts.GetByName("admin-account-1").Address.String(), "f1d6e0586b0a20c7")
+	assert.Equal(t, conf.Accounts.GetByName("admin-account-3").Address.String(), "f3d6e0586b0a20c7")
+	assert.Equal(t, conf.Accounts.GetByName("admin-account-5").Address.String(), "f5d6e0586b0a20c7")
+}
+
+func Test_JSONEnv(t *testing.T) {
+	b := []byte(`{
+		"accounts": {
+			"emulator-account": {
+				"address": "f8d6e0586b0a20c7",
+				"keys": "$EMULATOR_KEY",
+				"chain": "flow-emulator"
+			}
+		}
+	}`)
+
+	os.Setenv("EMULATOR_KEY", "21c5dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7")
+
+	mockFS := afero.NewMemMapFs()
+	err := afero.WriteFile(mockFS, "test2-flow.json", b, 0644)
+
+	require.NoError(t, err)
+
+	composer := config.NewLoader(mockFS)
+	composer.AddConfigParser(json.NewParser())
+	conf, loadErr := composer.Load([]string{"test2-flow.json"})
+
+	require.NoError(t, loadErr)
+	assert.Equal(t, 1, len(conf.Accounts))
+	assert.Equal(t, "21c5dfdeb0ff03a7a73ef39788563b62c89adea67bbb21ab95e5f710bd1d40b7", conf.Accounts[0].Keys[0].Context["privateKey"])
+}
diff --git a/flow/project/cli/project_test.go b/flow/project/cli/project_test.go
index 3179bed2..e3f11f2d 100644
--- a/flow/project/cli/project_test.go
+++ b/flow/project/cli/project_test.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ package cli
 
 import (
 	"fmt"
+	"github.com/spf13/afero"
 	"sort"
 	"testing"
 
@@ -30,6 +31,8 @@ import (
 	"github.com/thoas/go-funk"
 )
 
+var composer = config.NewLoader(afero.NewOsFs())
+
 func generateComplexProject() Project {
 	config := config.Config{
 		Emulators: config.Emulators{{
@@ -122,7 +125,7 @@ func generateComplexProject() Project {
 		}},
 	}
 
-	p, err := newProject(&config)
+	p, err := newProject(&config, composer)
 	if err != nil {
 		fmt.Println(err)
 	}
@@ -168,7 +171,7 @@ func generateSimpleProject() Project {
 		}},
 	}
 
-	p, err := newProject(&config)
+	p, err := newProject(&config, composer)
 	if err != nil {
 		fmt.Println(err)
 	}
@@ -219,7 +222,7 @@ func generateAliasesProject() Project {
 		}},
 	}
 
-	p, err := newProject(&config)
+	p, err := newProject(&config, composer)
 	if err != nil {
 		fmt.Println(err)
 	}
@@ -300,7 +303,7 @@ func generateAliasesComplexProject() Project {
 		}},
 	}
 
-	p, err := newProject(&config)
+	p, err := newProject(&config, composer)
 	if err != nil {
 		fmt.Println(err)
 	}
diff --git a/flow/project/contracts/contracts_test.go b/flow/project/contracts/contracts_test.go
index abc7dbfc..f5973b0e 100644
--- a/flow/project/contracts/contracts_test.go
+++ b/flow/project/contracts/contracts_test.go
@@ -1,7 +1,7 @@
 /*
  * Flow CLI
  *
- * Copyright 2019-2020 Dapper Labs, Inc.
+ * Copyright 2019-2021 Dapper Labs, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.