diff --git a/frontend/src/api/cubicweb.ts b/frontend/src/api/cubicweb.ts
index d8620eb1106f86a39cea57fd8ba7be2a4f8909ef_ZnJvbnRlbmQvc3JjL2FwaS9jdWJpY3dlYi50cw==..6ad5b6834f8e431d991fcc6609b6a5ab88b74bcc_ZnJvbnRlbmQvc3JjL2FwaS9jdWJpY3dlYi50cw== 100644
--- a/frontend/src/api/cubicweb.ts
+++ b/frontend/src/api/cubicweb.ts
@@ -9,6 +9,9 @@
 } from "@cubicweb/client";
 import { useRouter } from "next/navigation";
 
+const INSERT_FILE_RQL =
+  "INSERT File X: X data %(data)s, X data_format %(data_format)s, X data_name %(data_name)s";
+
 export function useApiLogin() {
   const client = useClient();
   return (login: string, password: string) => client.login(login, password);
@@ -42,8 +45,35 @@
   return (eid: number) => client.execute(rql, { eid });
 }
 
+export function useApiUploadDataServiceFile() {
+  const client = useClient();
+
+  return async (file: File): Promise<string> => {
+    const transaction = new Transaction();
+    transaction.setBinaries({ file });
+    const insertQuery = transaction.push(INSERT_FILE_RQL, {
+      data: {
+        type: "binary_reference",
+        ref: "file",
+      },
+      data_format: file.type,
+      data_name: file.name,
+    });
+    const downloadUrlQuery = transaction.push(
+      "Any X.download_url() WHERE X eid %(eid)s",
+      {
+        eid: insertQuery.ref().row(0).column(0),
+      },
+    );
+    const result = await client.executeTransaction(transaction);
+    return result.resolveScalar(
+      downloadUrlQuery.ref().row(0).column(0),
+    ) as string;
+  };
+}
+
 function pushSetProjectFiles(
   transaction: Transaction,
   eid: number | TransactionQueryScalarRef,
   data: Omit<Project, "import_recipes">,
 ) {
@@ -45,10 +75,8 @@
 function pushSetProjectFiles(
   transaction: Transaction,
   eid: number | TransactionQueryScalarRef,
   data: Omit<Project, "import_recipes">,
 ) {
-  const insertFileRql =
-    "INSERT File X: X data %(data)s, X data_format %(data_format)s, X data_name %(data_name)s";
   const setOntologyRql =
     "Set X ontology_file %(ontology_file)s WHERE X eid %(eid)s";
   const setShaclRql = "Set X shacl_files %(shacl_files)s WHERE X eid %(eid)s";
@@ -57,7 +85,7 @@
   if (data.ontology_file?.data) {
     const file = data.ontology_file.data;
     binaries["ontology_file"] = file;
-    const insertQuery = transaction.push(insertFileRql, {
+    const insertQuery = transaction.push(INSERT_FILE_RQL, {
       data: {
         type: "binary_reference",
         ref: "ontology_file",
@@ -73,7 +101,7 @@
   if (data.shacl_files?.data) {
     const file = data.shacl_files.data;
     binaries["shacl_files"] = file;
-    const insertQuery = transaction.push(insertFileRql, {
+    const insertQuery = transaction.push(INSERT_FILE_RQL, {
       data: {
         type: "binary_reference",
         ref: "shacl_files",
diff --git a/frontend/src/components/DataServiceForm.tsx b/frontend/src/components/DataServiceForm.tsx
index d8620eb1106f86a39cea57fd8ba7be2a4f8909ef_ZnJvbnRlbmQvc3JjL2NvbXBvbmVudHMvRGF0YVNlcnZpY2VGb3JtLnRzeA==..6ad5b6834f8e431d991fcc6609b6a5ab88b74bcc_ZnJvbnRlbmQvc3JjL2NvbXBvbmVudHMvRGF0YVNlcnZpY2VGb3JtLnRzeA== 100644
--- a/frontend/src/components/DataServiceForm.tsx
+++ b/frontend/src/components/DataServiceForm.tsx
@@ -7,6 +7,13 @@
   Select,
   MenuItem,
   FormHelperText,
+  Button,
+  Typography,
+  CircularProgress,
+  Dialog,
+  DialogTitle,
+  DialogContent,
+  DialogActions,
 } from "@mui/material";
 import SaveIcon from "@mui/icons-material/Save";
 import { DataService } from "@/types";
@@ -10,8 +17,14 @@
 } from "@mui/material";
 import SaveIcon from "@mui/icons-material/Save";
 import { DataService } from "@/types";
-import { Controller, SubmitHandler, useForm } from "react-hook-form";
-import { useState } from "react";
+import {
+  Controller,
+  FormProvider,
+  SubmitHandler,
+  useForm,
+  useFormContext,
+} from "react-hook-form";
+import { ChangeEventHandler, useRef, useState } from "react";
 import { useRouter } from "next/navigation";
 import { LoadingButton } from "@mui/lab";
 import { useHandleAuthErrors } from "@/hooks/useHandleAuthErrors";
@@ -19,6 +32,9 @@
   useApiCreateDataService,
   useApiUpdateDataService,
 } from "@/api/cubicweb";
+import FileUploadIcon from "@mui/icons-material/FileUpload";
+import CancelIcon from "@mui/icons-material/Cancel";
+import { useUploadDataServiceFile } from "@/hooks/useUploadDataServiceFile";
 
 export interface DataServiceFormProps {
   dataService?: DataService;
@@ -26,18 +42,14 @@
 
 export function DataServiceForm({ dataService }: DataServiceFormProps) {
   const [loading, setLoading] = useState(false);
-  const {
-    handleSubmit,
-    control,
-    formState: { isDirty },
-    reset,
-  } = useForm<DataService>({
-    defaultValues: dataService
-      ? dataService
-      : {
-          refresh_period: "weekly",
-        },
-  });
+  const { handleSubmit, formState, reset, ...useFormProps } =
+    useForm<DataService>({
+      defaultValues: dataService
+        ? dataService
+        : {
+            refresh_period: "weekly",
+          },
+    });
   const handleAuthErrors = useHandleAuthErrors();
   const createDataService = useApiCreateDataService();
   const updateDataService = useApiUpdateDataService();
@@ -63,7 +75,54 @@
   };
 
   return (
-    <form onSubmit={handleSubmit(onSubmit)}>
-      <Stack spacing={2}>
-        {!dataService ? (
+    <FormProvider
+      handleSubmit={handleSubmit}
+      formState={formState}
+      reset={reset}
+      {...useFormProps}
+    >
+      <form onSubmit={handleSubmit(onSubmit)}>
+        <Stack spacing={2}>
+          {!dataService ? (
+            <Controller
+              name="name"
+              rules={{ required: true }}
+              render={({ field, fieldState: { error } }) => (
+                <TextField
+                  label="Nom"
+                  required={true}
+                  disabled={loading}
+                  {...field}
+                  error={error !== undefined}
+                  helperText={
+                    error?.type === "required" ? "Champ requis" : error?.message
+                  }
+                />
+              )}
+            />
+          ) : null}
+          <Stack direction={"row"} spacing={2}>
+            <Box flex={1}>
+              <Controller
+                name="data_url"
+                rules={{ required: true }}
+                render={({ field, fieldState: { error } }) => (
+                  <TextField
+                    label="Data URL"
+                    required={true}
+                    disabled={loading}
+                    {...field}
+                    error={error !== undefined}
+                    helperText={
+                      error?.type === "required"
+                        ? "Champ requis"
+                        : error?.message
+                    }
+                    fullWidth
+                  />
+                )}
+              />
+            </Box>
+            <GenerateLinkButton />
+          </Stack>
           <Controller
@@ -69,5 +128,4 @@
           <Controller
-            name="name"
-            control={control}
+            name="refresh_period"
             rules={{ required: true }}
             render={({ field, fieldState: { error } }) => (
@@ -72,3 +130,27 @@
             rules={{ required: true }}
             render={({ field, fieldState: { error } }) => (
+              <FormControl fullWidth>
+                <InputLabel id="update-freq-label">
+                  Fréquence de mise à jour
+                </InputLabel>
+                <Select
+                  labelId="update-freq-label"
+                  label="Fréquence de mise à jour"
+                  {...field}
+                  error={error !== undefined}
+                  disabled={loading}
+                >
+                  <MenuItem value={"daily"}>Journalier</MenuItem>
+                  <MenuItem value={"weekly"}>Hebdomadaire</MenuItem>
+                  <MenuItem value={"monthly"}>Mensuel</MenuItem>
+                </Select>
+                {error ? (
+                  <FormHelperText error={true}>{error?.message}</FormHelperText>
+                ) : null}
+              </FormControl>
+            )}
+          />
+          <Controller
+            name="description"
+            render={({ field, fieldState: { error } }) => (
               <TextField
@@ -74,6 +156,5 @@
               <TextField
-                label="Nom"
-                required={true}
+                label="Description"
                 disabled={loading}
                 {...field}
                 error={error !== undefined}
@@ -77,29 +158,8 @@
                 disabled={loading}
                 {...field}
                 error={error !== undefined}
-                helperText={
-                  error?.type === "required" ? "Champ requis" : error?.message
-                }
-              />
-            )}
-          />
-        ) : null}
-        <Stack direction={"row"} spacing={2}>
-          <Controller
-            name="data_url"
-            control={control}
-            rules={{ required: true }}
-            render={({ field, fieldState: { error } }) => (
-              <TextField
-                label="Data URL"
-                required={true}
-                disabled={loading}
-                {...field}
-                error={error !== undefined}
-                helperText={
-                  error?.type === "required" ? "Champ requis" : error?.message
-                }
+                helperText={error?.message}
                 fullWidth
               />
             )}
           />
@@ -102,35 +162,16 @@
                 fullWidth
               />
             )}
           />
-          <Box minWidth={200}>
-            <Controller
-              name="refresh_period"
-              control={control}
-              rules={{ required: true }}
-              render={({ field, fieldState: { error } }) => (
-                <FormControl fullWidth>
-                  <InputLabel id="update-freq-label">
-                    Fréquence de mise à jour
-                  </InputLabel>
-                  <Select
-                    labelId="update-freq-label"
-                    label="Fréquence de mise à jour"
-                    {...field}
-                    error={error !== undefined}
-                    disabled={loading}
-                  >
-                    <MenuItem value={"daily"}>Journalier</MenuItem>
-                    <MenuItem value={"weekly"}>Hebdomadaire</MenuItem>
-                    <MenuItem value={"monthly"}>Mensuel</MenuItem>
-                  </Select>
-                  {error ? (
-                    <FormHelperText error={true}>
-                      {error?.message}
-                    </FormHelperText>
-                  ) : null}
-                </FormControl>
-              )}
-            />
+          <Box display={"flex"} flexDirection={"row-reverse"}>
+            <LoadingButton
+              variant="contained"
+              startIcon={<SaveIcon />}
+              loading={loading}
+              type="submit"
+              disabled={!formState.isDirty}
+            >
+              {dataService ? "Sauvegarder" : "Ajouter"}
+            </LoadingButton>
           </Box>
         </Stack>
@@ -135,31 +176,6 @@
           </Box>
         </Stack>
-        <Controller
-          name="description"
-          control={control}
-          render={({ field, fieldState: { error } }) => (
-            <TextField
-              label="Description"
-              disabled={loading}
-              {...field}
-              error={error !== undefined}
-              helperText={error?.message}
-              fullWidth
-            />
-          )}
-        />
-        <Box display={"flex"} flexDirection={"row-reverse"}>
-          <LoadingButton
-            variant="contained"
-            startIcon={<SaveIcon />}
-            loading={loading}
-            type="submit"
-            disabled={!isDirty}
-          >
-            {dataService ? "Sauvegarder" : "Ajouter"}
-          </LoadingButton>
-        </Box>
-      </Stack>
-    </form>
+      </form>
+    </FormProvider>
   );
 }
@@ -164,2 +180,69 @@
   );
 }
+
+function GenerateLinkButton() {
+  const { setValue } = useFormContext();
+  const ref = useRef<HTMLInputElement>(null);
+  const { uploadFile, error, loading } = useUploadDataServiceFile();
+  const [errorModalOpen, setErrorModalOpen] = useState(false);
+
+  const chooseFile = () => {
+    ref.current?.click();
+  };
+
+  const onFileChange: ChangeEventHandler<HTMLInputElement> = async (e) => {
+    if (e.target.files && e.target.files.length > 0) {
+      const file = e.target.files[0];
+      const url = await uploadFile(file);
+      if (url) {
+        setValue("data_url", url, { shouldDirty: true });
+      } else {
+        setErrorModalOpen(true);
+      }
+    }
+  };
+
+  function closeErrorDialog() {
+    setErrorModalOpen(false);
+  }
+
+  return (
+    <>
+      <input
+        type="file"
+        style={{ display: "none" }}
+        ref={ref}
+        onChange={onFileChange}
+      />
+      <Button
+        variant="outlined"
+        startIcon={<FileUploadIcon />}
+        onClick={chooseFile}
+      >
+        à partir d&apos;un fichier
+      </Button>
+      <Dialog open={loading}>
+        <DialogTitle>Mise en ligne du fichier...</DialogTitle>
+        <DialogContent>
+          <Box display={"flex"} alignItems={"center"} justifyContent={"center"}>
+            <CircularProgress />
+          </Box>
+        </DialogContent>
+      </Dialog>
+      <Dialog open={errorModalOpen} onClose={closeErrorDialog}>
+        <DialogTitle>Erreur de mise en ligne</DialogTitle>
+        <DialogContent>
+          <Typography>{error}</Typography>
+          <Box display={"flex"} alignItems={"center"} justifyContent={"center"}>
+            <CancelIcon color="error" fontSize="large" />
+          </Box>
+        </DialogContent>
+        <DialogActions>
+          <Button onClick={closeErrorDialog} autoFocus>
+            Close
+          </Button>
+        </DialogActions>
+      </Dialog>
+    </>
+  );
+}
diff --git a/frontend/src/hooks/useUploadDataServiceFile.tsx b/frontend/src/hooks/useUploadDataServiceFile.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6ad5b6834f8e431d991fcc6609b6a5ab88b74bcc_ZnJvbnRlbmQvc3JjL2hvb2tzL3VzZVVwbG9hZERhdGFTZXJ2aWNlRmlsZS50c3g=
--- /dev/null
+++ b/frontend/src/hooks/useUploadDataServiceFile.tsx
@@ -0,0 +1,29 @@
+import { useApiUploadDataServiceFile } from "@/api/cubicweb";
+import { useState } from "react";
+import { useHandleAuthErrors } from "./useHandleAuthErrors";
+
+export function useUploadDataServiceFile() {
+  const [loading, setLoading] = useState(false);
+  const [error, setError] = useState<string | undefined>();
+  const handleAuthErrors = useHandleAuthErrors();
+  const uploadDataServiceFile = useApiUploadDataServiceFile();
+
+  async function uploadFile(file: File): Promise<string | null> {
+    setLoading(true);
+    setError(undefined);
+    try {
+      const downloadUrl = await uploadDataServiceFile(file);
+      setLoading(false);
+      return downloadUrl;
+    } catch (e) {
+      setLoading(false);
+      try {
+        handleAuthErrors(e);
+      } catch {
+        setError(`Une erreur inconnue est survenue`);
+      }
+    }
+    return null;
+  }
+  return { loading, uploadFile, error };
+}