diff --git a/server/http.go b/server/http.go
index 276feb9a1..ea4d0823a 100644
--- a/server/http.go
+++ b/server/http.go
@@ -43,7 +43,9 @@ const (
 	routeAPIUserInfo                            = "/userinfo"
 	routeAPISubscribeWebhook                    = "/webhook"
 	routeAPISubscriptionsChannel                = "/subscriptions/channel"
+	routeAPISubscriptionTemplates               = "/subscription-templates"
 	routeAPISubscriptionsChannelWithID          = routeAPISubscriptionsChannel + "/{id:[A-Za-z0-9]+}"
+	routeAPISubscriptionTemplatesWithID         = routeAPISubscriptionTemplates + "/{id:[A-Za-z0-9]+}"
 	routeAPISettingsInfo                        = "/settingsinfo"
 	routeIssueTransition                        = "/transition"
 	routeAPIUserDisconnect                      = "/api/v3/disconnect"
@@ -150,6 +152,12 @@ func (p *Plugin) initializeRouter() {
 	apiRouter.HandleFunc(routeAPISubscriptionsChannel, p.checkAuth(p.handleResponse(p.httpChannelCreateSubscription))).Methods(http.MethodPost)
 	apiRouter.HandleFunc(routeAPISubscriptionsChannel, p.checkAuth(p.handleResponse(p.httpChannelEditSubscription))).Methods(http.MethodPut)
 	apiRouter.HandleFunc(routeAPISubscriptionsChannelWithID, p.checkAuth(p.handleResponse(p.httpChannelDeleteSubscription))).Methods(http.MethodDelete)
+
+	// Subscription Templates
+	apiRouter.HandleFunc(routeAPISubscriptionTemplates, p.checkAuth(p.handleResponse(p.httpCreateSubscriptionTemplate))).Methods(http.MethodPost)
+	apiRouter.HandleFunc(routeAPISubscriptionTemplates, p.checkAuth(p.handleResponse(p.httpEditSubscriptionTemplates))).Methods(http.MethodPut)
+	apiRouter.HandleFunc(routeAPISubscriptionTemplatesWithID, p.checkAuth(p.handleResponse(p.httpDeleteSubscriptionTemplate))).Methods(http.MethodDelete)
+	apiRouter.HandleFunc(routeAPISubscriptionTemplates, p.checkAuth(p.handleResponse(p.httpGetSubscriptionTemplates))).Methods(http.MethodGet)
 }
 
 func (p *Plugin) ServeHTTP(c *plugin.Context, w http.ResponseWriter, r *http.Request) {
diff --git a/server/http_test.go b/server/http_test.go
index c7ade75e5..109d5e430 100644
--- a/server/http_test.go
+++ b/server/http_test.go
@@ -23,6 +23,7 @@ import (
 const TestDataLongSubscriptionName = `aaaaaaaaaabbbbbbbbbbccccccccccddddddddddaaaaaaaaaabbbbbbbbbbccccccccccddddddddddaaaaaaaaaabbbbbbbbbbccccccccccddddddddddaaaaaaaaaabbbbbbbbbbccccccccccddddddddddaaaaaaaaaabbbbbbbbbbccccccccccddddddddddaaaaaaaaaabbbbbbbbbbccccccccccddddddddddaaaaaaaaaabbbbbbbbbbccccccccccdddddddddd`
 
 var testSubKey = keyWithInstanceID(mockInstance1URL, JiraSubscriptionsKey)
+var testTemplateKey = keyWithInstanceID(mockInstance1URL, templateKey)
 
 func checkSubscriptionsEqual(t *testing.T, ls1 []ChannelSubscription, ls2 []ChannelSubscription) {
 	assert.Equal(t, len(ls1), len(ls2))
@@ -45,6 +46,27 @@ func checkSubscriptionsEqual(t *testing.T, ls1 []ChannelSubscription, ls2 []Chan
 	}
 }
 
+func checkSubscriptionTemplatesEqual(t *testing.T, st1 []SubscriptionTemplate, st2 []SubscriptionTemplate) {
+	assert.Equal(t, len(st1), len(st2))
+
+	for _, a := range st1 {
+		match := false
+		for _, b := range st2 {
+			if a.ID == b.ID {
+				match = true
+				assert.True(t, a.Filters.Projects.Equals(b.Filters.Projects))
+				assert.True(t, a.Filters.IssueTypes.Equals(b.Filters.IssueTypes))
+				assert.True(t, a.Filters.Events.Equals(b.Filters.Events))
+				break
+			}
+		}
+
+		if !match {
+			assert.Fail(t, "Subscription template arrays are not equal")
+		}
+	}
+}
+
 func checkNotSubscriptions(subsToCheck []ChannelSubscription, existing *Subscriptions, t *testing.T) func(api *plugintest.API) {
 	return func(api *plugintest.API) {
 		var existingBytes []byte
@@ -76,6 +98,36 @@ func checkNotSubscriptions(subsToCheck []ChannelSubscription, existing *Subscrip
 	}
 }
 
+func checkNotSubscriptionTemplates(templatesToCheck []SubscriptionTemplate, existing *Templates, t *testing.T) func(api *plugintest.API) {
+	return func(api *plugintest.API) {
+		var existingBytes []byte
+		if existing != nil {
+			var err error
+			existingBytes, err = json.Marshal(existing)
+			assert.Nil(t, err)
+		}
+
+		api.On("HasPermissionTo", mock.AnythingOfType("string"), mock.Anything).Return(true)
+		api.On("KVGet", testTemplateKey).Return(existingBytes, nil)
+		api.On("KVCompareAndSet", testTemplateKey, existingBytes, mock.MatchedBy(func(data []byte) bool {
+			t.Log(string(data))
+			var savedTemplates Templates
+			err := json.Unmarshal(data, &savedTemplates)
+			assert.Nil(t, err)
+
+			for _, templateToCheck := range templatesToCheck {
+				for _, savedSub := range savedTemplates.Templates.ByID {
+					if templateToCheck.ID == savedSub.ID {
+						return false
+					}
+				}
+			}
+
+			return true
+		})).Return(true, nil)
+	}
+}
+
 func checkHasSubscriptions(subsToCheck []ChannelSubscription, existing *Subscriptions, t *testing.T) func(api *plugintest.API) {
 	return func(api *plugintest.API) {
 		var existingBytes []byte
@@ -125,6 +177,46 @@ func checkHasSubscriptions(subsToCheck []ChannelSubscription, existing *Subscrip
 	}
 }
 
+func checkHasSubscriptionTemplates(templatesToCheck []SubscriptionTemplate, existing *Templates, t *testing.T) func(api *plugintest.API) {
+	return func(api *plugintest.API) {
+		var existingBytes []byte
+		if existing != nil {
+			var err error
+			existingBytes, err = json.Marshal(existing)
+			assert.Nil(t, err)
+		}
+
+		api.On("HasPermissionTo", mock.AnythingOfType("string"), mock.Anything).Return(true)
+		api.On("KVGet", testTemplateKey).Return(existingBytes, nil)
+		api.On("KVCompareAndSet", testTemplateKey, existingBytes, mock.MatchedBy(func(data []byte) bool {
+			t.Log(string(data))
+			var savedTemplates Templates
+			err := json.Unmarshal(data, &savedTemplates)
+			assert.Nil(t, err)
+
+			for _, templateToCheck := range templatesToCheck {
+				var foundSub *SubscriptionTemplate
+				for _, savedSub := range savedTemplates.Templates.ByID {
+					if templateToCheck.Filters.Projects.Equals(savedSub.Filters.Projects) &&
+						templateToCheck.Filters.IssueTypes.Equals(savedSub.Filters.IssueTypes) &&
+						templateToCheck.Filters.Events.Equals(savedSub.Filters.Events) {
+						savedSub := savedSub // fix gosec G601
+						foundSub = &savedSub
+						break
+					}
+				}
+
+				// Check subscription exists
+				if foundSub == nil {
+					return false
+				}
+			}
+
+			return true
+		})).Return(true, nil)
+	}
+}
+
 func hasSubscriptions(subscriptions []ChannelSubscription, t *testing.T) func(api *plugintest.API) {
 	return func(api *plugintest.API) {
 		subs := withExistingChannelSubscriptions(subscriptions)
@@ -138,6 +230,26 @@ func hasSubscriptions(subscriptions []ChannelSubscription, t *testing.T) func(ap
 	}
 }
 
+func hasSubscriptionTemplates(templates []SubscriptionTemplate, t *testing.T) func(api *plugintest.API) {
+	return func(api *plugintest.API) {
+		templates := withExistingChannelSubscriptionTemplates(templates)
+
+		existingBytes, err := json.Marshal(&templates)
+		assert.Nil(t, err)
+
+		api.On("HasPermissionTo", mock.AnythingOfType("string"), mock.Anything).Return(true)
+		api.On("KVGet", testTemplateKey).Return(existingBytes, nil)
+	}
+}
+
+func getMockSubscriptionFilter(event string) *SubscriptionFilters {
+	return &SubscriptionFilters{
+		Events:     NewStringSet(event),
+		Projects:   NewStringSet("myproject"),
+		IssueTypes: NewStringSet("10001"),
+	}
+}
+
 func TestSubscribe(t *testing.T) {
 	for name, tc := range map[string]struct {
 		subscription       string
@@ -854,3 +966,492 @@ func TestGetSubscriptionsForChannel(t *testing.T) {
 		})
 	}
 }
+
+func TestDeleteSubscriptionTemplate(t *testing.T) {
+	for name, tc := range map[string]struct {
+		templateID         string
+		expectedStatusCode int
+		skipAuthorize      bool
+		apiCalls           func(*plugintest.API)
+	}{
+		"Invalid": {
+			templateID:         "mockTemplateID1",
+			expectedStatusCode: http.StatusBadRequest,
+		},
+		"Not Authorized": {
+			templateID:         model.NewId(),
+			expectedStatusCode: http.StatusUnauthorized,
+			skipAuthorize:      true,
+		},
+		"Successful delete": {
+			templateID:         "mockTemplateID1aaaaaaaaaaa",
+			expectedStatusCode: http.StatusOK,
+			apiCalls: checkNotSubscriptionTemplates([]SubscriptionTemplate{
+				{
+					ID:      "mockTemplateID1___________",
+					Filters: getMockSubscriptionFilter("jira:issue_created"),
+				},
+			},
+				withExistingChannelSubscriptionTemplates(
+					[]SubscriptionTemplate{
+						{
+							ID:      "mockTemplateID1___________",
+							Filters: getMockSubscriptionFilter("jira:issue_created"),
+						},
+						{
+							ID:      "mockTemplateID2___________",
+							Filters: getMockSubscriptionFilter("jira:issue_created"),
+						},
+					}), t),
+		},
+	} {
+		t.Run(name, func(t *testing.T) {
+			api := &plugintest.API{}
+			p := Plugin{}
+
+			api.On("LogDebug", mockAnythingOfTypeBatch("string", 11)...).Return(nil)
+			api.On("LogError", mockAnythingOfTypeBatch("string", 13)...).Return(nil)
+			api.On("LogWarn", mockAnythingOfTypeBatch("string", 11)...).Return()
+			api.On("LogWarn", mockAnythingOfTypeBatch("string", 7)...).Return()
+			api.On("GetChannelMember", mockAnythingOfTypeBatch("string", 2)...).Return(&model.ChannelMember{}, (*model.AppError)(nil))
+			api.On("CreatePost", mock.AnythingOfType("*model.Post")).Return(&model.Post{}, nil)
+			api.On("SendEphemeralPost", mock.Anything, mock.Anything).Return(nil)
+
+			if tc.apiCalls != nil {
+				tc.apiCalls(api)
+			}
+
+			p.updateConfig(func(conf *config) {
+				conf.Secret = someSecret
+			})
+
+			p.initializeRouter()
+			p.SetAPI(api)
+			p.client = pluginapi.NewClient(api, p.Driver)
+			p.userStore = mockUserStore{}
+			p.instanceStore = p.getMockInstanceStoreKV(1)
+
+			w := httptest.NewRecorder()
+
+			request := httptest.NewRequest(http.MethodDelete,
+				"/api/v2/subscription-templates/"+tc.templateID+"?instance_id="+testInstance1.GetID().String()+"&project_key=myproject",
+				nil)
+
+			if !tc.skipAuthorize {
+				request.Header.Set(HeaderMattermostUserID, model.NewId())
+			}
+
+			p.ServeHTTP(&plugin.Context{}, w, request)
+			body, _ := io.ReadAll(w.Result().Body)
+			t.Log(string(body))
+			assert.Equal(t, tc.expectedStatusCode, w.Result().StatusCode)
+		})
+	}
+}
+
+func TestEditSubscriptionTemplate(t *testing.T) {
+	count := 0
+	for name, tc := range map[string]struct {
+		subscriptionTemplate string
+		expectedStatusCode   int
+		skipAuthorize        bool
+		apiCalls             func(*plugintest.API)
+	}{
+		"Not Authorized": {
+			subscriptionTemplate: "{}",
+			expectedStatusCode:   http.StatusUnauthorized,
+			skipAuthorize:        true,
+		},
+		"Won't Decode": {
+			subscriptionTemplate: "{test",
+			expectedStatusCode:   http.StatusBadRequest,
+		},
+		"Editing subscription template": {
+			subscriptionTemplate: `{
+				"instance_id": "https://jiraurl1.com",
+				"name": "mockName",
+				"id": "mockTemplateID1___________",
+				"filters": {
+				  "events": [
+					"jira:issue_created"
+				  ],
+				  "projects": [
+					"myproject"
+				  ],
+				  "issue_types": [
+					"10001"
+				  ],
+				  "fields": []
+				}
+			  }`,
+			expectedStatusCode: http.StatusOK,
+			apiCalls: checkHasSubscriptionTemplates([]SubscriptionTemplate{
+				{
+					ID:      "mockTemplateID1___________",
+					Filters: getMockSubscriptionFilter("jira:issue_created"),
+				},
+			},
+				withExistingChannelSubscriptionTemplates(
+					[]SubscriptionTemplate{
+						{
+							ID:      "mockTemplateID1___________",
+							Filters: getMockSubscriptionFilter("jira:issue_created"),
+						},
+					}), t),
+		},
+		"Editing subscription template, no name provided": {
+			subscriptionTemplate: `{"instance_id": "https://jiraurl1.com", "name": "", "id": "mockTemplateID1___________", "channel_id": "mockChannelID_____________", "filters": {"events": ["jira:issue_created"], "projects": ["otherproject"], "issue_types": ["10001"]}}`,
+			expectedStatusCode:   http.StatusInternalServerError,
+			apiCalls: checkHasSubscriptionTemplates([]SubscriptionTemplate{},
+				withExistingChannelSubscriptionTemplates(
+					[]SubscriptionTemplate{
+						{
+							ID:      "mockTemplateID1___________",
+							Filters: getMockSubscriptionFilter("jira:issue_created"),
+						},
+					}), t),
+		},
+		"Editing subscription template, name too long": {
+			subscriptionTemplate: `{"instance_id": "https://jiraurl1.com", "name": "` + TestDataLongSubscriptionName + `", "id": "mockTemplateID1___________", "channel_id": "mockChannelID_____________", "filters": {"events": ["jira:issue_created"], "projects": ["otherproject"], "issue_types": ["10001"]}}`,
+			expectedStatusCode:   http.StatusInternalServerError,
+			apiCalls: checkHasSubscriptionTemplates([]SubscriptionTemplate{},
+				withExistingChannelSubscriptionTemplates(
+					[]SubscriptionTemplate{
+						{
+							ID:      "mockTemplateID1___________",
+							Filters: getMockSubscriptionFilter("jira:issue_created"),
+						},
+					}), t),
+		},
+		"Editing subscription template, no project provided": {
+			subscriptionTemplate: `{"instance_id": "https://jiraurl1.com", "name": "mockName", "id": "mockTemplateID1___________", "channel_id": "mockChannelID_____________", "filters": {"events": ["jira:issue_created"], "projects": [], "issue_types": ["10001"]}}`,
+			expectedStatusCode:   http.StatusInternalServerError,
+			apiCalls: checkHasSubscriptionTemplates([]SubscriptionTemplate{},
+				withExistingChannelSubscriptionTemplates(
+					[]SubscriptionTemplate{
+						{
+							ID:      "mockTemplateID1___________",
+							Filters: getMockSubscriptionFilter("jira:issue_created"),
+						},
+					}), t),
+		},
+		"Editing subscription template, no events provided": {
+			subscriptionTemplate: `{"instance_id": "https://jiraurl1.com", "name": "mockName", "id": "mockTemplateID1___________", "channel_id": "mockChannelID_____________", "filters": {"events": [], "projects": ["otherproject"], "issue_types": ["10001"]}}`,
+			expectedStatusCode:   http.StatusInternalServerError,
+			apiCalls: checkHasSubscriptionTemplates([]SubscriptionTemplate{},
+				withExistingChannelSubscriptionTemplates(
+					[]SubscriptionTemplate{
+						{
+							ID:      "mockTemplateID1___________",
+							Filters: getMockSubscriptionFilter("jira:issue_created"),
+						},
+					}), t),
+		},
+		"Editing subscription template, no issue types provided": {
+			subscriptionTemplate: `{"instance_id": "https://jiraurl1.com", "name": "mockName", "id": "mockTemplateID1___________", "channel_id": "mockChannelID_____________", "filters": {"events": ["jira:issue_created"], "projects": ["otherproject"], "issue_types": []}}`,
+			expectedStatusCode:   http.StatusInternalServerError,
+			apiCalls: checkHasSubscriptionTemplates([]SubscriptionTemplate{},
+				withExistingChannelSubscriptionTemplates(
+					[]SubscriptionTemplate{
+						{
+							ID:      "mockTemplateID1___________",
+							Filters: getMockSubscriptionFilter("jira:issue_created"),
+						},
+					}), t),
+		},
+		"Editing subscription template, GetProject mocked error. Existing subscription has a non-existent project.": {
+			subscriptionTemplate: fmt.Sprintf(`{"instance_id": "https://jiraurl1.com", "id": "mockTemplateID2___________", "name": "subscription name", "channel_id": "channelaaaaaaaaaabbbbbbbbb", "filters": {"events": ["jira:issue_created"], "projects": ["%s"], "issue_types": ["10001"]}}`, nonExistantProjectKey),
+			expectedStatusCode:   http.StatusInternalServerError,
+			apiCalls: checkHasSubscriptionTemplates([]SubscriptionTemplate{},
+				withExistingChannelSubscriptionTemplates(
+					[]SubscriptionTemplate{
+						{
+							ID:      "mockTemplateID2___________",
+							Filters: getMockSubscriptionFilter("jira:issue_updated"),
+						},
+					}), t),
+		},
+		"Editing subscription template, GetProject mocked error. Existing subscription has existing project.": {
+			subscriptionTemplate: fmt.Sprintf(`{"instance_id": "https://jiraurl1.com", "id": "mockTemplateID2___________", "name": "subscription name", "channel_id": "channelaaaaaaaaaabbbbbbbbb", "filters": {"events": ["jira:issue_created"], "projects": ["%s"], "issue_types": ["10001"]}}`, nonExistantProjectKey),
+			expectedStatusCode:   http.StatusInternalServerError,
+			apiCalls: checkHasSubscriptionTemplates([]SubscriptionTemplate{},
+				withExistingChannelSubscriptionTemplates(
+					[]SubscriptionTemplate{
+						{
+							ID:      "mockTemplateID2___________",
+							Filters: getMockSubscriptionFilter("jira:issue_updated"),
+						},
+					}), t),
+		},
+	} {
+		t.Run(name, func(t *testing.T) {
+			count++
+			fmt.Print(count)
+			api := &plugintest.API{}
+			p := Plugin{}
+
+			api.On("LogDebug", mockAnythingOfTypeBatch("string", 11)...).Return(nil)
+			api.On("LogError", mockAnythingOfTypeBatch("string", 13)...).Return(nil)
+			api.On("LogWarn", mockAnythingOfTypeBatch("string", 7)...).Return()
+			api.On("GetChannelMember", mockAnythingOfTypeBatch("string", 2)...).Return(&model.ChannelMember{}, (*model.AppError)(nil))
+			api.On("CreatePost", mock.AnythingOfType("*model.Post")).Return(&model.Post{}, nil)
+			api.On("SendEphemeralPost", mock.Anything, mock.Anything).Return(nil)
+
+			if tc.apiCalls != nil {
+				tc.apiCalls(api)
+			}
+
+			p.updateConfig(func(conf *config) {
+				conf.Secret = someSecret
+			})
+			p.initializeRouter()
+			p.SetAPI(api)
+			p.client = pluginapi.NewClient(api, p.Driver)
+			p.userStore = mockUserStore{}
+			p.instanceStore = p.getMockInstanceStoreKV(1)
+
+			w := httptest.NewRecorder()
+			request := httptest.NewRequest(http.MethodPut, "/api/v2/subscription-templates", io.NopCloser(bytes.NewBufferString(tc.subscriptionTemplate)))
+			if !tc.skipAuthorize {
+				request.Header.Set(HeaderMattermostUserID, model.NewId())
+			}
+			p.ServeHTTP(&plugin.Context{}, w, request)
+			assert.Equal(t, tc.expectedStatusCode, w.Result().StatusCode)
+		})
+	}
+}
+
+func TestCreateSubscriptionTemplate(t *testing.T) {
+	for name, tc := range map[string]struct {
+		subscriptionTemplate string
+		expectedStatusCode   int
+		skipAuthorize        bool
+		apiCalls             func(*plugintest.API)
+	}{
+		"Invalid": {
+			subscriptionTemplate: "{}",
+			expectedStatusCode:   http.StatusInternalServerError,
+		},
+		"Not Authorized": {
+			subscriptionTemplate: "{}",
+			expectedStatusCode:   http.StatusUnauthorized,
+			skipAuthorize:        true,
+		},
+		"Initial Subscription Template": {
+			subscriptionTemplate: `{"instance_id": "https://jiraurl1.com", "name": "some name", "channel_id": "mockChannelID_____________", "filters": {"events": ["jira:issue_created"], "projects": ["myproject"], "issue_types": ["10001"]}}`,
+			expectedStatusCode:   http.StatusOK,
+			apiCalls: checkHasSubscriptionTemplates([]SubscriptionTemplate{
+				{
+					Filters: getMockSubscriptionFilter("jira:issue_created"),
+				},
+			}, nil, t),
+		},
+		"Initial Subscription Template, GetProject mocked error": {
+			subscriptionTemplate: fmt.Sprintf(`{"instance_id": "https://jiraurl1.com", "name": "some name", "channel_id": "mockChannelID_____________", "filters": {"events": ["jira:issue_created"], "projects": ["%s"], "issue_types": ["10001"]}}`, nonExistantProjectKey),
+			expectedStatusCode:   http.StatusInternalServerError,
+			apiCalls:             hasSubscriptionTemplates([]SubscriptionTemplate{}, t),
+		},
+		"Initial Subscription Template, empty name provided": {
+			subscriptionTemplate: `{"instance_id": "https://jiraurl1.com", "name": "", "channel_id": "mockChannelID_____________", "filters": {"events": ["jira:issue_created"], "projects": ["myproject"], "issue_types": ["10001"]}}`,
+			expectedStatusCode:   http.StatusInternalServerError,
+			apiCalls:             hasSubscriptionTemplates([]SubscriptionTemplate{}, t),
+		},
+		"Initial Subscription Template, long name provided": {
+			subscriptionTemplate: `{"instance_id": "https://jiraurl1.com", "name": "` + TestDataLongSubscriptionName + `", "channel_id": "mockChannelID_____________", "filters": {"events": ["jira:issue_created"], "projects": ["myproject"], "issue_types": ["10001"]}}`,
+			expectedStatusCode:   http.StatusInternalServerError,
+			apiCalls:             hasSubscriptionTemplates([]SubscriptionTemplate{}, t),
+		},
+		"Initial Subscription Template, no project provided": {
+			subscriptionTemplate: `{"instance_id": "https://jiraurl1.com", "name": "mockName", "channel_id": "mockChannelID_____________", "filters": {"events": ["jira:issue_created"], "projects": [], "issue_types": ["10001"]}}`,
+			expectedStatusCode:   http.StatusInternalServerError,
+			apiCalls:             hasSubscriptionTemplates([]SubscriptionTemplate{}, t),
+		},
+		"Initial Subscription Template, no issue types provided": {
+			subscriptionTemplate: `{"instance_id": "https://jiraurl1.com", "name": "mockName", "channel_id": "mockChannelID_____________", "filters": {"events": ["jira:issue_created"], "projects": ["myproject"], "issue_types": []}}`,
+			expectedStatusCode:   http.StatusInternalServerError,
+			apiCalls:             hasSubscriptionTemplates([]SubscriptionTemplate{}, t),
+		},
+		"Adding to existing templates in a different channel": {
+			subscriptionTemplate: `{"instance_id": "https://jiraurl1.com", "name": "some name", "channel_id": "mockChannelID_____________", "filters": {"events": ["jira:issue_created"], "projects": ["myproject"], "issue_types": ["10001"]}}`,
+			expectedStatusCode:   http.StatusOK,
+			apiCalls: checkHasSubscriptionTemplates([]SubscriptionTemplate{
+				{
+					Filters: getMockSubscriptionFilter("jira:issue_created"),
+				},
+				{
+					Filters: getMockSubscriptionFilter("jira:issue_created"),
+				},
+			},
+				withExistingChannelSubscriptionTemplates(
+					[]SubscriptionTemplate{
+						{
+							ID:      model.NewId(),
+							Filters: getMockSubscriptionFilter("jira:issue_created"),
+						},
+					}), t),
+		},
+		"Adding to existing templates in the same channel": {
+			subscriptionTemplate: `{"instance_id": "https://jiraurl1.com", "name": "subscription name", "channel_id": "mockChannelID_____________", "filters": {"events": ["jira:issue_created"], "projects": ["myproject"], "issue_types": ["10001"]}}`,
+			expectedStatusCode:   http.StatusOK,
+			apiCalls: checkHasSubscriptionTemplates([]SubscriptionTemplate{
+				{
+					Filters: getMockSubscriptionFilter("jira:issue_created"),
+				},
+				{
+					Filters: getMockSubscriptionFilter("jira:issue_updated"),
+				},
+			},
+				withExistingChannelSubscriptionTemplates(
+					[]SubscriptionTemplate{
+						{
+							ID:      model.NewId(),
+							Filters: getMockSubscriptionFilter("jira:issue_updated"),
+						},
+					}), t),
+		},
+		"Adding to existing templates with same name in the same channel": {
+			subscriptionTemplate: `{"instance_id": "https://jiraurl1.com", "name": "SubscriptionName", "channel_id": "mockChannelID_____________", "filters": {"events": ["jira:issue_created"], "projects": ["myproject"], "issue_types": ["10001"]}}`,
+			expectedStatusCode:   http.StatusInternalServerError,
+			apiCalls: checkHasSubscriptionTemplates([]SubscriptionTemplate{
+				{
+					Filters: getMockSubscriptionFilter("jira:issue_created"),
+				},
+			},
+				withExistingChannelSubscriptionTemplates(
+					[]SubscriptionTemplate{
+						{
+							Name:    "SubscriptionName",
+							ID:      model.NewId(),
+							Filters: getMockSubscriptionFilter("jira:issue_updated"),
+						},
+					}), t),
+		},
+	} {
+		t.Run(name, func(t *testing.T) {
+			api := &plugintest.API{}
+			p := Plugin{}
+
+			api.On("LogDebug", mockAnythingOfTypeBatch("string", 11)...).Return(nil)
+			api.On("LogError", mockAnythingOfTypeBatch("string", 13)...).Return(nil)
+			api.On("LogWarn", mockAnythingOfTypeBatch("string", 7)...).Return()
+			api.On("GetChannelMember", mockAnythingOfTypeBatch("string", 2)...).Return(&model.ChannelMember{}, (*model.AppError)(nil))
+			api.On("CreatePost", mock.AnythingOfType("*model.Post")).Return(&model.Post{}, nil)
+			api.On("SendEphemeralPost", mock.Anything, mock.Anything).Return(nil)
+
+			if tc.apiCalls != nil {
+				tc.apiCalls(api)
+			}
+
+			p.updateConfig(func(conf *config) {
+				conf.Secret = someSecret
+			})
+			p.initializeRouter()
+			p.SetAPI(api)
+			p.client = pluginapi.NewClient(api, p.Driver)
+			p.userStore = mockUserStore{}
+			p.instanceStore = p.getMockInstanceStoreKV(1)
+
+			w := httptest.NewRecorder()
+			request := httptest.NewRequest(http.MethodPost, "/api/v2/subscription-templates", io.NopCloser(bytes.NewBufferString(tc.subscriptionTemplate)))
+			if !tc.skipAuthorize {
+				request.Header.Set(HeaderMattermostUserID, model.NewId())
+			}
+			p.ServeHTTP(&plugin.Context{}, w, request)
+			body, _ := io.ReadAll(w.Result().Body)
+			t.Log(string(body))
+			assert.Equal(t, tc.expectedStatusCode, w.Result().StatusCode)
+		})
+	}
+}
+
+func TestGetSubscriptionTemplate(t *testing.T) {
+	for name, tc := range map[string]struct {
+		expectedStatusCode            int
+		skipAuthorize                 bool
+		apiCalls                      func(*plugintest.API)
+		returnedSubscriptionTemplates []SubscriptionTemplate
+	}{
+		"Not Authorized": {
+			expectedStatusCode: http.StatusUnauthorized,
+			skipAuthorize:      true,
+		},
+		"Only Subscription": {
+			expectedStatusCode: http.StatusOK,
+			returnedSubscriptionTemplates: []SubscriptionTemplate{
+				{
+					ID:      "mockTemplateID1___________",
+					Filters: getMockSubscriptionFilter("jira:issue_created"),
+				},
+			},
+			apiCalls: hasSubscriptionTemplates(
+				[]SubscriptionTemplate{
+					{
+						ID:      "mockTemplateID1___________",
+						Filters: getMockSubscriptionFilter("jira:issue_created"),
+					},
+				}, t),
+		},
+		"Multiple subscriptions": {
+			expectedStatusCode: http.StatusOK,
+			returnedSubscriptionTemplates: []SubscriptionTemplate{
+				{
+					ID:      "mockTemplateID1___________",
+					Filters: getMockSubscriptionFilter("jira:issue_created"),
+				},
+				{
+					ID:      "mockTemplateID2___________",
+					Filters: getMockSubscriptionFilter("jira:issue_created"),
+				},
+			},
+			apiCalls: hasSubscriptionTemplates(
+				[]SubscriptionTemplate{
+					{
+						ID:      "mockTemplateID1___________",
+						Filters: getMockSubscriptionFilter("jira:issue_created"),
+					},
+					{
+						ID:      "mockTemplateID2___________",
+						Filters: getMockSubscriptionFilter("jira:issue_created"),
+					},
+				}, t),
+		},
+	} {
+		t.Run(name, func(t *testing.T) {
+			api := &plugintest.API{}
+			p := Plugin{}
+
+			api.On("LogDebug", mockAnythingOfTypeBatch("string", 11)...).Return(nil)
+			api.On("LogError", mockAnythingOfTypeBatch("string", 13)...).Return(nil)
+			api.On("GetChannelMember", mockAnythingOfTypeBatch("string", 2)...).Return(&model.ChannelMember{}, (*model.AppError)(nil))
+
+			if tc.apiCalls != nil {
+				tc.apiCalls(api)
+			}
+
+			p.updateConfig(func(conf *config) {
+				conf.Secret = someSecret
+			})
+			p.initializeRouter()
+			p.SetAPI(api)
+			p.client = pluginapi.NewClient(api, p.Driver)
+			p.userStore = mockUserStore{}
+			p.instanceStore = p.getMockInstanceStoreKV(1)
+
+			w := httptest.NewRecorder()
+			request := httptest.NewRequest(http.MethodGet, "/api/v2/subscription-templates?instance_id="+testInstance1.GetID().String()+"&project_key=myproject", nil)
+			if !tc.skipAuthorize {
+				request.Header.Set(HeaderMattermostUserID, model.NewId())
+			}
+			p.ServeHTTP(&plugin.Context{}, w, request)
+			assert.Equal(t, tc.expectedStatusCode, w.Result().StatusCode)
+
+			if tc.returnedSubscriptionTemplates != nil {
+				subscriptions := []SubscriptionTemplate{}
+				body, _ := io.ReadAll(w.Result().Body)
+				err := json.NewDecoder(bytes.NewReader(body)).Decode(&subscriptions)
+				assert.Nil(t, err)
+				checkSubscriptionTemplatesEqual(t, tc.returnedSubscriptionTemplates, subscriptions)
+			}
+		})
+	}
+}
diff --git a/server/jira_test_util_test.go b/server/jira_test_util_test.go
index 503504dda..21f4a3b11 100644
--- a/server/jira_test_util_test.go
+++ b/server/jira_test_util_test.go
@@ -9,7 +9,10 @@ import (
 	"path/filepath"
 )
 
-const someSecret = "somesecret"
+const (
+	someSecret     = "somesecret"
+	mockProjectKey = "myproject"
+)
 
 func getJiraTestData(filename string) ([]byte, error) {
 	f, err := os.Open(filepath.Join("testdata", filename))
@@ -30,3 +33,11 @@ func withExistingChannelSubscriptions(subscriptions []ChannelSubscription) *Subs
 	}
 	return ret
 }
+
+func withExistingChannelSubscriptionTemplates(templates []SubscriptionTemplate) *Templates {
+	ret := NewTemplates()
+	for i := range templates {
+		ret.Templates.add(mockProjectKey, &templates[i])
+	}
+	return ret
+}
diff --git a/server/subscribe.go b/server/subscribe.go
old mode 100644
new mode 100755
index caf743888..b3b0933e6
--- a/server/subscribe.go
+++ b/server/subscribe.go
@@ -25,6 +25,7 @@ import (
 
 const (
 	JiraSubscriptionsKey = "jirasub"
+	templateKey          = "templates"
 
 	FilterIncludeAny     = "include_any"
 	FilterIncludeAll     = "include_all"
@@ -32,7 +33,10 @@ const (
 	FilterEmpty          = "empty"
 	FilterIncludeOrEmpty = "include_or_empty"
 
-	MaxSubscriptionNameLength = 100
+	MaxSubscriptionNameLength         = 100
+	MaxSubscriptionTemplateNameLength = 100
+
+	QueryParamProjectKey = "project_key"
 )
 
 type FieldFilter struct {
@@ -56,6 +60,14 @@ type ChannelSubscription struct {
 	InstanceID types.ID            `json:"instance_id"`
 }
 
+type SubscriptionTemplate struct {
+	ID         string               `json:"id"`
+	ChannelID  string               `json:"channel_id"`
+	Filters    *SubscriptionFilters `json:"filters"`
+	Name       string               `json:"name"`
+	InstanceID types.ID             `json:"instance_id"`
+}
+
 type ChannelSubscriptions struct {
 	ByID          map[string]ChannelSubscription `json:"by_id"`
 	IDByChannelID map[string]StringSet           `json:"id_by_channel_id"`
@@ -93,6 +105,18 @@ type Subscriptions struct {
 	Channel       *ChannelSubscriptions
 }
 
+type SubscriptionTemplateCollection map[string]*SubscriptionTemplate
+
+type SubscriptionTemplates struct {
+	ByID        map[string]SubscriptionTemplate           `json:"by_id"`
+	ByProjectID map[string]SubscriptionTemplateCollection `json:"by_project_id"`
+}
+
+type Templates struct {
+	PluginVersion string
+	Templates     *SubscriptionTemplates
+}
+
 func NewSubscriptions() *Subscriptions {
 	return &Subscriptions{
 		PluginVersion: manifest.Version,
@@ -100,6 +124,20 @@ func NewSubscriptions() *Subscriptions {
 	}
 }
 
+func NewSubscriptionTemplates() *SubscriptionTemplates {
+	return &SubscriptionTemplates{
+		ByID:        map[string]SubscriptionTemplate{},
+		ByProjectID: map[string]SubscriptionTemplateCollection{},
+	}
+}
+
+func NewTemplates() *Templates {
+	return &Templates{
+		PluginVersion: manifest.Version,
+		Templates:     NewSubscriptionTemplates(),
+	}
+}
+
 func SubscriptionsFromJSON(bytes []byte, instanceID types.ID) (*Subscriptions, error) {
 	var subs *Subscriptions
 	if len(bytes) != 0 {
@@ -121,6 +159,20 @@ func SubscriptionsFromJSON(bytes []byte, instanceID types.ID) (*Subscriptions, e
 	return subs, nil
 }
 
+func SubscriptionTemplatesFromJSON(bytes []byte) (*Templates, error) {
+	var subs *Templates
+	if len(bytes) != 0 {
+		if unmarshalErr := json.Unmarshal(bytes, &subs); unmarshalErr != nil {
+			return nil, unmarshalErr
+		}
+		subs.PluginVersion = manifest.Version
+	} else {
+		subs = NewTemplates()
+	}
+
+	return subs, nil
+}
+
 func (p *Plugin) getUserID() string {
 	return p.getConfig().botUserID
 }
@@ -231,6 +283,15 @@ func (p *Plugin) getSubscriptions(instanceID types.ID) (*Subscriptions, error) {
 	}
 	return SubscriptionsFromJSON(data, instanceID)
 }
+func (p *Plugin) getTemplates(instanceID types.ID) (*Templates, error) {
+	subKey := keyWithInstanceID(instanceID, templateKey)
+	data, appErr := p.API.KVGet(subKey)
+	if appErr != nil {
+		return nil, appErr
+	}
+
+	return SubscriptionTemplatesFromJSON(data)
+}
 
 func (p *Plugin) getSubscriptionsForChannel(instanceID types.ID, channelID string) ([]ChannelSubscription, error) {
 	subs, err := p.getSubscriptions(instanceID)
@@ -250,6 +311,25 @@ func (p *Plugin) getSubscriptionsForChannel(instanceID types.ID, channelID strin
 	return channelSubscriptions, nil
 }
 
+func (p *Plugin) getSubscriptionTemplatesForInstance(instanceID types.ID) (*Templates, error) {
+	subs, err := p.getTemplates(instanceID)
+	if err != nil {
+		return nil, err
+	}
+
+	return subs, nil
+}
+
+func (p *Plugin) getSubscriptionTemplatesByID(instanceID, templateID types.ID) (*SubscriptionTemplate, error) {
+	subs, err := p.getTemplates(instanceID)
+	if err != nil {
+		return nil, err
+	}
+
+	sub := subs.Templates.ByID[string(templateID)]
+	return &sub, nil
+}
+
 func (p *Plugin) getChannelSubscription(instanceID types.ID, subscriptionID string) (*ChannelSubscription, error) {
 	subs, err := p.getSubscriptions(instanceID)
 	if err != nil {
@@ -313,6 +393,146 @@ func (p *Plugin) addChannelSubscription(instanceID types.ID, newSubscription *Ch
 	})
 }
 
+func (t *SubscriptionTemplates) add(projectKey string, newSubscriptionTemplate *SubscriptionTemplate) {
+	t.ByID[newSubscriptionTemplate.ID] = *newSubscriptionTemplate
+	if _, valid := t.ByProjectID[projectKey]; !valid {
+		t.ByProjectID[projectKey] = make(SubscriptionTemplateCollection)
+	}
+
+	t.ByProjectID[projectKey][newSubscriptionTemplate.ID] = newSubscriptionTemplate
+}
+
+func (t *SubscriptionTemplates) delete(projectKey, subscriptionTemplateID string) {
+	delete(t.ByID, subscriptionTemplateID)
+	delete(t.ByProjectID[projectKey], subscriptionTemplateID)
+}
+
+func (p *Plugin) addSubscriptionTemplate(instanceID types.ID, newSubscriptionTemplate *SubscriptionTemplate, client Client) error {
+	subKey := keyWithInstanceID(instanceID, templateKey)
+	return p.client.KV.SetAtomicWithRetries(subKey, func(initialBytes []byte) (interface{}, error) {
+		oldSubscriptionTemplates, err := SubscriptionTemplatesFromJSON(initialBytes)
+		if err != nil {
+			return nil, err
+		}
+
+		projectKey := ""
+		if newSubscriptionTemplate.Filters.Projects.Len() == 1 {
+			projectKey = newSubscriptionTemplate.Filters.Projects.Elems()[0]
+		}
+
+		if err = p.validateSubscriptionTemplate(newSubscriptionTemplate, instanceID, client, projectKey); err != nil {
+			return nil, err
+		}
+
+		newSubscriptionTemplate.ID = model.NewId()
+		oldSubscriptionTemplates.Templates.add(projectKey, newSubscriptionTemplate)
+
+		modifiedBytes, marshalErr := json.Marshal(&oldSubscriptionTemplates)
+		if marshalErr != nil {
+			return nil, marshalErr
+		}
+
+		return modifiedBytes, nil
+	})
+}
+
+func (p *Plugin) editSubscriptionTemplate(instanceID types.ID, modifiedSubscriptionTemplate *SubscriptionTemplate, client Client) error {
+	subKey := keyWithInstanceID(instanceID, templateKey)
+	return p.client.KV.SetAtomicWithRetries(subKey, func(initialBytes []byte) (interface{}, error) {
+		subscriptionTemplates, err := SubscriptionTemplatesFromJSON(initialBytes)
+		if err != nil {
+			return nil, err
+		}
+
+		oldSubscriptionTemplate, ok := subscriptionTemplates.Templates.ByID[modifiedSubscriptionTemplate.ID]
+		if !ok {
+			return nil, errors.New("subscription template does not exist")
+		}
+
+		oldProjectKey := ""
+		if oldSubscriptionTemplate.Filters.Projects.Len() == 1 {
+			oldProjectKey = oldSubscriptionTemplate.Filters.Projects.Elems()[0]
+		}
+
+		newProjectKey := ""
+		if modifiedSubscriptionTemplate.Filters.Projects.Len() == 1 {
+			newProjectKey = modifiedSubscriptionTemplate.Filters.Projects.Elems()[0]
+		}
+
+		if err = p.validateSubscriptionTemplate(modifiedSubscriptionTemplate, instanceID, client, newProjectKey); err != nil {
+			return nil, err
+		}
+
+		subscriptionTemplates.Templates.delete(oldProjectKey, oldSubscriptionTemplate.ID)
+		subscriptionTemplates.Templates.add(newProjectKey, modifiedSubscriptionTemplate)
+
+		modifiedBytes, marshalErr := json.Marshal(&subscriptionTemplates)
+		if marshalErr != nil {
+			return nil, marshalErr
+		}
+
+		return modifiedBytes, nil
+	})
+}
+
+func (p *Plugin) removeSubscriptionTemplate(instanceID types.ID, subscriptionTemplateID, projectKey string) error {
+	subKey := keyWithInstanceID(instanceID, templateKey)
+	return p.client.KV.SetAtomicWithRetries(subKey, func(initialBytes []byte) (interface{}, error) {
+		oldSubscriptionTemplates, err := SubscriptionTemplatesFromJSON(initialBytes)
+		if err != nil {
+			return nil, err
+		}
+
+		oldSubscriptionTemplates.Templates.delete(projectKey, subscriptionTemplateID)
+
+		modifiedBytes, marshalErr := json.Marshal(&oldSubscriptionTemplates)
+		if marshalErr != nil {
+			return nil, marshalErr
+		}
+
+		return modifiedBytes, nil
+	})
+}
+
+func (p *Plugin) validateSubscriptionTemplate(subscriptionTemplate *SubscriptionTemplate, instanceID types.ID, client Client, projectKey string) error {
+	if len(subscriptionTemplate.Name) == 0 {
+		return errors.New("please provide a name for the subscription")
+	}
+
+	if len(subscriptionTemplate.Name) >= MaxSubscriptionTemplateNameLength {
+		return errors.Errorf("please provide a name less than %d characters", MaxSubscriptionTemplateNameLength)
+	}
+
+	if len(subscriptionTemplate.Filters.Events) == 0 {
+		return errors.New("please provide at least one event type")
+	}
+
+	if len(subscriptionTemplate.Filters.IssueTypes) == 0 {
+		return errors.New("please provide at least one issue type")
+	}
+
+	if (len(subscriptionTemplate.Filters.Projects)) == 0 {
+		return errors.New("please provide a project identifier")
+	}
+
+	if _, err := client.GetProject(projectKey); err != nil {
+		return errors.WithMessagef(err, "failed to get project %q", projectKey)
+	}
+
+	templates, err := p.getSubscriptionTemplatesForInstance(instanceID)
+	if err != nil {
+		return err
+	}
+
+	for _, template := range templates.Templates.ByProjectID[projectKey] {
+		if template.Name == subscriptionTemplate.Name && template.ID != subscriptionTemplate.ID {
+			return errors.Errorf("Subscription name, '%s', already exists. Please choose another name.", subscriptionTemplate.Name)
+		}
+	}
+
+	return nil
+}
+
 func (p *Plugin) validateSubscription(instanceID types.ID, subscription *ChannelSubscription, client Client) error {
 	if len(subscription.Name) == 0 {
 		return errors.New("please provide a name for the subscription")
@@ -1005,3 +1225,152 @@ func (p *Plugin) httpChannelGetSubscriptions(w http.ResponseWriter, r *http.Requ
 
 	return respondJSON(w, subscriptions)
 }
+
+func (p *Plugin) httpGetSubscriptionTemplates(w http.ResponseWriter, r *http.Request) (int, error) {
+	fmt.Print("/n httpGetSubscriptionTemplates")
+	mattermostUserID := r.Header.Get("Mattermost-User-Id")
+	instanceID := types.ID(r.FormValue(QueryParamInstanceID))
+	if len(instanceID) < 2 {
+		return respondErr(w, http.StatusBadRequest, errors.New("bad or missing instance id"))
+	}
+
+	subscriptionTemplates, err := p.getSubscriptionTemplatesForInstance(instanceID)
+	if err != nil {
+		return respondErr(w, http.StatusInternalServerError, errors.Wrap(err, "unable to get subscription templates"))
+	}
+
+	subTemplates := make([]*SubscriptionTemplate, 0)
+
+	projectKey := r.FormValue(QueryParamProjectKey)
+	if len(projectKey) < 1 {
+		client, _, _, err := p.getClient(instanceID, types.ID(mattermostUserID))
+		if err != nil {
+			return respondErr(w, http.StatusInternalServerError, err)
+		}
+
+		pList, err := client.ListProjects("", -1, false)
+		if err != nil {
+			return respondErr(w, http.StatusInternalServerError, err)
+		}
+
+		for _, project := range pList {
+			listSubscriptionTemplate := subscriptionTemplates.Templates.ByProjectID[project.Key]
+			for _, subTemplate := range listSubscriptionTemplate {
+				subTemplates = append(subTemplates, subTemplate)
+			}
+		}
+	} else {
+		for _, subTemplate := range subscriptionTemplates.Templates.ByProjectID[projectKey] {
+			subTemplates = append(subTemplates, subTemplate)
+		}
+	}
+
+	return respondJSON(w, subTemplates)
+}
+
+func (p *Plugin) httpEditSubscriptionTemplates(w http.ResponseWriter, r *http.Request) (int, error) {
+	mattermostUserID := r.Header.Get("Mattermost-User-Id")
+	subscriptionTemplate := SubscriptionTemplate{}
+	if err := json.NewDecoder(r.Body).Decode(&subscriptionTemplate); err != nil {
+		return respondErr(w, http.StatusBadRequest, errors.WithMessage(err, "failed to decode the incoming request"))
+	}
+
+	client, _, connection, err := p.getClient(subscriptionTemplate.InstanceID, types.ID(mattermostUserID))
+	if err != nil {
+		return respondErr(w, http.StatusInternalServerError, err)
+	}
+
+	if err = p.editSubscriptionTemplate(subscriptionTemplate.InstanceID, &subscriptionTemplate, client); err != nil {
+		return respondErr(w, http.StatusInternalServerError, err)
+	}
+
+	_ = p.API.SendEphemeralPost(mattermostUserID, &model.Post{
+		UserId:    p.getConfig().botUserID,
+		ChannelId: subscriptionTemplate.ChannelID,
+		Message:   fmt.Sprintf("Jira subscription template, %q, was updated by %s", subscriptionTemplate.Name, connection.DisplayName),
+	})
+
+	code, err := respondJSON(w, &subscriptionTemplate)
+	if err != nil {
+		return code, err
+	}
+
+	return http.StatusOK, nil
+}
+
+func (p *Plugin) httpCreateSubscriptionTemplate(w http.ResponseWriter, r *http.Request) (int, error) {
+	mattermostUserID := r.Header.Get("Mattermost-User-Id")
+	subscriptionTemplate := SubscriptionTemplate{}
+	if err := json.NewDecoder(r.Body).Decode(&subscriptionTemplate); err != nil {
+		return respondErr(w, http.StatusBadRequest, errors.WithMessage(err, "failed to decode incoming request"))
+	}
+
+	client, _, connection, err := p.getClient(subscriptionTemplate.InstanceID, types.ID(mattermostUserID))
+	if err != nil {
+		return respondErr(w, http.StatusInternalServerError, err)
+	}
+
+	if err = p.addSubscriptionTemplate(subscriptionTemplate.InstanceID, &subscriptionTemplate, client); err != nil {
+		return respondErr(w, http.StatusInternalServerError, err)
+	}
+
+	_ = p.API.SendEphemeralPost(mattermostUserID, &model.Post{
+		UserId:    p.getConfig().botUserID,
+		ChannelId: subscriptionTemplate.ChannelID,
+		Message:   fmt.Sprintf("Jira subscription template, %q, was added by %s", subscriptionTemplate.Name, connection.DisplayName),
+	})
+
+	code, err := respondJSON(w, &subscriptionTemplate)
+	if err != nil {
+		return code, err
+	}
+
+	return http.StatusCreated, nil
+}
+
+func (p *Plugin) httpDeleteSubscriptionTemplate(w http.ResponseWriter, r *http.Request) (int, error) {
+	mattermostUserID := r.Header.Get("Mattermost-User-Id")
+
+	params := mux.Vars(r)
+	subscriptionTemplateID := params["id"]
+	if len(subscriptionTemplateID) != 26 {
+		return respondErr(w, http.StatusBadRequest, errors.New("bad subscription id"))
+	}
+
+	instanceID := types.ID(r.FormValue(QueryParamInstanceID))
+	if len(instanceID) < 2 {
+		return respondErr(w, http.StatusBadRequest, errors.New("bad or missing instance id"))
+	}
+
+	projectKey := r.FormValue(QueryParamProjectKey)
+	if projectKey == "" {
+		return respondErr(w, http.StatusBadRequest, errors.New("missing project key"))
+	}
+
+	subscriptionTemplate, err := p.getSubscriptionTemplatesByID(instanceID, types.ID(subscriptionTemplateID))
+	if err != nil {
+		return respondErr(w, http.StatusInternalServerError, errors.Wrap(err, "unable to find the subscription template"))
+	}
+
+	_, _, connection, err := p.getClient(instanceID, types.ID(mattermostUserID))
+	if err != nil {
+		return respondErr(w, http.StatusInternalServerError, err)
+	}
+
+	if rErr := p.removeSubscriptionTemplate(instanceID, subscriptionTemplateID, projectKey); rErr != nil {
+		return respondErr(w, http.StatusInternalServerError, errors.Wrap(err, "unable to remove channel subscription template"))
+	}
+
+	_ = p.API.SendEphemeralPost(mattermostUserID, &model.Post{
+		UserId:    p.getConfig().botUserID,
+		ChannelId: subscriptionTemplate.ChannelID,
+		Message:   fmt.Sprintf("Jira subscription template, %q, was removed by %s", subscriptionTemplate.Name, connection.DisplayName),
+	})
+
+	code, err := respondJSON(w, map[string]interface{}{model.STATUS: model.StatusOk})
+	if err != nil {
+		return code, err
+	}
+
+	return http.StatusOK, nil
+}
diff --git a/server/subscribe_test.go b/server/subscribe_test.go
index 6ebd8974e..298f7d571 100644
--- a/server/subscribe_test.go
+++ b/server/subscribe_test.go
@@ -513,7 +513,7 @@ func TestGetChannelsSubscribed(t *testing.T) {
 						Projects:   NewStringSet("TES"),
 						IssueTypes: NewStringSet("10001"),
 					},
-					InstanceID: "jiraurl1",
+					InstanceID: "https://jiraurl1.com",
 				},
 			},
 		},
@@ -554,7 +554,7 @@ func TestGetChannelsSubscribed(t *testing.T) {
 						Projects:   NewStringSet(),
 						IssueTypes: NewStringSet("10001"),
 					},
-					InstanceID: "jiraurl1",
+					InstanceID: "https://jiraurl1.com",
 				},
 			},
 		},
@@ -580,7 +580,7 @@ func TestGetChannelsSubscribed(t *testing.T) {
 						Projects:   NewStringSet("TES", "OTHER"),
 						IssueTypes: NewStringSet("10001"),
 					},
-					InstanceID: "jiraurl1",
+					InstanceID: "https://jiraurl1.com",
 				},
 			},
 		},
@@ -621,7 +621,7 @@ func TestGetChannelsSubscribed(t *testing.T) {
 						Projects:   NewStringSet("TES"),
 						IssueTypes: NewStringSet(),
 					},
-					InstanceID: "jiraurl1",
+					InstanceID: "https://jiraurl1.com",
 				},
 			},
 		},
@@ -662,7 +662,7 @@ func TestGetChannelsSubscribed(t *testing.T) {
 						Projects:   NewStringSet("TES"),
 						IssueTypes: NewStringSet("10001"),
 					},
-					InstanceID: "jiraurl1",
+					InstanceID: "https://jiraurl1.com",
 				},
 			},
 		},
@@ -713,7 +713,7 @@ func TestGetChannelsSubscribed(t *testing.T) {
 							Inclusion: "include_any",
 						}},
 					},
-					InstanceID: "jiraurl1",
+					InstanceID: "https://jiraurl1.com",
 				},
 			},
 		},
@@ -739,7 +739,7 @@ func TestGetChannelsSubscribed(t *testing.T) {
 						Projects:   NewStringSet("TES"),
 						IssueTypes: NewStringSet("10001"),
 					},
-					InstanceID: "jiraurl1",
+					InstanceID: "https://jiraurl1.com",
 				},
 			},
 		},
@@ -765,7 +765,7 @@ func TestGetChannelsSubscribed(t *testing.T) {
 						Projects:   NewStringSet("TES"),
 						IssueTypes: NewStringSet("10001"),
 					},
-					InstanceID: "jiraurl1",
+					InstanceID: "https://jiraurl1.com",
 				},
 			},
 		},
@@ -822,7 +822,7 @@ func TestGetChannelsSubscribed(t *testing.T) {
 						Projects:   NewStringSet("TES"),
 						IssueTypes: NewStringSet("10001"),
 					},
-					InstanceID: "jiraurl1",
+					InstanceID: "https://jiraurl1.com",
 				},
 			},
 		},
@@ -848,7 +848,7 @@ func TestGetChannelsSubscribed(t *testing.T) {
 						Projects:   NewStringSet("TES"),
 						IssueTypes: NewStringSet("10001"),
 					},
-					InstanceID: "jiraurl1",
+					InstanceID: "https://jiraurl1.com",
 				},
 			},
 		},
@@ -883,7 +883,7 @@ func TestGetChannelsSubscribed(t *testing.T) {
 						Projects:   NewStringSet("TES"),
 						IssueTypes: NewStringSet("10001"),
 					},
-					InstanceID: "jiraurl1",
+					InstanceID: "https://jiraurl1.com",
 				},
 				{
 					ID:        "8hduqxgiwiyi5fw3q4d6q56uho",
@@ -893,7 +893,7 @@ func TestGetChannelsSubscribed(t *testing.T) {
 						Projects:   NewStringSet("TES"),
 						IssueTypes: NewStringSet("10001"),
 					},
-					InstanceID: "jiraurl1",
+					InstanceID: "https://jiraurl1.com",
 				},
 			},
 		},
@@ -928,7 +928,7 @@ func TestGetChannelsSubscribed(t *testing.T) {
 						Projects:   NewStringSet("TES"),
 						IssueTypes: NewStringSet("10001"),
 					},
-					InstanceID: "jiraurl1",
+					InstanceID: "https://jiraurl1.com",
 				}},
 		},
 		"multiple subscriptions, neither acceptable": {
@@ -983,7 +983,7 @@ func TestGetChannelsSubscribed(t *testing.T) {
 							{Key: "status", Values: NewStringSet("10004"), Inclusion: FilterIncludeAny},
 						},
 					},
-					InstanceID: "jiraurl1",
+					InstanceID: "https://jiraurl1.com",
 				},
 			},
 		},
@@ -1033,7 +1033,7 @@ func TestGetChannelsSubscribed(t *testing.T) {
 							{Key: "customfield_10068", Values: NewStringSet("10033", "10034"), Inclusion: FilterIncludeAll},
 						},
 					},
-					InstanceID: "jiraurl1",
+					InstanceID: "https://jiraurl1.com",
 				},
 			},
 		},
@@ -1101,7 +1101,7 @@ func TestGetChannelsSubscribed(t *testing.T) {
 							{Key: "status", Values: NewStringSet("10005"), Inclusion: FilterExcludeAny},
 						},
 					},
-					InstanceID: "jiraurl1",
+					InstanceID: "https://jiraurl1.com",
 				},
 			},
 		},
@@ -1133,7 +1133,7 @@ func TestGetChannelsSubscribed(t *testing.T) {
 							{Key: "customfield_10060", Values: NewStringSet(), Inclusion: FilterEmpty},
 						},
 					},
-					InstanceID: "jiraurl1",
+					InstanceID: "https://jiraurl1.com",
 				},
 			},
 		},
@@ -1183,7 +1183,7 @@ func TestGetChannelsSubscribed(t *testing.T) {
 							{Key: "customfield_10068", Values: NewStringSet("10033"), Inclusion: FilterIncludeAny},
 						},
 					},
-					InstanceID: "jiraurl1",
+					InstanceID: "https://jiraurl1.com",
 				},
 			},
 		},
@@ -1233,7 +1233,7 @@ func TestGetChannelsSubscribed(t *testing.T) {
 							{Key: "customfield_10076", Values: NewStringSet("10039"), Inclusion: FilterIncludeAny},
 						},
 					},
-					InstanceID: "jiraurl1",
+					InstanceID: "https://jiraurl1.com",
 				},
 			},
 		},
@@ -1283,7 +1283,7 @@ func TestGetChannelsSubscribed(t *testing.T) {
 							{Key: "customfield_10078", Values: NewStringSet("some value"), Inclusion: FilterIncludeAny},
 						},
 					},
-					InstanceID: "jiraurl1",
+					InstanceID: "https://jiraurl1.com",
 				},
 			},
 		},
@@ -1333,7 +1333,7 @@ func TestGetChannelsSubscribed(t *testing.T) {
 							{Key: "customfield_10071", Values: NewStringSet("value1"), Inclusion: FilterIncludeAny},
 						},
 					},
-					InstanceID: "jiraurl1",
+					InstanceID: "https://jiraurl1.com",
 				},
 			},
 		},
@@ -1384,7 +1384,7 @@ func TestGetChannelsSubscribed(t *testing.T) {
 							{Key: "customfield_10071", Values: NewStringSet("value1", "value3"), Inclusion: FilterIncludeAny},
 						},
 					},
-					InstanceID: "jiraurl1",
+					InstanceID: "https://jiraurl1.com",
 				},
 			},
 		},
@@ -1434,7 +1434,7 @@ func TestGetChannelsSubscribed(t *testing.T) {
 							{Key: "fixVersions", Values: NewStringSet("10000"), Inclusion: FilterIncludeAny},
 						},
 					},
-					InstanceID: "jiraurl1",
+					InstanceID: "https://jiraurl1.com",
 				},
 			},
 		},
@@ -1466,7 +1466,7 @@ func TestGetChannelsSubscribed(t *testing.T) {
 							{Key: "Priority", Values: NewStringSet("1"), Inclusion: FilterIncludeAny},
 						},
 					},
-					InstanceID: "jiraurl1",
+					InstanceID: "https://jiraurl1.com",
 				},
 			},
 		},
diff --git a/webapp/src/action_types/index.ts b/webapp/src/action_types/index.ts
old mode 100644
new mode 100755
index 67d654134..e9f199ee1
--- a/webapp/src/action_types/index.ts
+++ b/webapp/src/action_types/index.ts
@@ -32,6 +32,12 @@ export default {
     CREATED_CHANNEL_SUBSCRIPTION: `${PluginId}_created_channel_subscription`,
     EDITED_CHANNEL_SUBSCRIPTION: `${PluginId}_edited_channel_subscription`,
 
+    CREATED_SUBSCRIPTION_TEMPLATE: `${PluginId}_created_subscription_template`,
+    DELETED_SUBSCRIPTION_TEMPLATE: `${PluginId}_deleted_subscription_template`,
+    EDITED_SUBSCRIPTION_TEMPLATE: `${PluginId}_edited_subscription_template`,
+    RECEIVED_SUBSCRIPTION_TEMPLATES_PROJECT_KEY: `${PluginId}_received_subscription_templates_project_key`,
+
     RECEIVED_CHANNEL_SUBSCRIPTIONS: `${PluginId}_recevied_channel_subscriptions`,
+    RECEIVED_SUBSCRIPTION_TEMPLATES: `${PluginId}_recevied_subscription_templates`,
     DELETED_CHANNEL_SUBSCRIPTION: `${PluginId}_deleted_channel_subscription`,
 };
diff --git a/webapp/src/actions/index.ts b/webapp/src/actions/index.ts
old mode 100644
new mode 100755
index ee1866b8f..e32fd405c
--- a/webapp/src/actions/index.ts
+++ b/webapp/src/actions/index.ts
@@ -17,6 +17,7 @@ import {
     InstanceType,
     ProjectMetadata,
     SearchIssueParams,
+    SubscriptionTemplate,
 } from 'types/model';
 
 export const openConnectModal = () => {
@@ -225,6 +226,48 @@ export const createChannelSubscription = (subscription: ChannelSubscription) =>
     };
 };
 
+export const createSubscriptionTemplate = (subscriptionTemplate: SubscriptionTemplate) => {
+    return async (dispatch, getState) => {
+        const baseUrl = getPluginServerRoute(getState());
+        try {
+            const data = await doFetch(`${baseUrl}/api/v2/subscription-templates`, {
+                method: 'post',
+                body: JSON.stringify(subscriptionTemplate),
+            });
+
+            dispatch({
+                type: ActionTypes.CREATED_SUBSCRIPTION_TEMPLATE,
+                data,
+            });
+
+            return {data};
+        } catch (error) {
+            return {error};
+        }
+    };
+};
+
+export const editSubscriptionTemplate = (subscriptionTemplate: SubscriptionTemplate) => {
+    return async (dispatch, getState) => {
+        const baseUrl = getPluginServerRoute(getState());
+        try {
+            const data = await doFetch(`${baseUrl}/api/v2/subscription-templates`, {
+                method: 'put',
+                body: JSON.stringify(subscriptionTemplate),
+            });
+
+            dispatch({
+                type: ActionTypes.EDITED_SUBSCRIPTION_TEMPLATE,
+                data,
+            });
+
+            return {data};
+        } catch (error) {
+            return {error};
+        }
+    };
+};
+
 export const editChannelSubscription = (subscription: ChannelSubscription) => {
     return async (dispatch, getState) => {
         const baseUrl = getPluginServerRoute(getState());
@@ -266,6 +309,26 @@ export const deleteChannelSubscription = (subscription: ChannelSubscription) =>
     };
 };
 
+export const deleteSubscriptionTemplate = (subscriptionTemplate: SubscriptionTemplate) => {
+    return async (dispatch, getState) => {
+        const baseUrl = getPluginServerRoute(getState());
+        try {
+            await doFetch(`${baseUrl}/api/v2/subscription-templates/${subscriptionTemplate.id}?instance_id=${subscriptionTemplate.instance_id}&project_key=${subscriptionTemplate.filters.projects[0]}`, {
+                method: 'delete',
+            });
+
+            dispatch({
+                type: ActionTypes.DELETED_SUBSCRIPTION_TEMPLATE,
+                data: subscriptionTemplate,
+            });
+
+            return {data: subscriptionTemplate};
+        } catch (error) {
+            return {error};
+        }
+    };
+};
+
 export const fetchChannelSubscriptions = (channelId: string) => {
     return async (dispatch, getState) => {
         const baseUrl = getPluginServerRoute(getState());
@@ -295,7 +358,7 @@ export const fetchChannelSubscriptions = (channelId: string) => {
             }
         }
 
-        if (errors.length > 0 && allResponses.length === errors.length) {
+        if (errors.length && allResponses.length === errors.length) {
             return {error: new Error(errors[0])};
         }
 
@@ -309,6 +372,66 @@ export const fetchChannelSubscriptions = (channelId: string) => {
     };
 };
 
+export const fetchAllSubscriptionTemplates = () => {
+    return async (dispatch, getState) => {
+        const baseUrl = getPluginServerRoute(getState());
+        const connectedInstances = getUserConnectedInstances(getState());
+        const instances = connectedInstances.map((instance) => {
+            return doFetch(`${baseUrl}/api/v2/subscription-templates?instance_id=${instance.instance_id}`, {
+                method: 'get',
+            });
+        });
+
+        let allResponses;
+        try {
+            allResponses = await Promise.allSettled(instances);
+        } catch (error) {
+            return {error};
+        }
+
+        const errors: string[] = [];
+        let data: ChannelSubscription[] = [];
+        for (const res of allResponses) {
+            if (res.status === 'rejected') {
+                errors.push(res.reason);
+            } else {
+                data = data.concat(res.value);
+            }
+        }
+
+        if (errors.length && allResponses.length === errors.length) {
+            return {error: new Error(errors[0])};
+        }
+
+        dispatch({
+            type: ActionTypes.RECEIVED_SUBSCRIPTION_TEMPLATES,
+            data,
+        });
+
+        return {data};
+    };
+};
+
+export const fetchSubscriptionTemplatesForProjectKey = (instanceId: string, projectKey: string) => {
+    return async (dispatch, getState) => {
+        const baseUrl = getPluginServerRoute(getState());
+        try {
+            const data = await doFetch(`${baseUrl}/api/v2/subscription-templates?instance_id=${instanceId}&project_key=${projectKey}`, {
+                method: 'get',
+            });
+
+            dispatch({
+                type: ActionTypes.RECEIVED_SUBSCRIPTION_TEMPLATES_PROJECT_KEY,
+                data,
+            });
+
+            return {data};
+        } catch (error) {
+            return {error};
+        }
+    };
+};
+
 export function getSettings() {
     return async (dispatch, getState) => {
         let data;
diff --git a/webapp/src/components/modals/channel_subscriptions/__snapshots__/edit_channel_subscription.test.tsx.snap b/webapp/src/components/modals/channel_subscriptions/__snapshots__/edit_channel_subscription.test.tsx.snap
index 5a7731a9d..5e0556589 100644
--- a/webapp/src/components/modals/channel_subscriptions/__snapshots__/edit_channel_subscription.test.tsx.snap
+++ b/webapp/src/components/modals/channel_subscriptions/__snapshots__/edit_channel_subscription.test.tsx.snap
@@ -87,7 +87,7 @@ exports[`components/EditChannelSubscription should match snapshot 1`] = `
       confirmButtonClass="btn btn-danger"
       confirmButtonText="Delete"
       hideCancel={false}
-      message="Delete Subscription \\"SubTestName\\"?"
+      message="Are you sure to delete the subscription \\"SubTestName\\"?"
       onCancel={[Function]}
       onConfirm={[Function]}
       show={false}
@@ -214,6 +214,44 @@ exports[`components/EditChannelSubscription should match snapshot after fetching
           }
         }
       />
+      <ReactSelectSetting
+        isLoading={false}
+        label="Use Template"
+        name="template"
+        onChange={[Function]}
+        options={null}
+        required={false}
+        theme={
+          Object {
+            "awayIndicator": "#ffbc42",
+            "buttonBg": "#166de0",
+            "buttonColor": "#ffffff",
+            "centerChannelBg": "#ffffff",
+            "centerChannelColor": "#3d3c40",
+            "codeTheme": "github",
+            "dndIndicator": "#f74343",
+            "errorTextColor": "#fd5960",
+            "linkColor": "#2389d7",
+            "mentionBg": "#ffffff",
+            "mentionBj": "#ffffff",
+            "mentionColor": "#145dbf",
+            "mentionHighlightBg": "#ffe577",
+            "mentionHighlightLink": "#166de0",
+            "newMessageSeparator": "#ff8800",
+            "onlineIndicator": "#06d6a0",
+            "sidebarBg": "#145dbf",
+            "sidebarHeaderBg": "#1153ab",
+            "sidebarHeaderTextColor": "#ffffff",
+            "sidebarText": "#ffffff",
+            "sidebarTextActiveBorder": "#579eff",
+            "sidebarTextActiveColor": "#ffffff",
+            "sidebarTextHoverBg": "#4578bf",
+            "sidebarUnreadText": "#ffffff",
+            "type": "Mattermost",
+          }
+        }
+        value={null}
+      />
       <ReactSelectSetting
         addValidate={[Function]}
         isMulti={true}
@@ -3794,7 +3832,7 @@ exports[`components/EditChannelSubscription should match snapshot after fetching
       confirmButtonClass="btn btn-danger"
       confirmButtonText="Delete"
       hideCancel={false}
-      message="Delete Subscription \\"SubTestName\\"?"
+      message="Are you sure to delete the subscription \\"SubTestName\\"?"
       onCancel={[Function]}
       onConfirm={[Function]}
       show={false}
@@ -3928,7 +3966,7 @@ exports[`components/EditChannelSubscription should match snapshot with no issue
       confirmButtonClass="btn btn-danger"
       confirmButtonText="Delete"
       hideCancel={false}
-      message="Delete Subscription \\"SubTestName\\"?"
+      message="Are you sure to delete the subscription \\"SubTestName\\"?"
       onCancel={[Function]}
       onConfirm={[Function]}
       show={false}
@@ -3983,10 +4021,7 @@ exports[`components/EditChannelSubscription should match snapshot with no subscr
     className="margin-bottom x3 text-center"
   >
     <h2>
-      Edit Jira Subscription for 
-      <strong>
-        FGFG
-      </strong>
+      Add Subscription Template
     </h2>
   </div>
   <div
@@ -4084,7 +4119,7 @@ exports[`components/EditChannelSubscription should match snapshot with no subscr
     />
     <FormButton
       btnClass="btn-primary"
-      defaultMessage="Save Subscription"
+      defaultMessage="Add Template"
       disabled={true}
       extraClasses=""
       onClick={[Function]}
@@ -4183,7 +4218,7 @@ exports[`components/EditChannelSubscription should produce subscription error wh
       confirmButtonClass="btn btn-danger"
       confirmButtonText="Delete"
       hideCancel={false}
-      message="Delete Subscription \\"SubTestName\\"?"
+      message="Are you sure to delete the subscription \\"SubTestName\\"?"
       onCancel={[Function]}
       onConfirm={[Function]}
       show={false}
diff --git a/webapp/src/components/modals/channel_subscriptions/channel_subscriptions.test.tsx b/webapp/src/components/modals/channel_subscriptions/channel_subscriptions.test.tsx
index 571ec31b3..71a2db332 100644
--- a/webapp/src/components/modals/channel_subscriptions/channel_subscriptions.test.tsx
+++ b/webapp/src/components/modals/channel_subscriptions/channel_subscriptions.test.tsx
@@ -18,6 +18,7 @@ describe('components/ChannelSettingsModal', () => {
         theme: {},
         fetchJiraProjectMetadataForAllInstances: jest.fn().mockResolvedValue({}),
         fetchChannelSubscriptions: jest.fn().mockResolvedValue({}),
+        fetchAllSubscriptionTemplates: jest.fn().mockResolvedValue({}),
         sendEphemeralPost: jest.fn(),
         jiraIssueMetadata: {} as IssueMetadata,
         jiraProjectMetadata: {} as ProjectMetadata,
@@ -50,6 +51,7 @@ describe('components/ChannelSettingsModal', () => {
         });
 
         await props.fetchChannelSubscriptions(testChannel.id);
+        await props.fetchAllSubscriptionTemplates();
         await props.fetchJiraProjectMetadataForAllInstances();
 
         expect(wrapper.find(ChannelSubscriptionsModalInner).length).toEqual(1);
diff --git a/webapp/src/components/modals/channel_subscriptions/channel_subscriptions.tsx b/webapp/src/components/modals/channel_subscriptions/channel_subscriptions.tsx
old mode 100644
new mode 100755
index f9fe01684..62f1ef740
--- a/webapp/src/components/modals/channel_subscriptions/channel_subscriptions.tsx
+++ b/webapp/src/components/modals/channel_subscriptions/channel_subscriptions.tsx
@@ -60,6 +60,8 @@ export default class ChannelSubscriptionsModal extends PureComponent<Props, Stat
             return;
         }
 
+        const templatesResponse = await this.props.fetchAllSubscriptionTemplates();
+
         this.setState({showModal: true, allProjectMetadata: projectResponses.data});
     };
 
diff --git a/webapp/src/components/modals/channel_subscriptions/channel_subscriptions_internal.tsx b/webapp/src/components/modals/channel_subscriptions/channel_subscriptions_internal.tsx
index 224b0f750..4bef4db05 100644
--- a/webapp/src/components/modals/channel_subscriptions/channel_subscriptions_internal.tsx
+++ b/webapp/src/components/modals/channel_subscriptions/channel_subscriptions_internal.tsx
@@ -14,6 +14,9 @@ import {SharedProps} from './shared_props';
 type State = {
     creatingSubscription: boolean;
     selectedSubscription: ChannelSubscription | null;
+    creatingSubscriptionTemplate: boolean;
+    selectedSubscriptionTemplate: ChannelSubscription | null;
+
 }
 
 type Props = SharedProps & {
@@ -24,38 +27,52 @@ export default class ChannelSubscriptionsModalInner extends React.PureComponent<
     state = {
         creatingSubscription: false,
         selectedSubscription: null,
+        creatingSubscriptionTemplate: false,
+        selectedSubscriptionTemplate: null,
     };
 
     showEditChannelSubscription = (subscription: ChannelSubscription): void => {
         this.setState({selectedSubscription: subscription, creatingSubscription: false});
     };
 
+    showEditSubscriptionTemplate = (subscription: ChannelSubscription): void => {
+        this.setState({selectedSubscriptionTemplate: subscription, creatingSubscriptionTemplate: false});
+    };
+
     showCreateChannelSubscription = (): void => {
         this.setState({selectedSubscription: null, creatingSubscription: true});
     };
 
+    showCreateSubscriptionTemplate = (): void => {
+        this.setState({selectedSubscriptionTemplate: null, creatingSubscriptionTemplate: true});
+    };
+
     finishEditSubscription = (): void => {
-        this.setState({selectedSubscription: null, creatingSubscription: false});
+        this.setState({selectedSubscription: null, creatingSubscription: false, selectedSubscriptionTemplate: null, creatingSubscriptionTemplate: false});
     };
 
     handleBack = (): void => {
         this.setState({
             creatingSubscription: false,
             selectedSubscription: null,
+            creatingSubscriptionTemplate: false,
+            selectedSubscriptionTemplate: null,
         });
     };
 
     render(): JSX.Element {
-        const {selectedSubscription, creatingSubscription} = this.state;
+        const {selectedSubscription, creatingSubscription, creatingSubscriptionTemplate, selectedSubscriptionTemplate} = this.state;
 
         let form;
-        if (selectedSubscription || creatingSubscription) {
+        if (selectedSubscription || creatingSubscription || creatingSubscriptionTemplate || selectedSubscriptionTemplate) {
             form = (
                 <EditChannelSubscription
                     {...this.props}
                     finishEditSubscription={this.finishEditSubscription}
                     selectedSubscription={selectedSubscription}
                     creatingSubscription={creatingSubscription}
+                    creatingSubscriptionTemplate={creatingSubscriptionTemplate}
+                    selectedSubscriptionTemplate={selectedSubscriptionTemplate}
                 />
             );
         } else {
@@ -65,12 +82,14 @@ export default class ChannelSubscriptionsModalInner extends React.PureComponent<
                     allProjectMetadata={this.props.allProjectMetadata}
                     showEditChannelSubscription={this.showEditChannelSubscription}
                     showCreateChannelSubscription={this.showCreateChannelSubscription}
+                    showEditSubscriptionTemplate={this.showEditSubscriptionTemplate}
+                    showCreateSubscriptionTemplate={this.showCreateSubscriptionTemplate}
                 />
             );
         }
 
         let backIcon;
-        if (this.state.creatingSubscription || this.state.selectedSubscription) {
+        if (this.state.creatingSubscription || this.state.selectedSubscription || this.state.creatingSubscriptionTemplate || this.state.selectedSubscriptionTemplate) {
             backIcon = (
                 <BackIcon
                     className='back'
diff --git a/webapp/src/components/modals/channel_subscriptions/channel_subscriptions_modal.scss b/webapp/src/components/modals/channel_subscriptions/channel_subscriptions_modal.scss
index 93aaa123e..6a3f6d5e4 100644
--- a/webapp/src/components/modals/channel_subscriptions/channel_subscriptions_modal.scss
+++ b/webapp/src/components/modals/channel_subscriptions/channel_subscriptions_modal.scss
@@ -23,7 +23,10 @@
             margin: 2rem 0;
         }
 
-        th,
+        .th-col {
+            width: 25%;
+        }
+
         td {
             padding: 1rem 0;
         }
@@ -40,6 +43,10 @@
                 border-color: transparent;
             }
         }
+
+        td {
+            word-break: break-word;
+        }
     }
     &__learnMore {
         margin-top: 1em;
diff --git a/webapp/src/components/modals/channel_subscriptions/edit_channel_subscription.test.tsx b/webapp/src/components/modals/channel_subscriptions/edit_channel_subscription.test.tsx
index 7fc9d945e..c4b32c975 100644
--- a/webapp/src/components/modals/channel_subscriptions/edit_channel_subscription.test.tsx
+++ b/webapp/src/components/modals/channel_subscriptions/edit_channel_subscription.test.tsx
@@ -22,6 +22,14 @@ describe('components/EditChannelSubscription', () => {
         deleteChannelSubscription: jest.fn().mockResolvedValue({}),
         editChannelSubscription: jest.fn().mockResolvedValue({}),
         fetchChannelSubscriptions: jest.fn().mockResolvedValue({}),
+        createSubscriptionTemplate: jest.fn().mockResolvedValue({}),
+        deleteSubscriptionTemplate: jest.fn().mockResolvedValue({}),
+        editSubscriptionTemplate: jest.fn().mockResolvedValue({}),
+        fetchAllSubscriptionTemplates: jest.fn().mockResolvedValue({}),
+        fetchSubscriptionTemplatesForProjectKey: jest.fn().mockResolvedValue({}),
+        sendEphemeralPost: jest.fn().mockResolvedValue({}),
+        getConnected: jest.fn().mockResolvedValue({}),
+        fetchJiraProjectMetadataForAllInstances: jest.fn().mockResolvedValue({}),
         fetchJiraIssueMetadataForProjects: jest.fn().mockResolvedValue({data: cloudIssueMetadata}),
     };
 
@@ -80,6 +88,7 @@ describe('components/EditChannelSubscription', () => {
         close: jest.fn(),
         selectedSubscription: channelSubscriptionForCloud,
         creatingSubscription: false,
+        creatingSubscriptionTemplate: false,
         securityLevelEmptyForJiraSubscriptions: true,
     };
 
diff --git a/webapp/src/components/modals/channel_subscriptions/edit_channel_subscription.tsx b/webapp/src/components/modals/channel_subscriptions/edit_channel_subscription.tsx
old mode 100644
new mode 100755
index 1edc90f60..e8107f999
--- a/webapp/src/components/modals/channel_subscriptions/edit_channel_subscription.tsx
+++ b/webapp/src/components/modals/channel_subscriptions/edit_channel_subscription.tsx
@@ -64,6 +64,8 @@ export type Props = SharedProps & {
     finishEditSubscription: () => void;
     selectedSubscription: ChannelSubscription | null;
     creatingSubscription: boolean;
+    creatingSubscriptionTemplate: boolean;
+    selectedSubscriptionTemplate: ChannelSubscription | null;
 };
 
 export type State = {
@@ -71,12 +73,15 @@ export type State = {
     instanceID: string;
     fetchingIssueMetadata: boolean;
     jiraIssueMetadata: IssueMetadata | null;
+    templateOptions: ReactSelectOption[] | null;
     error: string | null;
     getMetaDataErr: string | null;
     submitting: boolean;
+    submittingTemplate: boolean;
     subscriptionName: string | null;
     showConfirmModal: boolean;
     conflictingError: string | null;
+    selectedTemplateID: string | null;
 };
 
 export default class EditChannelSubscription extends PureComponent<Props, State> {
@@ -98,14 +103,23 @@ export default class EditChannelSubscription extends PureComponent<Props, State>
             subscriptionName = props.selectedSubscription.name;
         }
 
+        if (props.selectedSubscriptionTemplate) {
+            filters = Object.assign({}, filters, props.selectedSubscriptionTemplate.filters);
+            subscriptionName = props.selectedSubscriptionTemplate.name;
+        }
+
         filters.fields = filters.fields || [];
 
         let instanceID = '';
+        let fetchingIssueMetadata = false;
         if (this.props.selectedSubscription) {
             instanceID = this.props.selectedSubscription.instance_id;
         }
 
-        let fetchingIssueMetadata = false;
+        if (this.props.selectedSubscriptionTemplate) {
+            instanceID = this.props.selectedSubscriptionTemplate.instance_id;
+        }
+
         if (filters.projects.length && instanceID) {
             fetchingIssueMetadata = true;
             this.fetchIssueMetadata(filters.projects, instanceID);
@@ -115,6 +129,7 @@ export default class EditChannelSubscription extends PureComponent<Props, State>
             error: null,
             getMetaDataErr: null,
             submitting: false,
+            submittingTemplate: false,
             filters,
             fetchingIssueMetadata,
             jiraIssueMetadata: null,
@@ -122,11 +137,29 @@ export default class EditChannelSubscription extends PureComponent<Props, State>
             showConfirmModal: false,
             conflictingError: null,
             instanceID,
+            selectedTemplateID: null,
+            templateOptions: null,
         };
 
         this.validator = new Validator();
     }
 
+    componentDidMount() {
+        if (this.props.selectedSubscription) {
+            const projects = this.props.selectedSubscription.filters.projects;
+            if (projects.length) {
+                this.fetchSubscriptionTemplateForProjectKey(this.state.instanceID, projects[0]);
+            }
+        }
+
+        if (this.props.selectedSubscriptionTemplate) {
+            const projects = this.props.selectedSubscriptionTemplate.filters.projects;
+            if (projects.length) {
+                this.fetchSubscriptionTemplateForProjectKey(this.state.instanceID, projects[0]);
+            }
+        }
+    }
+
     handleClose = (e?: React.FormEvent) => {
         if (e && e.preventDefault) {
             e.preventDefault();
@@ -148,6 +181,16 @@ export default class EditChannelSubscription extends PureComponent<Props, State>
                 }
             });
         }
+
+        if (this.props.selectedSubscriptionTemplate) {
+            this.props.deleteSubscriptionTemplate(this.props.selectedSubscriptionTemplate).then((res) => {
+                if (res.error) {
+                    this.setState({error: res.error.message});
+                } else {
+                    this.handleClose();
+                }
+            });
+        }
     };
 
     handleCancelDelete = () => {
@@ -245,6 +288,26 @@ export default class EditChannelSubscription extends PureComponent<Props, State>
         });
     };
 
+    fetchSubscriptionTemplateForProjectKey = (instanceId: string, projectId: string) => {
+        this.setState({selectedTemplateID: null, fetchingIssueMetadata: true});
+        this.props.fetchSubscriptionTemplatesForProjectKey(instanceId, projectId).then((subs) => {
+            if (subs.error) {
+                this.setState({error: subs.error.message});
+                return;
+            }
+
+            const subscriptionTemplate = subs.data as ChannelSubscription[];
+            let templateOptions: ReactSelectOption[] | null = null;
+            if (subscriptionTemplate) {
+                templateOptions = subscriptionTemplate.map((template: ChannelSubscription) => (
+                    {label: template.name || template.id, value: template.id}
+                ));
+            }
+
+            this.setState({templateOptions, fetchingIssueMetadata: false});
+        });
+    };
+
     handleJiraInstanceChange = (instanceID: string) => {
         if (instanceID === this.state.instanceID) {
             return;
@@ -283,6 +346,11 @@ export default class EditChannelSubscription extends PureComponent<Props, State>
             this.fetchIssueMetadata(projects, this.state.instanceID);
         }
 
+        if (this.state.instanceID && projectID) {
+            fetchingIssueMetadata = true;
+            this.fetchSubscriptionTemplateForProjectKey(this.state.instanceID, projectID);
+        }
+
         this.setState({
             fetchingIssueMetadata,
             getMetaDataErr: null,
@@ -324,9 +392,29 @@ export default class EditChannelSubscription extends PureComponent<Props, State>
             instance_id: this.state.instanceID,
         } as ChannelSubscription;
 
-        this.setState({submitting: true, error: null});
+        if (this.props.selectedSubscriptionTemplate) {
+            this.setState({submittingTemplate: true, error: null});
+            subscription.id = this.props.selectedSubscriptionTemplate.id;
+            this.props.editSubscriptionTemplate(subscription).then((edited) => {
+                if (edited.error) {
+                    this.setState({error: edited.error.message, submittingTemplate: false});
+                    return;
+                }
 
-        if (this.props.selectedSubscription) {
+                this.handleClose(e);
+            });
+        } else if (this.props.creatingSubscriptionTemplate) {
+            this.setState({submittingTemplate: true, error: null});
+            this.props.createSubscriptionTemplate(subscription).then((created) => {
+                if (created.error) {
+                    this.setState({error: created.error.message, submittingTemplate: false});
+                    return;
+                }
+
+                this.handleClose(e);
+            });
+        } else if (this.props.selectedSubscription) {
+            this.setState({submitting: true, error: null});
             subscription.id = this.props.selectedSubscription.id;
             this.props.editChannelSubscription(subscription).then((edited) => {
                 if (edited.error) {
@@ -336,6 +424,7 @@ export default class EditChannelSubscription extends PureComponent<Props, State>
                 this.handleClose(e);
             });
         } else {
+            this.setState({submitting: true, error: null});
             this.props.createChannelSubscription(subscription).then((created) => {
                 if (created.error) {
                     this.setState({error: created.error.message, submitting: false});
@@ -346,6 +435,15 @@ export default class EditChannelSubscription extends PureComponent<Props, State>
         }
     };
 
+    handleTemplateChange = (_: any, templateId: string) => {
+        const templateChoosen = this.props.subscriptionTemplates.find((template) => template.id === templateId);
+        this.handleProjectChange(templateChoosen.filters.projects[0]);
+        this.setState({
+            filters: templateChoosen.filters,
+            selectedTemplateID: templateId,
+        });
+    };
+
     render(): JSX.Element {
         const style = getModalStyles(this.props.theme);
 
@@ -375,8 +473,18 @@ export default class EditChannelSubscription extends PureComponent<Props, State>
                 innerComponent = (
                     <React.Fragment>
                         <ReactSelectSetting
-                            name={'events'}
-                            label={'Events'}
+                            name='template'
+                            label='Use Template'
+                            options={this.state.templateOptions}
+                            onChange={this.handleTemplateChange}
+                            value={this.state.templateOptions && this.state.templateOptions.find((option) => option.value === this.state.selectedTemplateID)}
+                            required={false}
+                            theme={this.props.theme}
+                            isLoading={false}
+                        />
+                        <ReactSelectSetting
+                            name='events'
+                            label='Events'
                             required={true}
                             onChange={this.handleSettingChange}
                             options={eventOptions}
@@ -387,8 +495,8 @@ export default class EditChannelSubscription extends PureComponent<Props, State>
                             removeValidate={this.validator.removeComponent}
                         />
                         <ReactSelectSetting
-                            name={'issue_types'}
-                            label={'Issue Type'}
+                            name='issue_types'
+                            label='Issue Type'
                             required={true}
                             onChange={this.handleIssueChange}
                             options={issueOptions}
@@ -442,8 +550,8 @@ export default class EditChannelSubscription extends PureComponent<Props, State>
                 <React.Fragment>
                     <div className='container-fluid'>
                         <Input
-                            label={'Subscription Name'}
-                            placeholder={'Name'}
+                            label='Subscription Name'
+                            placeholder='Name'
                             type={'input'}
                             maxLength={100}
                             required={true}
@@ -476,24 +584,25 @@ export default class EditChannelSubscription extends PureComponent<Props, State>
 
         const {showConfirmModal} = this.state;
 
-        let confirmDeleteMessage = 'Delete Subscription?';
-        if (this.props.selectedSubscription && this.props.selectedSubscription.name) {
-            confirmDeleteMessage = `Delete Subscription "${this.props.selectedSubscription.name}"?`;
+        let confirmDeleteMessage = '';
+        confirmDeleteMessage = `Are you sure to delete the subscription template ${(this.props.selectedSubscriptionTemplate && this.props.selectedSubscriptionTemplate.name) ? `"${this.props.selectedSubscriptionTemplate.name}"` : ''}?`;
+        if (this.props.selectedSubscription) {
+            confirmDeleteMessage = `Are you sure to delete the subscription ${this.props.selectedSubscription.name ? `"${this.props.selectedSubscription.name}"` : ''}?`;
         }
 
         let confirmComponent;
-        if (this.props.selectedSubscription) {
+        if (this.props.selectedSubscription || this.props.selectedSubscriptionTemplate) {
             confirmComponent = (
                 <ConfirmModal
-                    cancelButtonText={'Cancel'}
-                    confirmButtonText={'Delete'}
+                    cancelButtonText='Cancel'
+                    confirmButtonText='Delete'
                     confirmButtonClass={'btn btn-danger'}
                     hideCancel={false}
                     message={confirmDeleteMessage}
                     onCancel={this.handleCancelDelete}
                     onConfirm={this.handleConfirmDelete}
                     show={showConfirmModal}
-                    title={'Subscription'}
+                    title={this.props.selectedSubscription ? 'Subscription' : 'Subscription Template'}
                 />
             );
         }
@@ -508,13 +617,23 @@ export default class EditChannelSubscription extends PureComponent<Props, State>
         }
 
         const enableSubmitButton = Boolean(this.state.filters.projects[0]);
-        const enableDeleteButton = Boolean(this.props.selectedSubscription);
-
-        let saveSubscriptionButtonText = 'Save Subscription';
-        let headerText = 'Edit Jira Subscription for ';
-        if (this.props.creatingSubscription) {
-            saveSubscriptionButtonText = 'Add Subscription';
-            headerText = 'Add Jira Subscription in ';
+        const enableDeleteButton = Boolean(this.props.selectedSubscription || this.props.selectedSubscriptionTemplate);
+        let saveSubscriptionButtonText = '';
+        let headerText = '';
+        if (this.props.selectedSubscription || this.props.creatingSubscription) {
+            saveSubscriptionButtonText = 'Save Subscription';
+            headerText = 'Edit Jira Subscription for ';
+            if (this.props.creatingSubscription) {
+                saveSubscriptionButtonText = 'Add Subscription';
+                headerText = 'Add Jira Subscription in ';
+            }
+        } else {
+            saveSubscriptionButtonText = 'Add Template';
+            headerText = 'Add Subscription Template';
+            if (this.props.selectedSubscriptionTemplate && this.props.selectedSubscriptionTemplate.name) {
+                saveSubscriptionButtonText = 'Save Template';
+                headerText = 'Edit Subscription Template';
+            }
         }
 
         return (
@@ -522,7 +641,7 @@ export default class EditChannelSubscription extends PureComponent<Props, State>
                 role='form'
             >
                 <div className='margin-bottom x3 text-center'>
-                    <h2>{headerText}<strong>{this.props.channel.display_name}</strong></h2>
+                    {this.props.selectedSubscription || this.props.creatingSubscription ? <h2>{headerText}<strong>{this.props.channel.display_name}</strong></h2> : <h2>{headerText}</h2>}
                 </div>
                 <div style={style.modalBody}>
                     {component}
@@ -549,7 +668,7 @@ export default class EditChannelSubscription extends PureComponent<Props, State>
                         onClick={this.handleCreate}
                         disabled={!enableSubmitButton}
                         btnClass='btn-primary'
-                        saving={this.state.submitting}
+                        saving={this.props.creatingSubscriptionTemplate || this.props.selectedSubscriptionTemplate ? this.state.submittingTemplate : this.state.submitting}
                         defaultMessage={saveSubscriptionButtonText}
                         savingMessage='Saving...'
                     />
diff --git a/webapp/src/components/modals/channel_subscriptions/index.ts b/webapp/src/components/modals/channel_subscriptions/index.ts
old mode 100644
new mode 100755
index 8e3ddce03..f36df710d
--- a/webapp/src/components/modals/channel_subscriptions/index.ts
+++ b/webapp/src/components/modals/channel_subscriptions/index.ts
@@ -10,12 +10,17 @@ import {isDirectChannel, isGroupChannel} from 'mattermost-redux/utils/channel_ut
 import {
     closeChannelSettings,
     createChannelSubscription,
+    createSubscriptionTemplate,
     deleteChannelSubscription,
+    deleteSubscriptionTemplate,
     editChannelSubscription,
+    editSubscriptionTemplate,
+    fetchAllSubscriptionTemplates,
     fetchChannelSubscriptions,
     fetchJiraIssueMetadataForProjects,
     fetchJiraProjectMetadata,
     fetchJiraProjectMetadataForAllInstances,
+    fetchSubscriptionTemplatesForProjectKey,
     getConnected,
     sendEphemeralPost,
 } from 'actions';
@@ -25,6 +30,7 @@ import {
     getChannelSubscriptions,
     getInstalledInstances,
     getPluginSettings,
+    getSubscriptionTemplates,
     getUserConnectedInstances,
 } from 'selectors';
 
@@ -41,7 +47,7 @@ const mapStateToProps = (state) => {
     }
 
     const channelSubscriptions = getChannelSubscriptions(state)[channelId];
-
+    const subscriptionTemplates = getSubscriptionTemplates(state).subscriptionTemplates;
     const installedInstances = getInstalledInstances(state);
     const connectedInstances = getUserConnectedInstances(state);
     const pluginSettings = getPluginSettings(state);
@@ -50,6 +56,7 @@ const mapStateToProps = (state) => {
     return {
         omitDisplayName,
         channelSubscriptions,
+        subscriptionTemplates,
         channel,
         installedInstances,
         connectedInstances,
@@ -63,9 +70,14 @@ const mapDispatchToProps = (dispatch) => bindActionCreators({
     fetchJiraProjectMetadataForAllInstances,
     fetchJiraIssueMetadataForProjects,
     createChannelSubscription,
+    createSubscriptionTemplate,
+    fetchAllSubscriptionTemplates,
+    fetchSubscriptionTemplatesForProjectKey,
     fetchChannelSubscriptions,
     deleteChannelSubscription,
+    deleteSubscriptionTemplate,
     editChannelSubscription,
+    editSubscriptionTemplate,
     getConnected,
     sendEphemeralPost,
 }, dispatch);
diff --git a/webapp/src/components/modals/channel_subscriptions/select_channel_subscription.tsx b/webapp/src/components/modals/channel_subscriptions/select_channel_subscription.tsx
old mode 100644
new mode 100755
index bd1a3c223..1bd7cc8b7
--- a/webapp/src/components/modals/channel_subscriptions/select_channel_subscription.tsx
+++ b/webapp/src/components/modals/channel_subscriptions/select_channel_subscription.tsx
@@ -8,7 +8,9 @@ import {SharedProps} from './shared_props';
 
 type Props = SharedProps & {
     showEditChannelSubscription: (subscription: ChannelSubscription) => void;
+    showEditSubscriptionTemplate: (subscription: ChannelSubscription) => void;
     showCreateChannelSubscription: () => void;
+    showCreateSubscriptionTemplate: () => void;
     allProjectMetadata: AllProjectMetadata | null;
 };
 
@@ -16,6 +18,7 @@ type State = {
     error: string | null;
     showConfirmModal: boolean;
     subscriptionToDelete: ChannelSubscription | null;
+    isTemplate: boolean;
 }
 
 export default class SelectChannelSubscriptionInternal extends React.PureComponent<Props, State> {
@@ -23,6 +26,7 @@ export default class SelectChannelSubscriptionInternal extends React.PureCompone
         error: null,
         showConfirmModal: false,
         subscriptionToDelete: null,
+        isTemplate: false,
     };
 
     handleCancelDelete = (): void => {
@@ -31,13 +35,18 @@ export default class SelectChannelSubscriptionInternal extends React.PureCompone
 
     handleConfirmDelete = (): void => {
         this.setState({showConfirmModal: false});
-        this.deleteChannelSubscription(this.state.subscriptionToDelete);
+        if (this.state.isTemplate) {
+            this.deleteSubscriptionTemplate(this.state.subscriptionToDelete);
+        } else {
+            this.deleteChannelSubscription(this.state.subscriptionToDelete);
+        }
     };
 
-    handleDeleteChannelSubscription = (sub: ChannelSubscription): void => {
+    handleDeleteChannelSubscription = (sub: ChannelSubscription, isTemplate = false): void => {
         this.setState({
             showConfirmModal: true,
             subscriptionToDelete: sub,
+            isTemplate,
         });
     };
 
@@ -49,6 +58,14 @@ export default class SelectChannelSubscriptionInternal extends React.PureCompone
         });
     };
 
+    deleteSubscriptionTemplate = (sub: ChannelSubscription): void => {
+        this.props.deleteSubscriptionTemplate(sub).then((res: {error?: {message: string}}) => {
+            if (res.error) {
+                this.setState({error: res.error.message});
+            }
+        });
+    };
+
     getProjectName = (sub: ChannelSubscription): string => {
         const projectKey = sub.filters.projects[0];
         if (!this.props.allProjectMetadata) {
@@ -66,7 +83,7 @@ export default class SelectChannelSubscriptionInternal extends React.PureCompone
         return projectKey;
     };
 
-    renderRow = (sub: ChannelSubscription): JSX.Element => {
+    renderRow = (sub: ChannelSubscription, forTemplates = false): JSX.Element => {
         const projectName = this.getProjectName(sub);
 
         const showInstanceColumn = this.props.installedInstances.length > 1;
@@ -74,6 +91,13 @@ export default class SelectChannelSubscriptionInternal extends React.PureCompone
         const alias = this.props.installedInstances.filter((instance) => instance.instance_id === sub.instance_id)[0].alias;
         const instanceName = alias || sub.instance_id;
 
+        if (!forTemplates) {
+            return this.renderSubscriptionRow(sub, projectName, showInstanceColumn, instanceName);
+        }
+        return this.renderSubscriptionTemplateRow(sub, projectName, showInstanceColumn, instanceName);
+    };
+
+    renderSubscriptionRow(sub: ChannelSubscription, projectName: string, showInstanceColumn: boolean, instanceName: string): JSX.Element {
         return (
             <tr
                 key={sub.id}
@@ -110,11 +134,44 @@ export default class SelectChannelSubscriptionInternal extends React.PureCompone
                 </td>
             </tr>
         );
-    };
+    }
+
+    renderSubscriptionTemplateRow(sub: ChannelSubscription, projectName: string, showInstanceColumn: boolean, instanceName: string): JSX.Element {
+        return (
+            <tr
+                key={sub.id}
+                className='select-channel-subscriptions-row'
+            >
+                <td>{sub.name || '(no name)'}</td>
+                <td>{projectName}</td>
+                {showInstanceColumn && (
+                    <td>{instanceName}</td>
+                )}
+
+                <td>
+                    <button
+                        className='style--none color--link'
+                        onClick={() => this.props.showEditSubscriptionTemplate(sub)}
+                        type='button'
+                    >
+                        {'Edit'}
+                    </button>
+                    {' - '}
+                    <button
+                        className='style--none color--link'
+                        onClick={() => this.handleDeleteChannelSubscription(sub, true)}
+                        type='button'
+                    >
+                        {'Delete'}
+                    </button>
+                </td>
+            </tr>
+        );
+    }
 
     render(): React.ReactElement {
-        const {channel, channelSubscriptions, omitDisplayName} = this.props;
-        const {error, showConfirmModal, subscriptionToDelete} = this.state;
+        const {channel, channelSubscriptions, subscriptionTemplates, omitDisplayName} = this.props;
+        const {error, showConfirmModal, subscriptionToDelete, isTemplate} = this.state;
 
         let errorDisplay = null;
         if (error) {
@@ -123,9 +180,10 @@ export default class SelectChannelSubscriptionInternal extends React.PureCompone
             );
         }
 
-        let confirmDeleteMessage = 'Delete Subscription?';
+        let confirmDeleteMessage = '';
+        confirmDeleteMessage = `Are you sure to delete the subscription ${isTemplate ? 'template' : ''}?`;
         if (subscriptionToDelete && subscriptionToDelete.name) {
-            confirmDeleteMessage = `Delete Subscription "${subscriptionToDelete.name}"?`;
+            confirmDeleteMessage = `Are you sure to delete the subscription  ${isTemplate ? 'template' : ''} "${subscriptionToDelete.name}"?`;
         }
 
         let confirmModal = null;
@@ -140,7 +198,7 @@ export default class SelectChannelSubscriptionInternal extends React.PureCompone
                     onCancel={this.handleCancelDelete}
                     onConfirm={this.handleConfirmDelete}
                     show={true}
-                    title={'Subscription'}
+                    title={isTemplate ? 'Subscription Template' : 'Subscription'}
                 />
             );
         }
@@ -150,21 +208,39 @@ export default class SelectChannelSubscriptionInternal extends React.PureCompone
             titleMessage = <h2 className='text-center'>{'Jira Subscriptions'}</h2>;
         }
 
+        const subscriptionTemplateTitle = <h2 className='text-center'>{'Jira Subscription Templates'}</h2>;
         const showInstanceColumn = this.props.installedInstances.length > 1;
         let subscriptionRows;
+        let subscriptionTemplateRows;
+        const columns = (
+            <thead>
+                <tr>
+                    <th scope='col'>{'Name'}</th>
+                    <th
+                        className='th-col'
+                        scope='col'
+                    >{'Project'}</th>
+                    {showInstanceColumn &&
+                    <th
+                        className='th-col'
+                        scope='col'
+                    >{'Instance'}</th>
+                    }
+                    <th
+                        className='th-col'
+                        scope='col'
+                    >{'Actions'}</th>
+                </tr>
+            </thead>
+        );
         if (channelSubscriptions.length) {
             subscriptionRows = (
                 <table className='table'>
-                    <thead>
-                        <tr>
-                            <th scope='col'>{'Name'}</th>
-                            <th scope='col'>{'Project'}</th>
-                            {showInstanceColumn && <th scope='col'>{'Instance'}</th>}
-                            <th scope='col'>{'Actions'}</th>
-                        </tr>
-                    </thead>
+                    {columns}
                     <tbody>
-                        {channelSubscriptions.map(this.renderRow)}
+                        {channelSubscriptions.map((element) => (
+                            this.renderRow(element, false)
+                        ))}
                     </tbody>
                 </table>
             );
@@ -176,6 +252,23 @@ export default class SelectChannelSubscriptionInternal extends React.PureCompone
             );
         }
 
+        if (subscriptionTemplates.length) {
+            subscriptionTemplateRows = (
+                <table className='table'>
+                    {columns}
+                    <tbody>
+                        {subscriptionTemplates.map((element) => (
+                            this.renderRow(element, true)
+                        ))}
+                    </tbody>
+                </table>
+            );
+        } else {
+            subscriptionTemplateRows = (
+                <p>{'Click "Create Template" to create a subscription template.'}</p>
+            );
+        }
+
         return (
             <div>
                 <div className='d-flex justify-content-between align-items-center margin-bottom x3 title-message'>
@@ -188,9 +281,20 @@ export default class SelectChannelSubscriptionInternal extends React.PureCompone
                         {'Create Subscription'}
                     </button>
                 </div>
+                {subscriptionRows}
+                <div className='d-flex justify-content-between align-items-center margin-bottom x3'>
+                    {subscriptionTemplateTitle}
+                    <button
+                        className='btn btn-primary'
+                        onClick={this.props.showCreateSubscriptionTemplate}
+                        type='button'
+                    >
+                        {'Create Template'}
+                    </button>
+                </div>
+                {subscriptionTemplateRows}
                 {confirmModal}
                 {errorDisplay}
-                {subscriptionRows}
             </div>
         );
     }
diff --git a/webapp/src/components/modals/channel_subscriptions/shared_props.ts b/webapp/src/components/modals/channel_subscriptions/shared_props.ts
old mode 100644
new mode 100755
index aca654c90..6935f42e0
--- a/webapp/src/components/modals/channel_subscriptions/shared_props.ts
+++ b/webapp/src/components/modals/channel_subscriptions/shared_props.ts
@@ -17,15 +17,21 @@ export type SharedProps = {
     channel: Channel | null;
     theme: Theme;
     channelSubscriptions: ChannelSubscription[];
+    subscriptionTemplates: ChannelSubscription[];
     omitDisplayName: boolean;
     installedInstances: Instance[];
     connectedInstances: Instance[];
     createChannelSubscription: (sub: ChannelSubscription) => Promise<APIResponse<{}>>;
+    createSubscriptionTemplate: (sub: ChannelSubscription) => Promise<APIResponse<{}>>;
+    deleteSubscriptionTemplate: (sub: ChannelSubscription) => Promise<APIResponse<{}>>;
     deleteChannelSubscription: (sub: ChannelSubscription) => Promise<APIResponse<{}>>;
+    editSubscriptionTemplate: (sub: ChannelSubscription) => Promise<APIResponse<{}>>;
     editChannelSubscription: (sub: ChannelSubscription) => Promise<APIResponse<{}>>;
+    fetchSubscriptionTemplatesForProjectKey: (instanceId: string, projectKey: string) => Promise<APIResponse<ChannelSubscription[]>>;
     fetchJiraProjectMetadataForAllInstances: () => Promise<APIResponse<AllProjectMetadata>>;
     fetchJiraIssueMetadataForProjects: (projectKeys: string[], instanceID: string) => Promise<APIResponse<IssueMetadata>>;
     fetchChannelSubscriptions: (channelId: string) => Promise<APIResponse<ChannelSubscription[]>>;
+    fetchAllSubscriptionTemplates: () => Promise<APIResponse<ChannelSubscription[]>>;
     getConnected: () => Promise<GetConnectedResponse>;
     close: () => void;
     sendEphemeralPost: (message: string) => void;
diff --git a/webapp/src/reducers/index.js b/webapp/src/reducers/index.js
old mode 100644
new mode 100755
index efed52332..81a93e2d4
--- a/webapp/src/reducers/index.js
+++ b/webapp/src/reducers/index.js
@@ -151,6 +151,61 @@ const channelIdWithSettingsOpen = (state = '', action) => {
     }
 };
 
+const subscriptionTemplates = (state = '', action) => {
+    switch (action.type) {
+    case ActionTypes.RECEIVED_SUBSCRIPTION_TEMPLATES: {
+        const nextState = {...state};
+        nextState.subscriptionTemplates = action.data;
+        return nextState;
+    }
+    case ActionTypes.DELETED_SUBSCRIPTION_TEMPLATE: {
+        const subTemplate = action.data;
+        const nextState = {...state};
+
+        nextState.subscriptionTemplates = nextState.subscriptionTemplates.filter((st) => {
+            return st.id !== subTemplate.id;
+        });
+
+        return nextState;
+    }
+    case ActionTypes.CREATED_SUBSCRIPTION_TEMPLATE: {
+        const subTemplate = action.data;
+        const nextState = {...state};
+        nextState.subscriptionTemplates = [...nextState.subscriptionTemplates, subTemplate];
+
+        return nextState;
+    }
+    case ActionTypes.EDITED_SUBSCRIPTION_TEMPLATE: {
+        const subTemplate = action.data;
+        const nextState = {...state};
+
+        const index = nextState.subscriptionTemplates.findIndex((template) => template.id === subTemplate.id);
+
+        const newArray = [...nextState.subscriptionTemplates];
+        newArray[index] = subTemplate;
+
+        return {
+            ...nextState,
+            subscriptionTemplates: newArray,
+        };
+    }
+    default:
+        return state;
+    }
+};
+
+const subscriptionTemplatesForProjectKey = (state = '', action) => {
+    switch (action.type) {
+    case ActionTypes.RECEIVED_SUBSCRIPTION_TEMPLATES_PROJECT_KEY: {
+        const nextState = {...state};
+        nextState.subscriptionTemplatesForProjectKey = action.data;
+        return nextState;
+    }
+    default:
+        return state;
+    }
+};
+
 const channelSubscriptions = (state = {}, action) => {
     switch (action.type) {
     case ActionTypes.RECEIVED_CHANNEL_SUBSCRIPTIONS: {
@@ -207,5 +262,7 @@ export default combineReducers({
     attachCommentToIssueModalVisible,
     attachCommentToIssueModalForPostId,
     channelIdWithSettingsOpen,
+    subscriptionTemplates,
+    subscriptionTemplatesForProjectKey,
     channelSubscriptions,
 });
diff --git a/webapp/src/selectors/index.ts b/webapp/src/selectors/index.ts
old mode 100644
new mode 100755
index 0488f67a4..24bbdd79a
--- a/webapp/src/selectors/index.ts
+++ b/webapp/src/selectors/index.ts
@@ -53,6 +53,10 @@ export const getChannelIdWithSettingsOpen = (state) => getPluginState(state).cha
 
 export const getChannelSubscriptions = (state) => getPluginState(state).channelSubscriptions;
 
+export const getSubscriptionTemplates = (state) => getPluginState(state).subscriptionTemplates;
+
+export const getSubscriptionTemplatesForProjectKey = (state) => getPluginState(state).subscriptionTemplatesForProjectKey;
+
 export const isUserConnected = (state) => getUserConnectedInstances(state).length > 0;
 
 export const canUserConnect = (state) => getPluginState(state).userCanConnect;
diff --git a/webapp/src/types/model.ts b/webapp/src/types/model.ts
index cbc4a245a..108af1a50 100644
--- a/webapp/src/types/model.ts
+++ b/webapp/src/types/model.ts
@@ -172,6 +172,8 @@ export type ChannelSubscription = {
     instance_id: string;
 }
 
+export type SubscriptionTemplate = ChannelSubscription
+
 export enum InstanceType {
     CLOUD = 'cloud',
     SERVER = 'server',