diff --git a/Cargo.lock b/Cargo.lock
index cfa0e817e6be045736e1bbea98e9c36866e019e0..2892c70ab092e114a602809ff7605180a21add7d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -101,7 +101,7 @@ dependencies = [
  "http",
  "httparse",
  "indexmap",
- "itoa",
+ "itoa 0.4.8",
  "language-tags",
  "lazy_static",
  "log",
@@ -128,6 +128,24 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "actix-multipart"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "774bfeb11b54bf9c857a005b8ab893293da4eaff79261a66a9200dab7f5ab6e3"
+dependencies = [
+ "actix-service",
+ "actix-utils",
+ "actix-web",
+ "bytes 0.5.6",
+ "derive_more",
+ "futures-util",
+ "httparse",
+ "log",
+ "mime",
+ "twoway",
+]
+
 [[package]]
 name = "actix-router"
 version = "0.2.7"
@@ -1111,13 +1129,13 @@ dependencies = [
 
 [[package]]
 name = "http"
-version = "0.2.4"
+version = "0.2.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11"
+checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
 dependencies = [
  "bytes 1.1.0",
  "fnv",
- "itoa",
+ "itoa 1.0.2",
 ]
 
 [[package]]
@@ -1163,7 +1181,7 @@ dependencies = [
  "http-body",
  "httparse",
  "httpdate",
- "itoa",
+ "itoa 0.4.8",
  "pin-project 1.0.8",
  "socket2",
  "tokio",
@@ -1257,6 +1275,12 @@ version = "0.4.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
 
+[[package]]
+name = "itoa"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
+
 [[package]]
 name = "js-sys"
 version = "0.3.55"
@@ -1281,6 +1305,7 @@ name = "kuadrado_server"
 version = "2.0.2"
 dependencies = [
  "actix-files",
+ "actix-multipart",
  "actix-web",
  "actix-web-middleware-redirect-https",
  "chrono",
@@ -1313,9 +1338,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
 
 [[package]]
 name = "libc"
-version = "0.2.102"
+version = "0.2.126"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103"
+checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
 
 [[package]]
 name = "linked-hash-map"
@@ -1325,9 +1350,9 @@ checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
 
 [[package]]
 name = "lock_api"
-version = "0.4.5"
+version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
+checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b"
 dependencies = [
  "scopeguard",
 ]
@@ -1588,9 +1613,9 @@ checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170"
 
 [[package]]
 name = "once_cell"
-version = "1.8.0"
+version = "1.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
+checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
 
 [[package]]
 name = "opaque-debug"
@@ -2107,7 +2132,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8"
 dependencies = [
  "indexmap",
- "itoa",
+ "itoa 0.4.8",
  "ryu",
  "serde",
 ]
@@ -2119,7 +2144,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
 dependencies = [
  "form_urlencoded",
- "itoa",
+ "itoa 0.4.8",
  "ryu",
  "serde",
 ]
@@ -2537,9 +2562,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
 
 [[package]]
 name = "tracing"
-version = "0.1.28"
+version = "0.1.35"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "84f96e095c0c82419687c20ddf5cb3eadb61f4e1405923c9dc8e53a1adacbda8"
+checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160"
 dependencies = [
  "cfg-if 1.0.0",
  "log",
@@ -2549,11 +2574,11 @@ dependencies = [
 
 [[package]]
 name = "tracing-core"
-version = "0.1.20"
+version = "0.1.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46125608c26121c81b0c6d693eab5a420e416da7e43c426d2e8f7df8da8a3acf"
+checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7"
 dependencies = [
- "lazy_static",
+ "once_cell",
 ]
 
 [[package]]
@@ -2612,6 +2637,16 @@ version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
 
+[[package]]
+name = "twoway"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c57ffb460d7c24cd6eda43694110189030a3d1dfe418416d9468fd1c1d290b47"
+dependencies = [
+ "memchr",
+ "unchecked-index",
+]
+
 [[package]]
 name = "typed-builder"
 version = "0.4.1"
@@ -2635,6 +2670,12 @@ version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
 
+[[package]]
+name = "unchecked-index"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c"
+
 [[package]]
 name = "unicase"
 version = "2.6.0"
diff --git a/Cargo.toml b/Cargo.toml
index 739a0298ba08e292c595723f136a32d05746bfe3..714776bc2f5f279815ba715c3b596804d5a5c0f6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -25,3 +25,4 @@ time = "0.2.7"
 regex = "1.5"
 tokio = { version = "0.2", features = ["full"] }
 sitemap = "0.4.1"
+actix-multipart = "0.3"
diff --git a/admin-frontend/src/components/root.js b/admin-frontend/src/components/root.js
index e4cf91679b9ed73c8c0773b96b0a0737a4e044f6..449b476ba543595827774d8b0bdc27a92e09ae73 100644
--- a/admin-frontend/src/components/root.js
+++ b/admin-frontend/src/components/root.js
@@ -1,4 +1,5 @@
 
+const { fetch_post_file } = require("../xhr");
 const CreateArticleForm = require("./create-article-form");
 const UpdateArticleForm = require("./update-article-form");
 
@@ -30,6 +31,19 @@ class RootComponent {
             tag: "main",
             contents: [
                 { tag: "h1", contents: "Kuadrado admin panel" },
+                {
+                    tag: "form",
+                    style_rules: { border: "1px solid black" },
+                    enctype: "multipart/form-data",
+                    onsubmit: function (e) {
+                        e.preventDefault();
+                        fetch_post_file(e.target).then(res => console.log(res)).catch(err => console.log(err));
+                    },
+                    contents: [
+                        { tag: "input", name: "file", type: "file" },
+                        { tag: "input", type: "submit" }
+                    ]
+                },
                 {
                     tag: "nav",
                     contents: [
diff --git a/admin-frontend/src/xhr.js b/admin-frontend/src/xhr.js
index a650a6d56620d976e02130ec6aebe703ebf7f9ec..870dd8e61b89ca93cd7f6ec5c999c8dc730707cd 100644
--- a/admin-frontend/src/xhr.js
+++ b/admin-frontend/src/xhr.js
@@ -104,6 +104,23 @@ function fetch_all_articles() {
     });
 }
 
+function fetch_post_file(form) {
+    return new Promise((resolve, reject) => {
+        fetch("/post-file", {
+            method: "POST",
+            body: new FormData(form),
+        }).then(async res => {
+            const res_text = await res.text();
+            if (res.status >= 400 && res.status < 600) {
+                reject(res_text)
+            } else {
+                resolve(res_text);
+            }
+        })
+    })
+
+}
+
 
 module.exports = {
     fetch_article,
@@ -112,4 +129,5 @@ module.exports = {
     fetch_update_article,
     fetch_delete_article,
     fetch_all_articles,
+    fetch_post_file,
 }
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index 7506cd5a0579606933a2e8c53730db4884903174..cdeaa0f2958b810168728e124c8dd480c4f73f68 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -87,6 +87,7 @@ async fn main() -> std::io::Result<()> {
             .service(get_articles_by_category)
             .service(get_article)
             .service(get_all_articles)
+            .service(post_file)
             /////////////////////////////////////////////////////////////////////////////////////////////////////////////
             // STANDARD FILES ///////////////////////////////////////////////////////////////////////////////////////////
             .service(resource("/favicon.ico").route(get().to(favicon)))
diff --git a/src/service.rs b/src/service.rs
index 91376c0ac331b964e71130ef122c5047fb8a152c..6f1d53fa59f271d829cb6839afa4b94b1ca278e6 100644
--- a/src/service.rs
+++ b/src/service.rs
@@ -1,4 +1,6 @@
 mod admin_auth;
 mod articles;
+mod static_files;
 pub use admin_auth::*;
 pub use articles::*;
+pub use static_files::*;
diff --git a/src/service/static_files.rs b/src/service/static_files.rs
new file mode 100644
index 0000000000000000000000000000000000000000..1842be788cba58122e5e2843e5691640cb7de877
--- /dev/null
+++ b/src/service/static_files.rs
@@ -0,0 +1,110 @@
+use crate::{middleware::AuthenticatedAdminMiddleware, AppState};
+use actix_multipart::Multipart;
+use actix_web::{
+    post,
+    web::{block, Data},
+    HttpRequest, HttpResponse, Responder,
+};
+use futures::StreamExt;
+use std::{
+    fs::{remove_file, File},
+    io::Write,
+    path::Path,
+    sync::Arc,
+};
+
+#[post("/post-file")]
+pub async fn post_file(
+    app_state: Data<AppState>,
+    mut payload: Multipart,
+    middleware: Data<AuthenticatedAdminMiddleware<'_>>,
+    req: HttpRequest,
+) -> impl Responder {
+    if middleware.exec(&app_state, &req, None).await.is_err() {
+        return HttpResponse::Unauthorized().finish();
+    }
+
+    while let Some(item) = payload.next().await {
+        match item {
+            Ok(mut field) => {
+                // Field in turn is stream of *Bytes* object
+                // A multipart/form-data stream has to contain `content_disposition`
+                let content_disposition = field
+                    .content_disposition()
+                    .expect("Missing Content Disposition header");
+
+                let filename = content_disposition.get_filename().expect("Missin filename");
+
+                let filepath = Arc::new(Path::new(format!("./tmp/{filename}")));
+
+                // File::create is blocking operation, use threadpool
+                let mut f = block(|| File::create(*filepath.clone())).await;
+
+                if f.is_err() {
+                    return HttpResponse::InternalServerError()
+                        .body(format!("Error creating file {:?}", f));
+                }
+
+                let f = f.unwrap();
+
+                let mut error = None;
+
+                // Field in turn is stream of *Bytes* object
+                'chunks: while let Some(chunk) = field.next().await {
+                    match chunk {
+                        Ok(chunk) => {
+                            // filesystem operations are blocking, we have to use threadpool
+                            if block(move || f.write_all(&chunk).map(|_| f)).await.is_err() {
+                                error = Some("Error writing chunk".to_string());
+                                break 'chunks;
+                            }
+                        }
+                        Err(e) => {
+                            error = format!("Error writing file {} : {:?}", filename, e);
+                            break 'chunks;
+                        }
+                    }
+                }
+
+                if let Some(err) = error {
+                    block(|| remove_file(*filepath.clone())).await.unwrap();
+                    return HttpResponse::InternalServerError().body(err);
+                }
+            }
+            Err(e) => {
+                return HttpResponse::InternalServerError().body(format!("FIELD ERR {:?}", e))
+            }
+        }
+    }
+
+    HttpResponse::Ok().body("File was successfully uploaded")
+}
+
+// use futures::TryStreamExt;
+// use std::io::Write;
+
+// pub async fn save_file(mut payload: Multipart) -> impl Responder {
+//     println!("SAVE FILE");
+//     // iterate over multipart stream
+//     while let Some(mut field) = payload.try_next().await.unwrap() {
+//         // A multipart/form-data stream has to contain `content_disposition`
+//         let content_disposition = field.content_disposition().unwrap();
+//         let filename = content_disposition.get_filename().unwrap();
+//         let filepath = format!("./tmp/{filename}");
+
+//         // File::create is blocking operation, use threadpool
+//         let mut f = actix_web::web::block(|| std::fs::File::create(filepath))
+//             .await
+//             .unwrap();
+
+//         // Field in turn is stream of *Bytes* object
+//         while let Some(chunk) = field.try_next().await.unwrap() {
+//             // filesystem operations are blocking, we have to use threadpool
+//             f = actix_web::web::block(move || f.write_all(&chunk).map(|_| f))
+//                 .await
+//                 .unwrap();
+//         }
+//     }
+
+//     HttpResponse::Ok().body("sucess")
+// }