diff --git a/go.mod b/go.mod
index 5b1ff211661b8873917cdd56c34674080e555286..34ffdba5a712c27fc37b6c5018c994793f6f2016 100644
--- a/go.mod
+++ b/go.mod
@@ -29,6 +29,7 @@ require (
 	github.com/ipfs/go-cid v0.0.4
 	github.com/ipfs/go-ipfs v0.4.22-0.20191119151441-b8ec598d5801
 	github.com/ipfs/go-ipfs-config v0.0.11
+	github.com/ipfs/go-ipfs-files v0.0.4
 	github.com/ipfs/go-ipfs-http-client v0.0.5
 	github.com/ipfs/interface-go-ipfs-core v0.2.6
 	github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 // indirect
diff --git a/ipfs_proxy/api.go b/ipfs_proxy/api.go
index 0be1279639e0368c7ef5de07a23f6a4bf96ee356..ba61094bf181f66da78492a15bba72af30f7fd8b 100644
--- a/ipfs_proxy/api.go
+++ b/ipfs_proxy/api.go
@@ -1,6 +1,8 @@
 package ipfs_proxy
 
 import (
+	"io"
+
 	"github.com/sirupsen/logrus"
 	"github.com/turt2live/matrix-media-repo/common/config"
 	"github.com/turt2live/matrix-media-repo/common/rcontext"
@@ -59,3 +61,7 @@ func getImpl() IPFSImplementation {
 func GetObject(contentId string, ctx rcontext.RequestContext) (*ipfs_models.IPFSObject, error) {
 	return getImpl().GetObject(contentId, ctx)
 }
+
+func PutObject(data io.Reader, ctx rcontext.RequestContext) (string, error) {
+	return getImpl().PutObject(data, ctx)
+}
diff --git a/ipfs_proxy/iface.go b/ipfs_proxy/iface.go
index 6fa26de48f321d870bfe11ae4d1205aac204f439..e80a55189c8001de2f36f9a754dc345e2b987d9c 100644
--- a/ipfs_proxy/iface.go
+++ b/ipfs_proxy/iface.go
@@ -1,11 +1,14 @@
 package ipfs_proxy
 
 import (
+	"io"
+
 	"github.com/turt2live/matrix-media-repo/common/rcontext"
 	"github.com/turt2live/matrix-media-repo/ipfs_proxy/ipfs_models"
 )
 
 type IPFSImplementation interface {
 	GetObject(contentId string, ctx rcontext.RequestContext) (*ipfs_models.IPFSObject, error)
+	PutObject(data io.Reader, ctx rcontext.RequestContext) (string, error)
 	Stop()
 }
diff --git a/ipfs_proxy/ipfs_embedded/impl.go b/ipfs_proxy/ipfs_embedded/impl.go
index 24b59f7a51bba4e66992747d0ed777fca474fe71..d348f4f1ae0061b6030cf5e77283d21062bbc623 100644
--- a/ipfs_proxy/ipfs_embedded/impl.go
+++ b/ipfs_proxy/ipfs_embedded/impl.go
@@ -3,6 +3,7 @@ package ipfs_embedded
 import (
 	"bytes"
 	"context"
+	"io"
 	"io/ioutil"
 	"path/filepath"
 	"sync"
@@ -10,6 +11,7 @@ import (
 
 	"github.com/ipfs/go-cid"
 	ipfsConfig "github.com/ipfs/go-ipfs-config"
+	files "github.com/ipfs/go-ipfs-files"
 	"github.com/ipfs/go-ipfs/core"
 	"github.com/ipfs/go-ipfs/core/coreapi"
 	"github.com/ipfs/go-ipfs/core/node/libp2p"
@@ -27,14 +29,18 @@ import (
 )
 
 type IPFSEmbedded struct {
-	api  icore.CoreAPI
-	node *core.IpfsNode
+	api         icore.CoreAPI
+	node        *core.IpfsNode
+	ctx         context.Context
+	cancelCtxFn context.CancelFunc
 }
 
 func NewEmbeddedIPFSNode() (IPFSEmbedded, error) {
 	// Startup routine modified from:
 	// https://github.com/ipfs/go-ipfs/blob/083ef47ce84a5bd9a93f0ce0afaf668881dc1f35/docs/examples/go-ipfs-as-a-library/main.go
 
+	ctx, cancel := context.WithCancel(context.Background())
+
 	blank := IPFSEmbedded{}
 
 	// Load plugins
@@ -85,7 +91,7 @@ func NewEmbeddedIPFSNode() (IPFSEmbedded, error) {
 	}
 
 	logrus.Info("Building IPFS embedded node")
-	node, err := core.NewNode(context.Background(), nodeOptions)
+	node, err := core.NewNode(ctx, nodeOptions)
 	if err != nil {
 		return blank, err
 	}
@@ -135,7 +141,7 @@ func NewEmbeddedIPFSNode() (IPFSEmbedded, error) {
 	for _, peerInfo := range peerInfos {
 		go func(peerInfo *peerstore.PeerInfo) {
 			defer wg.Done()
-			err := api.Swarm().Connect(context.Background(), *peerInfo)
+			err := api.Swarm().Connect(ctx, *peerInfo)
 			if err != nil {
 				logrus.Error(err)
 			} else {
@@ -147,8 +153,10 @@ func NewEmbeddedIPFSNode() (IPFSEmbedded, error) {
 
 	logrus.Info("Done building IPFS embedded node")
 	return IPFSEmbedded{
-		api:  api,
-		node: node,
+		api:         api,
+		node:        node,
+		ctx:         ctx,
+		cancelCtxFn: cancel,
 	}, nil
 }
 
@@ -160,7 +168,7 @@ func (i IPFSEmbedded) GetObject(contentId string, ctx rcontext.RequestContext) (
 	}
 
 	ctx.Log.Info("Resolving path and node")
-	timeoutCtx, cancel := context.WithTimeout(ctx.Context, 10 * time.Second)
+	timeoutCtx, cancel := context.WithTimeout(ctx.Context, 10*time.Second)
 	defer cancel()
 	ipfsPath := icorepath.IpfsPath(ipfsCid)
 	node, err := i.api.ResolveNode(timeoutCtx, ipfsPath)
@@ -177,6 +185,16 @@ func (i IPFSEmbedded) GetObject(contentId string, ctx rcontext.RequestContext) (
 	}, nil
 }
 
+func (i IPFSEmbedded) PutObject(data io.Reader, ctx rcontext.RequestContext) (string, error) {
+	ipfsFile := files.NewReaderFile(data)
+	p, err := i.api.Unixfs().Add(ctx.Context, ipfsFile)
+	if err != nil {
+		return "", err
+	}
+	return p.Cid().String(), nil
+}
+
 func (i IPFSEmbedded) Stop() {
+	i.cancelCtxFn()
 	i.node.Close()
 }
diff --git a/ipfs_proxy/ipfs_local/impl.go b/ipfs_proxy/ipfs_local/impl.go
index f37cb699528e9abfd2f60beb1183aadab3233cd7..25c38dec9cb9761b7b40cb3c3647a874e7f31b77 100644
--- a/ipfs_proxy/ipfs_local/impl.go
+++ b/ipfs_proxy/ipfs_local/impl.go
@@ -2,8 +2,10 @@ package ipfs_local
 
 import (
 	"bytes"
+	"io"
 
 	"github.com/ipfs/go-cid"
+	files "github.com/ipfs/go-ipfs-files"
 	httpapi "github.com/ipfs/go-ipfs-http-client"
 	"github.com/ipfs/interface-go-ipfs-core/path"
 	"github.com/turt2live/matrix-media-repo/common/rcontext"
@@ -42,6 +44,15 @@ func (i IPFSLocal) GetObject(contentId string, ctx rcontext.RequestContext) (*ip
 	}, nil
 }
 
+func (i IPFSLocal) PutObject(data io.Reader, ctx rcontext.RequestContext) (string, error) {
+	ipfsFile := files.NewReaderFile(data)
+	p, err := i.client.Unixfs().Add(ctx.Context, ipfsFile)
+	if err != nil {
+		return "", err
+	}
+	return p.Cid().String(), nil
+}
+
 func (i IPFSLocal) Stop() {
 	// Nothing to do
 }