public inbox for [email protected]  
help / color / mirror / Atom feed
From: Pradip Parkale <[email protected]>
To: Akshay Joshi <[email protected]>
Cc: pgadmin-hackers <[email protected]>
Subject: Re: [pgAdmin][RM5912]: Added support for Logical Replication.
Date: Thu, 14 Jan 2021 18:58:34 +0530
Message-ID: <CAJ9T6SveOoLxbyKivRzweCvOabv991rSYSN9D5WX98hg91m6CA@mail.gmail.com> (raw)
In-Reply-To: <CANxoLDeDV5cRYGbffhJXdsUEtcs220C_bDpyptTm30xGfsie6A@mail.gmail.com>
References: <CAJ9T6Su-iFZ1LikdjUAXEnTXYg8wBZrR23iWYfeGk2pLOVcqsg@mail.gmail.com>
	<CAJ9T6Sv3p+JwD11VDtH0_PQBgw4oHd49tyyGQ+tj3wzZRKZHNA@mail.gmail.com>
	<CANxoLDeDV5cRYGbffhJXdsUEtcs220C_bDpyptTm30xGfsie6A@mail.gmail.com>

Hi Akshay,
Please find the latest patch. I missed some files in my previous patch.

On Thu, Jan 14, 2021 at 5:49 PM Akshay Joshi <[email protected]>
wrote:

> Hi Pradip
>
> The patch is applied, but not working. Please check and send the patch
> again.
>
> On Wed, Jan 13, 2021 at 9:08 AM Pradip Parkale <
> [email protected]> wrote:
>
>> Hi Akshay,
>>
>> Please ignore my previous patch. Please find my updated patch.
>>
>>
>> On Mon, Jan 11, 2021 at 5:07 PM Pradip Parkale <
>> [email protected]> wrote:
>>
>>> Hi Hackers,
>>>
>>> Please find the attached patch for logical replication support.
>>>
>>> --
>>> Thanks & Regards,
>>> Pradip Parkale
>>> Software Engineer | EnterpriseDB Corporation
>>>
>>
>>
>> --
>> Thanks & Regards,
>> Pradip Parkale
>> Software Engineer | EnterpriseDB Corporation
>>
>
>
> --
> *Thanks & Regards*
> *Akshay Joshi*
> *pgAdmin Hacker | Principal Software Architect*
> *EDB Postgres <http://edbpostgres.com>*
>
> *Mobile: +91 976-788-8246*
>


-- 
Thanks & Regards,
Pradip Parkale
Software Engineer | EnterpriseDB Corporation


Attachments:

  [application/octet-stream] RM-5912_v3.patch (230.5K, 3-RM-5912_v3.patch)
  download | inline diff:
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/publications/__init__.py
new file mode 100644
index 000000000..c9b83c108
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/__init__.py
@@ -0,0 +1,828 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Implements Publication Node"""
+import simplejson as json
+from functools import wraps
+
+import pgadmin.browser.server_groups.servers.databases as databases
+from flask import render_template, request, jsonify
+from flask_babelex import gettext
+from pgadmin.browser.collection import CollectionNodeModule
+from pgadmin.browser.utils import PGChildNodeView
+from pgadmin.utils.ajax import make_json_response, internal_server_error, \
+    make_response as ajax_response, gone
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+from pgadmin.tools.schema_diff.compare import SchemaDiffObjectCompare
+
+
+class PublicationModule(CollectionNodeModule):
+    """
+    class PublicationModule(CollectionNodeModule)
+
+        A module class for Publication node derived from CollectionNodeModule.
+
+    Methods:
+    -------
+    * __init__(*args, **kwargs)
+      - Method is used to initialize the PublicationModule and it's
+      base module.
+
+    * get_nodes(gid, sid, did)
+      - Method is used to generate the browser collection node.
+
+    * node_inode()
+      - Method is overridden from its base class to make the node as leaf node.
+
+    * script_load()
+      - Load the module script for publication, when any of the database node
+      is initialized.
+    """
+
+    _NODE_TYPE = 'publication'
+    _COLLECTION_LABEL = gettext("Publications")
+
+    def __init__(self, *args, **kwargs):
+        """
+        Method is used to initialize the PublicationModule and it's
+        base module.
+
+        Args:
+            *args:
+            **kwargs:
+        """
+        super(PublicationModule, self).__init__(*args, **kwargs)
+        self.min_ver = 100000
+        self.max_ver = None
+
+    def get_nodes(self, gid, sid, did):
+        """
+        Method is used to generate the browser collection node
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database Id
+        """
+        yield self.generate_browser_collection_node(did)
+
+    @property
+    def node_inode(self):
+        """
+        Override this property to make the node a leaf node.
+
+        Returns: False as this is the leaf node
+        """
+        return False
+
+    @property
+    def script_load(self):
+        """
+        Load the module script for publication, when any of the database nodes
+        are initialized.
+
+        Returns: node type of the server module.
+        """
+        return databases.DatabaseModule.node_type
+
+    @property
+    def module_use_template_javascript(self):
+        """
+        Returns whether Jinja2 template is used for generating the javascript
+        module.
+        """
+        return False
+
+
+blueprint = PublicationModule(__name__)
+
+
+class PublicationView(PGChildNodeView, SchemaDiffObjectCompare):
+    """
+    class PublicationView(PGChildNodeView)
+
+        A view class for Publication node derived from PGChildNodeView.
+        This class is responsible for all the stuff related to view like
+        updating publication node, showing properties, showing sql in sql pane.
+
+    Methods:
+    -------
+    * __init__(**kwargs)
+      - Method is used to initialize the PublicationView and it's base view.
+
+    * check_precondition()
+      - This function will behave as a decorator which will checks
+        database connection before running view, it will also attaches
+        manager,conn & template_path properties to self
+
+    * list()
+      - This function is used to list all the publication nodes within that
+      collection.
+
+    * nodes()
+      - This function will used to create all the child node within that
+      collection. Here it will create all the publication node.
+
+    * properties(gid, sid, did, pbid)
+      - This function will show the properties of the selected publication node
+
+    * update(gid, sid, did, pbid)
+      - This function will update the data for the selected publication node
+
+    * create(gid, sid, did)
+      - This function will create the new publication node
+
+    * delete(gid, sid, did, pbid)
+      - This function will delete the selected publication node
+
+    * msql(gid, sid, did, pbid)
+      - This function is used to return modified SQL for the selected
+      publication node
+
+    * get_sql(data, pbid)
+      - This function will generate sql from model data
+
+    * get_tables(gid, sid, did)
+      - This function returns the handler and inline functions for the
+      selected publication node
+
+    * get_templates(gid, sid, did)
+      - This function returns publication templates.
+
+    * sql(gid, sid, did, pbid):
+      - This function will generate sql to show it in sql pane for the
+      selected publication node.
+
+    * dependents(gid, sid, did, pbid):
+      - This function get the dependents and return ajax response for the
+      publication node.
+
+    * dependencies(self, gid, sid, did, pbid):
+      - This function get the dependencies and return ajax response for the
+      publication node.
+    """
+
+    _NOT_FOUND_PUB_INFORMATION = \
+        gettext("Could not find the publication information.")
+    node_type = blueprint.node_type
+
+    parent_ids = [
+        {'type': 'int', 'id': 'gid'},
+        {'type': 'int', 'id': 'sid'},
+        {'type': 'int', 'id': 'did'}
+    ]
+    ids = [
+        {'type': 'int', 'id': 'pbid'}
+    ]
+
+    operations = dict({
+        'obj': [
+            {'get': 'properties', 'delete': 'delete', 'put': 'update'},
+            {'get': 'list', 'post': 'create', 'delete': 'delete'}
+        ],
+        'nodes': [{'get': 'node'}, {'get': 'nodes'}],
+        'sql': [{'get': 'sql'}],
+        'msql': [{'get': 'msql'}, {'get': 'msql'}],
+        'stats': [{'get': 'statistics'}],
+        'dependency': [{'get': 'dependencies'}],
+        'dependent': [{'get': 'dependents'}],
+        'get_tables': [{}, {'get': 'get_tables'}],
+        'get_templates': [{}, {'get': 'get_templates'}],
+        'delete': [{'delete': 'delete'}, {'delete': 'delete'}]
+    })
+
+    def _init_(self, **kwargs):
+        """
+        Method is used to initialize the PublicationView and its base view.
+        Initialize all the variables create/used dynamically like conn,
+        template_path.
+
+        Args:
+            **kwargs:
+        """
+        self.conn = None
+        self.template_path = None
+        self.manager = None
+
+        super(PublicationView, self).__init__(**kwargs)
+
+    def check_precondition(f):
+        """
+        This function will behave as a decorator which will check the
+        database connection before running the view. It also attaches
+        manager, conn & template_path properties to self
+        """
+
+        @wraps(f)
+        def wrap(*args, **kwargs):
+            # Here args[0] will hold self & kwargs will hold gid,sid,did
+            self = args[0]
+            self.driver = get_driver(PG_DEFAULT_DRIVER)
+            self.manager = self.driver.connection_manager(kwargs['sid'])
+            self.conn = self.manager.connection(did=kwargs['did'])
+            self.datlastsysoid = self.manager.db_info[kwargs['did']][
+                'datlastsysoid'] if self.manager.db_info is not None \
+                and kwargs['did'] in self.manager.db_info else 0
+
+            # Set the template path for the SQL scripts
+            self.template_path = (
+                "publications/sql/#gpdb#{0}#".format(self.manager.version) if
+                self.manager.server_type == 'gpdb' else
+                "publications/sql/#{0}#".format(self.manager.version)
+            )
+
+            return f(*args, **kwargs)
+
+        return wrap
+
+    @staticmethod
+    def _parser_data_input_from_client(data):
+        """
+
+        :param data:
+        :return: data
+        """
+
+        if 'pubtable' in data and data['pubtable'] != '':
+            data['pubtable'] = json.loads(
+                data['pubtable'], encoding='utf-8'
+            )
+        return data
+
+    @check_precondition
+    def list(self, gid, sid, did):
+        """
+        This function is used to list all the publication nodes within that
+        collection.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+        """
+        sql = render_template("/".join([self.template_path,
+                                        self._PROPERTIES_SQL]))
+        status, res = self.conn.execute_dict(sql)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+        return ajax_response(
+            response=res['rows'],
+            status=200
+        )
+
+    @check_precondition
+    def nodes(self, gid, sid, did):
+        """
+        This function is used to create all the child nodes within the
+        collection. Here it will create all the publication nodes.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+        """
+        res = []
+        sql = render_template("/".join([self.template_path,
+                                        'nodes.sql']))
+        status, result = self.conn.execute_2darray(sql)
+        if not status:
+            return internal_server_error(errormsg=result)
+
+        for row in result['rows']:
+            res.append(
+                self.blueprint.generate_browser_node(
+                    row['oid'],
+                    did,
+                    row['name'],
+                    icon="icon-publication"
+                ))
+
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    @check_precondition
+    def node(self, gid, sid, did, pbid):
+        """
+        This function will fetch properties of the publication nodes.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            pbid: Publication ID
+        """
+        sql = render_template("/".join([self.template_path,
+                                        self._PROPERTIES_SQL]),
+                              pbid=pbid)
+        status, result = self.conn.execute_2darray(sql)
+        if not status:
+            return internal_server_error(errormsg=result)
+
+        for row in result['rows']:
+            return make_json_response(
+                data=self.blueprint.generate_browser_node(
+                    row['oid'],
+                    did,
+                    row['name'],
+                    icon="icon-publication"
+                ),
+                status=200
+            )
+
+        return gone(gettext("Could not find the specified publication."))
+
+    @check_precondition
+    def properties(self, gid, sid, did, pbid):
+        """
+        This function will show the properties of the
+        selected publication node.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            pbid: Publication ID
+        """
+        status, res = self._fetch_properties(did, pbid)
+
+        if not status:
+            return res
+
+        if 'all_table' in res and res['all_table']:
+            res['pubtable'] = ''
+
+        return ajax_response(
+            response=res,
+            status=200
+        )
+
+    def _fetch_properties(self, did, pbid):
+        """
+        This function fetch the properties of the extension.
+        :param did:
+        :param pbid:
+        :return:
+        """
+        sql = render_template(
+            "/".join([self.template_path, self._PROPERTIES_SQL]),
+            pbid=pbid
+        )
+
+        status, res = self.conn.execute_dict(sql)
+
+        if not status:
+            return False, internal_server_error(errormsg=res)
+
+        if len(res['rows']) == 0:
+            return False, gone(self._NOT_FOUND_PUB_INFORMATION)
+
+        get_name_sql = render_template(
+            "/".join([self.template_path, self._DELETE_SQL]),
+            pbid=pbid, conn=self.conn
+        )
+        status, pname = self.conn.execute_scalar(get_name_sql)
+        table_sql = render_template(
+            "/".join([self.template_path, 'get_tables.sql']),
+            pname=pname
+        )
+
+        pub_table = []
+        status, table_res = self.conn.execute_dict(table_sql)
+
+        for table in table_res['rows']:
+            pub_table.append(table['pubtable'])
+
+        res['rows'][0]['pubtable'] = pub_table
+
+        return True, res['rows'][0]
+
+    @check_precondition
+    def update(self, gid, sid, did, pbid):
+        """
+        This function will update the data for the selected publication node.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            pbid: Publication ID
+        """
+        data = request.form if request.form else json.loads(
+            request.data, encoding='utf-8'
+        )
+
+        try:
+            data = self._parser_data_input_from_client(data)
+
+            sql, name = self.get_sql(data, pbid)
+
+            # Most probably this is due to error
+            if not isinstance(sql, str):
+                return sql
+            sql = sql.strip('\n').strip(' ')
+            status, res = self.conn.execute_dict(sql)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            return jsonify(
+                node=self.blueprint.generate_browser_node(
+                    pbid,
+                    did,
+                    name,
+                    icon="icon-%s" % self.node_type
+                )
+            )
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def create(self, gid, sid, did):
+        """
+        This function will create the publication object
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+        """
+        required_args = [
+            'name'
+        ]
+
+        data = request.form if request.form else json.loads(
+            request.data, encoding='utf-8'
+        )
+        for arg in required_args:
+            if arg not in data:
+                return make_json_response(
+                    status=410,
+                    success=0,
+                    errormsg=gettext(
+                        "Could not find the required parameter ({})."
+                    ).format(arg)
+                )
+
+        try:
+            data = self._parser_data_input_from_client(data)
+
+            sql = render_template("/".join([self.template_path,
+                                            self._CREATE_SQL]),
+                                  data=data, conn=self.conn)
+
+            status, res = self.conn.execute_dict(sql)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            sql = render_template(
+                "/".join([self.template_path, 'get_position.sql']),
+                conn=self.conn, pubname=data['name']
+            )
+
+            status, r_set = self.conn.execute_dict(sql)
+            if not status:
+                return internal_server_error(errormsg=r_set)
+
+            return jsonify(
+                node=self.blueprint.generate_browser_node(
+                    r_set['rows'][0]['oid'],
+                    did,
+                    r_set['rows'][0]['name'],
+                    icon='icon-publication'
+                )
+            )
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def delete(self, gid, sid, did, pbid=None):
+        """
+        This function will drop the publication object
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            pbid: Publication ID
+        """
+        if pbid is None:
+            data = request.form if request.form else json.loads(
+                request.data, encoding='utf-8'
+            )
+        else:
+            data = {'ids': [pbid]}
+
+        cascade = self._check_cascade_operation()
+
+        try:
+            for pbid in data['ids']:
+                # Get name for publication from pbid
+                sql = render_template(
+                    "/".join([self.template_path, self._DELETE_SQL]),
+                    pbid=pbid, conn=self.conn
+                )
+                status, pname = self.conn.execute_scalar(sql)
+
+                if not status:
+                    return internal_server_error(errormsg=pname)
+
+                # drop publication
+                sql = render_template(
+                    "/".join([self.template_path, self._DELETE_SQL]),
+                    pname=pname, cascade=cascade, conn=self.conn
+                )
+
+                status, res = self.conn.execute_scalar(sql)
+                if not status:
+                    return internal_server_error(errormsg=res)
+
+            return make_json_response(
+                success=1,
+                info=gettext("Publication dropped")
+            )
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def msql(self, gid, sid, did, pbid=None):
+        """
+        This function is used to return modified SQL for the selected
+        publication node.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            pbid: Publication ID
+        """
+        data = {}
+        for k, v in request.args.items():
+            try:
+                # comments should be taken as is because if user enters a
+                # json comment it is parsed by loads which should not happen
+                if k in ('description',):
+                    data[k] = v
+                else:
+                    data[k] = json.loads(v, encoding='utf-8')
+            except ValueError:
+                data[k] = v
+        try:
+            sql, name = self.get_sql(data, pbid)
+            # Most probably this is due to error
+            if not isinstance(sql, str):
+                return sql
+            if sql == '':
+                sql = "--modified SQL"
+
+            return make_json_response(
+                data=sql,
+                status=200
+            )
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @staticmethod
+    def _get_option_details(old_data, data):
+        """
+        Return the option details
+        :param old_data:
+        :param data:
+        :return: data
+        """
+        if 'evnt_insert' in data or 'evnt_delete' in data or \
+                'evnt_update' in data or 'evnt_truncate' in data:
+
+            if 'evnt_insert' not in data:
+                data['evnt_insert'] = old_data['evnt_insert']
+
+            if 'evnt_delete' not in data:
+                data['evnt_delete'] = old_data['evnt_delete']
+
+            if 'evnt_update' not in data:
+                data['evnt_update'] = old_data['evnt_update']
+
+            if 'evnt_truncate' not in data and 'evnt_truncate' in old_data:
+                data['evnt_truncate'] = old_data['evnt_truncate']
+
+        return data
+
+    def get_sql(self, data, pbid=None):
+        """
+        This function will generate sql from model data.
+
+        Args:
+            data: Contains the data of the selected publication node.
+            pbid: Publication ID
+        """
+        required_args = [
+            'name'
+        ]
+        drop_table = False
+
+        if pbid is not None:
+            sql = render_template(
+                "/".join([self.template_path, self._PROPERTIES_SQL]), pbid=pbid
+            )
+            status, res = self.conn.execute_dict(sql)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            if len(res['rows']) == 0:
+                return gone(self._NOT_FOUND_PUB_INFORMATION)
+
+            old_data = self._get_old_table_data(res['rows'][0]['name'], res)
+
+            drop_table_data = []
+
+            for table in old_data['pubtable']:
+                if table not in data['pubtable']:
+                    drop_table_data.append(table)
+                    drop_table = True
+
+            for arg in required_args:
+                if arg not in data:
+                    data[arg] = old_data[arg]
+
+            # Add old event setting for future reference
+            data = self._get_option_details(old_data, data)
+
+            sql = render_template(
+                "/".join([self.template_path, self._UPDATE_SQL]),
+                data=data, o_data=old_data, conn=self.conn,
+                drop_table=drop_table, drop_table_data=drop_table_data
+            )
+            return sql.strip('\n'), data['name'] if 'name' in data \
+                else old_data['name']
+        else:
+
+            sql = render_template("/".join([self.template_path,
+                                            self._CREATE_SQL]),
+                                  data=data, conn=self.conn)
+            return sql.strip('\n'), data['name']
+
+    @check_precondition
+    def get_tables(self, gid, sid, did):
+        """
+        This function returns the tables list.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+        """
+        res = [{'label': '', 'value': ''}]
+
+        sql = render_template("/".join([self.template_path,
+                                        'get_all_tables.sql']),
+                              show_sys_objects=self.blueprint.
+                              show_system_objects,
+                              server_type=self.manager.server_type
+                              )
+        status, rset = self.conn.execute_2darray(sql)
+        if not status:
+            return internal_server_error(errormsg=res)
+        for row in rset['rows']:
+            res.append(
+                {
+                    'label': row['table'],
+                    'value': row['table']
+                }
+            )
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    def _get_old_table_data(self, pname, res):
+        """
+        This function return table details before update
+        :param pname:
+        :param res:
+        :return:old_data
+        """
+
+        table_sql = render_template(
+            "/".join([self.template_path, 'get_tables.sql']),
+            pname=pname
+        )
+
+        pub_table = []
+        status, table_res = self.conn.execute_dict(table_sql)
+
+        for table in table_res['rows']:
+            pub_table.append(table['pubtable'])
+
+        res['rows'][0]['pubtable'] = pub_table
+
+        # Making copy of output for future use
+        old_data = dict(res['rows'][0])
+
+        if 'all_table' in old_data and old_data['all_table']:
+            old_data['pubtable'] = ''
+
+        return old_data
+
+    @check_precondition
+    def sql(self, gid, sid, did, pbid, json_resp=True):
+        """
+        This function will generate sql to show in the sql panel for the
+        selected publication node.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            pbid: Publication ID
+            json_resp:
+        """
+        sql = render_template(
+            "/".join([self.template_path, self._PROPERTIES_SQL]),
+            pbid=pbid
+        )
+        status, res = self.conn.execute_dict(sql)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        if len(res['rows']) == 0:
+            return gone(self._NOT_FOUND_PUB_INFORMATION)
+
+        get_name_sql = render_template(
+            "/".join([self.template_path, self._DELETE_SQL]),
+            pbid=pbid, conn=self.conn
+        )
+        status, pname = self.conn.execute_scalar(get_name_sql)
+
+        # Get old table details
+        old_data = self._get_old_table_data(pname, res)
+
+        sql = render_template("/".join([self.template_path,
+                                        self._CREATE_SQL]),
+                              data=old_data, conn=self.conn)
+
+        sql_header = "-- Publication : {};".format(old_data['name'])
+        sql_header += "\n"
+
+        sql_header += "-- "
+
+        sql_header += render_template(
+            "/".join([self.template_path, self._DELETE_SQL]),
+            pname=old_data['name'], )
+
+        sql_header += "\n"
+
+        sql = sql_header + sql
+
+        if not json_resp:
+            return sql
+
+        return ajax_response(response=sql)
+
+    @check_precondition
+    def dependents(self, gid, sid, did, pbid):
+        """
+        This function gets the dependents and returns an ajax response
+        for the publication node.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            pbid: Publication ID
+        """
+        dependents_result = self.get_dependents(self.conn, pbid)
+        return ajax_response(
+            response=dependents_result,
+            status=200
+        )
+
+    @check_precondition
+    def dependencies(self, gid, sid, did, pbid):
+        """
+        This function gets the dependencies and returns an ajax response
+        for the publication node.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            pbid: Publication ID
+        """
+        dependencies_result = self.get_dependencies(self.conn, pbid)
+        return ajax_response(
+            response=dependencies_result,
+            status=200
+        )
+
+
+PublicationView.register_node_view(blueprint)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/static/img/coll-publication.svg b/web/pgadmin/browser/server_groups/servers/databases/publications/static/img/coll-publication.svg
new file mode 100644
index 000000000..cc946919b
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/static/img/coll-publication.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#DDE1F0;stroke:#4B5FAD;stroke-width:0.65;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
+	.st1{fill:#4B5FAD;}
+	.st2{fill:none;stroke:#4B5FAD;stroke-width:0.65;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
+</style>
+<g>
+	<path class="st0" d="M10,13c-2,2-5.1,2-7.1,0s-2-5.1,0-7.1C4.5,7.5,8.5,11.5,10,13z"/>
+</g>
+<g>
+	<path class="st0" d="M11.8,11.3c-2,2-5.1,2-7.1,0s-2-5.1,0-7.1C6.2,5.7,10.2,9.8,11.8,11.3z"/>
+</g>
+<path class="st1" d="M8.7,7.8l0.8-0.7c0.1,0.1,0.3,0.1,0.4,0.1c0.6,0,1-0.4,1-1s-0.4-1-1-1s-1,0.4-1,1c0,0.1,0,0.3,0.1,0.4L8.2,7.3
+	L8.7,7.8z"/>
+<g>
+	<path class="st2" d="M10.5,1.6c2.2,0,4,1.8,4,4"/>
+	<path class="st2" d="M10.6,3.6c1.1,0,2,0.9,2,2"/>
+</g>
+</svg>
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/static/img/coll-subscription.svg b/web/pgadmin/browser/server_groups/servers/databases/publications/static/img/coll-subscription.svg
new file mode 100644
index 000000000..d14adfd39
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/static/img/coll-subscription.svg
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#FBEEDF;stroke:#E58E26;stroke-width:0.65;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
+	.st1{fill:#E58E26;}
+	.st2{fill:none;stroke:#E58E26;stroke-width:0.65;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
+</style>
+<g>
+	<g>
+		<g>
+			<path class="st0" d="M12.9,5.7c2,2,2,5.1,0,7.1s-5.1,2-7.1,0C7.3,11.3,11.4,7.2,12.9,5.7z"/>
+		</g>
+	</g>
+	<g>
+		<g>
+			<path class="st0" d="M11.4,4.2c2,2,2,5.1,0,7.1s-5.1,2-7.1,0C5.8,9.8,9.9,5.7,11.4,4.2z"/>
+		</g>
+		<g>
+			<path class="st1" d="M7.8,7.3L7.1,6.6c0.1-0.1,0.1-0.3,0.1-0.4c0-0.6-0.4-1-1-1s-1,0.4-1,1s0.4,1,1,1c0.1,0,0.3,0,0.4-0.1
+				l0.7,0.7"/>
+			<g>
+				<path class="st2" d="M5.6,1.7c0,2.2-1.8,4-4,4"/>
+				<path class="st2" d="M3.7,1.8c0,1.1-0.9,2-2,2"/>
+			</g>
+		</g>
+	</g>
+</g>
+</svg>
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/static/img/publication.svg b/web/pgadmin/browser/server_groups/servers/databases/publications/static/img/publication.svg
new file mode 100644
index 000000000..3b582cf51
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/static/img/publication.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 23.0.6, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#DDE1F0;stroke:#4B5FAD;stroke-width:0.65;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
+	.st1{fill:#4B5FAD;}
+	.st2{fill:none;stroke:#4B5FAD;stroke-width:0.65;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
+</style>
+<g>
+	<g>
+		<path class="st0" d="M11,12c-2,2-5.1,2-7.1,0s-2-5.1,0-7.1C5.4,6.4,9.5,10.5,11,12z"/>
+	</g>
+	<g>
+		<path class="st1" d="M7.9,8.6l0.7-0.7C8.7,8,8.9,8,9,8c0.6,0,1-0.4,1-1S9.6,6,9,6S8,6.4,8,7c0,0.1,0,0.3,0.1,0.4L7.4,8.1"/>
+		<g>
+			<path class="st2" d="M9.6,2.5c2.2,0,4,1.8,4,4"/>
+			<path class="st2" d="M9.7,4.4c1.1,0,2,0.9,2,2"/>
+		</g>
+	</g>
+</g>
+</svg>
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/static/img/subscription.svg b/web/pgadmin/browser/server_groups/servers/databases/publications/static/img/subscription.svg
new file mode 100644
index 000000000..63a153c6d
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/static/img/subscription.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 23.0.6, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#FBEEDF;stroke:#E58E26;stroke-width:0.65;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
+	.st1{fill:#E58E26;}
+	.st2{fill:none;stroke:#E58E26;stroke-width:0.65;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
+</style>
+<g>
+	<g>
+		<path class="st0" d="M12.2,5c2,2,2,5.1,0,7.1s-5.1,2-7.1,0C6.6,10.6,10.7,6.5,12.2,5z"/>
+	</g>
+	<g>
+		<path class="st1" d="M8.6,8.1L7.9,7.4C8,7.3,8,7.1,8,7c0-0.6-0.4-1-1-1S6,6.4,6,7s0.4,1,1,1c0.1,0,0.3,0,0.4-0.1l0.7,0.7"/>
+		<g>
+			<path class="st2" d="M6.4,2.5c0,2.2-1.8,4-4,4"/>
+			<path class="st2" d="M4.5,2.6c0,1.1-0.9,2-2,2"/>
+		</g>
+	</g>
+</g>
+</svg>
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/static/js/publication.js b/web/pgadmin/browser/server_groups/servers/databases/publications/static/js/publication.js
new file mode 100644
index 000000000..160ce2e8c
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/static/js/publication.js
@@ -0,0 +1,232 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2020, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+define('pgadmin.node.publication', [
+  'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
+  'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform',
+  'pgadmin.browser.collection', 'pgadmin.browser.server.privilege',
+], function(gettext, url_for, $, _, pgAdmin, pgBrowser, Backform) {
+
+  // Extend the browser's collection class for publications collection
+  if (!pgBrowser.Nodes['coll-publication']) {
+    pgBrowser.Nodes['coll-publication'] =
+      pgBrowser.Collection.extend({
+        node: 'publication',
+        label: gettext('Publications'),
+        type: 'coll-publication',
+        columns: ['name', 'pubowner', 'all_table'],
+      });
+  }
+
+  // Extend the browser's node class for publication node
+  if (!pgBrowser.Nodes['publication']) {
+    pgBrowser.Nodes['publication'] = pgBrowser.Node.extend({
+      parent_type: 'database',
+      type: 'publication',
+      sqlAlterHelp: 'sql-alterpublication.html',
+      sqlCreateHelp: 'sql-createpublication.html',
+      dialogHelp: url_for('help.static', {'filename': 'publication_dialog.html'}),
+      label: gettext('Publication'),
+      hasSQL:  true,
+      canDrop: true,
+      canDropCascade: true,
+      hasDepends: true,
+      Init: function() {
+
+        // Avoid multiple registration of menus
+        if (this.initialized)
+          return;
+
+        this.initialized = true;
+
+
+        // Add context menus for publication
+        pgBrowser.add_menus([{
+          name: 'create_publication_on_database', node: 'database', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: gettext('Publication...'),
+          icon: 'wcTabIcon icon-publication', data: {action: 'create'},
+          enable: pgBrowser.Nodes['database'].canCreate,
+        },{
+          name: 'create_publication_on_coll', node: 'coll-publication', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: gettext('Publication...'),
+          icon: 'wcTabIcon icon-publication', data: {action: 'create'},
+        },{
+          name: 'create_publication', node: 'publication', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: gettext('Publication...'),
+          icon: 'wcTabIcon icon-publication', data: {action: 'create'},
+        }]);
+      },
+      // Define the model for publication node
+      model: pgBrowser.Node.Model.extend({
+        idAttribute: 'oid',
+        defaults: {
+          name: undefined,
+          pubowner: undefined,
+          pubtable: undefined,
+          all_table: undefined,
+          evnt_insert:true,
+          evnt_delete:true,
+          evnt_update:true,
+          evnt_truncate:true,
+          only_table: undefined,
+        },
+
+        // Default values!
+        initialize: function(attrs, args) {
+          var isNew = (_.size(attrs) === 0);
+          if (isNew) {
+            var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
+
+            this.set({'pubowner': userInfo.name}, {silent: true});
+          }
+          pgBrowser.Node.Model.prototype.initialize.apply(this, arguments);
+        },
+
+        // Define the schema for the publication node
+        schema: [{
+          id: 'name', label: gettext('Name'), type: 'text',
+          mode: ['properties', 'create', 'edit'],
+          visible: function() {
+            if(!_.isUndefined(this.node_info) && !_.isUndefined(this.node_info.server)
+              && !_.isUndefined(this.node_info.server.version) &&
+                this.node_info.server.version >= 100000) {
+              return true;
+            }
+            return false;
+          },
+        },{
+          id: 'oid', label: gettext('OID'), cell: 'string', mode: ['properties'],
+          type: 'text',
+        },{
+          id: 'pubowner', label: gettext('Owner'), type: 'text',
+          control: Backform.NodeListByNameControl, node: 'role',
+          mode: ['edit', 'properties', 'create'], select2: { allowClear: false},
+        },{
+          id: 'all_table', label: gettext('All table?'), type: 'switch',
+          group: gettext('Definition'), mode: ['edit', 'properties', 'create'], deps: ['name'],
+          readonly: function(m) {return !m.isNew();},
+        },
+        {
+          id: 'only_table', label: gettext('Only table'), type: 'switch',
+          group: gettext('Definition'), mode: ['edit', 'create'],
+          deps: ['name', 'pubtable', 'all_table'], readonly: 'isTable',
+        },
+        {
+          id: 'pubtable', label: gettext('Table'), type: 'array',select2: { allowClear: true, multiple: true },
+          control: 'node-ajax-options', url:'get_tables',
+          group: gettext('Definition'), mode: ['edit', 'properties', 'create'],
+          deps: ['all_table'], disabled: 'isAllTable',
+        },
+        {
+          type: 'nested', control: 'fieldset', mode: ['create','edit', 'properties'],
+          label: gettext('With'), group: gettext('Definition'), contentClass: 'row',
+          schema:[{
+            id: 'evnt_insert', label: gettext('INSERT'),
+            type: 'switch', mode: ['create','edit', 'properties'],
+            group: gettext('With'),
+            extraToggleClasses: 'pg-el-sm-6',
+            controlLabelClassName: 'control-label pg-el-sm-5 pg-el-12',
+            controlsClassName: 'pgadmin-controls pg-el-sm-7 pg-el-12',
+          },{
+            id: 'evnt_update', label: gettext('UPDATE'),
+            type: 'switch', mode: ['create','edit', 'properties'],
+            group: gettext('With'),
+            extraToggleClasses: 'pg-el-sm-6',
+            controlLabelClassName: 'control-label pg-el-sm-5 pg-el-12',
+            controlsClassName: 'pgadmin-controls pg-el-sm-7 pg-el-12',
+          },{
+            id: 'evnt_delete', label: gettext('DELETE'),
+            type: 'switch', mode: ['create','edit', 'properties'],
+            group: gettext('With'),
+            extraToggleClasses: 'pg-el-sm-6',
+            controlLabelClassName: 'control-label pg-el-sm-5 pg-el-12',
+            controlsClassName: 'pgadmin-controls pg-el-sm-7 pg-el-12',
+          },{
+            id: 'evnt_truncate', label: gettext('TRUNCATE'),
+            type: 'switch', group: gettext('With'),
+            extraToggleClasses: 'pg-el-sm-6',
+            controlLabelClassName: 'control-label pg-el-sm-5 pg-el-12',
+            controlsClassName: 'pgadmin-controls pg-el-sm-7 pg-el-12',
+            visible: function(m) {
+              if(!_.isUndefined(m.node_info) && !_.isUndefined(m.node_info.server)
+              && !_.isUndefined(m.node_info.server.version) &&
+                m.node_info.server.version >= 110000)
+                return true;
+              return false;
+            },
+
+          }],
+        },
+        ],
+
+        isAllTable:  function(m){
+          var all_table = m.get('all_table');
+          if(all_table){
+            setTimeout( function() {
+              m.set('pubtable', '');
+            }, 10);
+            return true;
+          }
+          return false;
+        },
+        isTable:  function(m){
+          var all_table = m.get('all_table'),
+            table = m.get('pubtable');
+          if(all_table){
+            setTimeout( function() {
+              m.set('only_table', false);
+            }, 10);
+            return true;
+          }
+
+          if (_.isUndefined(table) || table.length < 1){
+            return true;
+          }
+          return false;
+        },
+
+        /* validate function is used to validate the input given by
+         * the user. In case of error, message will be displayed on
+         * the GUI for the respective control.
+         */
+        validate: function() {
+          var name = this.get('name'),
+            msg;
+
+          if (_.isUndefined(name) || _.isNull(name) ||
+            String(name).replace(/^\s+|\s+$/g, '') == '') {
+            msg = gettext('Name cannot be empty.');
+            this.errorModel.set('name', msg);
+            return msg;
+          } else {
+            this.errorModel.unset('name');
+          }
+          return null;
+        },
+        canCreate: function(itemData, item) {
+
+          var treeData = this.getTreeNodeHierarchy(item),
+            server = treeData['server'];
+
+          // If server is less than 10 then do not allow 'create' menu
+          if (server && server.version < 100000)
+            return false;
+
+          // by default we want to allow create menu
+          return true;
+        },
+
+      }),
+    });
+  }
+  return pgBrowser.Nodes['coll-publication'];
+});
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/11_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/11_plus/properties.sql
new file mode 100644
index 000000000..42f537238
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/11_plus/properties.sql
@@ -0,0 +1,8 @@
+select c.oid as oid, c.pubname as name,
+pubinsert as evnt_insert, pubupdate as evnt_update, pubdelete as evnt_delete, pubtruncate as evnt_truncate,
+puballtables as all_table,
+pga.rolname as pubowner from pg_publication c
+join pg_authid pga on c.pubowner= pga.oid
+{%  if pbid %}
+    where c.oid = {{ pbid }}
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/create.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/create.sql
new file mode 100644
index 000000000..272def992
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/create.sql
@@ -0,0 +1,20 @@
+{% if data.evnt_delete or data.evnt_update or data.evnt_truncate %}
+{% set add_comma_after_insert = 'insert' %}
+{% endif %}
+{% if data.evnt_truncate %}
+{% set add_comma_after_delete = 'delete' %}
+{% endif %}
+{% if data.evnt_delete or data.evnt_truncate%}
+{% set add_comma_after_update = 'update' %}
+{% endif %}
+
+CREATE PUBLICATION {{ conn|qtIdent(data.name) }}
+{% if data.all_table %}
+    FOR ALL TABLES
+{% elif data.pubtable %}
+    FOR TABLE {% if data.only_table%}ONLY {% endif %}{% for table in data.pubtable %}{% if loop.index != 1 %}, {% endif %}{{ table }}{% endfor %}
+
+{% endif %}
+{% if data.evnt_insert or data.evnt_update or data.evnt_delete or data.evnt_truncate %}
+WITH (publish = '{% if data.evnt_insert %}insert{% if add_comma_after_insert == 'insert' %}, {% endif %}{% endif %}{% if data.evnt_update %}update{% if add_comma_after_update == 'update' %}, {% endif %}{% endif %}{% if data.evnt_delete %}delete{% if add_comma_after_delete == 'delete' %}, {% endif %}{% endif %}{% if data.evnt_truncate %}truncate{% endif %}');
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/delete.sql
new file mode 100644
index 000000000..98cb5d0d1
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/delete.sql
@@ -0,0 +1,8 @@
+{# ============= Get the publication name using oid ============= #}
+{% if pbid %}
+    SELECT pubname FROM pg_publication WHERE oid = {{pbid}}::oid;
+{% endif %}
+{# ============= Drop the publication ============= #}
+{% if pname %}
+DROP PUBLICATION {{ conn|qtIdent(pname) }}{% if cascade %} CASCADE{% endif%};
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/get_all_tables.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/get_all_tables.sql
new file mode 100644
index 000000000..b493abc7b
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/get_all_tables.sql
@@ -0,0 +1,6 @@
+SELECT quote_ident(c.table_schema)||'.'||quote_ident(c.table_name) AS table
+from information_schema.tables c
+where c.table_type = 'BASE TABLE'
+      AND c.table_schema NOT LIKE 'pg\_%'
+      AND c.table_schema NOT LIKE 'pgagent'
+      AND c.table_schema NOT IN ('information_schema') ORDER BY 1;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/get_position.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/get_position.sql
new file mode 100644
index 000000000..293f4f692
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/get_position.sql
@@ -0,0 +1 @@
+select oid, pubname as name from pg_publication where pubname = '{{ pubname }}';
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/get_tables.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/get_tables.sql
new file mode 100644
index 000000000..7f51c72d1
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/get_tables.sql
@@ -0,0 +1 @@
+select quote_ident(pgb_table.schemaname)||'.'||quote_ident(pgb_table.tablename)  as pubtable  from pg_publication_tables pgb_table where pubname = '{{ pname }}' and pgb_table.schemaname NOT LIKE 'pgagent';
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/nodes.sql
new file mode 100644
index 000000000..c8157f180
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/nodes.sql
@@ -0,0 +1 @@
+select oid , pubname as name from pg_publication;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/properties.sql
new file mode 100644
index 000000000..75ba075e4
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/properties.sql
@@ -0,0 +1,8 @@
+select c.oid as oid, c.pubname as name,
+pubinsert as evnt_insert, pubupdate as evnt_update, pubdelete as evnt_delete,
+puballtables as all_table,
+pga.rolname as pubowner from pg_publication c
+join pg_authid pga on c.pubowner= pga.oid
+{%  if pbid %}
+    where c.oid = {{ pbid }}
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/update.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/update.sql
new file mode 100644
index 000000000..190bbf3d6
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/templates/publications/sql/default/update.sql
@@ -0,0 +1,34 @@
+{% if data.evnt_delete or data.evnt_update or data.evnt_truncate %}
+{% set add_comma_after_insert = 'insert' %}
+{% endif %}
+{% if data.evnt_truncate %}
+{% set add_comma_after_delete = 'delete' %}
+{% endif %}
+{% if data.evnt_delete or data.evnt_truncate%}
+{% set add_comma_after_update = 'update' %}
+{% endif %}
+
+{% if data.pubowner %}
+ALTER PUBLICATION {{ conn|qtIdent(o_data.name) }} OWNER TO {{ data.pubowner }};
+{% endif %}
+
+{% if data.evnt_insert or data.evnt_update or data.evnt_delete %}
+ALTER PUBLICATION {{ conn|qtIdent(o_data.name) }} SET
+(publish = '{% if data.evnt_insert %}insert{% if add_comma_after_insert == 'insert' %}, {% endif %}{% endif %}{% if data.evnt_update %}update{% if add_comma_after_update == 'update' %}, {% endif %}{% endif %}{% if data.evnt_delete %}delete{% if add_comma_after_delete == 'delete' %}, {% endif %}{% endif %}{% if data.evnt_truncate %}truncate{% endif %}');
+{% endif %}
+
+{% if drop_table %}
+ALTER PUBLICATION {{ conn|qtIdent(o_data.name) }} DROP TABLE {% if data.only_table%}ONLY {% endif %}{% for table in drop_table_data %}{% if loop.index != 1 %}, {% endif %}{{ table }}{% endfor %};
+{% endif %}
+
+{% if data.pubtable %}
+ALTER PUBLICATION {{ conn|qtIdent(o_data.name) }} SET TABLE {% if data.only_table%}ONLY {% endif %}{% for table in data.pubtable %}{% if loop.index != 1 %}, {% endif %}{{ table }}{% endfor %};
+{% endif %}
+
+{% if data.name != o_data.name %}
+ALTER PUBLICATION {{ conn|qtIdent(o_data.name) }} RENAME TO {{ conn|qtIdent(data.name) }};
+{% endif %}
+
+
+
+
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/alter_publication.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/alter_publication.sql
new file mode 100644
index 000000000..530ba149b
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/alter_publication.sql
@@ -0,0 +1,7 @@
+-- Publication : alterd_publication;
+-- DROP PUBLICATION alterd_publication;
+
+
+CREATE PUBLICATION alterd_publication
+    FOR ALL TABLES
+WITH (publish = 'insert, update');
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/alter_publication_event.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/alter_publication_event.sql
new file mode 100644
index 000000000..530ba149b
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/alter_publication_event.sql
@@ -0,0 +1,7 @@
+-- Publication : alterd_publication;
+-- DROP PUBLICATION alterd_publication;
+
+
+CREATE PUBLICATION alterd_publication
+    FOR ALL TABLES
+WITH (publish = 'insert, update');
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/alter_publication_event_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/alter_publication_event_msql.sql
new file mode 100644
index 000000000..f01594da2
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/alter_publication_event_msql.sql
@@ -0,0 +1,2 @@
+ALTER PUBLICATION alterd_publication_event SET
+(publish = 'insert, update');
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/alter_publication_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/alter_publication_msql.sql
new file mode 100644
index 000000000..30869d2a8
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/alter_publication_msql.sql
@@ -0,0 +1 @@
+ALTER PUBLICATION test_publication_to_alter RENAME TO alterd_publication;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/create_publication.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/create_publication.sql
new file mode 100644
index 000000000..7c09608ae
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/create_publication.sql
@@ -0,0 +1,7 @@
+-- Publication : test_publication_create;
+-- DROP PUBLICATION test_publication_create;
+
+
+CREATE PUBLICATION test_publication_create
+    FOR ALL TABLES
+WITH (publish = 'insert, update');
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/create_publication_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/create_publication_msql.sql
new file mode 100644
index 000000000..b6d1e6f7c
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/create_publication_msql.sql
@@ -0,0 +1,3 @@
+CREATE PUBLICATION test_publication_create
+    FOR ALL TABLES
+WITH (publish = 'insert, update');
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/create_publication_update.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/create_publication_update.sql
new file mode 100644
index 000000000..2e1f8eedf
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/create_publication_update.sql
@@ -0,0 +1,7 @@
+-- Publication : test_publication_with_update;
+-- DROP PUBLICATION test_publication_with_update;
+
+
+CREATE PUBLICATION test_publication_with_update
+    FOR ALL TABLES
+WITH (publish = 'update');
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/create_publication_update_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/create_publication_update_msql.sql
new file mode 100644
index 000000000..0da18f656
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/create_publication_update_msql.sql
@@ -0,0 +1,3 @@
+CREATE PUBLICATION test_publication_with_update
+    FOR ALL TABLES
+WITH (publish = 'update');
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/test.json b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/test.json
new file mode 100644
index 000000000..99d605783
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/10_plus/test.json
@@ -0,0 +1,79 @@
+{
+  "scenarios": [
+    {
+      "type": "create",
+      "name": "Create Table For publication",
+      "endpoint": "NODE-table.obj",
+      "sql_endpoint": "NODE-table.sql_id",
+      "data": {
+        "name": "test_publication",
+        "columns": [
+          {
+            "name": "emp_id",
+            "cltype": "integer",
+            "is_primary_key": true
+          },
+          {
+            "name": "name",
+            "cltype": "text"
+          },
+          {
+            "name": "salary",
+            "cltype": "bigint"
+          }
+        ],
+        "is_partitioned": false,
+        "schema": "public",
+        "spcname": "pg_default"
+      },
+      "store_object_id": true
+    },
+    {
+      "type": "create",
+      "name": "Create  Publication",
+      "endpoint": "NODE-publication.obj",
+      "sql_endpoint": "NODE-publication.sql_id",
+      "msql_endpoint": "NODE-publication.msql",
+      "data": {
+        "name": "test_publication_create",
+        "evnt_insert": true,
+        "evnt_update": true,
+        "evnt_delete": false,
+        "evnt_truncate": false,
+        "pubowner": "postgres",
+        "all_table": true,
+        "pubtable": ""
+      },
+      "expected_sql_file": "create_publication.sql",
+      "expected_msql_file": "create_publication_msql.sql"
+    },
+    {
+      "type": "alter",
+      "name": "Alter  Publication name",
+      "endpoint": "NODE-publication.obj_id",
+      "sql_endpoint": "NODE-publication.sql_id",
+      "data": {
+        "name": "alterd_publication"
+      },
+      "expected_sql_file": "alter_publication.sql"
+    },
+    {
+      "type": "alter",
+      "name": "Alter  Publication event",
+      "endpoint": "NODE-publication.obj_id",
+      "sql_endpoint": "NODE-publication.sql_id",
+      "data": {
+        "evnt_insert": true
+      },
+      "expected_sql_file": "alter_publication_event.sql"
+    },
+    {
+      "type": "delete",
+      "name": "Drop publication",
+      "endpoint": "NODE-publication.delete_id",
+      "data": {
+        "name": "alterd_publication_event"
+      }
+    }
+  ]
+}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/__init__.py
new file mode 100644
index 000000000..27cd89067
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/__init__.py
@@ -0,0 +1,16 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+from pgadmin.utils.route import BaseTestGenerator
+
+
+class RulesTestGenerator(BaseTestGenerator):
+
+    def runTest(self):
+        return []
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/publication_test_data.json b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/publication_test_data.json
new file mode 100644
index 000000000..da08408db
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/publication_test_data.json
@@ -0,0 +1,397 @@
+{
+  "add_publication": [
+    {
+      "name": "Create publication with insert and update",
+      "url": "/browser/publication/obj/",
+      "is_positive_test": true,
+      "test_data": {
+        "name": "PLACEHOLDER",
+        "evnt_insert": true,
+        "evnt_update": true,
+        "evnt_delete": false,
+        "evnt_truncate": false,
+        "pubowner": "postgres",
+        "all_table": true,
+        "pubtable": ""
+      },
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Create publication for few tables",
+      "url": "/browser/publication/obj/",
+      "is_positive_test": true,
+      "few_tables": true,
+      "test_data": {
+        "name": "PLACEHOLDER",
+        "evnt_insert": true,
+        "evnt_update": true,
+        "evnt_delete": false,
+        "evnt_truncate": false,
+        "pubowner": "postgres",
+        "all_table": false,
+        "pubtable": "PLACE_HOLDER"
+      },
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Create a publication without name",
+      "url": "/browser/publication/obj/",
+      "is_positive_test": false,
+      "without_name": true,
+      "test_data": {
+        "name": "PLACEHOLDER",
+        "evnt_insert": true,
+        "evnt_update": true,
+        "evnt_delete": false,
+        "evnt_truncate": false,
+        "pubowner": "postgres",
+        "all_table": true,
+        "pubtable": ""
+      },
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 410
+      }
+    },
+    {
+      "name": "Error while adding a publication",
+      "url": "/browser/publication/obj/",
+      "is_positive_test": false,
+      "error_creating_publication": true,
+      "test_data": {
+        "name": "PLACEHOLDER",
+        "evnt_insert": true,
+        "evnt_update": true,
+        "evnt_delete": false,
+        "evnt_truncate": false,
+        "pubowner": "postgres",
+        "all_table": true,
+        "pubtable": ""
+      },
+      "mocking_required": true,
+      "mock_data": {
+        "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict",
+        "return_value": "(False, 'Mocked Internal Server Error ')"
+      },
+      "expected_data": {
+        "status_code": 500
+      }
+    },
+    {
+      "name": "Exception while adding a publication",
+      "url": "/browser/publication/obj/",
+      "is_positive_test": false,
+      "test_data": {
+        "name": "PLACEHOLDER",
+        "evnt_insert": true,
+        "evnt_update": true,
+        "evnt_delete": false,
+        "evnt_truncate": false,
+        "pubowner": "postgres",
+        "all_table": true,
+        "pubtable": ""
+      },
+      "mocking_required": true,
+      "mock_data": {
+        "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict",
+        "return_value": "(True, True)(False, 'Mocked Internal Server Error ')"
+      },
+      "expected_data": {
+        "status_code": 500
+      }
+    }
+  ],
+  "get_publication": [
+    {
+      "name": "Get a publication URL",
+      "url": "/browser/publication/obj/",
+      "is_positive_test": true,
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Error while fetching a publication properties",
+      "url": "/browser/publication/obj/",
+      "is_positive_test": false,
+      "mocking_required": true,
+      "mock_data": {
+        "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict",
+        "return_value": "(False, 'Mocked Internal Server Error')"
+      },
+      "expected_data": {
+        "status_code": 500
+      }
+    },
+    {
+      "name": "Get a publication properties under database nodes",
+      "url": "/browser/publication/obj/",
+      "is_positive_test": true,
+      "database_nodes": true,
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Error while fetching a publication properties under database nodes",
+      "url": "/browser/publication/obj/",
+      "is_positive_test": false,
+      "database_nodes": true,
+      "mocking_required": true,
+      "mock_data": {
+        "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict",
+        "return_value": "(False, 'Mocked Internal Server Error')"
+      },
+      "expected_data": {
+        "status_code": 500
+      }
+    },
+    {
+      "name": "Get a publication Node",
+      "url": "/browser/publication/nodes/",
+      "is_positive_test": true,
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Get a publication Node dependants",
+      "url": "/browser/publication/dependent/",
+      "is_positive_test": true,
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Get a publication Node dependency",
+      "url": "/browser/publication/dependency/",
+      "is_positive_test": true,
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Error while fetching the publication under the database nodes using wrong database id",
+      "url": "/browser/publication/nodes/",
+      "is_positive_test": false,
+      "mocking_required": true,
+      "mock_data": {
+        "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_2darray",
+        "return_value": "(False, 'Mocked Internal Server Error')"
+      },
+      "expected_data": {
+        "status_code": 500
+      }
+    },
+    {
+      "name": "Get all the publication under the database nodes",
+      "url": "/browser/publication/nodes/",
+      "is_positive_test": true,
+      "mocking_required": false,
+      "database_nodes": true,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Get all the publication under the database nodes using wrong database id",
+      "url": "/browser/publication/nodes/",
+      "is_positive_test": true,
+      "incorrect_database_id": true,
+      "database_nodes": true,
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Error while fetching all the publication under the database nodes using wrong database id",
+      "url": "/browser/publication/nodes/",
+      "is_positive_test": false,
+      "database_nodes": true,
+      "mocking_required": true,
+      "mock_data": {
+        "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_2darray",
+        "return_value": "(False, 'Mocked Internal Server Error')"
+      },
+      "expected_data": {
+        "status_code": 500
+      }
+    }
+  ],
+  "delete_publication": [
+    {
+      "name": "Delete a publication URL",
+      "url": "/browser/publication/obj/",
+      "is_positive_test": true,
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Error while deleting the publication",
+      "url": "/browser/publication/obj/",
+      "is_positive_test": false,
+      "mocking_required": true,
+      "mock_data": {
+        "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_scalar",
+        "return_value": "(False, 'Mocked Internal Server Error')"
+      },
+      "expected_data": {
+        "status_code": 500
+      }
+    }
+  ],
+  "update_publication": [
+    {
+      "name": "update a publication name",
+      "url": "/browser/publication/obj/",
+      "is_positive_test": true,
+      "update_name": true,
+      "test_data": {
+        "name": "PLACE_HOLDER",
+        "id": "PLACE_HOLDER"
+      },
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "update a publication event insert",
+      "url": "/browser/publication/obj/",
+      "is_positive_test": true,
+      "owner_publication": true,
+      "test_data": {
+        "id": "PLACE_HOLDER",
+        "evnt_insert": "PLACEHOLDER"
+      },
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "update a publication event delete",
+      "url": "/browser/publication/obj/",
+      "is_positive_test": true,
+      "owner_publication": true,
+      "test_data": {
+        "id": "PLACE_HOLDER",
+        "evnt_delete": "PLACEHOLDER"
+      },
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Error while fetching a publication to update",
+      "url": "/browser/publication/obj/",
+      "is_positive_test": false,
+      "mocking_required": true,
+      "test_data": {
+        "name": "PLACE_HOLDER",
+        "id": "PLACE_HOLDER"
+      },
+      "mock_data": {
+        "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict",
+        "return_value": "(False, 'Mocked Internal Server Error')"
+      },
+      "expected_data": {
+        "status_code": 500
+      }
+    },
+    {
+      "name": "Error while fetching a publication to update using wrong publication id",
+      "url": "/browser/publication/obj/",
+      "is_positive_test": true,
+      "wrong_publication_id": true,
+      "mocking_required": false,
+      "test_data": {
+        "id": "PLACE_HOLDER"
+      },
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 500
+      }
+    }
+  ],
+  "delete_multiple_publication": [
+    {
+      "name": "Delete multiple publication",
+      "url": "/browser/publication/obj/",
+      "is_positive_test": true,
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    }
+  ],
+  "sql_publication": [
+    {
+      "name": "Fetch the publication SQL",
+      "url": "/browser/publication/sql/",
+      "is_positive_test": true,
+      "mocking_required": false,
+      "mock_data": {
+      },
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Error while fetching a publication SQL",
+      "url": "/browser/publication/sql/",
+      "is_positive_test": false,
+      "mocking_required": true,
+      "mock_data": {
+        "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict",
+        "return_value": "(False, 'Mocked Internal Server Error')"
+      },
+      "expected_data": {
+        "status_code": 500
+      }
+    },
+    {
+      "name": "Fetching a publication mSQL",
+      "url": "/browser/publication/msql/",
+      "is_positive_test": true,
+      "mocking_required": false,
+      "mock_data": {
+        "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict",
+        "return_value": "(False, 'Mocked Internal Server Error')"
+      },
+      "expected_data": {
+        "status_code": 200
+      }
+    }
+  ]
+}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_create.py b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_create.py
new file mode 100644
index 000000000..aad3b33d2
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_create.py
@@ -0,0 +1,95 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import json
+import uuid
+from unittest.mock import patch
+
+from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \
+    import utils as tables_utils
+from pgadmin.browser.server_groups.servers.databases.tests import utils as \
+    database_utils
+from pgadmin.utils.route import BaseTestGenerator
+from regression import parent_node_dict
+from regression.python_test_utils import test_utils as utils
+from . import utils as publication_utils
+
+
+class PublicationsAddTestCase(BaseTestGenerator):
+    """This class will add new publication"""
+    scenarios = utils.generate_scenarios('add_publication',
+                                         publication_utils.test_cases)
+
+    def setUp(self):
+        self.db_name = parent_node_dict["database"][-1]["db_name"]
+        schema_info = parent_node_dict["schema"][-1]
+        self.server_id = schema_info["server_id"]
+        self.db_id = schema_info["db_id"]
+        self.schema_name = schema_info["schema_name"]
+
+        self.server_version = schema_info["server_version"]
+        if self.server_version < 99999:
+            self.skipTest(
+                "Logical replication is not supported "
+                "for server version less than 10"
+
+            )
+
+        db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
+                                                 self.server_id, self.db_id)
+        if not db_con['data']["connected"]:
+            raise Exception(
+                "Could not connect to database to add a publication.")
+
+        if self.is_positive_test and hasattr(self, 'few_tables'):
+            self.table_name = "table_column_%s" % (str(uuid.uuid4())[1:8])
+            self.table_id = tables_utils. \
+                create_table(self.server, self.db_name, self.schema_name,
+                             self.table_name)
+
+            self.test_data['pubtable'] = publication_utils.get_tables(self)
+
+    def runTest(self):
+        """This function will publication."""
+        self.test_data['name'] = \
+            "test_publication_add_%s" % (str(uuid.uuid4())[1:8])
+
+        data = self.test_data
+        if self.is_positive_test:
+            response = self.create_publication()
+        else:
+            if hasattr(self, 'without_name'):
+                del data["name"]
+                response = self.create_publication()
+            elif hasattr(self, 'error_creating_publication'):
+                with patch(self.mock_data["function_name"],
+                           return_value=eval(self.mock_data["return_value"])):
+                    response = self.create_publication()
+            else:
+                with patch(self.mock_data["function_name"],
+                           side_effect=self.mock_data["return_value"]):
+                    response = self.create_publication()
+        self.assertEqual(response.status_code,
+                         self.expected_data["status_code"])
+
+    def create_publication(self):
+        return self.tester.post(
+            self.url + str(utils.SERVER_GROUP) + '/' +
+            str(self.server_id) + '/' + str(
+                self.db_id) + '/',
+            data=json.dumps(self.test_data),
+            content_type='html/json')
+
+    def tearDown(self):
+        if not hasattr(self, 'without_name'):
+            publication_utils.delete_publication(self.server, self.db_name,
+                                                 self.test_data['name'])
+
+        # Disconnect the database
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_delete.py b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_delete.py
new file mode 100644
index 000000000..8839bd23f
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_delete.py
@@ -0,0 +1,94 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import uuid
+from unittest.mock import patch
+from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
+    utils as schema_utils
+from pgadmin.browser.server_groups.servers.databases.tests import utils as \
+    database_utils
+from pgadmin.utils.route import BaseTestGenerator
+from regression import parent_node_dict
+from regression.python_test_utils import test_utils as utils
+from . import utils as publication_utils
+
+
+class PublicationDeleteTestCase(BaseTestGenerator):
+    """This class will delete publication."""
+    scenarios = utils.generate_scenarios('delete_publication',
+                                         publication_utils.test_cases)
+
+    def setUp(self):
+        self.db_name = parent_node_dict["database"][-1]["db_name"]
+        schema_info = parent_node_dict["schema"][-1]
+        self.server_id = schema_info["server_id"]
+        self.db_id = schema_info["db_id"]
+        self.server_version = schema_info["server_version"]
+        if self.server_version < 99999:
+            self.skipTest(
+                "Logical replication is not supported "
+                "for server version less than 10"
+
+            )
+        db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
+                                                 self.server_id, self.db_id)
+        if not db_con['data']["connected"]:
+            raise Exception(
+                "Could not connect to database to delete publication.")
+        self.schema_id = schema_info["schema_id"]
+        self.schema_name = schema_info["schema_name"]
+
+        schema_response = schema_utils.verify_schemas(self.server,
+                                                      self.db_name,
+                                                      self.schema_name)
+        if not schema_response:
+            raise Exception("Could not find the schema to delete publication.")
+        self.publication_name = "test_publication_delete_%s" % (
+            str(uuid.uuid4())[1:8])
+
+        self.publication_id = \
+            publication_utils.create_publication(self.server,
+                                                 self.db_name,
+                                                 self.publication_name)
+
+    def delete_publication(self):
+        return self.tester.delete(
+            self.url + str(utils.SERVER_GROUP) + '/' +
+            str(self.server_id) + '/' + str(self.db_id) +
+            '/' + str(self.publication_id),
+            follow_redirects=True)
+
+    def runTest(self):
+        """This function will delete publication"""
+        publication_response = publication_utils. \
+            verify_publication(self.server,
+                               self.db_name,
+                               self.publication_name)
+        if not publication_response:
+            raise Exception("Could not find the publication to delete.")
+
+        if self.is_positive_test:
+            if hasattr(self, "invalid_publication_id"):
+                self.publication_id = 9999
+            response = self.delete_publication()
+        else:
+            with patch(self.mock_data["function_name"],
+                       return_value=eval(self.mock_data["return_value"])):
+                response = self.delete_publication()
+
+        self.assertEqual(response.status_code,
+                         self.expected_data["status_code"])
+
+    def tearDown(self):
+        # Disconnect the database
+        if not self.is_positive_test or hasattr(self,
+                                                'invalid_publication_id'):
+            publication_utils.delete_publication(self.server, self.db_name,
+                                                 self.publication_name)
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_delete_multiple.py b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_delete_multiple.py
new file mode 100644
index 000000000..a96812b24
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_delete_multiple.py
@@ -0,0 +1,103 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import uuid
+import json
+
+from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \
+    import utils as tables_utils
+from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
+    utils as schema_utils
+from pgadmin.browser.server_groups.servers.databases.tests import utils as \
+    database_utils
+from pgadmin.utils.route import BaseTestGenerator
+from regression import parent_node_dict
+from regression.python_test_utils import test_utils as utils
+from . import utils as publication_utils
+
+
+class PublicationDeleteTestCases(BaseTestGenerator):
+    """This class will delete publication."""
+
+    scenarios = utils.generate_scenarios('delete_multiple_publication',
+                                         publication_utils.test_cases)
+
+    def setUp(self):
+        self.db_name = parent_node_dict["database"][-1]["db_name"]
+        schema_info = parent_node_dict["schema"][-1]
+        self.server_id = schema_info["server_id"]
+        self.db_id = schema_info["db_id"]
+        db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
+                                                 self.server_id, self.db_id)
+        if not db_con['data']["connected"]:
+            raise Exception(
+                "Could not connect to database to delete publication.")
+        self.schema_id = schema_info["schema_id"]
+        self.schema_name = schema_info["schema_name"]
+        self.server_version = schema_info["server_version"]
+        if self.server_version < 99999:
+            self.skipTest(
+                "Logical replication is not supported "
+                "for server version less than 10"
+
+            )
+        schema_response = schema_utils.verify_schemas(self.server,
+                                                      self.db_name,
+                                                      self.schema_name)
+        if not schema_response:
+            raise Exception("Could not find the schema to delete publication.")
+        self.table_name = "table_column_%s" % (str(uuid.uuid4())[1:8])
+        self.table_id = tables_utils.create_table(self.server, self.db_name,
+                                                  self.schema_name,
+                                                  self.table_name)
+        self.publication_name = "test_publication_delete_%s" % (
+            str(uuid.uuid4())[1:8])
+        self.publication_name_1 = "test_publication_delete_%s" % (
+            str(uuid.uuid4())[1:8])
+        self.publication_ids = [
+            publication_utils.create_publication(self.server, self.db_name,
+                                                 self.publication_name),
+            publication_utils.create_publication(self.server, self.db_name,
+                                                 self.publication_name_1),
+        ]
+
+    def delete_multiple_publication(self, data):
+        return self.tester.delete(
+            "{0}{1}/{2}/{3}/".format(self.url, utils.SERVER_GROUP,
+                                     self.server_id, self.db_id
+                                     ),
+            follow_redirects=True,
+            data=json.dumps(data),
+            content_type='html/json'
+        )
+
+    def runTest(self):
+        """This function will delete publication."""
+        rule_response = publication_utils. \
+            verify_publication(self.server,
+                               self.db_name,
+                               self.publication_name)
+        if not rule_response:
+            raise Exception("Could not find the publication to delete.")
+
+        rule_response = publication_utils. \
+            verify_publication(self.server,
+                               self.db_name,
+                               self.publication_name_1)
+        if not rule_response:
+            raise Exception("Could not find the publication to delete.")
+
+        data = {'ids': self.publication_ids}
+        response = self.delete_multiple_publication(data)
+        self.assertEqual(response.status_code,
+                         self.expected_data["status_code"])
+
+    def tearDown(self):
+        # Disconnect the database
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_get.py b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_get.py
new file mode 100644
index 000000000..cba8794cf
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_get.py
@@ -0,0 +1,100 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import uuid
+from unittest.mock import patch
+
+from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \
+    import utils as tables_utils
+from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
+    utils as schema_utils
+from pgadmin.browser.server_groups.servers.databases.tests import utils as \
+    database_utils
+from pgadmin.utils.route import BaseTestGenerator
+from regression import parent_node_dict
+from regression.python_test_utils import test_utils as utils
+from . import utils as publication_utils
+
+
+class PublicationGetTestCase(BaseTestGenerator):
+    """This class will fetch the publication under table node."""
+    scenarios = utils.generate_scenarios('get_publication',
+                                         publication_utils.test_cases)
+
+    def setUp(self):
+        self.db_name = parent_node_dict["database"][-1]["db_name"]
+        schema_info = parent_node_dict["schema"][-1]
+        self.server_id = schema_info["server_id"]
+        self.db_id = schema_info["db_id"]
+        self.server_version = schema_info["server_version"]
+        if self.server_version < 99999:
+            self.skipTest(
+                "Logical replication is not supported "
+                "for server version less than 10"
+
+            )
+        db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
+                                                 self.server_id, self.db_id)
+        if not db_con['data']["connected"]:
+            raise Exception(
+                "Could not connect to database to delete publication.")
+        self.schema_id = schema_info["schema_id"]
+        self.schema_name = schema_info["schema_name"]
+        schema_response = schema_utils.verify_schemas(self.server,
+                                                      self.db_name,
+                                                      self.schema_name)
+
+        if not schema_response:
+            raise Exception("Could not find the schema to delete publication.")
+        self.table_name = "table_column_%s" % (str(uuid.uuid4())[1:8])
+        self.table_id = tables_utils.create_table(self.server, self.db_name,
+                                                  self.schema_name,
+                                                  self.table_name)
+        self.publication_name = "test_publication_get_%s" % (
+            str(uuid.uuid4())[1:8])
+        self.publication_id = publication_utils. \
+            create_publication(self.server,
+                               self.db_name,
+                               self.publication_name)
+
+    def get_publication(self):
+        return self.tester.get(
+            self.url + str(utils.SERVER_GROUP) + '/' + str(
+                self.server_id) + '/' +
+            str(self.db_id) + '/' + str(self.publication_id),
+            content_type='html/json')
+
+    def runTest(self):
+        """This function will fetch the publication."""
+
+        if self.is_positive_test:
+            if hasattr(self, "database_nodes"):
+                self.publication_id = ''
+                response = self.get_publication()
+            else:
+                response = self.get_publication()
+        else:
+            if hasattr(self, "database_nodes"):
+                self.publication_id = ''
+                with patch(self.mock_data["function_name"],
+                           return_value=eval(self.mock_data["return_value"])):
+                    response = self.get_publication()
+            else:
+                with patch(self.mock_data["function_name"],
+                           return_value=eval(self.mock_data["return_value"])):
+                    response = self.get_publication()
+
+        self.assertEqual(response.status_code,
+                         self.expected_data["status_code"])
+
+    def tearDown(self):
+        publication_utils.delete_publication(self.server, self.db_name,
+                                             self.publication_name)
+        # Disconnect the database
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put.py b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put.py
new file mode 100644
index 000000000..bc31602ab
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_put.py
@@ -0,0 +1,127 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import json
+import uuid
+from unittest.mock import patch
+
+from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \
+    import utils as tables_utils
+from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
+    utils as schema_utils
+from pgadmin.browser.server_groups.servers.databases.tests import utils as \
+    database_utils
+from pgadmin.utils.route import BaseTestGenerator
+from regression import parent_node_dict
+from regression.python_test_utils import test_utils as utils
+from . import utils as publication_utils
+
+
+class PublicationUpdateTestCase(BaseTestGenerator):
+    """This class will update the publication."""
+    scenarios = utils.generate_scenarios('update_publication',
+                                         publication_utils.test_cases)
+
+    def setUp(self):
+        self.db_name = parent_node_dict["database"][-1]["db_name"]
+        schema_info = parent_node_dict["schema"][-1]
+        self.server_id = schema_info["server_id"]
+        self.db_id = schema_info["db_id"]
+        self.server_version = schema_info["server_version"]
+        if self.server_version < 99999:
+            self.skipTest(
+                "Logical replication is not supported "
+                "for server version less than 10"
+
+            )
+        db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
+                                                 self.server_id, self.db_id)
+        if not db_con['data']["connected"]:
+            raise Exception(
+                "Could not connect to database to delete publication.")
+        self.schema_id = schema_info["schema_id"]
+        self.schema_name = schema_info["schema_name"]
+        schema_response = schema_utils.verify_schemas(self.server,
+                                                      self.db_name,
+                                                      self.schema_name)
+        if not schema_response:
+            raise Exception("Could not find the schema to delete publication.")
+        self.table_name = "table_column_%s" % (str(uuid.uuid4())[1:8])
+
+        self.server_version = schema_info["server_version"]
+        if self.server_version < 99999:
+            self.skipTest(
+                "Logical replication is not supported "
+                "for server version less than 10"
+
+            )
+        self.table_id = tables_utils.create_table(self.server, self.db_name,
+                                                  self.schema_name,
+                                                  self.table_name)
+        self.publication_name = "test_publication_update_%s" % (
+            str(uuid.uuid4())[1:8])
+        self.publication_id = \
+            publication_utils.create_publication(self.server, self.db_name,
+                                                 self.publication_name)
+
+    def update_publication(self, data):
+        return self.tester.put(
+            self.url + str(utils.SERVER_GROUP) + '/' +
+            str(self.server_id) + '/' + str(
+                self.db_id) +
+            '/' + str(self.publication_id),
+            data=json.dumps(data),
+            follow_redirects=True)
+
+    def runTest(self):
+        """This function will update the publication."""
+
+        publication_name = publication_utils. \
+            verify_publication(self.server,
+                               self.db_name,
+                               self.publication_name)
+        if hasattr(self, "update_name"):
+            self.test_data['name'] = "test_publication_update_%s" % (
+                str(uuid.uuid4())[1:8])
+        else:
+            self.test_data['name'] = self.publication_name
+        self.test_data['id'] = self.publication_id
+
+        if not publication_name:
+            raise Exception("Could not find the publication to update.")
+
+        if self.is_positive_test:
+            if hasattr(self, "wrong_publication_id"):
+                self.publication_id = 9999
+            if hasattr(self, "plid_none"):
+                self.publication_id = ''
+            response = self.update_publication(self.test_data)
+        else:
+            with patch(self.mock_data["function_name"],
+                       return_value=eval(self.mock_data["return_value"])):
+                if hasattr(self, "wrong_publication_id"):
+                    self.publication_id = 9999
+                response = self.update_publication(self.test_data)
+
+        self.assertEqual(response.status_code,
+                         self.expected_data["status_code"])
+
+    def tearDown(self):
+        connection = utils.get_db_connection(self.server['db'],
+                                             self.server['username'],
+                                             self.server['db_password'],
+                                             self.server['host'],
+                                             self.server['port'],
+                                             self.server['sslmode'])
+
+        publication_utils.delete_publication(self.server, self.db_name,
+                                             self.test_data['name'])
+
+        # Disconnect the database
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_sql.py b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_sql.py
new file mode 100644
index 000000000..b1acb7874
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/test_publication_sql.py
@@ -0,0 +1,88 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import uuid
+from unittest.mock import patch
+import json
+from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \
+    import utils as tables_utils
+from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
+    utils as schema_utils
+from pgadmin.browser.server_groups.servers.databases.tests import utils as \
+    database_utils
+from pgadmin.utils.route import BaseTestGenerator
+from regression import parent_node_dict
+from regression.python_test_utils import test_utils as utils
+from . import utils as publication_utils
+
+
+class PublicationGetTestCase(BaseTestGenerator):
+    """This class will fetch the publication under table node."""
+    scenarios = utils.generate_scenarios('sql_publication',
+                                         publication_utils.test_cases)
+
+    def setUp(self):
+        self.db_name = parent_node_dict["database"][-1]["db_name"]
+        schema_info = parent_node_dict["schema"][-1]
+        self.server_id = schema_info["server_id"]
+        self.db_id = schema_info["db_id"]
+        self.server_version = schema_info["server_version"]
+        if self.server_version < 99999:
+            self.skipTest(
+                "Logical replication is not supported "
+                "for server version less than 10"
+
+            )
+        db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
+                                                 self.server_id, self.db_id)
+        if not db_con['data']["connected"]:
+            raise Exception(
+                "Could not connect to database to delete publication.")
+        self.schema_id = schema_info["schema_id"]
+        self.schema_name = schema_info["schema_name"]
+        schema_response = schema_utils.verify_schemas(self.server,
+                                                      self.db_name,
+                                                      self.schema_name)
+        if not schema_response:
+            raise Exception("Could not find the schema to delete publication.")
+        self.table_name = "table_column_%s" % (str(uuid.uuid4())[1:8])
+        self.table_id = tables_utils.create_table(self.server, self.db_name,
+                                                  self.schema_name,
+                                                  self.table_name)
+        self.publication_name = "test_publication_delete_%s" % (
+            str(uuid.uuid4())[1:8])
+        self.publication_id = \
+            publication_utils.create_publication(self.server, self.db_name,
+                                                 self.publication_name)
+
+    def get_sql(self):
+        return self.tester.get(
+            self.url + str(utils.SERVER_GROUP) + '/' + str(
+                self.server_id) + '/' +
+            str(self.db_id) + '/' + str(self.publication_id),
+            content_type='html/json')
+
+    def runTest(self):
+        """This function will fetch the publication under table node."""
+
+        if self.is_positive_test:
+            response = self.get_sql()
+        else:
+            with patch(self.mock_data["function_name"],
+                       return_value=eval(self.mock_data["return_value"])):
+                response = self.get_sql()
+
+        self.assertEqual(response.status_code,
+                         self.expected_data["status_code"])
+
+    def tearDown(self):
+        publication_utils.delete_publication(self.server, self.db_name,
+                                             self.publication_name)
+        # Disconnect the database
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/publications/tests/utils.py b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/utils.py
new file mode 100644
index 000000000..be399a4c6
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/publications/tests/utils.py
@@ -0,0 +1,152 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+
+import sys
+import os
+import json
+import traceback
+
+from regression.python_test_utils import test_utils as utils
+
+CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
+with open(CURRENT_PATH + "/publication_test_data.json") as data_file:
+    test_cases = json.load(data_file)
+
+
+def get_tables(self):
+    tables = self.tester.get(
+        '/browser/publication/get_tables/' + str(
+            utils.SERVER_GROUP) + '/' + str(
+            self.server_id) + '/' +
+        str(self.db_id) + '/',
+        content_type='html/json')
+    return json.dumps([tables.json['data'][1]['value']])
+
+
+def create_publication_api(self):
+    return self.tester.post(
+        self.url + str(utils.SERVER_GROUP) + '/' +
+        str(self.server_id) + '/' + str(
+            self.db_id) + '/',
+        data=json.dumps(self.test_data),
+        content_type='html/json')
+
+
+def create_publication(server, db_name, publication_name):
+    """
+    This function creates a publication under provided table.
+    :param server: server details
+    :type server: dict
+    :param db_name: database name
+    :type db_name: str
+    :param publication_name: publication name
+    :type publication_name: str
+    :return publication_id: publication id
+    :rtype: int
+    """
+    try:
+        connection = utils.get_db_connection(db_name,
+                                             server['username'],
+                                             server['db_password'],
+                                             server['host'],
+                                             server['port'],
+                                             server['sslmode'])
+        old_isolation_level = connection.isolation_level
+        connection.set_isolation_level(0)
+        pg_cursor = connection.cursor()
+        query = "CREATE publication %s FOR ALL TABLES" % \
+                (publication_name)
+        pg_cursor.execute(query)
+        connection.set_isolation_level(old_isolation_level)
+        connection.commit()
+        # Get role oid of newly added publication
+        pg_cursor.execute("select oid from pg_publication pub where "
+                          "pub.pubname='%s'" %
+                          publication_name)
+        publication = pg_cursor.fetchone()
+        publication_id = ''
+        if publication:
+            publication_id = publication[0]
+        connection.close()
+        return publication_id
+    except Exception:
+        traceback.print_exc(file=sys.stderr)
+        raise
+
+
+def verify_publication(server, db_name, publication_name):
+    """
+    This function verifies publication exist in database or not.
+    :param server: server details
+    :type server: dict
+    :param db_name: database name
+    :type db_name: str
+    :param publication_name: publication name
+    :type publication_name: str
+    :return publication: publication record from database
+    :rtype: tuple
+    """
+    try:
+        connection = utils.get_db_connection(db_name,
+                                             server['username'],
+                                             server['db_password'],
+                                             server['host'],
+                                             server['port'],
+                                             server['sslmode'])
+        pg_cursor = connection.cursor()
+        pg_cursor.execute("select * from pg_publication pub "
+                          "where pub.pubname='%s'" %
+                          publication_name)
+        publication = pg_cursor.fetchone()
+        connection.close()
+        return publication
+    except Exception:
+        traceback.print_exc(file=sys.stderr)
+        raise
+
+
+def delete_publication(server, db_name, publication_name):
+    """
+    This function use to delete the existing roles in the servers
+
+    :param db_name:  db_name
+    :type db_name: db_name object
+    :param server: server
+    :type server: server object
+    :param publication_name: publication name
+    :type publication_name: str
+    :return: None
+    """
+
+    try:
+        connection = utils.get_db_connection(db_name,
+                                             server['username'],
+                                             server['db_password'],
+                                             server['host'],
+                                             server['port'],
+                                             server['sslmode'])
+        pg_cursor = connection.cursor()
+
+        pg_cursor.execute("select * from pg_publication pub where "
+                          "pub.pubname='%s'" %
+                          publication_name)
+        publication_count = pg_cursor.fetchone()
+        if publication_count:
+            old_isolation_level = connection.isolation_level
+            connection.set_isolation_level(0)
+            pg_cursor = connection.cursor()
+            query = "DROP publication %s" % publication_name
+            pg_cursor.execute(query)
+            connection.set_isolation_level(old_isolation_level)
+            connection.commit()
+        connection.close()
+    except Exception:
+        traceback.print_exc(file=sys.stderr)
+        raise
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js
index ffeb69f66..b4a23c4ee 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/static/js/table.js
@@ -517,6 +517,11 @@ define('pgadmin.node.table', [
             return true;
           },
         },
+        {
+          id: 'replica_identity', label: gettext('Replica Identity'),
+          group: gettext('advanced'), type: 'text',mode: ['edit', 'properties'],
+        },
+
         {
           id: 'advanced', label: gettext('Advanced'), type: 'group',
           visible: ShowAdvancedTab.show_advanced_tab,
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/10_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/10_plus/properties.sql
index d52ec2a63..cb4d47c94 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/10_plus/properties.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/10_plus/properties.sql
@@ -4,6 +4,12 @@ SELECT rel.oid, rel.relname AS name, rel.reltablespace AS spcoid,rel.relacl AS r
     JOIN pg_tablespace sp ON dtb.dattablespace=sp.oid
     WHERE dtb.oid = {{ did }}::oid)
   END) as spcname,
+  (CASE rel.relreplident
+          WHEN 'd' THEN 'default'
+          WHEN 'n' THEN 'nothing'
+          WHEN 'f' THEN 'full'
+          WHEN 'i' THEN 'index'
+  END) as replica_identity,
   (select nspname FROM pg_namespace WHERE oid = {{scid}}::oid ) as schema,
   pg_get_userbyid(rel.relowner) AS relowner, rel.relhasoids, rel.relkind,
   (CASE WHEN rel.relkind = 'p' THEN true ELSE false END) AS is_partitioned,
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/11_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/11_plus/properties.sql
index 82f9a4358..fea316289 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/11_plus/properties.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/11_plus/properties.sql
@@ -4,6 +4,12 @@ SELECT rel.oid, rel.relname AS name, rel.reltablespace AS spcoid,rel.relacl AS r
     JOIN pg_tablespace sp ON dtb.dattablespace=sp.oid
     WHERE dtb.oid = {{ did }}::oid)
   END) as spcname,
+  (CASE rel.relreplident
+          WHEN 'd' THEN 'default'
+          WHEN 'n' THEN 'nothing'
+          WHEN 'f' THEN 'full'
+          WHEN 'i' THEN 'index'
+  END) as replica_identity,
   (select nspname FROM pg_namespace WHERE oid = {{scid}}::oid ) as schema,
   pg_get_userbyid(rel.relowner) AS relowner, rel.relhasoids, rel.relkind,
   (CASE WHEN rel.relkind = 'p' THEN true ELSE false END) AS is_partitioned,
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/11_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/11_plus/update.sql
index 48ec03494..35d769269 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/11_plus/update.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/11_plus/update.sql
@@ -279,3 +279,15 @@ ALTER TABLE {{conn|qtIdent(data.schema, data.name)}}
 {% endif %}
 
 {% endif %}
+{#####################################################}
+{## Change replica identity ##}
+{#####################################################}
+{% if data.replica_identity and data.replica_identity != o_data.replica_identity %}
+ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} REPLICA IDENTITY {{data.replica_identity }};
+{% endif %}
+{#####################################################}
+{## Change replica identity ##}
+{#####################################################}
+{% if data.replica_identity and data.replica_identity != o_data.replica_identity %}
+ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} REPLICA IDENTITY {{data.replica_identity }};
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/12_plus/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/12_plus/properties.sql
index 5a2dbb4a8..2c725b6bf 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/12_plus/properties.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/12_plus/properties.sql
@@ -4,6 +4,12 @@ SELECT rel.oid, rel.relname AS name, rel.reltablespace AS spcoid,rel.relacl AS r
     JOIN pg_tablespace sp ON dtb.dattablespace=sp.oid
     WHERE dtb.oid = {{ did }}::oid)
   END) as spcname,
+  (CASE rel.relreplident
+          WHEN 'd' THEN 'default'
+          WHEN 'n' THEN 'nothing'
+          WHEN 'f' THEN 'full'
+          WHEN 'i' THEN 'index'
+  END) as replica_identity,
   (select nspname FROM pg_namespace WHERE oid = {{scid}}::oid ) as schema,
   pg_get_userbyid(rel.relowner) AS relowner, rel.relkind,
   (CASE WHEN rel.relkind = 'p' THEN true ELSE false END) AS is_partitioned,
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/12_plus/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/12_plus/update.sql
index 25e1b6295..c2e61535f 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/12_plus/update.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/12_plus/update.sql
@@ -272,3 +272,15 @@ COMMENT ON TABLE {{conn|qtIdent(data.schema, data.name)}}
 {% endif %}
 
 {% endif %}
+{#####################################################}
+{## Change replica identity ##}
+{#####################################################}
+{% if data.replica_identity and data.replica_identity != o_data.replica_identity %}
+ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} REPLICA IDENTITY {{data.replica_identity }};
+{% endif %}
+{#####################################################}
+{## Change replica identity ##}
+{#####################################################}
+{% if data.replica_identity and data.replica_identity != o_data.replica_identity %}
+ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} REPLICA IDENTITY {{data.replica_identity }};
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/default/update.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/default/update.sql
index 4d84babce..ef0ec4e23 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/default/update.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/tables/sql/default/update.sql
@@ -256,3 +256,17 @@ COMMENT ON TABLE {{conn|qtIdent(data.schema, data.name)}}
 {% endif %}
 
 {% endif %}
+
+{#####################################################}
+{## Change replica identity ##}
+{#####################################################}
+{% if data.replica_identity and data.replica_identity != o_data.replica_identity %}
+ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} REPLICA IDENTITY {{data.replica_identity }};
+{% endif %}
+
+{#####################################################}
+{## Change replica identity ##}
+{#####################################################}
+{% if data.replica_identity and data.replica_identity != o_data.replica_identity %}
+ALTER TABLE {{conn|qtIdent(data.schema, data.name)}} REPLICA IDENTITY {{data.replica_identity }};
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js b/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js
index 6a97dd5ba..76b1a09d8 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js
@@ -95,6 +95,18 @@ define('pgadmin.node.database', [
 
         return server.connected && server.user.can_create_db;
       },
+      canCreate: function(itemData, item) {
+        var treeData = this.getTreeNodeHierarchy(item),
+          server = treeData['server'];
+
+        // If server is less than 10 then do not allow 'create' menu
+        if (server && server.version < 100000)
+          return false;
+
+        // by default we want to allow create menu
+        return true;
+      },
+
       is_not_connected: function(node) {
         return (node && node.connected != true && node.allowConn == true);
       },
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/__init__.py
new file mode 100644
index 000000000..f5dfd9cd6
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/__init__.py
@@ -0,0 +1,791 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""Implements Subscription Node"""
+
+import simplejson as json
+from functools import wraps
+
+import pgadmin.browser.server_groups.servers.databases as databases
+from flask import render_template, request, jsonify
+from flask_babelex import gettext
+from pgadmin.browser.collection import CollectionNodeModule
+from pgadmin.browser.utils import PGChildNodeView
+from pgadmin.utils.ajax import make_json_response, internal_server_error, \
+    make_response as ajax_response, gone
+from pgadmin.utils.driver import get_driver
+from config import PG_DEFAULT_DRIVER
+from pgadmin.tools.schema_diff.node_registry import SchemaDiffRegistry
+from pgadmin.tools.schema_diff.compare import SchemaDiffObjectCompare
+import psycopg2
+from pgadmin.utils import get_complete_file_path
+
+
+class SubscriptionModule(CollectionNodeModule):
+    """
+    class SubscriptionModule(CollectionNodeModule)
+
+        A module class for Subscription node derived from CollectionNodeModule.
+
+    Methods:
+    -------
+    * __init__(*args, **kwargs)
+      - Method is used to initialize the SubscriptionModule and it's
+      base module.
+
+    * get_nodes(gid, sid, did)
+      - Method is used to generate the browser collection node.
+
+    * node_inode()
+      - Method is overridden from its base class to make the node as leaf node.
+
+    * script_load()
+      - Load the module script for subscription, when any of the database node
+      is initialized.
+    """
+
+    _NODE_TYPE = 'subscription'
+    _COLLECTION_LABEL = gettext("Subscriptions")
+
+    def __init__(self, *args, **kwargs):
+        """
+        Method is used to initialize the SubscriptionModule and it's
+        base module.
+
+        Args:
+            *args:
+            **kwargs:
+        """
+        super(SubscriptionModule, self).__init__(*args, **kwargs)
+        self.min_ver = 100000
+        self.max_ver = None
+
+    def get_nodes(self, gid, sid, did):
+        """
+        Method is used to generate the browser collection node
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database Id
+        """
+        yield self.generate_browser_collection_node(did)
+
+    @property
+    def node_inode(self):
+        """
+        Override this property to make the node a leaf node.
+
+        Returns: False as this is the leaf node
+        """
+        return False
+
+    @property
+    def script_load(self):
+        """
+        Load the module script for subscription, when any of the database nodes
+        are initialized.
+
+        Returns: node type of the server module.
+        """
+        return databases.DatabaseModule.node_type
+
+    @property
+    def module_use_template_javascript(self):
+        """
+        Returns whether Jinja2 template is used for generating the javascript
+        module.
+        """
+        return False
+
+
+blueprint = SubscriptionModule(__name__)
+
+
+class SubscriptionView(PGChildNodeView, SchemaDiffObjectCompare):
+    """
+    class SubscriptionView(PGChildNodeView)
+
+        A view class for Subscription node derived from PGChildNodeView.
+        This class is responsible for all the stuff related to view like
+        updating subscription node, showing properties, showing sql
+        in sql pane.
+
+    Methods:
+    -------
+    * __init__(**kwargs)
+      - Method is used to initialize the SubscriptionView and it's base view.
+
+    * check_precondition()
+      - This function will behave as a decorator which will checks
+        database connection before running view, it will also attaches
+        manager,conn & template_path properties to self
+
+    * list()
+      - This function is used to list all the subscription nodes within that
+      collection.
+
+    * nodes()
+      - This function will used to create all the child node within that
+      collection. Here it will create all the subscription node.
+
+    * properties(gid, sid, did, subid)
+      - This function will show the properties of the selected
+      subscription node
+
+    * update(gid, sid, did, subid)
+      - This function will update the data for the selected subscription node
+
+    * create(gid, sid, did)
+      - This function will create the new subscription node
+
+    * delete(gid, sid, did, subid)
+      - This function will delete the selected subscription node
+
+    * msql(gid, sid, did, subid)
+      - This function is used to return modified SQL for the selected
+      subscription node
+
+    * get_sql(data, subid)
+      - This function will generate sql from model data
+
+    * get_publications(gid, sid, did)
+      - This function returns the publications list
+
+    * get_templates(gid, sid, did)
+      - This function returns subscription templates.
+
+    * sql(gid, sid, did, subid):
+      - This function will generate sql to show it in sql pane for the
+      selected subscription node.
+
+    * dependents(gid, sid, did, subid):
+      - This function get the dependents and return ajax response for the
+      subscription node.
+
+    * dependencies(self, gid, sid, did, subid):
+      - This function get the dependencies and return ajax response for the
+      subscription node.
+    """
+
+    _NOT_FOUND_PUB_INFORMATION = \
+        gettext("Could not find the subscription information.")
+    node_type = blueprint.node_type
+
+    parent_ids = [
+        {'type': 'int', 'id': 'gid'},
+        {'type': 'int', 'id': 'sid'},
+        {'type': 'int', 'id': 'did'}
+    ]
+    ids = [
+        {'type': 'int', 'id': 'subid'}
+    ]
+
+    operations = dict({
+        'obj': [
+            {'get': 'properties', 'delete': 'delete', 'put': 'update'},
+            {'get': 'list', 'post': 'create', 'delete': 'delete'}
+        ],
+        'nodes': [{'get': 'node'}, {'get': 'nodes'}],
+        'sql': [{'get': 'sql'}],
+        'msql': [{'get': 'msql'}, {'get': 'msql'}],
+        'stats': [{'get': 'statistics'}],
+        'dependency': [{'get': 'dependencies'}],
+        'dependent': [{'get': 'dependents'}],
+        'get_publications': [{}, {'get': 'get_publications'}],
+        'get_templates': [{}, {'get': 'get_templates'}],
+        'delete': [{'delete': 'delete'}, {'delete': 'delete'}]
+    })
+
+    def _init_(self, **kwargs):
+        """
+        Method is used to initialize the SubscriptionView and its base view.
+        Initialize all the variables create/used dynamically like conn,
+        template_path.
+
+        Args:
+            **kwargs:
+        """
+        self.conn = None
+        self.template_path = None
+        self.manager = None
+
+        super(SubscriptionView, self).__init__(**kwargs)
+
+    def check_precondition(f):
+        """
+        This function will behave as a decorator which will check the
+        database connection before running the view. It also attaches
+        manager, conn & template_path properties to self
+        """
+
+        @wraps(f)
+        def wrap(*args, **kwargs):
+            # Here args[0] will hold self & kwargs will hold gid,sid,did
+            self = args[0]
+            self.driver = get_driver(PG_DEFAULT_DRIVER)
+            self.manager = self.driver.connection_manager(kwargs['sid'])
+            self.conn = self.manager.connection(did=kwargs['did'])
+            self.datlastsysoid = self.manager.db_info[kwargs['did']][
+                'datlastsysoid'] if self.manager.db_info is not None \
+                and kwargs['did'] in self.manager.db_info else 0
+
+            # Set the template path for the SQL scripts
+            self.template_path = (
+                "subscriptions/sql/#gpdb#{0}#".format(self.manager.version) if
+                self.manager.server_type == 'gpdb' else
+                "subscriptions/sql/#{0}#".format(self.manager.version)
+            )
+
+            return f(*args, **kwargs)
+
+        return wrap
+
+    @check_precondition
+    def list(self, gid, sid, did):
+        """
+        This function is used to list all the subscription nodes within that
+        collection.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+        """
+        sql = render_template("/".join([self.template_path,
+                                        self._PROPERTIES_SQL]), did=did)
+        status, res = self.conn.execute_dict(sql)
+
+        if not status:
+            return internal_server_error(errormsg=res)
+        return ajax_response(
+            response=res['rows'],
+            status=200
+        )
+
+    @check_precondition
+    def nodes(self, gid, sid, did):
+        """
+        This function is used to create all the child nodes within the
+        collection. Here it will create all the subscription nodes.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+        """
+        res = []
+        sql = render_template("/".join([self.template_path,
+                                        'nodes.sql']), did=did)
+        status, result = self.conn.execute_2darray(sql)
+        if not status:
+            return internal_server_error(errormsg=result)
+
+        for row in result['rows']:
+            res.append(
+                self.blueprint.generate_browser_node(
+                    row['oid'],
+                    did,
+                    row['name'],
+                    icon="icon-subscription"
+                ))
+
+        return make_json_response(
+            data=res,
+            status=200
+        )
+
+    @check_precondition
+    def node(self, gid, sid, did, subid):
+        """
+        This function will fetch properties of the subscription nodes.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            subid: Subscription ID
+        """
+        sql = render_template("/".join([self.template_path,
+                                        self._PROPERTIES_SQL]),
+                              subid=subid)
+        status, result = self.conn.execute_2darray(sql)
+        if not status:
+            return internal_server_error(errormsg=result)
+
+        for row in result['rows']:
+            return make_json_response(
+                data=self.blueprint.generate_browser_node(
+                    row['oid'],
+                    did,
+                    row['name'],
+                    icon="icon-subscription"
+                ),
+                status=200
+            )
+
+        return gone(gettext("Could not find the specified subscription."))
+
+    @check_precondition
+    def properties(self, gid, sid, did, subid):
+        """
+        This function will show the properties of the selected subscription
+        node.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            subid: Subscription ID
+        """
+        status, res = self._fetch_properties(did, subid)
+
+        if not status:
+            return res
+
+        return ajax_response(
+            response=res,
+            status=200
+        )
+
+    def _fetch_properties(self, did, subid):
+        """
+        This function fetch the properties of the extension.
+        :param did:
+        :param subid:
+        :return:
+        """
+        sql = render_template(
+            "/".join([self.template_path, self._PROPERTIES_SQL]),
+            subid=subid, did=did,
+        )
+        status, res = self.conn.execute_dict(sql)
+
+        if not status:
+            return False, internal_server_error(errormsg=res)
+
+        if len(res['rows']) == 0:
+            return False, gone(self._NOT_FOUND_PUB_INFORMATION)
+
+        return True, res['rows'][0]
+
+    @check_precondition
+    def update(self, gid, sid, did, subid):
+        """
+        This function will update the data for the selected subscription node.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            subid: Subscription ID
+        """
+        data = request.form if request.form else json.loads(
+            request.data, encoding='utf-8'
+        )
+
+        try:
+            if 'pub' in data:
+                data['pub'] = json.loads(
+                    data['pub'], encoding='utf-8'
+                )
+            sql, name = self.get_sql(data, subid)
+            # Most probably this is due to error
+            if not isinstance(sql, str):
+                return sql
+            sql = sql.strip('\n').strip(' ')
+            status, res = self.conn.execute_dict(sql)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            return jsonify(
+                node=self.blueprint.generate_browser_node(
+                    subid,
+                    did,
+                    name,
+                    icon="icon-%s" % self.node_type
+                )
+            )
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def create(self, gid, sid, did):
+        """
+        This function will create the subscription object
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+        """
+        required_args = [
+            'name'
+        ]
+
+        data = request.form if request.form else json.loads(
+            request.data, encoding='utf-8'
+        )
+        for arg in required_args:
+            if arg not in data:
+                return make_json_response(
+                    status=410,
+                    success=0,
+                    errormsg=gettext(
+                        "Could not find the required parameter ({})."
+                    ).format(arg)
+                )
+
+        try:
+            data['pub'] = json.loads(
+                data['pub'], encoding='utf-8'
+            )
+
+            sql = render_template("/".join([self.template_path,
+                                            self._CREATE_SQL]),
+                                  data=data, conn=self.conn)
+            status, res = self.conn.execute_dict(sql)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            sql = render_template(
+                "/".join([self.template_path, 'get_position.sql']),
+                conn=self.conn, subname=data['name']
+            )
+
+            status, r_set = self.conn.execute_dict(sql)
+            if not status:
+                return internal_server_error(errormsg=r_set)
+
+            return jsonify(
+                node=self.blueprint.generate_browser_node(
+                    r_set['rows'][0]['oid'],
+                    did,
+                    r_set['rows'][0]['name'],
+                    icon='icon-subscription'
+                )
+            )
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def delete(self, gid, sid, did, subid=None):
+        """
+        This function will drop the subscription object
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            subid: Subscription ID
+            only_sql:
+        """
+        if subid is None:
+            data = request.form if request.form else json.loads(
+                request.data, encoding='utf-8'
+            )
+        else:
+            data = {'ids': [subid]}
+
+        cascade = self._check_cascade_operation()
+
+        try:
+            for subid in data['ids']:
+                # Get name for subscription from subid
+                sql = render_template(
+                    "/".join([self.template_path, self._DELETE_SQL]),
+                    subid=subid, conn=self.conn
+                )
+                status, subname = self.conn.execute_scalar(sql)
+
+                if not status:
+                    return internal_server_error(errormsg=subname)
+
+                # drop subscription
+                sql = render_template(
+                    "/".join([self.template_path, self._DELETE_SQL]),
+                    subname=subname, cascade=cascade, conn=self.conn
+                )
+
+                status, res = self.conn.execute_scalar(sql)
+                if not status:
+                    return internal_server_error(errormsg=res)
+
+            return make_json_response(
+                success=1,
+                info=gettext("Subscription dropped")
+            )
+
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    @check_precondition
+    def msql(self, gid, sid, did, subid=None):
+        """
+        This function is used to return modified SQL for the selected
+        subscription node.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            subid: Subscription ID
+        """
+        data = {}
+        for k, v in request.args.items():
+            try:
+                # comments should be taken as is because if user enters a
+                # json comment it is parsed by loads which should not happen
+                if k in ('description',):
+                    data[k] = v
+                else:
+                    data[k] = json.loads(v, encoding='utf-8')
+            except ValueError:
+                data[k] = v
+        try:
+            sql, name = self.get_sql(data, subid)
+            # Most probably this is due to error
+            if not isinstance(sql, str):
+                return sql
+            if sql == '':
+                sql = "--modified SQL"
+
+            return make_json_response(
+                data=sql,
+                status=200
+            )
+        except Exception as e:
+            return internal_server_error(errormsg=str(e))
+
+    def get_details(self, data, old_data):
+        """
+        This function returns the required data to create subscription
+        :param data:
+        :return:
+
+        """
+        required_args = ['name']
+
+        required_connection_args = ['host', 'port', 'username', 'db',
+                                    'service', 'passfile']
+        for arg in required_args:
+            if arg not in data and arg in old_data:
+                data[arg] = old_data[arg]
+
+        for arg in required_connection_args:
+            if arg not in data and arg in old_data:
+                data[arg] = old_data[arg]
+
+        return data
+
+    def get_sql(self, data, subid=None):
+        """
+        This function will generate sql from model data.
+
+        Args:
+            data: Contains the data of the selected subscription node.
+            subid: Subscription ID
+        """
+
+        required_args = ['name']
+
+        required_connection_args = ['host', 'port', 'username', 'db',
+                                    'service', 'passfile']
+
+        if subid is not None:
+            sql = render_template(
+                "/".join([self.template_path, self._PROPERTIES_SQL]),
+                subid=subid
+            )
+            status, res = self.conn.execute_dict(sql)
+            if not status:
+                return internal_server_error(errormsg=res)
+
+            if len(res['rows']) == 0:
+                return gone(self._NOT_FOUND_PUB_INFORMATION)
+
+            old_data = res['rows'][0]
+            for arg in required_args:
+                if arg not in data:
+                    data[arg] = old_data[arg]
+
+            for arg in required_connection_args:
+                if arg in data:
+                    old_data[arg] = data[arg]
+
+            sql = render_template(
+                "/".join([self.template_path, self._UPDATE_SQL]),
+                data=data, o_data=old_data, conn=self.conn
+            )
+            return sql.strip('\n'), data['name'] if 'name' in data \
+                else old_data['name']
+        else:
+
+            sql = render_template("/".join([self.template_path,
+                                            self._CREATE_SQL]),
+                                  data=data, conn=self.conn)
+            return sql.strip('\n'), data['name']
+
+    def get_connection(self, connection_details):
+
+        passfile = \
+            connection_details['passfile'] if 'passfile' \
+                                              in connection_details and \
+                                              connection_details[
+                                                  'passfile'] != '' else None
+
+        service = \
+            connection_details['service'] if 'service' in connection_details \
+                                             and connection_details['service']\
+                                             != '' else None
+
+        conn = psycopg2.connect(
+            host=connection_details['host'] if not service else None,
+            database=connection_details['db'],
+            user=connection_details['username'] if not service else None,
+            password=connection_details['password'] if not service else None,
+            port=connection_details['port'] if not service else None,
+            passfile=get_complete_file_path(passfile),
+            service=service
+        )
+        # create a cursor
+        cur = conn.cursor()
+        cur.execute('SELECT pubname from pg_publication')
+
+        publications = cur.fetchall()
+        # Close the connection
+        conn.close()
+
+        return publications
+
+    @check_precondition
+    def get_publications(self, gid, sid, did, *args, **kwargs):
+        """
+        This function returns the publication list
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+        """
+
+        url_params = None
+        if request.args:
+            url_params = {k: v for k, v in request.args.items()}
+
+        if 'oid' in url_params:
+            status, params = self._fetch_properties(did, url_params['oid'])
+            params['password'] = url_params['password']
+            url_params = params
+
+        res = self.get_connection(url_params)
+
+        result = []
+        for pub in res:
+            result.append({
+                "value": pub[0],
+                "label": pub[0]
+            })
+
+        return make_json_response(
+            data=result,
+            status=200
+        )
+
+    @check_precondition
+    def sql(self, gid, sid, did, subid, json_resp=True):
+        """
+        This function will generate sql to show in the sql pane for the
+        selected publication node.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            subid: Publication ID
+            json_resp:
+        """
+        sql = render_template(
+            "/".join([self.template_path, self._PROPERTIES_SQL]),
+            subid=subid
+        )
+        status, res = self.conn.execute_dict(sql)
+        if not status:
+            return internal_server_error(errormsg=res)
+
+        if len(res['rows']) == 0:
+            return gone(self._NOT_FOUND_PUB_INFORMATION)
+
+        # Making copy of output for future use
+        old_data = dict(res['rows'][0])
+        if old_data['slot_name'] is None and 'create_slot' not in old_data:
+            old_data['create_slot'] = False
+
+        sql = render_template("/".join([self.template_path,
+                                        self._CREATE_SQL]),
+                              data=old_data, conn=self.conn)
+        sql += "\n\n"
+
+        sql_header = "-- Subscription : {};".format(old_data['name'])
+        sql_header += "\n"
+
+        sql_header += "-- DROP SUBSCRIPTION {};".format(old_data['name'])
+
+        sql_header += render_template(
+            "/".join([self.template_path, self._DELETE_SQL]),
+            sname=old_data['name'], )
+
+        sql_header += "\n"
+
+        sql = sql_header + sql
+
+        if not json_resp:
+            return sql
+
+        return ajax_response(response=sql)
+
+    @check_precondition
+    def dependents(self, gid, sid, did, subid):
+        """
+        This function gets the dependents and returns an ajax response
+        for the subscription node.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            subid: Subscription ID
+        """
+        dependents_result = self.get_dependents(self.conn, subid)
+        return ajax_response(
+            response=dependents_result,
+            status=200
+        )
+
+    @check_precondition
+    def dependencies(self, gid, sid, did, subid):
+        """
+        This function gets the dependencies and returns an ajax response
+        for the subscription node.
+
+        Args:
+            gid: Server Group ID
+            sid: Server ID
+            did: Database ID
+            subid: Subscription ID
+        """
+        dependencies_result = self.get_dependencies(self.conn, subid)
+        return ajax_response(
+            response=dependencies_result,
+            status=200
+        )
+
+
+SubscriptionView.register_node_view(blueprint)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/static/img/coll-publication.svg b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/static/img/coll-publication.svg
new file mode 100644
index 000000000..cc946919b
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/static/img/coll-publication.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#DDE1F0;stroke:#4B5FAD;stroke-width:0.65;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
+	.st1{fill:#4B5FAD;}
+	.st2{fill:none;stroke:#4B5FAD;stroke-width:0.65;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
+</style>
+<g>
+	<path class="st0" d="M10,13c-2,2-5.1,2-7.1,0s-2-5.1,0-7.1C4.5,7.5,8.5,11.5,10,13z"/>
+</g>
+<g>
+	<path class="st0" d="M11.8,11.3c-2,2-5.1,2-7.1,0s-2-5.1,0-7.1C6.2,5.7,10.2,9.8,11.8,11.3z"/>
+</g>
+<path class="st1" d="M8.7,7.8l0.8-0.7c0.1,0.1,0.3,0.1,0.4,0.1c0.6,0,1-0.4,1-1s-0.4-1-1-1s-1,0.4-1,1c0,0.1,0,0.3,0.1,0.4L8.2,7.3
+	L8.7,7.8z"/>
+<g>
+	<path class="st2" d="M10.5,1.6c2.2,0,4,1.8,4,4"/>
+	<path class="st2" d="M10.6,3.6c1.1,0,2,0.9,2,2"/>
+</g>
+</svg>
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/static/img/coll-subscription.svg b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/static/img/coll-subscription.svg
new file mode 100644
index 000000000..d14adfd39
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/static/img/coll-subscription.svg
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#FBEEDF;stroke:#E58E26;stroke-width:0.65;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
+	.st1{fill:#E58E26;}
+	.st2{fill:none;stroke:#E58E26;stroke-width:0.65;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
+</style>
+<g>
+	<g>
+		<g>
+			<path class="st0" d="M12.9,5.7c2,2,2,5.1,0,7.1s-5.1,2-7.1,0C7.3,11.3,11.4,7.2,12.9,5.7z"/>
+		</g>
+	</g>
+	<g>
+		<g>
+			<path class="st0" d="M11.4,4.2c2,2,2,5.1,0,7.1s-5.1,2-7.1,0C5.8,9.8,9.9,5.7,11.4,4.2z"/>
+		</g>
+		<g>
+			<path class="st1" d="M7.8,7.3L7.1,6.6c0.1-0.1,0.1-0.3,0.1-0.4c0-0.6-0.4-1-1-1s-1,0.4-1,1s0.4,1,1,1c0.1,0,0.3,0,0.4-0.1
+				l0.7,0.7"/>
+			<g>
+				<path class="st2" d="M5.6,1.7c0,2.2-1.8,4-4,4"/>
+				<path class="st2" d="M3.7,1.8c0,1.1-0.9,2-2,2"/>
+			</g>
+		</g>
+	</g>
+</g>
+</svg>
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/static/img/publication.svg b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/static/img/publication.svg
new file mode 100644
index 000000000..3b582cf51
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/static/img/publication.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 23.0.6, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#DDE1F0;stroke:#4B5FAD;stroke-width:0.65;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
+	.st1{fill:#4B5FAD;}
+	.st2{fill:none;stroke:#4B5FAD;stroke-width:0.65;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
+</style>
+<g>
+	<g>
+		<path class="st0" d="M11,12c-2,2-5.1,2-7.1,0s-2-5.1,0-7.1C5.4,6.4,9.5,10.5,11,12z"/>
+	</g>
+	<g>
+		<path class="st1" d="M7.9,8.6l0.7-0.7C8.7,8,8.9,8,9,8c0.6,0,1-0.4,1-1S9.6,6,9,6S8,6.4,8,7c0,0.1,0,0.3,0.1,0.4L7.4,8.1"/>
+		<g>
+			<path class="st2" d="M9.6,2.5c2.2,0,4,1.8,4,4"/>
+			<path class="st2" d="M9.7,4.4c1.1,0,2,0.9,2,2"/>
+		</g>
+	</g>
+</g>
+</svg>
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/static/img/subscription.svg b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/static/img/subscription.svg
new file mode 100644
index 000000000..63a153c6d
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/static/img/subscription.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 23.0.6, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#FBEEDF;stroke:#E58E26;stroke-width:0.65;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
+	.st1{fill:#E58E26;}
+	.st2{fill:none;stroke:#E58E26;stroke-width:0.65;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
+</style>
+<g>
+	<g>
+		<path class="st0" d="M12.2,5c2,2,2,5.1,0,7.1s-5.1,2-7.1,0C6.6,10.6,10.7,6.5,12.2,5z"/>
+	</g>
+	<g>
+		<path class="st1" d="M8.6,8.1L7.9,7.4C8,7.3,8,7.1,8,7c0-0.6-0.4-1-1-1S6,6.4,6,7s0.4,1,1,1c0.1,0,0.3,0,0.4-0.1l0.7,0.7"/>
+		<g>
+			<path class="st2" d="M6.4,2.5c0,2.2-1.8,4-4,4"/>
+			<path class="st2" d="M4.5,2.6c0,1.1-0.9,2-2,2"/>
+		</g>
+	</g>
+</g>
+</svg>
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/static/js/subscription.js b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/static/js/subscription.js
new file mode 100644
index 000000000..013df4f65
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/static/js/subscription.js
@@ -0,0 +1,407 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2020, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+define('pgadmin.node.subscription', [
+  'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
+  'sources/pgadmin', 'pgadmin.browser', 'pgadmin.backform',
+  'sources/browser/server_groups/servers/model_validation', 'pgadmin.alertifyjs', 'pgadmin.browser.collection',
+], function(gettext, url_for, $, _, pgAdmin, pgBrowser, Backform, modelValidation, Alertify) {
+
+  // Extend the browser's collection class for subscriptions collection
+  if (!pgBrowser.Nodes['coll-subscription']) {
+    pgBrowser.Nodes['coll-subscription'] =
+      pgBrowser.Collection.extend({
+        node: 'subscription',
+        label: gettext('Subscriptions'),
+        type: 'coll-subscription',
+        columns: ['name', 'subowner', 'pub', 'enabled'],
+      });
+  }
+
+  // Extend the browser's node class for subscription node
+  if (!pgBrowser.Nodes['subscription']) {
+    pgBrowser.Nodes['subscription'] = pgBrowser.Node.extend({
+      parent_type: 'database',
+      type: 'subscription',
+      sqlAlterHelp: 'sql-altersubscription.html',
+      sqlCreateHelp: 'sql-createsubscription.html',
+      dialogHelp: url_for('help.static', {'filename': 'subscription_dialog.html'}),
+      label: gettext('Subscription'),
+      hasSQL:  true,
+      canDrop: true,
+      canDropCascade: true,
+      hasDepends: true,
+      width: '510px',
+      Init: function() {
+
+        // Avoid multiple registration of menus
+        if (this.initialized)
+          return;
+
+        this.initialized = true;
+
+
+        // Add context menus for subscription
+        pgBrowser.add_menus([{
+          name: 'create_subscription_on_database', node: 'database', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: gettext('Subscription...'),
+          icon: 'wcTabIcon icon-subscription', data: {action: 'create'},
+          enable: pgBrowser.Nodes['database'].canCreate,
+        },{
+          name: 'create_subscription_on_coll', node: 'coll-subscription', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: gettext('Subscription...'),
+          icon: 'wcTabIcon icon-subscription', data: {action: 'create'},
+          enable: 'canCreate',
+        },{
+          name: 'create_subscription', node: 'subscription', module: this,
+          applies: ['object', 'context'], callback: 'show_obj_properties',
+          category: 'create', priority: 4, label: gettext('Subscription...'),
+          icon: 'wcTabIcon icon-subscription', data: {action: 'create'},
+          enable: 'canCreate',
+        }]);
+      },
+      // Define the model for subscription node
+      model: pgBrowser.Node.Model.extend({
+        idAttribute: 'oid',
+        defaults: {
+          name: undefined,
+          subowner: undefined,
+          pubtable: undefined,
+          pub:[],
+          enabled:true,
+          create_slot: true,
+          copy_data:true,
+          connect:true,
+          sync:'off',
+        },
+
+        // Default values!
+        initialize: function(attrs, args) {
+          var isNew = (_.size(attrs) === 0);
+          if (isNew) {
+            var userInfo = pgBrowser.serverInfo[args.node_info.server._id].user;
+
+            this.set({'subowner': userInfo.name}, {silent: true});
+          }
+          pgBrowser.Node.Model.prototype.initialize.apply(this, arguments);
+        },
+
+        // Define the schema for the subscription node
+        schema: [{
+          id: 'name', label: gettext('Name'), type: 'text',
+          mode: ['properties', 'create', 'edit'],
+          visible: function() {
+            if(!_.isUndefined(this.node_info) && !_.isUndefined(this.node_info.server)
+              && !_.isUndefined(this.node_info.server.version) &&
+                this.node_info.server.version >= 100000) {
+              return true;
+            }
+            return false;
+          },
+        },{
+          id: 'oid', label: gettext('OID'), cell: 'string', mode: ['properties'],
+          type: 'text',
+        },
+        {
+          id: 'subowner', label: gettext('Owner'), type: 'text',
+          control: Backform.NodeListByNameControl, node: 'role',
+          mode: ['edit', 'properties', 'create'], select2: { allowClear: false},
+        },
+        {
+          id: 'host', label: gettext('Host name/address'), type: 'text', group: gettext('Connection'),
+          mode: ['properties', 'edit', 'create'],disabled: 'isShared',
+          control: Backform.InputControl.extend({
+            onChange: function() {
+              Backform.InputControl.prototype.onChange.apply(this, arguments);
+              if (!this.model || !this.model.changed) {
+                this.model.inform_text = undefined;
+                return;
+              }
+            },
+          }),
+        },{
+          id: 'port', label: gettext('Port'), type: 'int', group: gettext('Connection'),
+          mode: ['properties', 'edit', 'create'], min: 1, max: 65535, disabled: 'isShared',
+          control: Backform.InputControl.extend({
+            onChange: function() {
+              Backform.InputControl.prototype.onChange.apply(this, arguments);
+              if (!this.model || !this.model.changed) {
+                this.model.inform_text = undefined;
+                return;
+              }
+            },
+          }),
+        },{
+          id: 'username', label: gettext('Username'), type: 'text', group: gettext('Connection'),
+          mode: ['properties', 'edit', 'create'],
+          control: Backform.InputControl.extend({
+            onChange: function() {
+              Backform.InputControl.prototype.onChange.apply(this, arguments);
+              if (!this.model || !this.model.changed) {
+                this.model.inform_text = undefined;
+                return;
+              }
+            },
+          }),
+        },{
+          id: 'password', label: gettext('Password'), type: 'password', maxlength: null,
+          group: gettext('Connection'), control: 'input', mode: ['create', 'edit'], deps: ['connect_now'],
+        },{
+          id: 'db', label: gettext('Maintenance database'), type: 'text', group: gettext('Connection'),
+          mode: ['properties', 'edit', 'create'],
+        },{
+          id: 'service', label: gettext('Service'), type: 'text',
+          mode: ['properties', 'edit', 'create'],
+          group: gettext('Connection'),
+        },
+        {
+          id: 'passfile', label: gettext('Passfile'), type: 'text', group: gettext('Connection'),
+          mode: ['properties', 'edit', 'create'],
+        },
+        {
+          id: 'pub', label: gettext('Publication'), type: 'text', group: gettext('Connection'),
+          mode: ['properties'],
+        },
+        {
+          id: 'cur_pub', label: gettext('Current publication'), type: 'text', group: gettext('Connection'),
+          mode: ['edit'], disabled:true,
+        },
+        {
+          id: 'pub', label: gettext('Publication'), type: 'array', select2: { allowClear: true, multiple: true, width: '91%'},
+          group: gettext('Connection'), mode: ['create', 'edit'],controlsClassName: 'pgadmin-controls pg-el-sm-11 pg-el-12',
+          deps: ['all_table'],
+          helpMessage: gettext('Clicks on the button to get Publication'),
+          control: Backform.Select2Control.extend({
+            defaults: _.extend(Backform.Select2Control.prototype.defaults, {
+              select2: {
+                allowClear: true,
+                selectOnBlur: true,
+                tags: true,
+                placeholder: gettext('Select an item...'),
+                width: 'style',
+              },
+            }),
+            template: _.template([
+              '<label class="<%=Backform.controlLabelClassName%>" for="<%=cId%>"><%=label%></label>',
+              '<div class="<%=Backform.controlsClassName%>">',
+              '<div class="input-group">',
+              ' <select title="<%=name%>" id="<%=cId%>" class="<%=Backform.controlClassName%> <%=extraClasses.join(\' \')%>"',
+              '  name="<%=name%>" value="<%-value%>" <%=disabled ? "disabled" : ""%> <%=readonly ? "disabled" : ""%>',
+              '  <%=required ? "required" : ""%><%= select2.multiple ? " multiple>" : ">" %>',
+              '  <%=select2.first_empty ? " <option></option>" : ""%>',
+              '  <% for (var i=0; i < options.length; i++) {%>',
+              '   <% var option = options[i]; %>',
+              '   <option ',
+              '    <% if (option.image) { %> data-image=<%=option.image%> <%}%>',
+              '    value=<%- formatter.fromRaw(option.value) %>',
+              '    <% if (option.selected) {%>selected="selected"<%} else {%>',
+              '    <% if (!select2.multiple && option.value === rawValue) {%>selected="selected"<%}%>',
+              '    <% if (select2.multiple && rawValue && rawValue.indexOf(option.value) != -1){%>selected="selected" data-index="rawValue.indexOf(option.value)"<%}%>',
+              '    <%}%>',
+              '    <%= disabled ? "disabled" : ""%> <%=readonly ? "disabled" : ""%>><%-option.label%></option>',
+              '  <%}%>',
+              ' </select>',
+              '<div class="input-group-append">',
+              '<button class="btn btn-primary-icon fa fa-sync get_publication" <%=disabled ? "disabled" : ""%> <%=readonly ? "disabled" : ""%> aria-hidden="true" aria-label="' + gettext('Get Publication') + '" title="' + gettext('Get Publication') + '"></button>',
+              '</div>',
+              '</div>',
+              '<% if (helpMessage && helpMessage.length) { %>',
+              '<span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
+              '<% } %>',
+              '</div>',
+            ].join('\n')),
+
+            events:  _.extend({}, Backform.Select2Control.prototype.events(), {
+              'click .get_publication': 'getPublication',
+            }),
+
+            render: function(){
+              return Backform.Select2Control.prototype.render.apply(this, arguments);
+            },
+
+            getPublication: function() {
+              var self = this;
+              var publication_url = pgBrowser.Nodes['database'].generate_url.apply(
+                pgBrowser.Nodes['subscription'], [
+                  null, 'get_publications', this.field.get('node_data'), null,
+                  this.field.get('node_info'), pgBrowser.Nodes['database'].url_jump_after_node,
+                ]);
+              var result = '';
+
+              $.ajax({
+                url: publication_url,
+                type: 'GET',
+                data: self.model.toJSON(true, 'GET'),
+                dataType: 'json',
+                contentType: 'application/json',
+              })
+                .done(function(res) {
+                  result = res.data;
+                  self.field.set('options', result);
+                  Backform.Select2Control.prototype.render.apply(self, arguments);
+
+                  var transform = self.field.get('transform') || self.defaults.transform;
+                  if (transform && _.isFunction(transform)) {
+                    self.field.set('options', transform.bind(self, result));
+                  } else {
+                    self.field.set('options', result);
+                  }
+
+                })
+                .fail(function(res) {
+                  Alertify.alert(
+                    gettext('Check connection?'),
+                    gettext(res.responseJSON.errormsg)
+                  );
+                });
+            },
+          }),
+        },
+        {
+          id: 'copy_data_after_refresh', label: gettext('Copy data'),
+          type: 'switch', mode: ['edit'],
+          group: gettext('With'),
+          extraToggleClasses: 'pg-el-sm-6',
+          controlLabelClassName: 'control-label pg-el-sm-5 pg-el-12',
+          controlsClassName: 'pgadmin-controls pg-el-sm-7 pg-el-12',
+          readonly: 'isRefresh', deps :['refresh_pub'],
+        },
+        {
+          id: 'copy_data', label: gettext('Copy data'),
+          type: 'switch', mode: ['create'],
+          group: gettext('With'),
+          extraToggleClasses: 'pg-el-sm-6',
+          controlLabelClassName: 'control-label pg-el-sm-5 pg-el-12',
+          controlsClassName: 'pgadmin-controls pg-el-sm-7 pg-el-12',
+          readonly: 'isConnect', deps :['connect'],
+        },
+        {
+          id: 'create_slot', label: gettext('Create slot'),
+          type: 'switch', mode: ['create'],
+          group: gettext('With'),
+          extraToggleClasses: 'pg-el-sm-6',
+          controlLabelClassName: 'control-label pg-el-sm-5 pg-el-12',
+          controlsClassName: 'pgadmin-controls pg-el-sm-7 pg-el-12',
+          disabled: 'isDisable',
+          readonly: 'isConnect', deps :['connect'],
+
+        },
+        {
+          id: 'enabled', label: gettext('Enabled'),
+          type: 'switch', mode: ['create','edit', 'properties'],
+          group: gettext('With'),
+          extraToggleClasses: 'pg-el-sm-6',
+          controlLabelClassName: 'control-label pg-el-sm-5 pg-el-12',
+          controlsClassName: 'pgadmin-controls pg-el-sm-7 pg-el-12',
+          readonly: 'isConnect', deps :['connect'],
+        },
+        {
+          id: 'refresh_pub', label: gettext('Refresh publication'),
+          type: 'switch', mode: ['edit'],
+          group: gettext('With'),
+          extraToggleClasses: 'pg-el-sm-6',
+          controlLabelClassName: 'control-label pg-el-sm-5 pg-el-12',
+          controlsClassName: 'pgadmin-controls pg-el-sm-7 pg-el-12',
+        },{
+          id: 'connect', label: gettext('Connect'),
+          type: 'switch', mode: ['create'],
+          group: gettext('With'),
+          extraToggleClasses: 'pg-el-sm-6',
+          controlLabelClassName: 'control-label pg-el-sm-5 pg-el-12',
+          controlsClassName: 'pgadmin-controls pg-el-sm-7 pg-el-12',
+          disabled: 'isDisable', deps:['enabled', 'create_slot', 'copy_data'],
+        },
+        {
+          id: 'slot_name', label: gettext('Slot name'),
+          type: 'text', mode: ['create','edit', 'properties'],
+          group: gettext('With'),
+        },
+        {
+          id: 'sync', label: gettext('Synchronous commit'), control: 'select2', deps:['event'],
+          group: gettext('With'), type: 'text',
+          select2: {
+            width: '100%',
+            allowClear: false,
+          },
+          options:[
+            {label: 'local', value: 'local'},
+            {label: 'remote_write', value: 'remote_write'},
+            {label: 'remote_apply', value: 'remote_apply'},
+            {label: 'on', value: 'on'},
+            {label: 'off', value: 'off'},
+          ],
+        },
+        ],
+        isShared: function(){
+          return false;
+        },
+        isDisable:function(m){
+          if (m.isNew())
+            return false;
+          return true;
+        },
+
+        isConnect: function(m){
+          if(!m.get('connect')){
+            setTimeout( function() {
+              m.set('copy_data', false);
+              m.set('create_slot', false);
+              m.set('enabled', false);
+            }, 10);
+            return true;
+          }
+          return false;
+        },
+        isRefresh: function(m){
+          if (!m.get('refresh_pub') || _.isUndefined(m.get('refresh_pub'))){
+            setTimeout( function() {
+              m.set('copy_data_after_refresh', false);
+            }, 10);
+            return true;
+          }
+          return false;
+        },
+        /* validate function is used to validate the input given by
+         * the user. In case of error, message will be displayed on
+         * the GUI for the respective control.
+         */
+        validate: function() {
+          var msg;
+          this.errorModel.clear();
+          var name = this.get('name');
+
+          if (_.isUndefined(name) || _.isNull(name) ||
+            String(name).replace(/^\s+|\s+$/g, '') == '') {
+            msg = gettext('Name cannot be empty.');
+            this.errorModel.set('name', msg);
+            return msg;
+          }
+
+          const validateModel = new modelValidation.ModelValidation(this);
+          return validateModel.validate();
+        },
+        canCreate: function(itemData, item) {
+          var treeData = this.getTreeNodeHierarchy(item),
+            server = treeData['server'];
+
+          // If server is less than 10 then do not allow 'create' menu
+          if (server && server.version < 100000)
+            return false;
+
+          // by default we want to allow create menu
+          return true;
+        },
+
+      }),
+    });
+  }
+  return pgBrowser.Nodes['coll-subscription'];
+});
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/templates/subscriptions/sql/default/create.sql b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/templates/subscriptions/sql/default/create.sql
new file mode 100644
index 000000000..fec2c558b
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/templates/subscriptions/sql/default/create.sql
@@ -0,0 +1,24 @@
+{% if data.copy_data is defined or data.create_slot is defined or data.slot_name is defined or data.sync is defined %}
+{% set add_semicolon_after_enabled = 'enabled' %}
+{% endif %}
+{% if data.create_slot is defined or data.slot_name is defined %}
+{% set add_semicolon_after_copy_data = 'copy_data' %}
+{% endif %}
+{% if data.slot_name is defined or data.sync is defined %}
+{% set add_semicolon_after_create_slot = 'create_slot' %}
+{% endif %}
+{% if data.sync is defined %}
+{% set add_semicolon_after_slot_name = 'slot_name' %}
+{% endif %}
+
+CREATE SUBSCRIPTION {{ conn|qtIdent(data.name) }}
+{% if data.host or data.port or data.username or data.password  or data.db %}
+CONNECTION ' {% if data.host %}host={{ data.host}} {% endif %}{% if data.port %}port={{ data.port }} {% endif %}{% if data.username %}user={{ data.username }} {% endif %}{% if data.db %}dbname={{ data.db }} {% endif %}{% if data.service %}service={{ data.service }} {% endif %}{% if data.passfile %}passfile={{ data.passfile }} {% endif %}{% if data.password %}password={{ data.password}}{% endif %}'
+{% endif %}
+{% if data.pub %}
+PUBLICATION {% for pub in data.pub %}{% if loop.index != 1 %},{% endif %}{{ conn|qtIdent(pub) }}{% endfor %}
+{% endif %}
+
+with ({% if data.connect is defined %}connect = {{ data.connect}}, {% endif %}enabled = {{ data.enabled}}, {% if data.copy_data %}copy_data = {{ data.copy_data}}{% if add_semicolon_after_copy_data == 'copy_data' %},{% endif %}{% endif %}
+{% if data.create_slot is defined %} create_slot = {{ data.create_slot }}{% if add_semicolon_after_create_slot == 'create_slot' %},{% endif %}{% endif %}
+{% if data.slot_name is defined  and data.slot_name != ''%} slot_name = {{ data.slot_name }}{% if add_semicolon_after_slot_name == 'slot_name' %},{% endif %}{% endif %}{% if data.sync %} synchronous_commit = '{{ data.sync }}'{% endif %});
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/templates/subscriptions/sql/default/delete.sql b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/templates/subscriptions/sql/default/delete.sql
new file mode 100644
index 000000000..33942fa99
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/templates/subscriptions/sql/default/delete.sql
@@ -0,0 +1,8 @@
+{# ============= Get the subscription name using oid ============= #}
+{% if subid %}
+    SELECT subname FROM pg_subscription WHERE oid = {{subid}}::oid;
+{% endif %}
+{# ============= Drop the language ============= #}
+{% if subname %}
+DROP SUBSCRIPTION {{ conn|qtIdent(subname) }}{% if cascade %} CASCADE{% endif%};
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/templates/subscriptions/sql/default/get_position.sql b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/templates/subscriptions/sql/default/get_position.sql
new file mode 100644
index 000000000..46aac1181
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/templates/subscriptions/sql/default/get_position.sql
@@ -0,0 +1 @@
+select oid, subname as name from pg_subscription where subname = '{{ subname }}';
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/templates/subscriptions/sql/default/nodes.sql b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/templates/subscriptions/sql/default/nodes.sql
new file mode 100644
index 000000000..10f841743
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/templates/subscriptions/sql/default/nodes.sql
@@ -0,0 +1,7 @@
+select oid, sub.subname as name from pg_subscription sub
+WHERE
+{% if subid %}
+    sub.oid = {{ subid }};
+{% else %}
+    sub.subdbid = {{ did }};
+{% endif %};
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/templates/subscriptions/sql/default/properties.sql b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/templates/subscriptions/sql/default/properties.sql
new file mode 100644
index 000000000..7dd18b517
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/templates/subscriptions/sql/default/properties.sql
@@ -0,0 +1,21 @@
+SELECT  sub.oid as oid,
+        subname as name,
+        subpublications as pub,
+        sub.subsynccommit as sync,
+        subpublications as cur_pub,
+        pga.rolname as subowner,
+        subslotname as slot_name,
+        subenabled as enabled,
+		SPLIT_PART(SPLIT_PART(subconninfo,'port',1), '=',2) as host,
+		SPLIT_PART(SPLIT_PART(subconninfo,'port=',2), ' ',1) as port,
+		SPLIT_PART(SPLIT_PART(subconninfo,'user=',2), ' ',1) as username,
+		SPLIT_PART(SPLIT_PART(subconninfo,'dbname=',2), ' ',1) as db,
+		SPLIT_PART(SPLIT_PART(subconninfo,'service=',2), ' ',1) as service,
+		SPLIT_PART(SPLIT_PART(subconninfo,'passfile=',2), ' ',1) as passfile
+FROM pg_subscription sub join pg_authid pga on sub.subowner= pga.oid
+WHERE
+{% if subid %}
+    sub.oid = {{ subid }};
+{% else %}
+    sub.subdbid = {{ did }};
+{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/templates/subscriptions/sql/default/update.sql b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/templates/subscriptions/sql/default/update.sql
new file mode 100644
index 000000000..789761e06
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/templates/subscriptions/sql/default/update.sql
@@ -0,0 +1,52 @@
+{% if data.sync is defined %}
+{% set add_semicolon_after_slot_name = 'slot_name' %}
+{% endif %}
+
+{#####################################################}
+{## Change owner of subscription ##}
+{#####################################################}
+{% if data.subowner and data.subowner != o_data.subowner %}
+ALTER SUBSCRIPTION {{ conn|qtIdent(o_data.name) }} OWNER TO {{ data.subowner }};
+{% endif %}
+
+{% if data.name and data.name != o_data.name %}
+ALTER SUBSCRIPTION {{ conn|qtIdent(o_data.name) }} RENAME TO {{ conn|qtIdent(data.name) }};
+{% endif %}
+
+{% if data.enabled is defined and data.enabled != o_data.enabled %}
+{% if not data.enabled %}
+ALTER SUBSCRIPTION {{ conn|qtIdent(o_data.name) }} DISABLE;
+{% endif %}
+{% endif %}
+
+{% if data.slot_name or data.sync %}
+ALTER SUBSCRIPTION {{ conn|qtIdent(o_data.name) }} SET ({% if data.slot_name %}slot_name = {{ data.slot_name }}{% if add_semicolon_after_slot_name == 'slot_name' %}, {% endif %}{% endif %}{% if data.sync %}synchronous_commit = '{{ data.sync }}'{% endif %});
+{% endif %}
+
+{% if data.enabled is defined and data.enabled != o_data.enabled %}
+{% if data.enabled %}
+ALTER SUBSCRIPTION {{ conn|qtIdent(o_data.name) }} ENABLE;
+{% endif %}
+{% endif %}
+
+{% if data.refresh_pub %}
+ALTER SUBSCRIPTION {{ conn|qtIdent(o_data.name) }} REFRESH PUBLICATION{% if not data.copy_data_after_refresh %} WITH (copy_data = False){% else %} WITH (copy_data = True){% endif %};
+{% endif %}
+
+{% if data.pub%}
+{% if data.pub and not data.refresh_pub and not data.enabled %}
+ALTER SUBSCRIPTION {{ conn|qtIdent(o_data.name) }} SET PUBLICATION {% for pub in data.pub %}{% if loop.index != 1 %},{% endif %}{{ conn|qtIdent(pub) }}{% endfor %} WITH (refresh = false);
+{% else %}
+ALTER SUBSCRIPTION {{ conn|qtIdent(o_data.name) }} SET PUBLICATION {% for pub in data.pub %}{% if loop.index != 1 %},{% endif %}{{ conn|qtIdent(pub) }}{% endfor %};
+{% endif %}
+{% endif %}
+
+{% if data.host or data.port or data.username or data.password  or data.db or  data.service or data.passfile %}
+ALTER SUBSCRIPTION {{ conn|qtIdent(o_data.name) }} CONNECTION 'host={{ o_data.host}} port={{ o_data.port }} user={{ o_data.username }} dbname={{ o_data.db }}{% if data.service %} service={{ o_data.service }}{% endif %}{% if data.passfile %} passfile={{ o_data.passfile }}{% endif %}{% if data.password %} password={{ data.password}}{% endif %}';
+{% endif %}
+
+
+
+
+
+
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/10_plus/alter_maintenance_db.sql b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/10_plus/alter_maintenance_db.sql
new file mode 100644
index 000000000..6f626fabf
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/10_plus/alter_maintenance_db.sql
@@ -0,0 +1,7 @@
+-- Subscription : test_alter_subscription;
+-- DROP SUBSCRIPTION test_alter_subscription;
+
+CREATE SUBSCRIPTION test_alter_subscription
+CONNECTION ' host=localhost   port=5432 user=postgres dbname=edb '
+PUBLICATION sample__1
+with (enabled = False,  create_slot = False, slot_name = None, synchronous_commit = 'remote_apply');
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/10_plus/alter_subscription.sql b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/10_plus/alter_subscription.sql
new file mode 100644
index 000000000..5f896502c
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/10_plus/alter_subscription.sql
@@ -0,0 +1,7 @@
+-- Subscription : test_alter_subscription;
+-- DROP SUBSCRIPTION test_alter_subscription;
+
+CREATE SUBSCRIPTION test_alter_subscription
+CONNECTION ' host=localhost  port=5432 user=postgres dbname=postgres '
+PUBLICATION sample__1
+with (enabled = False,  create_slot = False, slot_name = None, synchronous_commit = 'off');
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/10_plus/alter_sync.sql b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/10_plus/alter_sync.sql
new file mode 100644
index 000000000..5e24dbb6a
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/10_plus/alter_sync.sql
@@ -0,0 +1,7 @@
+-- Subscription : test_alter_subscription;
+-- DROP SUBSCRIPTION test_alter_subscription;
+
+CREATE SUBSCRIPTION test_alter_subscription
+CONNECTION ' host=localhost  port=5432 user=postgres dbname=postgres '
+PUBLICATION sample__1
+with (enabled = False,  create_slot = False, slot_name = None, synchronous_commit = 'remote_apply');
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/10_plus/create_subscription.sql b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/10_plus/create_subscription.sql
new file mode 100644
index 000000000..a0a2338ab
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/10_plus/create_subscription.sql
@@ -0,0 +1,7 @@
+-- Subscription : test_create_subscription;
+-- DROP SUBSCRIPTION test_create_subscription;
+
+CREATE SUBSCRIPTION test_create_subscription
+CONNECTION ' host=localhost  port=5432 user=postgres dbname=postgres '
+PUBLICATION sample__1
+with (enabled = False,  create_slot = False, slot_name = None, synchronous_commit = 'off');
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/10_plus/create_subscription_msql.sql b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/10_plus/create_subscription_msql.sql
new file mode 100644
index 000000000..6f759ce27
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/10_plus/create_subscription_msql.sql
@@ -0,0 +1,4 @@
+CREATE SUBSCRIPTION test_create_subscription
+CONNECTION ' host=localhost port=5432 user=postgres dbname=postgres password=edb'
+PUBLICATION sample__1
+with (connect = False, enabled = False,  create_slot = False, slot_name = None, synchronous_commit = 'off');
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/10_plus/test.json b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/10_plus/test.json
new file mode 100644
index 000000000..d9cf45bdd
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/10_plus/test.json
@@ -0,0 +1,70 @@
+{
+  "scenarios": [
+    {
+      "type": "create",
+      "name": "Create  Subscription",
+      "endpoint": "NODE-subscription.obj",
+      "sql_endpoint": "NODE-subscription.sql_id",
+      "msql_endpoint": "NODE-subscription.msql",
+      "data": {
+          "username": "postgres",
+          "name": "test_create_subscription",
+          "connect": false,
+          "copy_data": false,
+          "create_slot": false,
+          "db": "postgres",
+          "subowner": "postgres",
+          "enabled": false,
+          "host": "localhost",
+          "slot_name": "None",
+          "service": "",
+          "port": 5432,
+          "password": "edb",
+          "sync": "off",
+          "pub": "[\"sample__1\"]"
+      },
+      "expected_sql_file": "create_subscription.sql",
+      "expected_msql_file": "create_subscription_msql.sql"
+    },
+    {
+      "type": "alter",
+      "name": "Alter  Subscription",
+      "endpoint": "NODE-subscription.obj_id",
+      "sql_endpoint": "NODE-subscription.sql_id",
+      "data": {
+          "name": "test_alter_subscription"
+      },
+      "expected_sql_file": "alter_subscription.sql"
+    },
+    {
+      "type": "alter",
+      "name": "Alter  sync of subscription",
+      "endpoint": "NODE-subscription.obj_id",
+      "sql_endpoint": "NODE-subscription.sql_id",
+      "data": {
+          "sync": "remote_apply"
+      },
+      "expected_sql_file": "alter_sync.sql"
+    },
+
+    {
+      "type": "alter",
+      "name": "Alter  maintainance DB in connection string of subscription",
+      "endpoint": "NODE-subscription.obj_id",
+      "sql_endpoint": "NODE-subscription.sql_id",
+      "data": {
+          "db": "edb"
+      },
+      "expected_sql_file": "alter_maintenance_db.sql"
+    },
+
+    {
+      "type": "delete",
+      "name": "Drop subscription",
+      "endpoint": "NODE-subscription.delete_id",
+      "data": {
+        "name": "test_alter_subscription"
+      }
+    }
+  ]
+}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/__init__.py
new file mode 100644
index 000000000..27cd89067
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/__init__.py
@@ -0,0 +1,16 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+from pgadmin.utils.route import BaseTestGenerator
+
+
+class RulesTestGenerator(BaseTestGenerator):
+
+    def runTest(self):
+        return []
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/subscription_test_data.json b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/subscription_test_data.json
new file mode 100644
index 000000000..4773557fe
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/subscription_test_data.json
@@ -0,0 +1,432 @@
+{
+  "add_subscription": [
+    {
+      "name": "Create subscription with insert and update",
+      "url": "/browser/subscription/obj/",
+      "is_positive_test": true,
+      "test_data": {
+          "username": "postgres",
+          "name": "PLACEHOLDER",
+          "connect": false,
+          "copy_data": false,
+          "create_slot": false,
+          "db": "postgres",
+          "subowner": "postgres",
+          "enabled": false,
+          "host": "localhost",
+          "slot_name": "NONE",
+          "service": "",
+          "port": 5432,
+          "password": "",
+          "sync": "off",
+          "pub": "PLACE_HOLDER"
+      },
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Create subscription for few tables",
+      "url": "/browser/subscription/obj/",
+      "is_positive_test": true,
+      "get_publication": true,
+      "test_data": {
+          "username": "postgres",
+          "name": "PLACEHOLDER",
+          "connect": false,
+          "copy_data": false,
+          "create_slot": false,
+          "db": "postgres",
+          "subowner": "postgres",
+          "enabled": false,
+          "host": "localhost",
+          "slot_name": "NONE",
+          "service": "",
+          "port": 5432,
+          "password": "",
+          "sync": "off",
+          "pub": "PLACE_HOLDER"
+      },
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Create a subscription without name",
+      "url": "/browser/subscription/obj/",
+      "is_positive_test": false,
+      "without_name": true,
+      "test_data": {
+          "username": "postgres",
+          "name": "PLACEHOLDER",
+          "connect": false,
+          "copy_data": false,
+          "create_slot": false,
+          "db": "postgres",
+          "subowner": "postgres",
+          "enabled": false,
+          "host": "localhost",
+          "slot_name": "NONE",
+          "service": "",
+          "port": 5432,
+          "password": "",
+          "sync": "off",
+          "pub": "PLACE_HOLDER"
+      },
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 410
+      }
+    },
+    {
+      "name": "Error while adding a subscription",
+      "url": "/browser/subscription/obj/",
+      "is_positive_test": false,
+      "error_creating_subscription": true,
+      "test_data": {
+          "username": "postgres",
+          "name": "PLACEHOLDER",
+          "connect": false,
+          "copy_data": false,
+          "create_slot": false,
+          "db": "postgres",
+          "subowner": "postgres",
+          "enabled": false,
+          "host": "localhost",
+          "slot_name": "NONE",
+          "service": "",
+          "port": 5432,
+          "password": "",
+          "sync": "off",
+          "pub": "PLACE_HOLDER"
+      },
+      "mocking_required": true,
+      "mock_data": {
+        "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict",
+        "return_value": "(False, 'Mocked Internal Server Error ')"
+      },
+      "expected_data": {
+        "status_code": 500
+      }
+    },
+    {
+      "name": "Exception while adding a subscription",
+      "url": "/browser/subscription/obj/",
+      "is_positive_test": false,
+      "test_data": {
+          "username": "postgres",
+          "name": "PLACEHOLDER",
+          "connect": false,
+          "copy_data": false,
+          "create_slot": false,
+          "db": "postgres",
+          "subowner": "postgres",
+          "enabled": false,
+          "host": "localhost",
+          "slot_name": "NONE",
+          "service": "",
+          "port": 5432,
+          "password": "",
+          "sync": "off",
+          "pub": "PLACE_HOLDER"
+      },
+      "mocking_required": true,
+      "mock_data": {
+        "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict",
+        "return_value": "(True, True)(False, 'Mocked Internal Server Error ')"
+      },
+      "expected_data": {
+        "status_code": 500
+      }
+    }
+  ],
+  "get_subscription": [
+    {
+      "name": "Get a subscription URL",
+      "url": "/browser/subscription/obj/",
+      "is_positive_test": true,
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Error while fetching a subscription properties",
+      "url": "/browser/subscription/obj/",
+      "is_positive_test": false,
+      "mocking_required": true,
+      "mock_data": {
+        "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict",
+        "return_value": "(False, 'Mocked Internal Server Error')"
+      },
+      "expected_data": {
+        "status_code": 500
+      }
+    },
+    {
+      "name": "Get a subscription properties under database nodes",
+      "url": "/browser/subscription/obj/",
+      "is_positive_test": true,
+      "database_nodes": true,
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Error while fetching a subscription properties under database nodes",
+      "url": "/browser/subscription/obj/",
+      "is_positive_test": false,
+      "database_nodes": true,
+      "mocking_required": true,
+      "mock_data": {
+        "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict",
+        "return_value": "(False, 'Mocked Internal Server Error')"
+      },
+      "expected_data": {
+        "status_code": 500
+      }
+    },
+    {
+      "name": "Get a subscription Node",
+      "url": "/browser/subscription/nodes/",
+      "is_positive_test": true,
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Get a subscription Node dependants",
+      "url": "/browser/subscription/dependent/",
+      "is_positive_test": true,
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Get a subscription Node dependency",
+      "url": "/browser/subscription/dependency/",
+      "is_positive_test": true,
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Error while fetching the subscription under the database nodes using wrong database id",
+      "url": "/browser/subscription/nodes/",
+      "is_positive_test": false,
+      "mocking_required": true,
+      "mock_data": {
+        "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_2darray",
+        "return_value": "(False, 'Mocked Internal Server Error')"
+      },
+      "expected_data": {
+        "status_code": 500
+      }
+    },
+    {
+      "name": "Get all the subscription under the database nodes",
+      "url": "/browser/subscription/nodes/",
+      "is_positive_test": true,
+      "mocking_required": false,
+      "database_nodes": true,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Get all the subscription under the database nodes using wrong database id",
+      "url": "/browser/subscription/nodes/",
+      "is_positive_test": true,
+      "incorrect_database_id": true,
+      "database_nodes": true,
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Error while fetching all the subscription under the database nodes using wrong database id",
+      "url": "/browser/subscription/nodes/",
+      "is_positive_test": false,
+      "database_nodes": true,
+      "mocking_required": true,
+      "mock_data": {
+        "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_2darray",
+        "return_value": "(False, 'Mocked Internal Server Error')"
+      },
+      "expected_data": {
+        "status_code": 500
+      }
+    }
+  ],
+  "delete_subscription": [
+    {
+      "name": "Delete a subscription URL",
+      "url": "/browser/subscription/obj/",
+      "is_positive_test": true,
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Error while deleting the subscription",
+      "url": "/browser/subscription/obj/",
+      "is_positive_test": false,
+      "mocking_required": true,
+      "mock_data": {
+        "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_scalar",
+        "return_value": "(False, 'Mocked Internal Server Error')"
+      },
+      "expected_data": {
+        "status_code": 500
+      }
+    }
+  ],
+  "update_subscription": [
+    {
+      "name": "update a subscription name",
+      "url": "/browser/subscription/obj/",
+      "is_positive_test": true,
+      "update_name": true,
+      "test_data": {
+        "name": "PLACE_HOLDER",
+        "id": "PLACE_HOLDER"
+      },
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "update a port in connection details of subscription",
+      "url": "/browser/subscription/obj/",
+      "is_positive_test": true,
+      "owner_subscription": true,
+      "test_data": {
+        "id": "PLACE_HOLDER",
+        "port": "5444"
+      },
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "update a username in connection details of subscription",
+      "url": "/browser/subscription/obj/",
+      "is_positive_test": true,
+      "owner_subscription": true,
+      "test_data": {
+        "id": "PLACE_HOLDER",
+        "username": "sample_username"
+      },
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Error while fetching a subscription to update",
+      "url": "/browser/subscription/obj/",
+      "is_positive_test": false,
+      "mocking_required": true,
+      "test_data": {
+        "name": "PLACE_HOLDER",
+        "id": "PLACE_HOLDER"
+      },
+      "mock_data": {
+        "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict",
+        "return_value": "(False, 'Mocked Internal Server Error')"
+      },
+      "expected_data": {
+        "status_code": 500
+      }
+    },
+    {
+      "name": "Error while fetching a subscription to update using wrong subscription id",
+      "url": "/browser/subscription/obj/",
+      "is_positive_test": true,
+      "wrong_subscription_id": true,
+      "mocking_required": false,
+      "test_data": {
+        "id": "PLACE_HOLDER"
+      },
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 500
+      }
+    }
+  ],
+  "delete_multiple_subscription": [
+    {
+      "name": "Delete multiple subscription",
+      "url": "/browser/subscription/obj/",
+      "is_positive_test": true,
+      "mocking_required": false,
+      "mock_data": {},
+      "expected_data": {
+        "status_code": 200
+      }
+    }
+  ],
+  "sql_subscription": [
+    {
+      "name": "Fetch the subscription SQL",
+      "url": "/browser/subscription/sql/",
+      "is_positive_test": true,
+      "mocking_required": false,
+      "mock_data": {
+      },
+      "expected_data": {
+        "status_code": 200
+      }
+    },
+    {
+      "name": "Error while fetching a subscription SQL",
+      "url": "/browser/subscription/sql/",
+      "is_positive_test": false,
+      "mocking_required": true,
+      "mock_data": {
+        "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict",
+        "return_value": "(False, 'Mocked Internal Server Error')"
+      },
+      "expected_data": {
+        "status_code": 500
+      }
+    },
+    {
+      "name": "Fetching a subscription mSQL",
+      "url": "/browser/subscription/msql/",
+      "is_positive_test": true,
+      "mocking_required": false,
+      "mock_data": {
+        "function_name": "pgadmin.utils.driver.psycopg2.connection.Connection.execute_dict",
+        "return_value": "(False, 'Mocked Internal Server Error')"
+      },
+      "expected_data": {
+        "status_code": 200
+      }
+    }
+  ]
+}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/test_subscription_create.py b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/test_subscription_create.py
new file mode 100644
index 000000000..73bd353b8
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/test_subscription_create.py
@@ -0,0 +1,97 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import json
+import uuid
+from unittest.mock import patch
+
+from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \
+    import utils as tables_utils
+from pgadmin.browser.server_groups.servers.databases.tests import utils as \
+    database_utils
+from pgadmin.utils.route import BaseTestGenerator
+from regression import parent_node_dict
+from regression.python_test_utils import test_utils as utils
+from . import utils as subscription_utils
+
+
+class PublicationsAddTestCase(BaseTestGenerator):
+    """This class will add new subscription"""
+    scenarios = utils.generate_scenarios('add_subscription',
+                                         subscription_utils.test_cases)
+
+    def setUp(self):
+        self.db_name = parent_node_dict["database"][-1]["db_name"]
+        schema_info = parent_node_dict["schema"][-1]
+        self.server_id = schema_info["server_id"]
+        self.db_id = schema_info["db_id"]
+        self.schema_name = schema_info["schema_name"]
+        self.server_version = schema_info["server_version"]
+        if self.server_version < 99999:
+            self.skipTest(
+                "Logical replication is not supported "
+                "for server version less than 10"
+
+            )
+
+        db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
+                                                 self.server_id, self.db_id)
+        if not db_con['data']["connected"]:
+            raise Exception(
+                "Could not connect to database to add a subscription.")
+
+        if self.is_positive_test and hasattr(self, 'few_tables_11'):
+            self.table_name = "table_column_%s" % (str(uuid.uuid4())[1:8])
+            self.table_id = tables_utils.create_table(self.server,
+                                                      self.db_name,
+                                                      self.schema_name,
+                                                      self.table_name)
+
+            self.test_data['pubtable'] = subscription_utils.get_tables(self)
+
+    def runTest(self):
+        """This function will subscription."""
+        self.test_data['name'] = \
+            "test_subscription_add_%s" % (str(uuid.uuid4())[1:8])
+
+        self.test_data['pub'] = """["sample__1"]"""
+
+        data = self.test_data
+        if self.is_positive_test:
+            response = self.create_subscription()
+        else:
+            if hasattr(self, 'without_name'):
+                del data["name"]
+                response = self.create_subscription()
+            elif hasattr(self, 'error_creating_subscription'):
+                with patch(self.mock_data["function_name"],
+                           return_value=eval(self.mock_data["return_value"])):
+                    response = self.create_subscription()
+            else:
+                with patch(self.mock_data["function_name"],
+                           side_effect=self.mock_data["return_value"]):
+                    response = self.create_subscription()
+        self.assertEqual(response.status_code,
+                         self.expected_data["status_code"])
+
+    def create_subscription(self):
+        return self.tester.post(
+            self.url + str(utils.SERVER_GROUP) + '/' +
+            str(self.server_id) + '/' + str(
+                self.db_id) + '/',
+            data=json.dumps(self.test_data),
+            content_type='html/json')
+
+    def tearDown(self):
+        if self.is_positive_test:
+            subscription_utils.delete_subscription(self.server, self.db_name,
+                                                   self.test_data['name'])
+
+        # Disconnect the database
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/test_subscription_delete.py b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/test_subscription_delete.py
new file mode 100644
index 000000000..7cd2aafce
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/test_subscription_delete.py
@@ -0,0 +1,93 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import uuid
+from unittest.mock import patch
+
+from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
+    utils as schema_utils
+from pgadmin.browser.server_groups.servers.databases.tests import utils as \
+    database_utils
+from pgadmin.utils.route import BaseTestGenerator
+from regression import parent_node_dict
+from regression.python_test_utils import test_utils as utils
+from . import utils as subscription_utils
+
+
+class PublicationDeleteTestCase(BaseTestGenerator):
+    """This class will delete subscription."""
+    scenarios = utils.generate_scenarios('delete_subscription',
+                                         subscription_utils.test_cases)
+
+    def setUp(self):
+        self.db_name = parent_node_dict["database"][-1]["db_name"]
+        schema_info = parent_node_dict["schema"][-1]
+        self.server_id = schema_info["server_id"]
+        self.db_id = schema_info["db_id"]
+        self.server_version = schema_info["server_version"]
+        if self.server_version < 99999:
+            self.skipTest(
+                "Logical replication is not supported "
+                "for server version less than 10"
+
+            )
+        db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
+                                                 self.server_id, self.db_id)
+        if not db_con['data']["connected"]:
+            raise Exception(
+                "Could not connect to database to delete subscription.")
+        self.schema_id = schema_info["schema_id"]
+        self.schema_name = schema_info["schema_name"]
+        schema_response = schema_utils.verify_schemas(self.server,
+                                                      self.db_name,
+                                                      self.schema_name)
+        if not schema_response:
+            raise Exception("Could not find the schema to delete "
+                            "subscription.")
+        self.subscription_name = "test_subscription_delete_%s" % (
+            str(uuid.uuid4())[1:8])
+
+        self.subscription_id = subscription_utils.create_subscription(
+            self.server,
+            self.db_name,
+            self.subscription_name)
+
+    def delete_subscription(self):
+        return self.tester.delete(
+            self.url + str(utils.SERVER_GROUP) + '/' +
+            str(self.server_id) + '/' + str(self.db_id) +
+            '/' + str(self.subscription_id),
+            follow_redirects=True)
+
+    def runTest(self):
+        """This function will delete subscription"""
+        subscription_response = subscription_utils.verify_subscription(
+            self.server,
+            self.db_name,
+            self.subscription_name)
+        if not subscription_response:
+            raise Exception("Could not find the subscription to delete.")
+
+        if self.is_positive_test:
+            if hasattr(self, "invalid_subscription_id"):
+                self.subscription_id = 9999
+            response = self.delete_subscription()
+        else:
+            with patch(self.mock_data["function_name"],
+                       return_value=eval(self.mock_data["return_value"])):
+                response = self.delete_subscription()
+
+        self.assertEqual(response.status_code,
+                         self.expected_data["status_code"])
+
+    def tearDown(self):
+        # Disconnect the database
+        subscription_utils.delete_subscription(self.server, self.db_name,
+                                               self.subscription_name)
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/test_subscription_delete_multiple.py b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/test_subscription_delete_multiple.py
new file mode 100644
index 000000000..ea93c06f2
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/test_subscription_delete_multiple.py
@@ -0,0 +1,106 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import uuid
+import json
+
+from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \
+    import utils as tables_utils
+from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
+    utils as schema_utils
+from pgadmin.browser.server_groups.servers.databases.tests import utils as \
+    database_utils
+from pgadmin.utils.route import BaseTestGenerator
+from regression import parent_node_dict
+from regression.python_test_utils import test_utils as utils
+from . import utils as subscription_utils
+
+
+class PublicationDeleteTestCases(BaseTestGenerator):
+    """This class will delete subscription."""
+
+    scenarios = utils.generate_scenarios('delete_multiple_subscription',
+                                         subscription_utils.test_cases)
+
+    def setUp(self):
+        self.db_name = parent_node_dict["database"][-1]["db_name"]
+        schema_info = parent_node_dict["schema"][-1]
+        self.server_id = schema_info["server_id"]
+        self.db_id = schema_info["db_id"]
+        self.server_version = schema_info["server_version"]
+        if self.server_version < 99999:
+            self.skipTest(
+                "Logical replication is not supported "
+                "for server version less than 10"
+
+            )
+        db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
+                                                 self.server_id, self.db_id)
+        if not db_con['data']["connected"]:
+            raise Exception(
+                "Could not connect to database to delete subscription.")
+        self.schema_id = schema_info["schema_id"]
+        self.schema_name = schema_info["schema_name"]
+        schema_response = schema_utils.verify_schemas(self.server,
+                                                      self.db_name,
+                                                      self.schema_name)
+        if not schema_response:
+            raise Exception("Could not find the schema to "
+                            "delete subscription.")
+        self.table_name = "table_column_%s" % (str(uuid.uuid4())[1:8])
+        self.table_id = tables_utils.create_table(self.server, self.db_name,
+                                                  self.schema_name,
+                                                  self.table_name)
+        self.subscription_name = "test_subscription_delete_%s" % (
+            str(uuid.uuid4())[1:8])
+        self.subscription_name_1 = "test_subscription_delete_%s" % (
+            str(uuid.uuid4())[1:8])
+        self.subscription_ids = [
+            subscription_utils.create_subscription(self.server, self.db_name,
+                                                   self.subscription_name),
+            subscription_utils.create_subscription(self.server, self.db_name,
+                                                   self.subscription_name_1),
+        ]
+
+    def delete_multiple_subscription(self, data):
+        return self.tester.delete(
+            "{0}{1}/{2}/{3}/".format(self.url, utils.SERVER_GROUP,
+                                     self.server_id, self.db_id
+                                     ),
+            follow_redirects=True,
+            data=json.dumps(data),
+            content_type='html/json'
+        )
+
+    def runTest(self):
+        """This function will delete subscription."""
+        subscription_response = subscription_utils.verify_subscription(
+            self.server,
+            self.db_name,
+            self.subscription_name)
+        if not subscription_response:
+            raise Exception("Could not find the subscription to delete.")
+
+        subscription_response = subscription_utils.verify_subscription(
+            self.server,
+            self.db_name,
+            self.subscription_name_1)
+        if not subscription_response:
+            raise Exception("Could not find the subscription to delete.")
+
+        data = {'ids': self.subscription_ids}
+        if self.is_positive_test:
+            response = self.delete_multiple_subscription(data)
+        self.assertEqual(response.status_code,
+                         self.expected_data["status_code"])
+
+    def tearDown(self):
+        # Disconnect the database
+
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/test_subscription_get.py b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/test_subscription_get.py
new file mode 100644
index 000000000..b607c3399
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/test_subscription_get.py
@@ -0,0 +1,99 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import uuid
+from unittest.mock import patch
+
+from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \
+    import utils as tables_utils
+from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
+    utils as schema_utils
+from pgadmin.browser.server_groups.servers.databases.tests import utils as \
+    database_utils
+from pgadmin.utils.route import BaseTestGenerator
+from regression import parent_node_dict
+from regression.python_test_utils import test_utils as utils
+from . import utils as subscription_utils
+
+
+class PublicationGetTestCase(BaseTestGenerator):
+    """This class will fetch the subscription under table node."""
+    scenarios = utils.generate_scenarios('get_subscription',
+                                         subscription_utils.test_cases)
+
+    def setUp(self):
+        self.db_name = parent_node_dict["database"][-1]["db_name"]
+        schema_info = parent_node_dict["schema"][-1]
+        self.server_id = schema_info["server_id"]
+        self.db_id = schema_info["db_id"]
+        self.server_version = schema_info["server_version"]
+        if self.server_version < 99999:
+            self.skipTest(
+                "Logical replication is not supported "
+                "for server version less than 10"
+
+            )
+        db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
+                                                 self.server_id, self.db_id)
+        if not db_con['data']["connected"]:
+            raise Exception("Could not connect to database to "
+                            "delete subscription.")
+        self.schema_id = schema_info["schema_id"]
+        self.schema_name = schema_info["schema_name"]
+        schema_response = schema_utils.verify_schemas(self.server,
+                                                      self.db_name,
+                                                      self.schema_name)
+        if not schema_response:
+            raise Exception("Could not find the schema to delete "
+                            "subscription.")
+        self.table_name = "table_column_%s" % (str(uuid.uuid4())[1:8])
+        self.table_id = tables_utils.create_table(self.server, self.db_name,
+                                                  self.schema_name,
+                                                  self.table_name)
+        self.subscription_name = "test_subscription_delete_%s" % (
+            str(uuid.uuid4())[1:8])
+        self.subscription_id = subscription_utils.create_subscription(
+            self.server, self.db_name,
+            self.subscription_name)
+
+    def get_subscription(self):
+        return self.tester.get(
+            self.url + str(utils.SERVER_GROUP) + '/' + str(
+                self.server_id) + '/' +
+            str(self.db_id) + '/' + str(self.subscription_id),
+            content_type='html/json')
+
+    def runTest(self):
+        """This function will fetch the subscription."""
+
+        if self.is_positive_test:
+            if hasattr(self, "database_nodes"):
+                self.subscription_id = ''
+                response = self.get_subscription()
+            else:
+                response = self.get_subscription()
+        else:
+            if hasattr(self, "database_nodes"):
+                self.subscription_id = ''
+                with patch(self.mock_data["function_name"],
+                           return_value=eval(self.mock_data["return_value"])):
+                    response = self.get_subscription()
+            else:
+                with patch(self.mock_data["function_name"],
+                           return_value=eval(self.mock_data["return_value"])):
+                    response = self.get_subscription()
+
+        self.assertEqual(response.status_code,
+                         self.expected_data["status_code"])
+
+    def tearDown(self):
+        # Disconnect the database
+        subscription_utils.delete_subscription(self.server, self.db_name,
+                                               self.subscription_name)
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/test_subscription_put.py b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/test_subscription_put.py
new file mode 100644
index 000000000..2d479b1a3
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/test_subscription_put.py
@@ -0,0 +1,112 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import json
+import uuid
+from unittest.mock import patch
+
+from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \
+    import utils as tables_utils
+from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
+    utils as schema_utils
+from pgadmin.browser.server_groups.servers.databases.tests import utils as \
+    database_utils
+from pgadmin.utils.route import BaseTestGenerator
+from regression import parent_node_dict
+from regression.python_test_utils import test_utils as utils
+from . import utils as subscription_utils
+
+
+class PublicationUpdateTestCase(BaseTestGenerator):
+    """This class will update the subscription."""
+    scenarios = utils.generate_scenarios('update_subscription',
+                                         subscription_utils.test_cases)
+
+    def setUp(self):
+        self.db_name = parent_node_dict["database"][-1]["db_name"]
+        schema_info = parent_node_dict["schema"][-1]
+        self.server_id = schema_info["server_id"]
+        self.db_id = schema_info["db_id"]
+        self.server_version = schema_info["server_version"]
+        if self.server_version < 99999:
+            self.skipTest(
+                "Logical replication is not supported "
+                "for server version less than 10"
+
+            )
+        db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
+                                                 self.server_id, self.db_id)
+        if not db_con['data']["connected"]:
+            raise Exception(
+                "Could not connect to database to delete subscription.")
+        self.schema_id = schema_info["schema_id"]
+        self.schema_name = schema_info["schema_name"]
+        schema_response = schema_utils.verify_schemas(self.server,
+                                                      self.db_name,
+                                                      self.schema_name)
+        if not schema_response:
+            raise Exception("Could not find the schema to delete "
+                            "subscription.")
+        self.table_name = "table_column_%s" % (str(uuid.uuid4())[1:8])
+        self.table_id = tables_utils.create_table(self.server, self.db_name,
+                                                  self.schema_name,
+                                                  self.table_name)
+        self.subscription_name = "test_subscription_update_%s" % (
+            str(uuid.uuid4())[1:8])
+        self.subscription_id = \
+            subscription_utils.create_subscription(self.server, self.db_name,
+                                                   self.subscription_name)
+
+    def update_subscription(self, data):
+        return self.tester.put(
+            self.url + str(utils.SERVER_GROUP) + '/' +
+            str(self.server_id) + '/' + str(
+                self.db_id) +
+            '/' + str(self.subscription_id),
+            data=json.dumps(data),
+            follow_redirects=True)
+
+    def runTest(self):
+        """This function will update the subscription."""
+        subscription_name = \
+            subscription_utils.verify_subscription(self.server, self.db_name,
+                                                   self.subscription_name)
+        if hasattr(self, "update_name"):
+            self.subscription_name = "test_subscription_update_2_%s" % (
+                str(uuid.uuid4())[1:8])
+            self.test_data['name'] = self.subscription_name
+        else:
+            self.test_data['name'] = self.subscription_name
+        self.test_data['id'] = self.subscription_id
+
+        if not subscription_name:
+            raise Exception("Could not find the subscription to update.")
+
+        if self.is_positive_test:
+            if hasattr(self, "wrong_subscription_id"):
+                self.subscription_id = 9999
+            if hasattr(self, "plid_none"):
+                self.subscription_id = ''
+            response = self.update_subscription(self.test_data)
+        else:
+            with patch(self.mock_data["function_name"],
+                       return_value=eval(self.mock_data["return_value"])):
+                if hasattr(self, "wrong_subscription_id"):
+                    self.subscription_id = 9999
+                response = self.update_subscription(self.test_data)
+
+        self.assertEqual(response.status_code,
+                         self.expected_data["status_code"])
+
+    def tearDown(self):
+
+        # Disconnect the database
+        subscription_utils.delete_subscription(self.server, self.db_name,
+                                               self.subscription_name)
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/test_subscription_sql.py b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/test_subscription_sql.py
new file mode 100644
index 000000000..797df3323
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/test_subscription_sql.py
@@ -0,0 +1,88 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+import uuid
+from unittest.mock import patch
+from pgadmin.browser.server_groups.servers.databases.schemas.tables.tests \
+    import utils as tables_utils
+from pgadmin.browser.server_groups.servers.databases.schemas.tests import \
+    utils as schema_utils
+from pgadmin.browser.server_groups.servers.databases.tests import utils as \
+    database_utils
+from pgadmin.utils.route import BaseTestGenerator
+from regression import parent_node_dict
+from regression.python_test_utils import test_utils as utils
+from . import utils as subscription_utils
+
+
+class PublicationGetTestCase(BaseTestGenerator):
+    """This class will fetch the subscription under table node."""
+    scenarios = utils.generate_scenarios('sql_subscription',
+                                         subscription_utils.test_cases)
+
+    def setUp(self):
+        self.db_name = parent_node_dict["database"][-1]["db_name"]
+        schema_info = parent_node_dict["schema"][-1]
+        self.server_id = schema_info["server_id"]
+        self.db_id = schema_info["db_id"]
+        self.server_version = schema_info["server_version"]
+        if self.server_version < 99999:
+            self.skipTest(
+                "Logical replication is not supported "
+                "for server version less than 10"
+
+            )
+        db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
+                                                 self.server_id, self.db_id)
+        if not db_con['data']["connected"]:
+            raise Exception(
+                "Could not connect to database to delete subscription.")
+        self.schema_id = schema_info["schema_id"]
+        self.schema_name = schema_info["schema_name"]
+        schema_response = schema_utils.verify_schemas(self.server,
+                                                      self.db_name,
+                                                      self.schema_name)
+        if not schema_response:
+            raise Exception("Could not find the schema to delete "
+                            "subscription.")
+        self.table_name = "table_column_%s" % (str(uuid.uuid4())[1:8])
+        self.table_id = tables_utils.create_table(self.server, self.db_name,
+                                                  self.schema_name,
+                                                  self.table_name)
+        self.subscription_name = "test_subscription_delete_%s" % (
+            str(uuid.uuid4())[1:8])
+        self.subscription_id = \
+            subscription_utils.create_subscription(self.server, self.db_name,
+                                                   self.subscription_name)
+
+    def get_sql(self):
+        return self.tester.get(
+            self.url + str(utils.SERVER_GROUP) + '/' + str(
+                self.server_id) + '/' +
+            str(self.db_id) + '/' + str(self.subscription_id),
+            content_type='html/json')
+
+    def runTest(self):
+        """This function will fetch the subscription under table node."""
+
+        if self.is_positive_test:
+            response = self.get_sql()
+        else:
+            with patch(self.mock_data["function_name"],
+                       return_value=eval(self.mock_data["return_value"])):
+                response = self.get_sql()
+
+        self.assertEqual(response.status_code,
+                         self.expected_data["status_code"])
+
+    def tearDown(self):
+        subscription_utils.delete_subscription(self.server, self.db_name,
+                                               self.subscription_name)
+        # Disconnect the database
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/utils.py b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/utils.py
new file mode 100644
index 000000000..385d7c73e
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/subscriptions/tests/utils.py
@@ -0,0 +1,157 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+
+import sys
+import os
+import json
+import traceback
+
+from regression.python_test_utils import test_utils as utils
+
+CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
+with open(CURRENT_PATH + "/subscription_test_data.json") as data_file:
+    test_cases = json.load(data_file)
+
+
+def get_tables(self):
+    tables = self.tester.get(
+        '/browser/subscription/get_tables/' + str(
+            utils.SERVER_GROUP) + '/' + str(
+            self.server_id) + '/' +
+        str(self.db_id) + '/',
+        content_type='html/json')
+    return json.dumps([tables.json['data'][1]['value']])
+
+
+def create_subscription_api(self):
+    return self.tester.post(
+        self.url + str(utils.SERVER_GROUP) + '/' +
+        str(self.server_id) + '/' + str(
+            self.db_id) + '/',
+        data=json.dumps(self.test_data),
+        content_type='html/json')
+
+
+def create_subscription(server, db_name, subscription_name):
+    """
+    This function creates a subscription.
+    :param server: server details
+    :type server: dict
+    :param db_name: database name
+    :type db_name: str
+    :param subscription_name: subscription name
+    :type subscription_name: str
+    :return subscription_id: subscription id
+    :rtype: int
+    """
+    try:
+        connection = utils.get_db_connection(db_name,
+                                             server['username'],
+                                             server['db_password'],
+                                             server['host'],
+                                             server['port'],
+                                             server['sslmode'])
+        old_isolation_level = connection.isolation_level
+        connection.set_isolation_level(0)
+        pg_cursor = connection.cursor()
+        query = """CREATE SUBSCRIPTION "%s" """ \
+                """CONNECTION 'host=192.168.1.50 port=5432 user=foo """ \
+                """dbname=foodb' """ \
+                """PUBLICATION insert_only WITH (create_slot = false, """ \
+                """enabled = false, slot_name=NONE, connect=false);""" % (
+                    subscription_name)
+        pg_cursor.execute(query)
+        connection.set_isolation_level(old_isolation_level)
+        connection.commit()
+        # Get role oid of newly added subscription
+        pg_cursor.execute("select oid from pg_subscription sub where "
+                          "sub.subname='%s'" %
+                          subscription_name)
+        subscription = pg_cursor.fetchone()
+        subscription_id = ''
+        if subscription:
+            subscription_id = subscription[0]
+        connection.close()
+        return subscription_id
+    except Exception:
+        traceback.print_exc(file=sys.stderr)
+        raise
+
+
+def verify_subscription(server, db_name, subscription_name):
+    """
+    This function verifies subscription exist in database or not.
+    :param server: server details
+    :type server: dict
+    :param db_name: database name
+    :type db_name: str
+    :param subscription_name: subscription name
+    :type subscription_name: str
+    :return subscription: subscription record from database
+    :rtype: tuple
+    """
+    try:
+        connection = utils.get_db_connection(db_name,
+                                             server['username'],
+                                             server['db_password'],
+                                             server['host'],
+                                             server['port'],
+                                             server['sslmode'])
+        pg_cursor = connection.cursor()
+        pg_cursor.execute("select * from pg_subscription sub "
+                          "where sub.subname='%s'" %
+                          subscription_name)
+        subscription = pg_cursor.fetchone()
+        connection.close()
+        return subscription
+    except Exception:
+        traceback.print_exc(file=sys.stderr)
+        raise
+
+
+def delete_subscription(server, db_name, subscription_name):
+    """
+    This function use to delete the existing subscription
+
+    :param db_name:  db_name
+    :type db_name: db_name object
+    :param server: server
+    :type server: server object
+    :param subscription_name: subscription name
+    :type subscription_name: str
+    :return: None
+    """
+
+    try:
+        connection = utils.get_db_connection(db_name,
+                                             server['username'],
+                                             server['db_password'],
+                                             server['host'],
+                                             server['port'],
+                                             server['sslmode'])
+        pg_cursor = connection.cursor()
+
+        pg_cursor.execute("select * from pg_subscription sub where "
+                          "sub.subname='%s'" %
+                          subscription_name)
+        subscription_count = pg_cursor.fetchone()
+        if subscription_count:
+            old_isolation_level = connection.isolation_level
+            connection.set_isolation_level(0)
+            pg_cursor = connection.cursor()
+            query = "DROP subscription %s" % \
+                    (subscription_name)
+            pg_cursor.execute(query)
+            connection.set_isolation_level(old_isolation_level)
+            connection.commit()
+        connection.close()
+    except Exception:
+        traceback.print_exc(file=sys.stderr)
+        raise
diff --git a/web/pgadmin/static/js/browser/server_groups/servers/model_validation.js b/web/pgadmin/static/js/browser/server_groups/servers/model_validation.js
index 51b110847..3ff250bf7 100644
--- a/web/pgadmin/static/js/browser/server_groups/servers/model_validation.js
+++ b/web/pgadmin/static/js/browser/server_groups/servers/model_validation.js
@@ -19,7 +19,8 @@ export class ModelValidation {
   }
 
   validate() {
-    const serviceId = this.model.get('service');
+    const serviceId = this.model.get('service'),
+      pub = this.model.get('pub');
 
     if (!this.model.isNew() && 'id' in this.model.sessAttrs) {
       this.err['id'] = gettext('The ID cannot be changed.');
@@ -38,8 +39,14 @@ export class ModelValidation {
       this.checkForEmpty('db', gettext('Maintenance database must be specified.'));
       this.checkForEmpty('username', gettext('Username must be specified.'));
       this.checkForEmpty('port', gettext('Port must be specified.'));
+      if(!_.isUndefined(pub) && pub.length == 0){
+        this.checkForEmpty('pub', gettext('Publication must be specified.'));
+      }
     } else {
       this.checkForEmpty('db', gettext('Maintenance database must be specified.'));
+      if(!_.isUndefined(pub) && pub.length == 0){
+        this.checkForEmpty('pub', gettext('Publication must be specified.'));
+      }
       this.clearHostAddressAndDbErrors();
     }
 
diff --git a/web/pgadmin/tools/search_objects/templates/search_objects/sql/pg/10_plus/search.sql b/web/pgadmin/tools/search_objects/templates/search_objects/sql/pg/10_plus/search.sql
index ac215eefd..3828d9068 100644
--- a/web/pgadmin/tools/search_objects/templates/search_objects/sql/pg/10_plus/search.sql
+++ b/web/pgadmin/tools/search_objects/templates/search_objects/sql/pg/10_plus/search.sql
@@ -320,6 +320,24 @@ FROM (
 {% if all_obj %}
     UNION
 {% endif %}
+
+{% if all_obj or obj_type in ['publication'] %}
+    SELECT 'publication'::text AS obj_type, pubname AS obj_name, ':publication.'||pub.oid||':/' || pubname AS obj_path, ''::text AS schema_name,
+    {{ show_node_prefs['publication'] }} AS show_node, NULL AS other_info
+    FROM pg_publication pub
+{% endif %}
+{% if all_obj %}
+    UNION
+{% endif %}
+
+{% if all_obj or obj_type in ['subscription'] %}
+    SELECT 'subscription'::text AS obj_type, subname AS obj_name, ':subscription.'||pub.oid||':/' || subname AS obj_path, ''::text AS schema_name,
+    {{ show_node_prefs['subscription'] }} AS show_node, NULL AS other_info
+    FROM pg_subscription pub
+{% endif %}
+{% if all_obj %}
+    UNION
+{% endif %}
 {% if all_obj or obj_type in ['language'] %}
     SELECT 'language'::text AS obj_type, lanname AS obj_name, ':language.'||lan.oid||':/' || lanname AS obj_path, ''::text AS schema_name,
     {{ show_node_prefs['language'] }} AS show_node, NULL AS other_info
diff --git a/web/pgadmin/tools/search_objects/templates/search_objects/sql/pg/11_plus/search.sql b/web/pgadmin/tools/search_objects/templates/search_objects/sql/pg/11_plus/search.sql
index 4e07ff079..e8631f136 100644
--- a/web/pgadmin/tools/search_objects/templates/search_objects/sql/pg/11_plus/search.sql
+++ b/web/pgadmin/tools/search_objects/templates/search_objects/sql/pg/11_plus/search.sql
@@ -337,6 +337,24 @@ FROM (
 {% if all_obj %}
     UNION
 {% endif %}
+
+{% if all_obj or obj_type in ['publication'] %}
+    SELECT 'publication'::text AS obj_type, pubname AS obj_name, ':publication.'||pub.oid||':/' || pubname AS obj_path, ''::text AS schema_name,
+    {{ show_node_prefs['publication'] }} AS show_node, NULL AS other_info
+    FROM pg_publication pub
+{% endif %}
+{% if all_obj %}
+    UNION
+{% endif %}
+
+{% if all_obj or obj_type in ['subscription'] %}
+    SELECT 'subscription'::text AS obj_type, subname AS obj_name, ':subscription.'||pub.oid||':/' || subname AS obj_path, ''::text AS schema_name,
+    {{ show_node_prefs['subscription'] }} AS show_node, NULL AS other_info
+    FROM pg_subscription pub
+{% endif %}
+{% if all_obj %}
+    UNION
+{% endif %}
 {% if all_obj or obj_type in ['language'] %}
     SELECT 'language'::text AS obj_type, lanname AS obj_name, ':language.'||lan.oid||':/' || lanname AS obj_path, ''::text AS schema_name,
     {{ show_node_prefs['language'] }} AS show_node, NULL AS other_info
diff --git a/web/pgadmin/tools/search_objects/templates/search_objects/sql/ppas/10_plus/search.sql b/web/pgadmin/tools/search_objects/templates/search_objects/sql/ppas/10_plus/search.sql
index a41a375eb..4ed715463 100644
--- a/web/pgadmin/tools/search_objects/templates/search_objects/sql/ppas/10_plus/search.sql
+++ b/web/pgadmin/tools/search_objects/templates/search_objects/sql/ppas/10_plus/search.sql
@@ -361,6 +361,25 @@ FROM (
 {% if all_obj %}
     UNION
 {% endif %}
+
+{% if all_obj or obj_type in ['publication'] %}
+    SELECT 'publication'::text AS obj_type, pubname AS obj_name, ':publication.'||pub.oid||':/' || pubname AS obj_path, ''::text AS schema_name,
+    {{ show_node_prefs['publication'] }} AS show_node, NULL AS other_info
+    FROM pg_publication pub
+{% endif %}
+{% if all_obj %}
+    UNION
+{% endif %}
+
+{% if all_obj or obj_type in ['subscription'] %}
+    SELECT 'subscription'::text AS obj_type, subname AS obj_name, ':subscription.'||pub.oid||':/' || subname AS obj_path, ''::text AS schema_name,
+    {{ show_node_prefs['subscription'] }} AS show_node, NULL AS other_info
+    FROM pg_subscription pub
+{% endif %}
+{% if all_obj %}
+    UNION
+{% endif %}
+
 {% if all_obj or obj_type in ['language'] %}
     SELECT 'language'::text AS obj_type, lanname AS obj_name, ':language.'||lan.oid||':/' || lanname AS obj_path, ''::text AS schema_name,
     {{ show_node_prefs['language'] }} AS show_node, NULL AS other_info
diff --git a/web/pgadmin/tools/search_objects/templates/search_objects/sql/ppas/12_plus/search.sql b/web/pgadmin/tools/search_objects/templates/search_objects/sql/ppas/12_plus/search.sql
index 796fc30b8..3633974ec 100644
--- a/web/pgadmin/tools/search_objects/templates/search_objects/sql/ppas/12_plus/search.sql
+++ b/web/pgadmin/tools/search_objects/templates/search_objects/sql/ppas/12_plus/search.sql
@@ -368,6 +368,25 @@ FROM (
 {% if all_obj %}
     UNION
 {% endif %}
+
+{% if all_obj or obj_type in ['publication'] %}
+    SELECT 'publication'::text AS obj_type, pubname AS obj_name, ':publication.'||pub.oid||':/' || pubname AS obj_path, ''::text AS schema_name,
+    {{ show_node_prefs['publication'] }} AS show_node, NULL AS other_info
+    FROM pg_publication pub
+{% endif %}
+{% if all_obj %}
+    UNION
+{% endif %}
+
+{% if all_obj or obj_type in ['subscription'] %}
+    SELECT 'subscription'::text AS obj_type, subname AS obj_name, ':subscription.'||pub.oid||':/' || subname AS obj_path, ''::text AS schema_name,
+    {{ show_node_prefs['subscription'] }} AS show_node, NULL AS other_info
+    FROM pg_subscription pub
+{% endif %}
+{% if all_obj %}
+    UNION
+{% endif %}
+
 {% if all_obj or obj_type in ['language'] %}
     SELECT 'language'::text AS obj_type, lanname AS obj_name, ':language.'||lan.oid||':/' || lanname AS obj_path, ''::text AS schema_name,
     {{ show_node_prefs['language'] }} AS show_node, NULL AS other_info
diff --git a/web/pgadmin/tools/search_objects/utils.py b/web/pgadmin/tools/search_objects/utils.py
index ce5ece28d..a7d556853 100644
--- a/web/pgadmin/tools/search_objects/utils.py
+++ b/web/pgadmin/tools/search_objects/utils.py
@@ -43,7 +43,8 @@ class SearchObjectsHelper:
             'trigger_function', 'fts_template', 'collation', 'view', 'mview',
             'fts_configuration', 'extension', 'language',
             'event_trigger', 'foreign_server', 'user_mapping',
-            'foreign_data_wrapper', 'row_security_policy'
+            'foreign_data_wrapper', 'row_security_policy',
+            'publication', 'subscription'
         ] if node_types is None else node_types
 
     @property
diff --git a/web/webpack.config.js b/web/webpack.config.js
index 96ef708ab..7f210955e 100644
--- a/web/webpack.config.js
+++ b/web/webpack.config.js
@@ -432,6 +432,8 @@ module.exports = [{
         ',pgadmin.node.database' +
         ',pgadmin.node.role' +
         ',pgadmin.node.cast' +
+        ',pgadmin.node.publication' +
+        ',pgadmin.node.subscription' +
         ',pgadmin.node.tablespace' +
         ',pgadmin.node.resource_group' +
         ',pgadmin.node.event_trigger' +
diff --git a/web/webpack.shim.js b/web/webpack.shim.js
index 6fc26021e..259348533 100644
--- a/web/webpack.shim.js
+++ b/web/webpack.shim.js
@@ -202,6 +202,8 @@ var webpackShimConfig = {
     'pgadmin.help': path.join(__dirname, './pgadmin/help/static/js/help'),
     'pgadmin.misc.explain': path.join(__dirname, './pgadmin/misc/static/explain/js/explain'),
     'pgadmin.node.cast': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/casts/static/js/cast'),
+    'pgadmin.node.publication': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/publications/static/js/publication'),
+    'pgadmin.node.subscription': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/subscriptions/static/js/subscription'),
     'pgadmin.node.catalog': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/static/js/catalog'),
     'pgadmin.node.catalog_object': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/catalog_objects/static/js/catalog_object'),
     'pgadmin.node.catalog_object_column': path.join(__dirname, './pgadmin/browser/server_groups/servers/databases/schemas/catalog_objects/columns/static/js/catalog_object_column'),


view thread (7+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected], [email protected]
  Subject: Re: [pgAdmin][RM5912]: Added support for Logical Replication.
  In-Reply-To: <CAJ9T6SveOoLxbyKivRzweCvOabv991rSYSN9D5WX98hg91m6CA@mail.gmail.com>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox