diff --git a/app/assets/javascripts/environments/components/edit_environment.vue b/app/assets/javascripts/environments/components/edit_environment.vue
index 91145db10e2923223195482f335e12521eaefddf..c835cab6175637aa45527596275aef3fa7cde792 100644
--- a/app/assets/javascripts/environments/components/edit_environment.vue
+++ b/app/assets/javascripts/environments/components/edit_environment.vue
@@ -3,7 +3,9 @@ import { GlLoadingIcon } from '@gitlab/ui';
 import { createAlert } from '~/alert';
 import axios from '~/lib/utils/axios_utils';
 import { visitUrl } from '~/lib/utils/url_utility';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
 import getEnvironment from '../graphql/queries/environment.query.graphql';
+import updateEnvironment from '../graphql/mutations/update_environment.mutation.graphql';
 import EnvironmentForm from './environment_form.vue';
 
 export default {
@@ -11,6 +13,7 @@ export default {
     GlLoadingIcon,
     EnvironmentForm,
   },
+  mixins: [glFeatureFlagsMixin()],
   inject: ['projectEnvironmentsPath', 'updateEnvironmentPath', 'projectPath', 'environmentName'],
   apollo: {
     environment: {
@@ -42,6 +45,44 @@ export default {
       this.formEnvironment = environment;
     },
     onSubmit() {
+      if (this.glFeatures?.environmentSettingsToGraphql) {
+        this.updateWithGraphql();
+      } else {
+        this.updateWithAxios();
+      }
+    },
+    async updateWithGraphql() {
+      this.loading = true;
+      try {
+        const { data } = await this.$apollo.mutate({
+          mutation: updateEnvironment,
+          variables: {
+            input: {
+              id: this.formEnvironment.id,
+              externalUrl: this.formEnvironment.externalUrl,
+            },
+          },
+        });
+
+        const { errors } = data.environmentUpdate;
+
+        if (errors.length > 0) {
+          throw new Error(errors[0]?.message ?? errors[0]);
+        }
+
+        const { path } = data.environmentUpdate.environment;
+
+        if (path) {
+          visitUrl(path);
+        }
+      } catch (error) {
+        const { message } = error;
+        createAlert({ message });
+      } finally {
+        this.loading = false;
+      }
+    },
+    updateWithAxios() {
       this.loading = true;
       axios
         .put(this.updateEnvironmentPath, {
diff --git a/app/assets/javascripts/environments/graphql/mutations/update_environment.mutation.graphql b/app/assets/javascripts/environments/graphql/mutations/update_environment.mutation.graphql
new file mode 100644
index 0000000000000000000000000000000000000000..9ea0e3609cbc72591e873d5bf73ef2c669579ff2
--- /dev/null
+++ b/app/assets/javascripts/environments/graphql/mutations/update_environment.mutation.graphql
@@ -0,0 +1,9 @@
+mutation updateEnvironment($input: EnvironmentUpdateInput!) {
+  environmentUpdate(input: $input) {
+    environment {
+      id
+      path
+    }
+    errors
+  }
+}
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index f91ec55573d44efee2eb432667395dfdf6cd0eef..0db26c544fa777adfb19bb932347f85f6fae6c2c 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -25,6 +25,10 @@ class Projects::EnvironmentsController < Projects::ApplicationController
     push_frontend_feature_flag(:kas_user_access_project, @project)
   end
 
+  before_action only: [:edit, :new] do
+    push_frontend_feature_flag(:environment_settings_to_graphql, @project)
+  end
+
   before_action :authorize_read_environment!, except: [:metrics, :additional_metrics, :metrics_dashboard, :metrics_redirect]
   before_action :authorize_create_environment!, only: [:new, :create]
   before_action :authorize_stop_environment!, only: [:stop]
diff --git a/config/feature_flags/development/environment_settings_to_graphql.yml b/config/feature_flags/development/environment_settings_to_graphql.yml
new file mode 100644
index 0000000000000000000000000000000000000000..89da0c7332468440661415c5575409e49b283a5e
--- /dev/null
+++ b/config/feature_flags/development/environment_settings_to_graphql.yml
@@ -0,0 +1,8 @@
+---
+name: environment_settings_to_graphql
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/121091
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/412332
+milestone: '16.1'
+type: development
+group: group::environments
+default_enabled: false
diff --git a/spec/frontend/environments/edit_environment_spec.js b/spec/frontend/environments/edit_environment_spec.js
index cc28e12788ba9cdaf4bcd6e3e433869855617957..853eb185786447d82380b58d97ad408442935d63 100644
--- a/spec/frontend/environments/edit_environment_spec.js
+++ b/spec/frontend/environments/edit_environment_spec.js
@@ -10,14 +10,21 @@ import axios from '~/lib/utils/axios_utils';
 import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status';
 import { visitUrl } from '~/lib/utils/url_utility';
 import getEnvironment from '~/environments/graphql/queries/environment.query.graphql';
+import updateEnvironment from '~/environments/graphql/mutations/update_environment.mutation.graphql';
 import { __ } from '~/locale';
 import createMockApollo from '../__helpers__/mock_apollo_helper';
 
 jest.mock('~/lib/utils/url_utility');
 jest.mock('~/alert');
 
+const newExternalUrl = 'https://google.ca';
 const environment = { id: '1', name: 'foo', externalUrl: 'https://foo.example.com' };
 const resolvedEnvironment = { project: { id: '1', environment } };
+const environmentUpdate = { environment: { id: '1', path: 'path/to/environment' }, errors: [] };
+const environmentUpdateError = {
+  environment: null,
+  errors: [{ message: 'uh oh!' }],
+};
 
 const provide = {
   projectEnvironmentsPath: '/projects/environments',
@@ -28,25 +35,40 @@ const provide = {
 };
 
 describe('~/environments/components/edit.vue', () => {
-  Vue.use(VueApollo);
-
   let wrapper;
   let mock;
 
-  const createWrapper = () => {
-    const mockApollo = createMockApollo([
-      [getEnvironment, jest.fn().mockResolvedValue({ data: resolvedEnvironment })],
-    ]);
+  const createMockApolloProvider = (mutationResult, environmentSettingsToGraphql) => {
+    Vue.use(VueApollo);
 
-    return mountExtended(EditEnvironment, {
-      provide,
-      apolloProvider: mockApollo,
-    });
+    const mocks = [[getEnvironment, jest.fn().mockResolvedValue({ data: resolvedEnvironment })]];
+
+    if (environmentSettingsToGraphql) {
+      mocks.push([
+        updateEnvironment,
+        jest.fn().mockResolvedValue({ data: { environmentUpdate: mutationResult } }),
+      ]);
+    }
+
+    return createMockApollo(mocks);
   };
 
-  afterEach(() => {
-    mock.restore();
-  });
+  const createWrapper = async ({
+    mutationResult = environmentUpdate,
+    environmentSettingsToGraphql = false,
+  } = {}) => {
+    wrapper = mountExtended(EditEnvironment, {
+      provide: {
+        ...provide,
+        glFeatures: {
+          environmentSettingsToGraphql,
+        },
+      },
+      apolloProvider: createMockApolloProvider(mutationResult, environmentSettingsToGraphql),
+    });
+
+    await waitForPromises();
+  };
 
   const findNameInput = () => wrapper.findByLabelText(__('Name'));
   const findExternalUrlInput = () => wrapper.findByLabelText(__('External URL'));
@@ -54,24 +76,14 @@ describe('~/environments/components/edit.vue', () => {
 
   const showsLoading = () => wrapper.findComponent(GlLoadingIcon).exists();
 
-  const submitForm = async (expected, response) => {
-    mock
-      .onPut(provide.updateEnvironmentPath, {
-        external_url: expected.url,
-        id: '1',
-      })
-      .reply(...response);
-    await findExternalUrlInput().setValue(expected.url);
-
+  const submitForm = async () => {
+    await findExternalUrlInput().setValue(newExternalUrl);
     await findForm().trigger('submit');
-    await waitForPromises();
   };
 
   describe('default', () => {
     beforeEach(async () => {
-      mock = new MockAdapter(axios);
-      wrapper = createWrapper();
-      await waitForPromises();
+      await createWrapper();
     });
 
     it('sets the title to Edit environment', () => {
@@ -79,50 +91,118 @@ describe('~/environments/components/edit.vue', () => {
       expect(header.exists()).toBe(true);
     });
 
-    it('shows loader after form is submitted', async () => {
-      const expected = { url: 'https://google.ca' };
+    it('renders a disabled "Name" field', () => {
+      const nameInput = findNameInput();
 
-      expect(showsLoading()).toBe(false);
+      expect(nameInput.attributes().disabled).toBe('disabled');
+      expect(nameInput.element.value).toBe(environment.name);
+    });
 
-      await submitForm(expected, [HTTP_STATUS_OK, { path: '/test' }]);
+    it('renders an "External URL" field', () => {
+      const urlInput = findExternalUrlInput();
 
-      expect(showsLoading()).toBe(true);
+      expect(urlInput.element.value).toBe(environment.externalUrl);
     });
+  });
 
-    it('submits the updated environment on submit', async () => {
-      const expected = { url: 'https://google.ca' };
+  describe('when environmentSettingsToGraphql feature is enabled', () => {
+    describe('when mutation successful', () => {
+      beforeEach(async () => {
+        await createWrapper({ environmentSettingsToGraphql: true });
+      });
 
-      await submitForm(expected, [HTTP_STATUS_OK, { path: '/test' }]);
+      it('shows loader after form is submitted', async () => {
+        expect(showsLoading()).toBe(false);
 
-      expect(visitUrl).toHaveBeenCalledWith('/test');
+        await submitForm();
+
+        expect(showsLoading()).toBe(true);
+      });
+
+      it('submits the updated environment on submit', async () => {
+        await submitForm();
+        await waitForPromises();
+
+        expect(visitUrl).toHaveBeenCalledWith(environmentUpdate.environment.path);
+      });
     });
 
-    it('shows errors on error', async () => {
-      const expected = { url: 'https://google.ca' };
+    describe('when mutation failed', () => {
+      beforeEach(async () => {
+        await createWrapper({
+          mutationResult: environmentUpdateError,
+          environmentSettingsToGraphql: true,
+        });
+      });
+
+      it('shows errors on error', async () => {
+        await submitForm();
+        await waitForPromises();
+
+        expect(createAlert).toHaveBeenCalledWith({ message: 'uh oh!' });
+        expect(showsLoading()).toBe(false);
+      });
+    });
+  });
 
-      await submitForm(expected, [HTTP_STATUS_BAD_REQUEST, { message: ['uh oh!'] }]);
+  describe('when environmentSettingsToGraphql feature is disabled', () => {
+    beforeEach(async () => {
+      mock = new MockAdapter(axios);
+      await createWrapper();
+    });
 
-      expect(createAlert).toHaveBeenCalledWith({ message: 'uh oh!' });
+    afterEach(() => {
+      mock.restore();
+    });
+
+    it('shows loader after form is submitted', async () => {
       expect(showsLoading()).toBe(false);
+
+      mock
+        .onPut(provide.updateEnvironmentPath, {
+          external_url: newExternalUrl,
+          id: environment.id,
+        })
+        .reply(...[HTTP_STATUS_OK, { path: '/test' }]);
+
+      await submitForm();
+
+      expect(showsLoading()).toBe(true);
     });
 
-    it('renders a disabled "Name" field', () => {
-      const nameInput = findNameInput();
+    it('submits the updated environment on submit', async () => {
+      mock
+        .onPut(provide.updateEnvironmentPath, {
+          external_url: newExternalUrl,
+          id: environment.id,
+        })
+        .reply(...[HTTP_STATUS_OK, { path: '/test' }]);
+
+      await submitForm();
+      await waitForPromises();
 
-      expect(nameInput.attributes().disabled).toBe('disabled');
-      expect(nameInput.element.value).toBe(environment.name);
+      expect(visitUrl).toHaveBeenCalledWith('/test');
     });
 
-    it('renders an "External URL" field', () => {
-      const urlInput = findExternalUrlInput();
+    it('shows errors on error', async () => {
+      mock
+        .onPut(provide.updateEnvironmentPath, {
+          external_url: newExternalUrl,
+          id: environment.id,
+        })
+        .reply(...[HTTP_STATUS_BAD_REQUEST, { message: ['uh oh!'] }]);
+
+      await submitForm();
+      await waitForPromises();
 
-      expect(urlInput.element.value).toBe(environment.externalUrl);
+      expect(createAlert).toHaveBeenCalledWith({ message: 'uh oh!' });
+      expect(showsLoading()).toBe(false);
     });
   });
 
   describe('when environment query is loading', () => {
     beforeEach(() => {
-      wrapper = createWrapper();
+      createWrapper();
     });
 
     it('renders loading icon', () => {