diff --git a/changelog/entries/unreleased/feature/1901_introduce_workspace_level_audit_log_feature.json b/changelog/entries/unreleased/feature/1901_introduce_workspace_level_audit_log_feature.json
new file mode 100644
index 000000000..9dc1a6c59
--- /dev/null
+++ b/changelog/entries/unreleased/feature/1901_introduce_workspace_level_audit_log_feature.json
@@ -0,0 +1,7 @@
+{
+    "type": "feature",
+    "message": "Introduce Workspace level audit log feature",
+    "issue_number": 1901,
+    "bullet_points": [],
+    "created_at": "2023-08-09"
+}
\ No newline at end of file
diff --git a/enterprise/backend/src/baserow_enterprise/api/admin/audit_log/urls.py b/enterprise/backend/src/baserow_enterprise/api/admin/audit_log/urls.py
index 0875e08c2..259b5894b 100755
--- a/enterprise/backend/src/baserow_enterprise/api/admin/audit_log/urls.py
+++ b/enterprise/backend/src/baserow_enterprise/api/admin/audit_log/urls.py
@@ -1,26 +1,21 @@
 from django.urls import re_path
 
-from .views import (
-    AdminAuditLogActionTypeFilterView,
-    AdminAuditLogUserFilterView,
-    AdminAuditLogView,
-    AdminAuditLogWorkspaceFilterView,
+from baserow_enterprise.api.audit_log.views import (
     AsyncAuditLogExportView,
+    AuditLogActionTypeFilterView,
+    AuditLogUserFilterView,
+    AuditLogView,
+    AuditLogWorkspaceFilterView,
 )
 
 app_name = "baserow_enterprise.api.audit_log"
 
 urlpatterns = [
-    re_path(r"^$", AdminAuditLogView.as_view(), name="list"),
-    re_path(r"users/$", AdminAuditLogUserFilterView.as_view(), name="users"),
+    re_path(r"^$", AuditLogView.as_view(), name="list"),
+    re_path(r"users/$", AuditLogUserFilterView.as_view(), name="users"),
+    re_path(r"workspaces/$", AuditLogWorkspaceFilterView.as_view(), name="workspaces"),
     re_path(
-        r"workspaces/$", AdminAuditLogWorkspaceFilterView.as_view(), name="workspaces"
-    ),
-    # GroupDeprecation
-    re_path(
-        r"action-types/$",
-        AdminAuditLogActionTypeFilterView.as_view(),
-        name="action_types",
+        r"action-types/$", AuditLogActionTypeFilterView.as_view(), name="action_types"
     ),
     re_path(r"export/$", AsyncAuditLogExportView.as_view(), name="export"),
 ]
diff --git a/enterprise/backend/src/baserow_enterprise/api/admin/audit_log/views.py b/enterprise/backend/src/baserow_enterprise/api/admin/audit_log/views.py
deleted file mode 100755
index 2c79c6336..000000000
--- a/enterprise/backend/src/baserow_enterprise/api/admin/audit_log/views.py
+++ /dev/null
@@ -1,214 +0,0 @@
-from django.contrib.auth import get_user_model
-from django.db import transaction
-from django.utils import translation
-
-from baserow_premium.api.admin.views import AdminListingView
-from baserow_premium.license.handler import LicenseHandler
-from drf_spectacular.utils import extend_schema
-from rest_framework.permissions import IsAdminUser
-from rest_framework.response import Response
-from rest_framework.status import HTTP_202_ACCEPTED
-from rest_framework.views import APIView
-
-from baserow.api.decorators import (
-    map_exceptions,
-    validate_body,
-    validate_query_parameters,
-)
-from baserow.api.jobs.errors import ERROR_MAX_JOB_COUNT_EXCEEDED
-from baserow.api.jobs.serializers import JobSerializer
-from baserow.api.schemas import CLIENT_SESSION_ID_SCHEMA_PARAMETER, get_error_schema
-from baserow.core.action.registries import action_type_registry
-from baserow.core.jobs.exceptions import MaxJobCountExceeded
-from baserow.core.jobs.handler import JobHandler
-from baserow.core.jobs.registries import job_type_registry
-from baserow.core.models import Workspace
-from baserow_enterprise.audit_log.job_types import AuditLogExportJobType
-from baserow_enterprise.audit_log.models import AuditLogEntry
-from baserow_enterprise.features import AUDIT_LOG
-
-from .serializers import (
-    AuditLogActionTypeSerializer,
-    AuditLogExportJobRequestSerializer,
-    AuditLogExportJobResponseSerializer,
-    AuditLogQueryParamsSerializer,
-    AuditLogSerializer,
-    AuditLogUserSerializer,
-    AuditLogWorkspaceSerializer,
-)
-
-User = get_user_model()
-
-
-class AdminAuditLogView(AdminListingView):
-    permission_classes = (IsAdminUser,)
-    serializer_class = AuditLogSerializer
-    filters_field_mapping = {
-        "user_id": "user_id",
-        "workspace_id": "workspace_id",
-        "action_type": "action_type",
-        "from_timestamp": "action_timestamp__gte",
-        "to_timestamp": "action_timestamp__lte",
-        "ip_address": "ip_address",
-    }
-    sort_field_mapping = {
-        "user": "user_email",
-        "workspace": "workspace_name",
-        "type": "action_type",
-        "timestamp": "action_timestamp",
-        "ip_address": "ip_address",
-    }
-    default_order_by = "-action_timestamp"
-
-    def get_queryset(self, request):
-        return AuditLogEntry.objects.all()
-
-    def get_serializer(self, request, *args, **kwargs):
-        return super().get_serializer(
-            request, *args, context={"request": request}, **kwargs
-        )
-
-    @extend_schema(
-        tags=["Admin"],
-        operation_id="admin_audit_log",
-        description="Lists all audit log entries.",
-        **AdminListingView.get_extend_schema_parameters(
-            "audit_log_entries", serializer_class, [], sort_field_mapping
-        ),
-    )
-    @validate_query_parameters(AuditLogQueryParamsSerializer)
-    def get(self, request, query_params):
-        LicenseHandler.raise_if_user_doesnt_have_feature_instance_wide(
-            AUDIT_LOG, request.user
-        )
-        with translation.override(request.user.profile.language):
-            return super().get(request)
-
-
-class AdminAuditLogUserFilterView(AdminListingView):
-    permission_classes = (IsAdminUser,)
-    serializer_class = AuditLogUserSerializer
-    search_fields = ["email"]
-    default_order_by = "email"
-
-    def get_queryset(self, request):
-        return User.objects.all()
-
-    @extend_schema(
-        tags=["Admin"],
-        operation_id="admin_audit_log_users",
-        description="List all users that have performed an action in the audit log.",
-        **AdminListingView.get_extend_schema_parameters(
-            "users", serializer_class, search_fields, {}
-        ),
-    )
-    def get(self, request):
-        LicenseHandler.raise_if_user_doesnt_have_feature_instance_wide(
-            AUDIT_LOG, request.user
-        )
-        return super().get(request)
-
-
-class AdminAuditLogWorkspaceFilterView(AdminListingView):
-    permission_classes = (IsAdminUser,)
-    serializer_class = AuditLogWorkspaceSerializer
-    search_fields = ["name"]
-    default_order_by = "name"
-
-    def get_queryset(self, request):
-        return Workspace.objects.filter(template__isnull=True)
-
-    @extend_schema(
-        tags=["Admin"],
-        operation_id="admin_audit_log_workspaces",
-        description="List all distinct workspace names related to an audit log entry.",
-        **AdminListingView.get_extend_schema_parameters(
-            "workspaces", serializer_class, search_fields, {}
-        ),
-    )
-    def get(self, request):
-        LicenseHandler.raise_if_user_doesnt_have_feature_instance_wide(
-            AUDIT_LOG, request.user
-        )
-        return super().get(request)
-
-
-class AdminAuditLogActionTypeFilterView(APIView):
-    permission_classes = (IsAdminUser,)
-    serializer_class = AuditLogActionTypeSerializer
-
-    def filter_action_types(self, action_types, search):
-        search_lower = search.lower()
-        return [
-            action_type
-            for action_type in action_types
-            if search_lower in action_type["value"].lower()
-        ]
-
-    @extend_schema(
-        tags=["Admin"],
-        operation_id="admin_audit_log_types",
-        description="List all distinct action types related to an audit log entry.",
-    )
-    def get(self, request):
-        LicenseHandler.raise_if_user_doesnt_have_feature_instance_wide(
-            AUDIT_LOG, request.user
-        )
-
-        # Since action's type is translated at runtime and there aren't that
-        # many, we can fetch them all and filter them in memory to match the
-        # search query on the translated value.
-        with translation.override(request.user.profile.language):
-            search = request.GET.get("search")
-
-            action_types = AuditLogActionTypeSerializer(
-                action_type_registry.get_all(), many=True
-            ).data
-
-            if search:
-                action_types = self.filter_action_types(action_types, search)
-
-            return Response(
-                {
-                    "count": len(action_types),
-                    "next": None,
-                    "previous": None,
-                    "results": sorted(action_types, key=lambda x: x["value"]),
-                }
-            )
-
-
-class AsyncAuditLogExportView(APIView):
-    permission_classes = (IsAdminUser,)
-
-    @extend_schema(
-        parameters=[CLIENT_SESSION_ID_SCHEMA_PARAMETER],
-        tags=["Audit log export"],
-        operation_id="export_audit_log",
-        description=("Creates a job to export the filtered audit log to a CSV file."),
-        request=AuditLogExportJobRequestSerializer,
-        responses={
-            202: AuditLogExportJobResponseSerializer,
-            400: get_error_schema(
-                ["ERROR_REQUEST_BODY_VALIDATION", "ERROR_MAX_JOB_COUNT_EXCEEDED"]
-            ),
-        },
-    )
-    @transaction.atomic
-    @map_exceptions({MaxJobCountExceeded: ERROR_MAX_JOB_COUNT_EXCEEDED})
-    @validate_body(AuditLogExportJobRequestSerializer)
-    def post(self, request, data):
-        """Creates a job to export the filtered audit log entries to a CSV file."""
-
-        LicenseHandler.raise_if_user_doesnt_have_feature_instance_wide(
-            AUDIT_LOG, request.user
-        )
-
-        csv_export_job = JobHandler().create_and_start_job(
-            request.user, AuditLogExportJobType.type, **data
-        )
-
-        serializer = job_type_registry.get_serializer(
-            csv_export_job, JobSerializer, context={"request": request}
-        )
-        return Response(serializer.data, status=HTTP_202_ACCEPTED)
diff --git a/enterprise/backend/src/baserow_enterprise/api/admin/audit_log/__init__.py b/enterprise/backend/src/baserow_enterprise/api/audit_log/__init__.py
similarity index 100%
rename from enterprise/backend/src/baserow_enterprise/api/admin/audit_log/__init__.py
rename to enterprise/backend/src/baserow_enterprise/api/audit_log/__init__.py
diff --git a/enterprise/backend/src/baserow_enterprise/api/admin/audit_log/serializers.py b/enterprise/backend/src/baserow_enterprise/api/audit_log/serializers.py
old mode 100755
new mode 100644
similarity index 73%
rename from enterprise/backend/src/baserow_enterprise/api/admin/audit_log/serializers.py
rename to enterprise/backend/src/baserow_enterprise/api/audit_log/serializers.py
index 6fdd413c2..b1e546d69
--- a/enterprise/backend/src/baserow_enterprise/api/admin/audit_log/serializers.py
+++ b/enterprise/backend/src/baserow_enterprise/api/audit_log/serializers.py
@@ -1,4 +1,5 @@
 from django.contrib.auth import get_user_model
+from django.utils import translation
 from django.utils.functional import lazy
 
 from drf_spectacular.types import OpenApiTypes
@@ -26,6 +27,25 @@ def render_action_type(action_type):
     return action_type_registry.get(action_type).get_short_description()
 
 
+class AuditLogQueryParamsSerializer(serializers.Serializer):
+    page = serializers.IntegerField(required=False, default=1)
+    search = serializers.CharField(required=False, default=None)
+    sorts = serializers.CharField(required=False, default=None)
+    user_id = serializers.IntegerField(min_value=1, required=False, default=None)
+    workspace_id = serializers.IntegerField(min_value=1, required=False, default=None)
+    action_type = serializers.ChoiceField(
+        choices=lazy(action_type_registry.get_types, list)(),
+        default=None,
+        required=False,
+    )
+    from_timestamp = serializers.DateTimeField(required=False, default=None)
+    to_timestamp = serializers.DateTimeField(required=False, default=None)
+
+
+class AuditLogWorkspaceFilterQueryParamsSerializer(serializers.Serializer):
+    workspace_id = serializers.IntegerField(min_value=1, required=False, default=None)
+
+
 class AuditLogSerializer(serializers.ModelSerializer):
     user = serializers.SerializerMethodField()
     group = serializers.SerializerMethodField()  # GroupDeprecation
@@ -34,14 +54,6 @@ class AuditLogSerializer(serializers.ModelSerializer):
     description = serializers.SerializerMethodField()
     timestamp = serializers.DateTimeField(source="action_timestamp")
 
-    @extend_schema_field(OpenApiTypes.STR)
-    def get_group(self, instance):  # GroupDeprecation
-        return self.get_workspace(instance)
-
-    @extend_schema_field(OpenApiTypes.STR)
-    def get_workspace(self, instance):
-        return render_workspace(instance.workspace_id, instance.workspace_name)
-
     @extend_schema_field(OpenApiTypes.STR)
     def get_user(self, instance):
         return render_user(instance.user_id, instance.user_email)
@@ -54,6 +66,14 @@ class AuditLogSerializer(serializers.ModelSerializer):
     def get_description(self, instance):
         return instance.description
 
+    @extend_schema_field(OpenApiTypes.STR)
+    def get_group(self, instance):  # GroupDeprecation
+        return self.get_workspace(instance)
+
+    @extend_schema_field(OpenApiTypes.STR)
+    def get_workspace(self, instance):
+        return render_workspace(instance.workspace_id, instance.workspace_name)
+
     class Meta:
         model = AuditLogEntry
         fields = (
@@ -98,6 +118,42 @@ class AuditLogActionTypeSerializer(serializers.Serializer):
         return render_action_type(instance.type)
 
 
+def serialize_filtered_action_types(user, search=None, exclude_types=None):
+    exclude_types = exclude_types or []
+
+    def filter_action_types(action_types, search):
+        search_lower = search.lower()
+        return [
+            action_type
+            for action_type in action_types
+            if search_lower in action_type["value"].lower()
+        ]
+
+    # Since action's type is translated at runtime and there aren't that
+    # many, we can fetch them all and filter them in memory to match the
+    # search query on the translated value.
+    with translation.override(user.profile.language):
+        filtered_action_types = [
+            action_type
+            for action_type in action_type_registry.get_all()
+            if action_type.type not in exclude_types
+        ]
+
+        action_types = AuditLogActionTypeSerializer(
+            filtered_action_types, many=True
+        ).data
+
+        if search:
+            action_types = filter_action_types(action_types, search)
+
+        return {
+            "count": len(action_types),
+            "next": None,
+            "previous": None,
+            "results": sorted(action_types, key=lambda x: x["value"]),
+        }
+
+
 AuditLogExportJobRequestSerializer = job_type_registry.get(
     AuditLogExportJobType.type
 ).get_serializer_class(
@@ -112,18 +168,3 @@ AuditLogExportJobResponseSerializer = job_type_registry.get(
     base_class=serializers.Serializer,
     meta_ref_name="SingleAuditLogExportJobResponseSerializer",
 )
-
-
-class AuditLogQueryParamsSerializer(serializers.Serializer):
-    page = serializers.IntegerField(required=False, default=1)
-    search = serializers.CharField(required=False, default=None)
-    sorts = serializers.CharField(required=False, default=None)
-    user_id = serializers.IntegerField(min_value=0, required=False, default=None)
-    workspace_id = serializers.IntegerField(min_value=0, required=False, default=None)
-    action_type = serializers.ChoiceField(
-        choices=lazy(action_type_registry.get_types, list)(),
-        default=None,
-        required=False,
-    )
-    from_timestamp = serializers.DateTimeField(required=False, default=None)
-    to_timestamp = serializers.DateTimeField(required=False, default=None)
diff --git a/enterprise/backend/src/baserow_enterprise/api/audit_log/urls.py b/enterprise/backend/src/baserow_enterprise/api/audit_log/urls.py
new file mode 100755
index 000000000..e631b2251
--- /dev/null
+++ b/enterprise/backend/src/baserow_enterprise/api/audit_log/urls.py
@@ -0,0 +1,25 @@
+from django.urls import re_path
+
+from .views import (
+    AsyncAuditLogExportView,
+    AuditLogActionTypeFilterView,
+    AuditLogUserFilterView,
+    AuditLogView,
+    AuditLogWorkspaceFilterView,
+)
+
+app_name = "baserow_enterprise.api.audit_log"
+
+urlpatterns = [
+    re_path(r"^$", AuditLogView.as_view(), name="list"),
+    re_path(r"users/$", AuditLogUserFilterView.as_view(), name="users"),
+    re_path(r"workspaces/$", AuditLogWorkspaceFilterView.as_view(), name="workspaces"),
+    re_path(
+        r"action-types/$", AuditLogActionTypeFilterView.as_view(), name="action_types"
+    ),
+    re_path(
+        r"export/$",
+        AsyncAuditLogExportView.as_view(),
+        name="async_export",
+    ),
+]
diff --git a/enterprise/backend/src/baserow_enterprise/api/audit_log/views.py b/enterprise/backend/src/baserow_enterprise/api/audit_log/views.py
new file mode 100755
index 000000000..c4578fcee
--- /dev/null
+++ b/enterprise/backend/src/baserow_enterprise/api/audit_log/views.py
@@ -0,0 +1,345 @@
+from typing import Optional
+
+from django.contrib.auth.models import AbstractBaseUser
+from django.db import transaction
+from django.utils import translation
+
+from baserow_premium.api.admin.views import APIListingView
+from baserow_premium.license.handler import LicenseHandler
+from drf_spectacular.types import OpenApiTypes
+from drf_spectacular.utils import OpenApiParameter, extend_schema
+from rest_framework.exceptions import PermissionDenied
+from rest_framework.permissions import IsAdminUser, IsAuthenticated
+from rest_framework.response import Response
+from rest_framework.status import HTTP_202_ACCEPTED
+from rest_framework.views import APIView
+
+from baserow.api.decorators import (
+    map_exceptions,
+    validate_body,
+    validate_query_parameters,
+)
+from baserow.api.errors import ERROR_GROUP_DOES_NOT_EXIST
+from baserow.api.jobs.errors import ERROR_MAX_JOB_COUNT_EXCEEDED
+from baserow.api.jobs.serializers import JobSerializer
+from baserow.api.schemas import CLIENT_SESSION_ID_SCHEMA_PARAMETER, get_error_schema
+from baserow.core.actions import DeleteWorkspaceActionType, OrderWorkspacesActionType
+from baserow.core.exceptions import WorkspaceDoesNotExist
+from baserow.core.handler import CoreHandler
+from baserow.core.jobs.exceptions import MaxJobCountExceeded
+from baserow.core.jobs.handler import JobHandler
+from baserow.core.jobs.registries import job_type_registry
+from baserow.core.models import User, Workspace
+from baserow_enterprise.audit_log.job_types import AuditLogExportJobType
+from baserow_enterprise.audit_log.models import AuditLogEntry
+from baserow_enterprise.audit_log.operations import (
+    ListWorkspaceAuditLogEntriesOperationType,
+)
+from baserow_enterprise.features import AUDIT_LOG
+
+from .serializers import (
+    AuditLogActionTypeSerializer,
+    AuditLogExportJobRequestSerializer,
+    AuditLogExportJobResponseSerializer,
+    AuditLogQueryParamsSerializer,
+    AuditLogSerializer,
+    AuditLogUserSerializer,
+    AuditLogWorkspaceFilterQueryParamsSerializer,
+    AuditLogWorkspaceSerializer,
+    serialize_filtered_action_types,
+)
+
+
+def check_for_license_and_permissions_or_raise(
+    user: AbstractBaseUser, workspace_id: Optional[int] = None
+):
+    """
+    Check if the user has the feature enabled and has the correct permissions to list
+    audit log entries. If not, an exception is raised.
+    """
+
+    if user.is_staff:
+        LicenseHandler.raise_if_user_doesnt_have_feature_instance_wide(AUDIT_LOG, user)
+        return True
+    elif workspace_id is not None:
+        workspace = CoreHandler().get_workspace(workspace_id)
+        LicenseHandler.raise_if_user_doesnt_have_feature(AUDIT_LOG, user, workspace)
+        CoreHandler().check_permissions(
+            user,
+            ListWorkspaceAuditLogEntriesOperationType.type,
+            workspace=workspace,
+            context=workspace,
+        )
+    else:
+        raise PermissionDenied()
+
+
+class AuditLogView(APIListingView):
+    permission_classes = (IsAuthenticated,)
+    serializer_class = AuditLogSerializer
+    filters_field_mapping = {
+        "user_id": "user_id",
+        "workspace_id": "workspace_id",
+        "action_type": "action_type",
+        "from_timestamp": "action_timestamp__gte",
+        "to_timestamp": "action_timestamp__lte",
+        "ip_address": "ip_address",
+    }
+    sort_field_mapping = {
+        "user": "user_email",
+        "workspace": "workspace_name",
+        "type": "action_type",
+        "timestamp": "action_timestamp",
+        "ip_address": "ip_address",
+    }
+    default_order_by = "-action_timestamp"
+
+    def get_queryset(self, request):
+        return AuditLogEntry.objects.all()
+
+    def get_serializer(self, request, *args, **kwargs):
+        return super().get_serializer(
+            request, *args, context={"request": request}, **kwargs
+        )
+
+    @extend_schema(
+        tags=["Audit log"],
+        operation_id="audit_log_list",
+        description=(
+            "Lists all audit log entries for the given workspace id."
+            "\n\nThis is a **enterprise** feature."
+        ),
+        **APIListingView.get_extend_schema_parameters(
+            "audit log entries",
+            serializer_class,
+            [],
+            sort_field_mapping,
+            extra_parameters=[
+                OpenApiParameter(
+                    name="user_id",
+                    location=OpenApiParameter.QUERY,
+                    type=OpenApiTypes.INT,
+                    description="Filter the audit log entries by user id.",
+                ),
+                OpenApiParameter(
+                    name="workspace_id",
+                    location=OpenApiParameter.QUERY,
+                    type=OpenApiTypes.INT,
+                    description=(
+                        "Filter the audit log entries by workspace id. "
+                        "This filter works only for the admin audit log."
+                    ),
+                ),
+                OpenApiParameter(
+                    name="action_type",
+                    location=OpenApiParameter.QUERY,
+                    type=OpenApiTypes.STR,
+                    description="Filter the audit log entries by action type.",
+                ),
+                OpenApiParameter(
+                    name="from_timestamp",
+                    location=OpenApiParameter.QUERY,
+                    type=OpenApiTypes.STR,
+                    description="The ISO timestamp to filter the audit log entries from.",
+                ),
+                OpenApiParameter(
+                    name="to_timestamp",
+                    location=OpenApiParameter.QUERY,
+                    type=OpenApiTypes.STR,
+                    description="The ISO timestamp to filter the audit log entries to.",
+                ),
+            ],
+        ),
+    )
+    @map_exceptions(
+        {
+            WorkspaceDoesNotExist: ERROR_GROUP_DOES_NOT_EXIST,
+        }
+    )
+    @validate_query_parameters(AuditLogQueryParamsSerializer)
+    def get(self, request, query_params):
+        workspace_id = query_params.get("workspace_id", None)
+        check_for_license_and_permissions_or_raise(request.user, workspace_id)
+
+        with translation.override(request.user.profile.language):
+            return super().get(request)
+
+
+class AuditLogActionTypeFilterView(APIView):
+    permission_classes = (IsAuthenticated,)
+    serializer_class = AuditLogActionTypeSerializer
+
+    @extend_schema(
+        tags=["Audit log"],
+        operation_id="audit_log_action_types",
+        description=(
+            "List all distinct action types related to an audit log entry."
+            "\n\nThis is a **enterprise** feature."
+        ),
+        parameters=[
+            OpenApiParameter(
+                name="search",
+                location=OpenApiParameter.QUERY,
+                type=OpenApiTypes.STR,
+                description="If provided only action_types with name "
+                "that match the query will be returned.",
+            ),
+            OpenApiParameter(
+                name="workspace_id",
+                location=OpenApiParameter.QUERY,
+                type=OpenApiTypes.INT,
+                description=("Return action types related to the workspace."),
+            ),
+        ],
+        responses={
+            200: serializer_class(many=True),
+            400: get_error_schema(
+                [
+                    "ERROR_PAGE_SIZE_LIMIT",
+                    "ERROR_INVALID_PAGE",
+                    "ERROR_INVALID_SORT_DIRECTION",
+                    "ERROR_INVALID_SORT_ATTRIBUTE",
+                ]
+            ),
+            401: None,
+        },
+    )
+    @map_exceptions(
+        {
+            WorkspaceDoesNotExist: ERROR_GROUP_DOES_NOT_EXIST,
+        }
+    )
+    @validate_query_parameters(AuditLogWorkspaceFilterQueryParamsSerializer)
+    def get(self, request, query_params):
+        workspace_id = query_params.get("workspace_id", None)
+
+        check_for_license_and_permissions_or_raise(
+            request.user, workspace_id=workspace_id
+        )
+        search = request.GET.get("search", None)
+
+        exclude_types = []
+        if workspace_id is not None:
+            exclude_types += [
+                DeleteWorkspaceActionType.type,
+                OrderWorkspacesActionType.type,
+            ]
+
+        return Response(
+            serialize_filtered_action_types(request.user, search, exclude_types)
+        )
+
+
+class AuditLogUserFilterView(APIListingView):
+    permission_classes = (IsAuthenticated,)
+    serializer_class = AuditLogUserSerializer
+    filters_field_mapping = {"workspace_id": "workspaceuser__workspace_id"}
+    search_fields = ["email"]
+    default_order_by = "email"
+
+    def get_queryset(self, request):
+        return User.objects.all()
+
+    @extend_schema(
+        tags=["Audit log"],
+        operation_id="audit_log_users",
+        description=(
+            "List all users that have performed an action in the audit log."
+            "\n\nThis is a **enterprise** feature."
+        ),
+        **APIListingView.get_extend_schema_parameters(
+            "users",
+            serializer_class,
+            search_fields,
+            {},
+            extra_parameters=[
+                OpenApiParameter(
+                    name="workspace_id",
+                    location=OpenApiParameter.QUERY,
+                    type=OpenApiTypes.INT,
+                    description=("Return users belonging to the given workspace_id."),
+                ),
+            ],
+        ),
+    )
+    @map_exceptions(
+        {
+            WorkspaceDoesNotExist: ERROR_GROUP_DOES_NOT_EXIST,
+        }
+    )
+    @validate_query_parameters(AuditLogWorkspaceFilterQueryParamsSerializer)
+    def get(self, request, query_params):
+        workspace_id = query_params.get("workspace_id", None)
+        check_for_license_and_permissions_or_raise(request.user, workspace_id)
+        return super().get(request)
+
+
+class AuditLogWorkspaceFilterView(APIListingView):
+    permission_classes = (IsAdminUser,)
+    serializer_class = AuditLogWorkspaceSerializer
+    search_fields = ["name"]
+    default_order_by = "name"
+
+    def get_queryset(self, request):
+        return Workspace.objects.filter(template__isnull=True)
+
+    @extend_schema(
+        tags=["Audit log"],
+        operation_id="audit_log_workspaces",
+        description=(
+            "List all distinct workspace names related to an audit log entry."
+            "\n\nThis is a **enterprise** feature."
+        ),
+        **APIListingView.get_extend_schema_parameters(
+            "workspaces", serializer_class, search_fields, {}
+        ),
+    )
+    def get(self, request):
+        LicenseHandler.raise_if_user_doesnt_have_feature_instance_wide(
+            AUDIT_LOG, request.user
+        )
+        return super().get(request)
+
+
+class AsyncAuditLogExportView(APIView):
+    permission_classes = (IsAuthenticated,)
+
+    @extend_schema(
+        parameters=[CLIENT_SESSION_ID_SCHEMA_PARAMETER],
+        tags=["Audit log"],
+        operation_id="async_audit_log_export",
+        description=(
+            "Creates a job to export the filtered audit log to a CSV file."
+            "\n\nThis is a **enterprise** feature."
+        ),
+        request=AuditLogExportJobRequestSerializer,
+        responses={
+            202: AuditLogExportJobResponseSerializer,
+            400: get_error_schema(
+                ["ERROR_REQUEST_BODY_VALIDATION", "ERROR_MAX_JOB_COUNT_EXCEEDED"]
+            ),
+            404: get_error_schema(["ERROR_GROUP_DOES_NOT_EXIST"]),
+        },
+    )
+    @transaction.atomic
+    @map_exceptions(
+        {
+            MaxJobCountExceeded: ERROR_MAX_JOB_COUNT_EXCEEDED,
+            WorkspaceDoesNotExist: ERROR_GROUP_DOES_NOT_EXIST,
+        }
+    )
+    @validate_body(AuditLogExportJobRequestSerializer, return_validated=True)
+    def post(self, request, data):
+        """Creates a job to export the filtered audit log entries to a CSV file."""
+
+        workspace_id = data.get("filter_workspace_id", None)
+        check_for_license_and_permissions_or_raise(request.user, workspace_id)
+
+        csv_export_job = JobHandler().create_and_start_job(
+            request.user, AuditLogExportJobType.type, **data
+        )
+
+        serializer = job_type_registry.get_serializer(
+            csv_export_job, JobSerializer, context={"request": request}
+        )
+        return Response(serializer.data, status=HTTP_202_ACCEPTED)
diff --git a/enterprise/backend/src/baserow_enterprise/api/urls.py b/enterprise/backend/src/baserow_enterprise/api/urls.py
index 13d43866c..e84e96bd2 100644
--- a/enterprise/backend/src/baserow_enterprise/api/urls.py
+++ b/enterprise/backend/src/baserow_enterprise/api/urls.py
@@ -1,6 +1,7 @@
 from django.urls import include, path
 
 from .admin import urls as admin_urls
+from .audit_log import urls as audit_log_urls
 from .role import urls as role_urls
 from .sso import urls as sso_urls
 from .teams import urls as teams_urls
@@ -12,4 +13,5 @@ urlpatterns = [
     path("role/", include(role_urls, namespace="role")),
     path("admin/", include(admin_urls, namespace="admin")),
     path("sso/", include(sso_urls, namespace="sso")),
+    path("audit-log/", include(audit_log_urls, namespace="audit_log")),
 ]
diff --git a/enterprise/backend/src/baserow_enterprise/apps.py b/enterprise/backend/src/baserow_enterprise/apps.py
index 53429f5f1..cedb22127 100755
--- a/enterprise/backend/src/baserow_enterprise/apps.py
+++ b/enterprise/backend/src/baserow_enterprise/apps.py
@@ -10,6 +10,9 @@ class BaserowEnterpriseConfig(AppConfig):
     def ready(self):
         from baserow.core.jobs.registries import job_type_registry
         from baserow_enterprise.audit_log.job_types import AuditLogExportJobType
+        from baserow_enterprise.audit_log.operations import (
+            ListWorkspaceAuditLogEntriesOperationType,
+        )
 
         job_type_registry.register(AuditLogExportJobType())
 
@@ -101,6 +104,7 @@ class BaserowEnterpriseConfig(AppConfig):
         operation_type_registry.register(UpdateRoleApplicationOperationType())
         operation_type_registry.register(ReadRoleTableOperationType())
         operation_type_registry.register(UpdateRoleTableOperationType())
+        operation_type_registry.register(ListWorkspaceAuditLogEntriesOperationType())
 
         from baserow.core.registries import subject_type_registry
 
diff --git a/enterprise/backend/src/baserow_enterprise/audit_log/handler.py b/enterprise/backend/src/baserow_enterprise/audit_log/handler.py
index bfc4aa7ca..0d5553e58 100755
--- a/enterprise/backend/src/baserow_enterprise/audit_log/handler.py
+++ b/enterprise/backend/src/baserow_enterprise/audit_log/handler.py
@@ -3,13 +3,10 @@ from typing import Any, Dict, Optional, Type
 
 from django.contrib.auth.models import AbstractUser
 
-from baserow_premium.license.handler import LicenseHandler
-
 from baserow.api.sessions import get_user_remote_addr_ip
 from baserow.core.action.registries import ActionType
 from baserow.core.action.signals import ActionCommandType
 from baserow.core.models import Workspace
-from baserow_enterprise.features import AUDIT_LOG
 
 from .models import AuditLogEntry
 
@@ -44,12 +41,8 @@ class AuditLogHandler:
             is sent so it can be used to identify other resources created at the
             same time (i.e. row_history entries).
         :param workspace: The workspace that the action was performed on.
-        :raises FeaturesNotAvailableError: When the AUDIT_LOG feature is not
-            available.
         """
 
-        LicenseHandler.raise_if_user_doesnt_have_feature_instance_wide(AUDIT_LOG, user)
-
         workspace_id, workspace_name = None, None
         if workspace is not None:
             workspace_id = workspace.id
diff --git a/enterprise/backend/src/baserow_enterprise/audit_log/job_types.py b/enterprise/backend/src/baserow_enterprise/audit_log/job_types.py
index 2c8dfb220..0f7ed8b05 100755
--- a/enterprise/backend/src/baserow_enterprise/audit_log/job_types.py
+++ b/enterprise/backend/src/baserow_enterprise/audit_log/job_types.py
@@ -30,6 +30,61 @@ from baserow_enterprise.features import AUDIT_LOG
 
 from .models import AuditLogEntry, AuditLogExportJob
 
+AUDIT_LOG_CSV_COLUMN_NAMES = OrderedDict(
+    {
+        "user_email": {
+            "field": "user_email",
+            "descr": _("User Email"),
+        },
+        "user_id": {
+            "field": "user_id",
+            "descr": _("User ID"),
+        },
+        "workspace_name": {
+            "field": "workspace_name",
+            "descr": _("Group Name"),
+        },
+        "workspace_id": {
+            "field": "workspace_id",
+            "descr": _("Group ID"),
+        },
+        "type": {
+            "field": "type",
+            "descr": _("Action Type"),
+        },
+        "description": {
+            "field": "description",
+            "descr": _("Description"),
+        },
+        "timestamp": {
+            "field": "action_timestamp",
+            "descr": _("Timestamp"),
+        },
+        "ip_address": {
+            "field": "ip_address",
+            "descr": _("IP Address"),
+        },
+    }
+)
+
+
+class CommaSeparatedCsvColumnsField(serializers.CharField):
+    def validate_values(self, value):
+        items = value.split(",")
+
+        if len(set(items)) != len(items):
+            raise serializers.ValidationError("Duplicate items are not allowed.")
+
+        if len(items) > 0:
+            for item in items:
+                if item not in AUDIT_LOG_CSV_COLUMN_NAMES.keys():
+                    raise serializers.ValidationError(f"{item} is not a valid choice.")
+
+        if len(items) == len(self.child.choices):
+            raise serializers.ValidationError("At least one column must be included.")
+
+        return value
+
 
 class AuditLogExportJobType(JobType):
     type = "audit_log_export"
@@ -46,24 +101,16 @@ class AuditLogExportJobType(JobType):
         "filter_action_type",
         "filter_from_timestamp",
         "filter_to_timestamp",
+        "exclude_columns",
     ]
 
     serializer_field_names = [
-        "csv_column_separator",
-        "csv_first_row_header",
-        "export_charset",
-        "filter_user_id",
-        "filter_workspace_id",
-        "filter_action_type",
-        "filter_from_timestamp",
-        "filter_to_timestamp",
+        *request_serializer_field_names,
         "created_on",
         "exported_file_name",
         "url",
     ]
-    serializer_field_overrides = {
-        # Map to the python encoding aliases at the same time by using a
-        # DisplayChoiceField
+    base_serializer_field_overrides = {
         "export_charset": DisplayChoiceField(
             choices=SUPPORTED_EXPORT_CHARSETS,
             default="utf-8",
@@ -106,10 +153,32 @@ class AuditLogExportJobType(JobType):
             required=False,
             help_text="Optional: The end date to filter the audit log by.",
         ),
+        "exclude_columns": CommaSeparatedCsvColumnsField(
+            required=False,
+            help_text=(
+                "Optional: A comma separated list of column names to exclude from the export. "
+                f"Available options are `{', '.join(AUDIT_LOG_CSV_COLUMN_NAMES.keys())}`."
+            ),
+        ),
+    }
+    request_serializer_field_overrides = {
+        **base_serializer_field_overrides,
+    }
+    serializer_field_overrides = {
+        # Map to the python encoding aliases at the same time by using a
+        # DisplayChoiceField
+        **base_serializer_field_overrides,
         "created_on": serializers.DateTimeField(
             read_only=True,
             help_text="The date and time when the export job was created.",
         ),
+        "exported_file_name": serializers.CharField(
+            read_only=True,
+            help_text="The name of the file that was created by the export job.",
+        ),
+        "url": serializers.SerializerMethodField(
+            help_text="The URL to download the exported file.",
+        ),
     }
 
     def before_delete(self, job):
@@ -135,18 +204,12 @@ class AuditLogExportJobType(JobType):
         if job.export_charset == "utf-8":
             file.write(b"\xef\xbb\xbf")
 
-        field_header_mapping = OrderedDict(
-            {
-                "user_email": _("User Email"),
-                "user_id": _("User ID"),
-                "workspace_name": _("Group Name"),  # GroupDeprecation
-                "workspace_id": _("Group ID"),
-                "type": _("Action Type"),
-                "description": _("Description"),
-                "action_timestamp": _("Timestamp"),
-                "ip_address": _("IP Address"),
-            }
-        )
+        exclude_columns = job.exclude_columns.split(",") if job.exclude_columns else []
+        field_header_mapping = {
+            k: v["descr"]
+            for (k, v) in AUDIT_LOG_CSV_COLUMN_NAMES.items()
+            if k not in exclude_columns
+        }
 
         writer = csv.writer(
             file,
@@ -158,7 +221,11 @@ class AuditLogExportJobType(JobType):
         if job.csv_first_row_header:
             writer.writerow(field_header_mapping.values())
 
-        fields = field_header_mapping.keys()
+        fields = [
+            v["field"]
+            for (k, v) in AUDIT_LOG_CSV_COLUMN_NAMES.items()
+            if k not in exclude_columns
+        ]
         paginator = Paginator(queryset.all(), 2000)
         export_progress = ChildProgressBuilder.build(
             progress.create_child_builder(represents_progress=progress.total),
diff --git a/enterprise/backend/src/baserow_enterprise/audit_log/models.py b/enterprise/backend/src/baserow_enterprise/audit_log/models.py
index a675c6897..289ead9a9 100755
--- a/enterprise/backend/src/baserow_enterprise/audit_log/models.py
+++ b/enterprise/backend/src/baserow_enterprise/audit_log/models.py
@@ -32,10 +32,10 @@ class AuditLogEntry(CreatedAndUpdatedOnMixin, models.Model):
         REDO = ActionCommandType.UNDO.name, _("REDONE")
 
     user_id = models.PositiveIntegerField(null=True)
-    user_email = models.CharField(max_length=150, null=True, blank=True)
+    user_email = models.EmailField(null=True, blank=True)
 
     workspace_id = models.PositiveIntegerField(null=True)
-    workspace_name = models.CharField(max_length=160, null=True, blank=True)
+    workspace_name = models.CharField(max_length=165, null=True, blank=True)
 
     action_uuid = models.CharField(max_length=36, null=True)
     action_type = models.TextField()
@@ -50,8 +50,8 @@ class AuditLogEntry(CreatedAndUpdatedOnMixin, models.Model):
     # we don't want break the audit log in case an action is removed or changed.
     # Storing the original description and type in the database we'll always be
     # able to fallback to them and show the original string in case. NOTE: if
-    # the _('$original_description') has been removed from the codebase, the
-    # entry won't be translated anymore.
+    # also the _('$original_description') has been removed from the codebase,
+    # the entry won't be translated anymore.
     original_action_short_descr = models.TextField(null=True, blank=True)
     original_action_long_descr = models.TextField(null=True, blank=True)
     original_action_context_descr = models.TextField(null=True, blank=True)
@@ -147,3 +147,8 @@ class AuditLogExportJob(Job):
         null=True,
         help_text="The CSV file containing the filtered audit log entries.",
     )
+    exclude_columns = models.CharField(
+        max_length=255,
+        null=True,
+        help_text="A comma separated list of column names to exclude from the export.",
+    )
diff --git a/enterprise/backend/src/baserow_enterprise/audit_log/operations.py b/enterprise/backend/src/baserow_enterprise/audit_log/operations.py
new file mode 100644
index 000000000..aaa8bea12
--- /dev/null
+++ b/enterprise/backend/src/baserow_enterprise/audit_log/operations.py
@@ -0,0 +1,5 @@
+from baserow.core.operations import WorkspaceCoreOperationType
+
+
+class ListWorkspaceAuditLogEntriesOperationType(WorkspaceCoreOperationType):
+    type = "workspace.list_audit_log_entries"
diff --git a/enterprise/backend/src/baserow_enterprise/audit_log/signals.py b/enterprise/backend/src/baserow_enterprise/audit_log/signals.py
index 514935170..65a3059bf 100755
--- a/enterprise/backend/src/baserow_enterprise/audit_log/signals.py
+++ b/enterprise/backend/src/baserow_enterprise/audit_log/signals.py
@@ -4,8 +4,6 @@ from typing import Any, Dict, Optional, Type
 from django.contrib.auth.models import AbstractUser
 from django.dispatch import receiver
 
-from baserow_premium.license.exceptions import FeaturesNotAvailableError
-
 from baserow.core.action.registries import ActionType
 from baserow.core.action.signals import ActionCommandType, action_done
 from baserow.core.models import Workspace
@@ -25,16 +23,13 @@ def log_action(
     workspace: Optional[Workspace] = None,
     **kwargs
 ):
-    try:
-        AuditLogHandler.log_action(
-            user,
-            action_type,
-            action_params,
-            action_timestamp,
-            action_command_type,
-            action_uuid=action_uuid,
-            workspace=workspace,
-            **kwargs
-        )
-    except FeaturesNotAvailableError:
-        pass
+    AuditLogHandler.log_action(
+        user,
+        action_type,
+        action_params,
+        action_timestamp,
+        action_command_type,
+        action_uuid=action_uuid,
+        workspace=workspace,
+        **kwargs
+    )
diff --git a/enterprise/backend/src/baserow_enterprise/audit_log/utils.py b/enterprise/backend/src/baserow_enterprise/audit_log/utils.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/enterprise/backend/src/baserow_enterprise/migrations/0022_workspace_audit_log.py b/enterprise/backend/src/baserow_enterprise/migrations/0022_workspace_audit_log.py
new file mode 100644
index 000000000..a29320edd
--- /dev/null
+++ b/enterprise/backend/src/baserow_enterprise/migrations/0022_workspace_audit_log.py
@@ -0,0 +1,31 @@
+# Generated by Django 3.2.20 on 2023-08-09 10:29
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("baserow_enterprise", "0021_auditlogentry_action_uuid"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="auditlogexportjob",
+            name="exclude_columns",
+            field=models.CharField(
+                help_text="A comma separated list of column names to exclude from the export.",
+                max_length=255,
+                null=True,
+            ),
+        ),
+        migrations.AlterField(
+            model_name="auditlogentry",
+            name="user_email",
+            field=models.EmailField(blank=True, max_length=254, null=True),
+        ),
+        migrations.AlterField(
+            model_name="auditlogentry",
+            name="workspace_name",
+            field=models.CharField(blank=True, max_length=165, null=True),
+        ),
+    ]
diff --git a/enterprise/backend/src/baserow_enterprise/role/default_roles.py b/enterprise/backend/src/baserow_enterprise/role/default_roles.py
index b74e95e88..3cf696235 100755
--- a/enterprise/backend/src/baserow_enterprise/role/default_roles.py
+++ b/enterprise/backend/src/baserow_enterprise/role/default_roles.py
@@ -179,6 +179,9 @@ from baserow.core.trash.operations import (
     ReadApplicationTrashOperationType,
     ReadWorkspaceTrashOperationType,
 )
+from baserow_enterprise.audit_log.operations import (
+    ListWorkspaceAuditLogEntriesOperationType,
+)
 from baserow_enterprise.role.constants import (
     ADMIN_ROLE_UID,
     BUILDER_ROLE_UID,
@@ -411,5 +414,6 @@ default_roles[ADMIN_ROLE_UID].extend(
         ListSnapshotsApplicationOperationType,
         DeleteApplicationSnapshotOperationType,
         RestoreDomainOperationType,
+        ListWorkspaceAuditLogEntriesOperationType,
     ]
 )
diff --git a/enterprise/backend/tests/baserow_enterprise_tests/api/admin/audit_log/test_audit_log_views.py b/enterprise/backend/tests/baserow_enterprise_tests/api/audit_log/test_audit_log_admin_views.py
similarity index 89%
rename from enterprise/backend/tests/baserow_enterprise_tests/api/admin/audit_log/test_audit_log_views.py
rename to enterprise/backend/tests/baserow_enterprise_tests/api/audit_log/test_audit_log_admin_views.py
index fa6dfd13d..99cd32bb2 100755
--- a/enterprise/backend/tests/baserow_enterprise_tests/api/admin/audit_log/test_audit_log_views.py
+++ b/enterprise/backend/tests/baserow_enterprise_tests/api/audit_log/test_audit_log_admin_views.py
@@ -28,15 +28,23 @@ from baserow_enterprise.audit_log.models import AuditLogEntry
 
 
 @pytest.mark.django_db
-@pytest.mark.parametrize("url_name", ["users", "workspaces", "action_types", "list"])
+@pytest.mark.parametrize(
+    "method,url_name",
+    [
+        ("get", "users"),
+        ("get", "action_types"),
+        ("get", "list"),
+        ("post", "async_export"),
+    ],
+)
 @override_settings(DEBUG=True)
-def test_admins_can_not_access_audit_log_endpoints_without_an_enterprise_license(
-    api_client, enterprise_data_fixture, url_name
+def test_admins_cannot_access_audit_log_endpoints_without_an_enterprise_license(
+    api_client, enterprise_data_fixture, method, url_name
 ):
-    user, token = enterprise_data_fixture.create_user_and_token(is_staff=True)
+    _, token = enterprise_data_fixture.create_user_and_token(is_staff=True)
 
-    response = api_client.get(
-        reverse(f"api:enterprise:admin:audit_log:{url_name}"),
+    response = getattr(api_client, method)(
+        reverse(f"api:enterprise:audit_log:{url_name}"),
         format="json",
         HTTP_AUTHORIZATION=f"JWT {token}",
     )
@@ -46,15 +54,15 @@ def test_admins_can_not_access_audit_log_endpoints_without_an_enterprise_license
 @pytest.mark.django_db
 @pytest.mark.parametrize("url_name", ["users", "workspaces", "action_types", "list"])
 @override_settings(DEBUG=True)
-def test_non_admins_can_not_access_audit_log_endpoints(
+def test_non_admins_cannot_access_audit_log_endpoints(
     api_client, enterprise_data_fixture, url_name
 ):
     enterprise_data_fixture.enable_enterprise()
 
-    user, token = enterprise_data_fixture.create_user_and_token()
+    _, token = enterprise_data_fixture.create_user_and_token()
 
     response = api_client.get(
-        reverse(f"api:enterprise:admin:audit_log:{url_name}"),
+        reverse(f"api:enterprise:audit_log:{url_name}"),
         format="json",
         HTTP_AUTHORIZATION=f"JWT {token}",
     )
@@ -63,30 +71,13 @@ def test_non_admins_can_not_access_audit_log_endpoints(
 
 @pytest.mark.django_db
 @override_settings(DEBUG=True)
-def test_admins_can_not_export_audit_log_to_csv_without_an_enterprise_license(
-    api_client, enterprise_data_fixture
-):
-    user, token = enterprise_data_fixture.create_user_and_token(is_staff=True)
-
-    response = api_client.post(
-        reverse(f"api:enterprise:admin:audit_log:export"),
-        format="json",
-        HTTP_AUTHORIZATION=f"JWT {token}",
-    )
-    assert response.status_code == HTTP_402_PAYMENT_REQUIRED
-
-
-@pytest.mark.django_db
-@override_settings(DEBUG=True)
-def test_non_admins_can_not_export_audit_log_to_csv(
-    api_client, enterprise_data_fixture
-):
+def test_non_admins_cannot_export_audit_log_to_csv(api_client, enterprise_data_fixture):
     enterprise_data_fixture.enable_enterprise()
 
     user, token = enterprise_data_fixture.create_user_and_token()
 
     response = api_client.post(
-        reverse(f"api:enterprise:admin:audit_log:export"),
+        reverse("api:enterprise:audit_log:async_export"),
         format="json",
         HTTP_AUTHORIZATION=f"JWT {token}",
     )
@@ -108,7 +99,7 @@ def test_audit_log_user_filter_returns_users_correctly(
 
     # no search query should return all users
     response = api_client.get(
-        reverse("api:enterprise:admin:audit_log:users"),
+        reverse("api:enterprise:audit_log:users"),
         format="json",
         HTTP_AUTHORIZATION=f"JWT {admin_token}",
     )
@@ -125,7 +116,7 @@ def test_audit_log_user_filter_returns_users_correctly(
 
     # searching by email should return only the correct user
     response = api_client.get(
-        reverse("api:enterprise:admin:audit_log:users") + "?search=admin",
+        reverse("api:enterprise:audit_log:users") + "?search=admin",
         format="json",
         HTTP_AUTHORIZATION=f"JWT {admin_token}",
     )
@@ -156,7 +147,7 @@ def test_audit_log_workspace_filter_returns_workspaces_correctly(
 
     # no search query should return all workspaces
     response = api_client.get(
-        reverse("api:enterprise:admin:audit_log:workspaces"),
+        reverse("api:enterprise:audit_log:workspaces"),
         format="json",
         HTTP_AUTHORIZATION=f"JWT {admin_token}",
     )
@@ -173,7 +164,7 @@ def test_audit_log_workspace_filter_returns_workspaces_correctly(
 
     # searching by name should return only the correct workspace
     response = api_client.get(
-        reverse("api:enterprise:admin:audit_log:workspaces") + "?search=1",
+        reverse("api:enterprise:audit_log:workspaces") + "?search=1",
         format="json",
         HTTP_AUTHORIZATION=f"JWT {admin_token}",
     )
@@ -200,7 +191,7 @@ def test_audit_log_action_type_filter_returns_action_types_correctly(
 
     # no search query should return all the available action types``
     response = api_client.get(
-        reverse("api:enterprise:admin:audit_log:action_types"),
+        reverse("api:enterprise:audit_log:action_types"),
         format="json",
         HTTP_AUTHORIZATION=f"JWT {admin_token}",
     )
@@ -220,7 +211,7 @@ def test_audit_log_action_type_filter_returns_action_types_correctly(
 
     # searching by name should return only the correct action_type
     response = api_client.get(
-        reverse("api:enterprise:admin:audit_log:action_types")
+        reverse("api:enterprise:audit_log:action_types")
         + f"?search=create+application",
         format="json",
         HTTP_AUTHORIZATION=f"JWT {admin_token}",
@@ -246,7 +237,7 @@ def test_audit_log_action_types_are_translated_in_the_admin_language(
 
     with patch("django.utils.translation.override") as mock_override:
         api_client.get(
-            reverse("api:enterprise:admin:audit_log:action_types"),
+            reverse("api:enterprise:audit_log:action_types"),
             format="json",
             HTTP_AUTHORIZATION=f"JWT {admin_token}",
         )
@@ -254,10 +245,7 @@ def test_audit_log_action_types_are_translated_in_the_admin_language(
 
     # the search works in the user language
     response = api_client.get(
-        (
-            reverse("api:enterprise:admin:audit_log:action_types")
-            + f"?search=crea+progetto"
-        ),
+        (reverse("api:enterprise:audit_log:action_types") + f"?search=crea+progetto"),
         format="json",
         HTTP_AUTHORIZATION=f"JWT {admin_token}",
     )
@@ -272,7 +260,7 @@ def test_audit_log_action_types_are_translated_in_the_admin_language(
 
 @pytest.mark.django_db
 @override_settings(DEBUG=True)
-def test_audit_log_entries_are_not_created_without_a_license(
+def test_audit_log_entries_are_created_even_without_a_license(
     api_client, enterprise_data_fixture
 ):
     user = enterprise_data_fixture.create_user()
@@ -283,7 +271,7 @@ def test_audit_log_entries_are_not_created_without_a_license(
     with freeze_time("2023-01-01 12:00:01"):
         CreateWorkspaceActionType.do(user, "workspace 2")
 
-    assert AuditLogEntry.objects.count() == 0
+    assert AuditLogEntry.objects.count() == 2
 
 
 @pytest.mark.django_db
@@ -319,7 +307,7 @@ def test_audit_log_entries_are_created_from_actions_and_returned_in_order(
     }
 
     response = api_client.get(
-        reverse("api:enterprise:admin:audit_log:list"),
+        reverse("api:enterprise:audit_log:list"),
         format="json",
         HTTP_AUTHORIZATION=f"JWT {admin_token}",
     )
@@ -366,14 +354,14 @@ def test_audit_log_entries_are_translated_in_the_user_language(
 
     with patch("django.utils.translation.override") as mock_override:
         api_client.get(
-            reverse("api:enterprise:admin:audit_log:list"),
+            reverse("api:enterprise:audit_log:list"),
             format="json",
             HTTP_AUTHORIZATION=f"JWT {admin_token}",
         )
         mock_override.assert_called_once_with("it")
 
     response = api_client.get(
-        reverse("api:enterprise:admin:audit_log:list"),
+        reverse("api:enterprise:audit_log:list"),
         format="json",
         HTTP_AUTHORIZATION=f"JWT {admin_token}",
     )
@@ -450,7 +438,7 @@ def test_audit_log_entries_can_be_filtered(api_client, enterprise_data_fixture):
 
     # by user_id
     response = api_client.get(
-        reverse("api:enterprise:admin:audit_log:list") + "?user_id=" + str(user.id),
+        reverse("api:enterprise:audit_log:list") + "?user_id=" + str(user.id),
         format="json",
         HTTP_AUTHORIZATION=f"JWT {admin_token}",
     )
@@ -464,7 +452,7 @@ def test_audit_log_entries_can_be_filtered(api_client, enterprise_data_fixture):
 
     # by workspace_id
     response = api_client.get(
-        reverse("api:enterprise:admin:audit_log:list")
+        reverse("api:enterprise:audit_log:list")
         + "?workspace_id="
         + str(workspace_1.id),
         format="json",
@@ -480,7 +468,7 @@ def test_audit_log_entries_can_be_filtered(api_client, enterprise_data_fixture):
 
     # by action_type
     response = api_client.get(
-        reverse("api:enterprise:admin:audit_log:list") + "?action_type=create_group",
+        reverse("api:enterprise:audit_log:list") + "?action_type=create_group",
         format="json",
         HTTP_AUTHORIZATION=f"JWT {admin_token}",
     )
@@ -493,8 +481,7 @@ def test_audit_log_entries_can_be_filtered(api_client, enterprise_data_fixture):
     }
 
     response = api_client.get(
-        reverse("api:enterprise:admin:audit_log:list")
-        + "?action_type=create_application",
+        reverse("api:enterprise:audit_log:list") + "?action_type=create_application",
         format="json",
         HTTP_AUTHORIZATION=f"JWT {admin_token}",
     )
@@ -508,7 +495,7 @@ def test_audit_log_entries_can_be_filtered(api_client, enterprise_data_fixture):
 
     # from timestamp
     response = api_client.get(
-        reverse("api:enterprise:admin:audit_log:list")
+        reverse("api:enterprise:audit_log:list")
         + "?from_timestamp=2023-01-01T12:00:01Z",
         format="json",
         HTTP_AUTHORIZATION=f"JWT {admin_token}",
@@ -523,8 +510,7 @@ def test_audit_log_entries_can_be_filtered(api_client, enterprise_data_fixture):
 
     # to timestamp
     response = api_client.get(
-        reverse("api:enterprise:admin:audit_log:list")
-        + "?to_timestamp=2023-01-01T12:00:00Z",
+        reverse("api:enterprise:audit_log:list") + "?to_timestamp=2023-01-01T12:00:00Z",
         format="json",
         HTTP_AUTHORIZATION=f"JWT {admin_token}",
     )
@@ -549,7 +535,7 @@ def test_audit_log_entries_return_400_for_invalid_values(
 
     # an invalid value in the query params should return a 400
     response = api_client.get(
-        reverse("api:enterprise:admin:audit_log:list") + "?user_id=wrong_type",
+        reverse("api:enterprise:audit_log:list") + "?user_id=wrong_type",
         format="json",
         HTTP_AUTHORIZATION=f"JWT {admin_token}",
     )
@@ -576,7 +562,7 @@ def test_audit_log_can_export_to_csv_all_entries(
         execute=True
     ):
         response = api_client.post(
-            reverse("api:enterprise:admin:audit_log:export"),
+            reverse("api:enterprise:audit_log:async_export"),
             data=csv_settings,
             format="json",
             HTTP_AUTHORIZATION=f"JWT {admin_token}",
@@ -631,6 +617,7 @@ def test_audit_log_can_export_to_csv_filtered_entries(
         "csv_column_separator": "|",
         "csv_first_row_header": False,
         "export_charset": "utf-8",
+        "exclude_columns": "ip_address",
     }
     filters = {
         "filter_user_id": admin_user.id,
@@ -642,7 +629,7 @@ def test_audit_log_can_export_to_csv_filtered_entries(
 
     # if the action type is invalid, it should return a 400
     response = api_client.post(
-        reverse("api:enterprise:admin:audit_log:export"),
+        reverse("api:enterprise:audit_log:async_export"),
         data={**csv_settings, "filter_action_type": "wrong_type"},
         format="json",
         HTTP_AUTHORIZATION=f"JWT {admin_token}",
@@ -653,7 +640,7 @@ def test_audit_log_can_export_to_csv_filtered_entries(
         execute=True
     ):
         response = api_client.post(
-            reverse("api:enterprise:admin:audit_log:export"),
+            reverse("api:enterprise:audit_log:async_export"),
             data={**csv_settings, **filters},
             format="json",
             HTTP_AUTHORIZATION=f"JWT {admin_token}",
@@ -731,7 +718,7 @@ def test_log_entries_still_work_correctly_if_the_action_type_is_removed(
     action_type_registry.unregister(TemporaryActionType.type)
 
     response = api_client.get(
-        reverse("api:enterprise:admin:audit_log:list"),
+        reverse("api:enterprise:audit_log:list"),
         format="json",
         HTTP_AUTHORIZATION=f"JWT {token}",
     )
@@ -775,7 +762,7 @@ def test_log_entries_still_work_correctly_if_the_action_type_is_removed(
     action_type_registry.register(TemporaryActionTypeV2())
 
     response = api_client.get(
-        reverse("api:enterprise:admin:audit_log:list"),
+        reverse("api:enterprise:audit_log:list"),
         format="json",
         HTTP_AUTHORIZATION=f"JWT {token}",
     )
@@ -805,7 +792,7 @@ def test_log_entries_still_work_correctly_if_the_action_type_is_removed(
     assert AuditLogEntry.objects.count() == 2
 
     response = api_client.get(
-        reverse("api:enterprise:admin:audit_log:list"),
+        reverse("api:enterprise:audit_log:list"),
         format="json",
         HTTP_AUTHORIZATION=f"JWT {token}",
     )
diff --git a/enterprise/backend/tests/baserow_enterprise_tests/api/audit_log/test_audit_log_workspace_views.py b/enterprise/backend/tests/baserow_enterprise_tests/api/audit_log/test_audit_log_workspace_views.py
new file mode 100644
index 000000000..bca004b48
--- /dev/null
+++ b/enterprise/backend/tests/baserow_enterprise_tests/api/audit_log/test_audit_log_workspace_views.py
@@ -0,0 +1,287 @@
+from django.shortcuts import reverse
+from django.test.utils import override_settings
+
+import pytest
+from freezegun import freeze_time
+from rest_framework.status import (
+    HTTP_200_OK,
+    HTTP_202_ACCEPTED,
+    HTTP_400_BAD_REQUEST,
+    HTTP_401_UNAUTHORIZED,
+    HTTP_402_PAYMENT_REQUIRED,
+    HTTP_404_NOT_FOUND,
+)
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize("url_name", ["users", "action_types", "list"])
+@override_settings(DEBUG=True)
+def test_workspace_admins_cannot_access_workspace_audit_log_endpoints_without_an_enterprise_license(
+    api_client, enterprise_data_fixture, url_name
+):
+    user, token = enterprise_data_fixture.create_user_and_token()
+    workspace = enterprise_data_fixture.create_workspace(user=user)
+
+    response = api_client.get(
+        reverse(f"api:enterprise:audit_log:{url_name}")
+        + f"?workspace_id={workspace.id}",
+        format="json",
+        HTTP_AUTHORIZATION=f"JWT {token}",
+    )
+    assert response.status_code == HTTP_402_PAYMENT_REQUIRED
+
+
+@pytest.mark.django_db
+@override_settings(DEBUG=True)
+def test_workspace_admins_cannot_export_workspace_audit_log_without_an_enterprise_license(
+    api_client, enterprise_data_fixture
+):
+    user, token = enterprise_data_fixture.create_user_and_token()
+    workspace = enterprise_data_fixture.create_workspace(user=user)
+
+    response = api_client.post(
+        reverse(f"api:enterprise:audit_log:async_export"),
+        {"filter_workspace_id": workspace.id},
+        format="json",
+        HTTP_AUTHORIZATION=f"JWT {token}",
+    )
+    assert response.status_code == HTTP_402_PAYMENT_REQUIRED
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize("url_name", ["users", "action_types", "list"])
+@override_settings(DEBUG=True)
+def test_non_admins_cannot_access_workspace_audit_log_endpoints(
+    api_client, enterprise_data_fixture, url_name
+):
+    enterprise_data_fixture.enable_enterprise()
+
+    admin = enterprise_data_fixture.create_user()
+    builder, token = enterprise_data_fixture.create_user_and_token()
+    workspace = enterprise_data_fixture.create_workspace(
+        user=admin, custom_permissions=[(builder, "BUILDER")]
+    )
+
+    response = api_client.get(
+        reverse(f"api:enterprise:audit_log:{url_name}")
+        + f"?workspace_id={workspace.id}",
+        format="json",
+        HTTP_AUTHORIZATION=f"JWT {token}",
+    )
+    assert response.status_code == HTTP_401_UNAUTHORIZED
+
+
+@pytest.mark.django_db
+@override_settings(DEBUG=True)
+def test_non_admins_cannot_export_workspace_audit_log_to_csv(
+    api_client, enterprise_data_fixture
+):
+    enterprise_data_fixture.enable_enterprise()
+
+    admin = enterprise_data_fixture.create_user()
+    builder, token = enterprise_data_fixture.create_user_and_token()
+    workspace = enterprise_data_fixture.create_workspace(
+        user=admin, custom_permissions=[(builder, "BUILDER")]
+    )
+
+    response = api_client.post(
+        reverse("api:enterprise:audit_log:async_export"),
+        {"filter_workspace_id": workspace.id},
+        format="json",
+        HTTP_AUTHORIZATION=f"JWT {token}",
+    )
+    assert response.status_code == HTTP_401_UNAUTHORIZED
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize("url_name", ["users", "action_types", "list"])
+@override_settings(DEBUG=True)
+def test_workspace_audit_log_endpoints_raise_404_if_workspace_doesnt_exist(
+    api_client, enterprise_data_fixture, url_name
+):
+    enterprise_data_fixture.enable_enterprise()
+
+    _, token = enterprise_data_fixture.create_user_and_token()
+
+    response = api_client.get(
+        reverse(f"api:enterprise:audit_log:{url_name}") + "?workspace_id=9999",
+        format="json",
+        HTTP_AUTHORIZATION=f"JWT {token}",
+    )
+    assert response.status_code == HTTP_404_NOT_FOUND
+    assert response.json()["error"] == "ERROR_GROUP_DOES_NOT_EXIST"
+
+
+@pytest.mark.django_db
+@override_settings(DEBUG=True)
+def test_workspace_audit_log_export_raise_404_if_workspace_doesnt_exist(
+    api_client, enterprise_data_fixture
+):
+    enterprise_data_fixture.enable_enterprise()
+
+    _, token = enterprise_data_fixture.create_user_and_token()
+
+    response = api_client.post(
+        reverse(f"api:enterprise:audit_log:async_export"),
+        {"filter_workspace_id": 9999},
+        format="json",
+        HTTP_AUTHORIZATION=f"JWT {token}",
+    )
+    assert response.status_code == HTTP_404_NOT_FOUND
+    assert response.json()["error"] == "ERROR_GROUP_DOES_NOT_EXIST"
+
+
+@pytest.mark.django_db
+@override_settings(DEBUG=True)
+def test_workspace_audit_log_user_filter_returns_only_workspace_users(
+    api_client, enterprise_data_fixture
+):
+    enterprise_data_fixture.enable_enterprise()
+
+    admin, token = enterprise_data_fixture.create_user_and_token(email="admin@test.com")
+    user_wp1 = enterprise_data_fixture.create_user(email="user_wp1@test.com")
+    user_wp2 = enterprise_data_fixture.create_user(email="user_wp2@test.com")
+
+    workspace_1 = enterprise_data_fixture.create_workspace(users=[admin, user_wp1])
+    workspace_2 = enterprise_data_fixture.create_workspace(users=[admin, user_wp2])
+
+    # no search query should return all users for worspace 1
+    response = api_client.get(
+        reverse("api:enterprise:audit_log:users") + f"?workspace_id={workspace_1.id}",
+        format="json",
+        HTTP_AUTHORIZATION=f"JWT {token}",
+    )
+    assert response.status_code == HTTP_200_OK
+    assert response.json() == {
+        "count": 2,
+        "next": None,
+        "previous": None,
+        "results": [
+            {"id": admin.id, "value": admin.email},
+            {"id": user_wp1.id, "value": user_wp1.email},
+        ],
+    }
+
+    # no search query should return all users for worspace 2
+    response = api_client.get(
+        reverse("api:enterprise:audit_log:users") + f"?workspace_id={workspace_2.id}",
+        format="json",
+        HTTP_AUTHORIZATION=f"JWT {token}",
+    )
+    assert response.status_code == HTTP_200_OK
+    assert response.json() == {
+        "count": 2,
+        "next": None,
+        "previous": None,
+        "results": [
+            {"id": admin.id, "value": admin.email},
+            {"id": user_wp2.id, "value": user_wp2.email},
+        ],
+    }
+
+    # searching by email should return only the correct user
+    response = api_client.get(
+        reverse("api:enterprise:audit_log:users")
+        + f"?workspace_id={workspace_1.id}&search=user",
+        format="json",
+        HTTP_AUTHORIZATION=f"JWT {token}",
+    )
+    assert response.status_code == HTTP_200_OK
+    assert response.json() == {
+        "count": 1,
+        "next": None,
+        "previous": None,
+        "results": [{"id": user_wp1.id, "value": "user_wp1@test.com"}],
+    }
+
+
+@pytest.mark.django_db
+@override_settings(DEBUG=True)
+def test_workspace_audit_log_can_export_to_csv_filtered_entries(
+    api_client,
+    enterprise_data_fixture,
+    synced_roles,
+    django_capture_on_commit_callbacks,
+):
+    enterprise_data_fixture.enable_enterprise()
+
+    admin_user, admin_token = enterprise_data_fixture.create_user_and_token(
+        email="admin@test.com"
+    )
+    workspace = enterprise_data_fixture.create_workspace(user=admin_user)
+
+    csv_settings = {
+        "csv_column_separator": "|",
+        "csv_first_row_header": False,
+        "export_charset": "utf-8",
+    }
+    filters = {
+        "filter_user_id": admin_user.id,
+        "filter_action_type": "create_application",
+        "filter_from_timestamp": "2023-01-01T00:00:00Z",
+        "filter_to_timestamp": "2023-01-03T00:00:00Z",
+        "filter_workspace_id": workspace.id,
+        "exclude_columns": "workspace_id,workspace_name",
+    }
+
+    # if the action type is invalid, it should return a 400
+    response = api_client.post(
+        reverse("api:enterprise:audit_log:async_export"),
+        data={**csv_settings, "filter_action_type": "wrong_type"},
+        format="json",
+        HTTP_AUTHORIZATION=f"JWT {admin_token}",
+    )
+    assert response.status_code == HTTP_400_BAD_REQUEST
+
+    # if the workspace id is invalid, it should return a 404
+    response = api_client.post(
+        reverse("api:enterprise:audit_log:async_export"),
+        data={**csv_settings, **filters, "filter_workspace_id": 9999},
+        format="json",
+        HTTP_AUTHORIZATION=f"JWT {admin_token}",
+    )
+    assert response.status_code == HTTP_404_NOT_FOUND
+
+    with freeze_time("2023-01-02 12:00"), django_capture_on_commit_callbacks(
+        execute=True
+    ):
+        response = api_client.post(
+            reverse("api:enterprise:audit_log:async_export"),
+            data={**csv_settings, **filters},
+            format="json",
+            HTTP_AUTHORIZATION=f"JWT {admin_token}",
+        )
+    assert response.status_code == HTTP_202_ACCEPTED, response.json()
+    job = response.json()
+    assert job["id"] is not None
+    assert job["state"] == "pending"
+    assert job["type"] == "audit_log_export"
+
+    response = api_client.get(
+        reverse(
+            "api:jobs:item",
+            kwargs={"job_id": job["id"]},
+        ),
+        HTTP_AUTHORIZATION=f"JWT {admin_token}",
+    )
+    assert response.status_code == HTTP_200_OK
+    job = response.json()
+    assert job["state"] == "finished"
+    assert job["type"] == "audit_log_export"
+    for key, value in csv_settings.items():
+        assert job[key] == value
+    for key in [
+        "filter_user_id",
+        "filter_action_type",
+        "filter_from_timestamp",
+        "filter_to_timestamp",
+    ]:
+        assert job[key] == filters[key]
+
+    assert job["exported_file_name"].endswith(".csv")
+    assert job["url"].startswith("http://localhost:8000/media/export_files/")
+    assert job["created_on"] == "2023-01-02T12:00:00Z"
+
+    # These filters are automatically added by the workspace endpoint
+    assert job["filter_workspace_id"] == workspace.id
+    assert job["exclude_columns"] == "workspace_id,workspace_name"
diff --git a/enterprise/backend/tests/baserow_enterprise_tests/audit_log/test_audit_log_export_job.py b/enterprise/backend/tests/baserow_enterprise_tests/audit_log/test_audit_log_export_job.py
index 1be300d14..29f748aa5 100755
--- a/enterprise/backend/tests/baserow_enterprise_tests/audit_log/test_audit_log_export_job.py
+++ b/enterprise/backend/tests/baserow_enterprise_tests/audit_log/test_audit_log_export_job.py
@@ -9,7 +9,7 @@ import pytest
 from freezegun import freeze_time
 
 from baserow.contrib.database.export.handler import ExportHandler
-from baserow.core.actions import CreateWorkspaceActionType
+from baserow.core.actions import CreateApplicationActionType, CreateWorkspaceActionType
 from baserow.core.jobs.constants import JOB_FINISHED
 from baserow.core.jobs.handler import JobHandler
 from baserow_enterprise.audit_log.job_types import AuditLogExportJobType
@@ -220,3 +220,52 @@ def test_audit_log_export_filters_work_correctly(
         datetime.strptime("2023-01-01 12:00:08", "%Y-%m-%d %H:%M:%S")
     )
     assert job_type.get_filtered_queryset(job).count() == 0
+
+
+@pytest.mark.django_db
+@override_settings(DEBUG=True)
+@patch("baserow.contrib.database.export.handler.default_storage")
+def test_audit_log_export_workspace_csv_correctly(
+    storage_mock, enterprise_data_fixture, synced_roles
+):
+    user, _ = enterprise_data_fixture.create_enterprise_admin_user_and_token()
+    workspace = enterprise_data_fixture.create_workspace(user=user)
+
+    with freeze_time("2023-01-01 12:00:00"):
+        app_1 = CreateApplicationActionType.do(user, workspace, "database", "App 1")
+
+    with freeze_time("2023-01-01 12:00:10"):
+        app_2 = CreateApplicationActionType.do(user, workspace, "database", "App 2")
+
+    csv_settings = {
+        "csv_column_separator": ",",
+        "csv_first_row_header": True,
+        "export_charset": "utf-8",
+        "filter_workspace_id": workspace.id,
+        "exclude_columns": "workspace_id,workspace_name",
+    }
+
+    stub_file = BytesIO()
+    storage_mock.open.return_value = stub_file
+    close = stub_file.close
+    stub_file.close = lambda: None
+
+    csv_export_job = JobHandler().create_and_start_job(
+        user, AuditLogExportJobType.type, **csv_settings, sync=True
+    )
+    csv_export_job.refresh_from_db()
+    assert csv_export_job.state == JOB_FINISHED
+
+    data = stub_file.getvalue().decode(csv_settings["export_charset"])
+    bom = "\ufeff"
+
+    assert data == (
+        bom
+        + "User Email,User ID,Action Type,Description,Timestamp,IP Address\r\n"
+        + f'{user.email},{user.id},Create application,"""{app_2.name}"" ({app_2.id}) database created '
+        + f'in group ""{workspace.name}"" ({workspace.id}).",2023-01-01 12:00:10+00:00,\r\n'
+        + f'{user.email},{user.id},Create application,"""{app_1.name}"" ({app_1.id}) database created '
+        + f'in group ""{workspace.name}"" ({workspace.id}).",2023-01-01 12:00:00+00:00,\r\n'
+    )
+
+    close()
diff --git a/enterprise/backend/tests/baserow_enterprise_tests/audit_log/test_audit_log_handler.py b/enterprise/backend/tests/baserow_enterprise_tests/audit_log/test_audit_log_handler.py
index 14fb4b8a8..2c8dc4e1e 100755
--- a/enterprise/backend/tests/baserow_enterprise_tests/audit_log/test_audit_log_handler.py
+++ b/enterprise/backend/tests/baserow_enterprise_tests/audit_log/test_audit_log_handler.py
@@ -11,25 +11,11 @@ from baserow_enterprise.audit_log.handler import AuditLogHandler
 from baserow_enterprise.audit_log.models import AuditLogEntry
 
 
-@pytest.mark.django_db(transaction=True)
-@override_settings(DEBUG=True)
-def test_actions_are_not_inserted_as_audit_log_entries_without_license(
-    api_client, enterprise_data_fixture
-):
-    user = enterprise_data_fixture.create_user()
-
-    with freeze_time("2023-01-01 12:00:00"):
-        CreateWorkspaceActionType.do(user, "workspace 1")
-
-    assert AuditLogEntry.objects.count() == 0
-
-
 @pytest.mark.django_db
 @override_settings(DEBUG=True)
-def test_actions_are_inserted_as_audit_log_entries_with_license(
+def test_actions_are_inserted_as_audit_log_entries_and_can_be_deleted_even_without_license(
     api_client, enterprise_data_fixture, synced_roles
 ):
-    enterprise_data_fixture.enable_enterprise()
     user = enterprise_data_fixture.create_user()
 
     with freeze_time("2023-01-01 12:00:00"):
@@ -40,6 +26,10 @@ def test_actions_are_inserted_as_audit_log_entries_with_license(
 
     assert AuditLogEntry.objects.count() == 2
 
+    AuditLogHandler.delete_entries_older_than(datetime(2023, 1, 1, 13, 0, 0))
+
+    assert AuditLogEntry.objects.count() == 0
+
 
 @pytest.mark.django_db
 @override_settings(DEBUG=True)
diff --git a/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/audit_log.scss b/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/audit_log.scss
index 105edf939..f0dbacdbe 100755
--- a/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/audit_log.scss
+++ b/enterprise/web-frontend/modules/baserow_enterprise/assets/scss/components/audit_log.scss
@@ -13,6 +13,11 @@
   }
 }
 
+.audit-log__filters--workspace {
+  max-width: 1024px;
+  grid-template-columns: 3fr 3fr minmax(15%, 120px) minmax(15%, 120px) max-content;
+}
+
 .audit-log__exported-list {
   margin-top: 30px;
   border-top: 1px solid $color-neutral-200;
diff --git a/enterprise/web-frontend/modules/baserow_enterprise/components/AuditLogSidebarWorkspace.vue b/enterprise/web-frontend/modules/baserow_enterprise/components/AuditLogSidebarWorkspace.vue
new file mode 100644
index 000000000..14c55ce0a
--- /dev/null
+++ b/enterprise/web-frontend/modules/baserow_enterprise/components/AuditLogSidebarWorkspace.vue
@@ -0,0 +1,58 @@
+<template>
+  <li
+    v-if="hasPermission"
+    v-tooltip="deactivated ? $t('auditLogSidebarWorkspace.deactivated') : null"
+    class="tree__item"
+    :class="{
+      'tree__item--loading': loading,
+      'tree__action--disabled': deactivated,
+      'tree__action--deactivated': deactivated,
+      active: $route.matched.some(({ name }) => name === 'workspace-audit-log'),
+    }"
+  >
+    <div class="tree__action">
+      <nuxt-link
+        :event="deactivated || !hasPermission ? null : 'click'"
+        class="tree__link"
+        :to="{
+          name: 'workspace-audit-log',
+          params: { workspaceId: workspace.id },
+        }"
+      >
+        <i class="tree__icon tree__icon--type fas fa-history"></i>
+        {{ $t('auditLogSidebarWorkspace.title') }}
+      </nuxt-link>
+    </div>
+  </li>
+</template>
+
+<script>
+import EnterpriseFeatures from '@baserow_enterprise/features'
+
+export default {
+  name: 'AuditLogSidebarWorkspace',
+  props: {
+    workspace: {
+      type: Object,
+      required: true,
+    },
+  },
+  data() {
+    return {
+      loading: false,
+    }
+  },
+  computed: {
+    deactivated() {
+      return !this.$hasFeature(EnterpriseFeatures.AUDIT_LOG)
+    },
+    hasPermission() {
+      return this.$hasPermission(
+        'workspace.list_audit_log_entries',
+        this.workspace,
+        this.workspace.id
+      )
+    },
+  },
+}
+</script>
diff --git a/enterprise/web-frontend/modules/baserow_enterprise/components/admin/modals/AuditLogExportModal.vue b/enterprise/web-frontend/modules/baserow_enterprise/components/admin/modals/AuditLogExportModal.vue
index 3d66dac0f..6e4a95b36 100644
--- a/enterprise/web-frontend/modules/baserow_enterprise/components/admin/modals/AuditLogExportModal.vue
+++ b/enterprise/web-frontend/modules/baserow_enterprise/components/admin/modals/AuditLogExportModal.vue
@@ -18,11 +18,7 @@
       <div v-if="job" class="audit-log__exported-list-item">
         <div class="audit-log__exported-list-item-info">
           <div class="audit-log__exported-list-item-name">
-            {{
-              $t('auditLogExportModal.exportFilename', {
-                date: localDate(job.created_on),
-              })
-            }}
+            {{ getExportedFilenameTitle(job) }}
           </div>
           <div class="audit-log__exported-list-item-details">
             {{ humanExportedAt(job.created_on) }}
@@ -37,11 +33,7 @@
       >
         <div class="audit-log__exported-list-item-info">
           <div class="audit-log__exported-list-item-name">
-            {{
-              $t('auditLogExportModal.exportFilename', {
-                date: localDate(finishedJob.created_on),
-              })
-            }}
+            {{ getExportedFilenameTitle(finishedJob) }}
           </div>
           <div class="audit-log__exported-list-item-details">
             {{ humanExportedAt(finishedJob.created_on) }}
@@ -67,8 +59,8 @@ import error from '@baserow/modules/core/mixins/error'
 import moment from '@baserow/modules/core/moment'
 import { getHumanPeriodAgoCount } from '@baserow/modules/core/utils/date'
 import ExportLoadingBar from '@baserow/modules/database/components/export/ExportLoadingBar'
-import AuditLogAdminService from '@baserow_enterprise/services/auditLogAdmin'
 import AuditLogExportForm from '@baserow_enterprise/components/admin/forms/AuditLogExportForm'
+import AuditLogAdminService from '@baserow_enterprise/services/auditLog'
 
 const MAX_EXPORT_FILES = 4
 
@@ -81,6 +73,10 @@ export default {
       type: Object,
       required: true,
     },
+    workspaceId: {
+      type: Number,
+      default: null,
+    },
   },
   data() {
     return {
@@ -96,8 +92,13 @@ export default {
     const jobs = await AuditLogAdminService(this.$client).getLastExportJobs(
       MAX_EXPORT_FILES
     )
-    this.lastFinishedJobs = jobs.filter((job) => job.state === 'finished')
-    const runningJob = jobs.find(
+    const filteredJobs = this.workspaceId
+      ? jobs.filter((job) => job.filter_workspace_id === this.workspaceId)
+      : jobs
+    this.lastFinishedJobs = filteredJobs.filter(
+      (job) => job.state === 'finished'
+    )
+    const runningJob = filteredJobs.find(
       (job) => !['failed', 'cancelled', 'finished'].includes(job.state)
     )
     this.job = runningJob || null
@@ -129,6 +130,18 @@ export default {
     getExportedFilename(job) {
       return job ? `audit_log_${job.created_on}.csv` : ''
     },
+    getExportedFilenameTitle(job) {
+      if (job.filter_workspace_id) {
+        return this.$t('auditLogExportModal.exportWorkspaceFilename', {
+          date: this.localDate(job.created_on),
+          workspaceId: job.filter_workspace_id,
+        })
+      } else {
+        return this.$t('auditLogExportModal.exportFilename', {
+          date: this.localDate(job.created_on),
+        })
+      }
+    },
     humanExportedAt(timestamp) {
       const { period, count } = getHumanPeriodAgoCount(timestamp)
       return this.$tc(`datetime.${period}Ago`, count)
@@ -154,11 +167,18 @@ export default {
           value,
         ])
       )
+      if (this.workspaceId) {
+        filters.filter_workspace_id = this.workspaceId
+        filters.exclude_columns = 'workspace_id,workspace_name'
+      }
 
       try {
         const { data } = await AuditLogAdminService(
           this.$client
-        ).startExportCsvJob({ ...values, ...filters })
+        ).startExportCsvJob({
+          ...values,
+          ...filters,
+        })
         this.lastFinishedJobs = this.lastFinishedJobs.slice(
           0,
           MAX_EXPORT_FILES - 1
diff --git a/enterprise/web-frontend/modules/baserow_enterprise/locales/en.json b/enterprise/web-frontend/modules/baserow_enterprise/locales/en.json
index d7ee8628d..5190d8ec6 100644
--- a/enterprise/web-frontend/modules/baserow_enterprise/locales/en.json
+++ b/enterprise/web-frontend/modules/baserow_enterprise/locales/en.json
@@ -8,7 +8,7 @@
         "sidebarTooltip": "Your account has access to the enterprise features globally",
         "rbac": "RBAC",
         "sso": "SSO",
-        "deactivated": "Available in enterprise version",
+        "deactivated": "Available in the advanced/enterprise version",
         "licenseDescription": "Viewers are free with Baserow Enterprise. If a user has any other role, in any workspace then they will use a paid seat automatically.",
         "overflowWarning": "You have too many non-viewer users and have used up all of your paid seats. Change users to become viewers on each workspaces members page."
     },
@@ -61,7 +61,8 @@
         "Authentication": "Authentication"
     },
     "auditLog": {
-        "title": "Audit log",
+        "adminTitle": "Audit log",
+        "workspaceTitle": "Audit log - {workspaceName}",
         "filterUserTitle": "User",
         "filterWorkspaceTitle": "Workspace",
         "filterActionTypeTitle": "Event Type",
@@ -84,7 +85,8 @@
     },
     "auditLogExportModal": {
         "title": "Export to CSV",
-        "exportFilename": "Audit Log Export - {date}",
+        "exportFilename": "Admin Audit Log Export - {date}",
+        "exportWorkspaceFilename": " Workspace ({workspaceId}) Audit Log Export - {date}",
         "cancelledTitle": "Export failed",
         "cancelledDescription": "Something went wrong while exporting the audit log. Please try again."
     },
@@ -274,5 +276,9 @@
     },
     "snapshotModalWarning": {
         "message": "Please be aware that a snapshot will include any permissions set on the application and its tables."
+    },
+    "auditLogSidebarWorkspace": {
+        "title": "Audit log",
+        "deactivated": "Available in the advanced/enterprise version"
     }
 }
diff --git a/enterprise/web-frontend/modules/baserow_enterprise/pages/admin/auditLog.vue b/enterprise/web-frontend/modules/baserow_enterprise/pages/auditLog.vue
similarity index 62%
rename from enterprise/web-frontend/modules/baserow_enterprise/pages/admin/auditLog.vue
rename to enterprise/web-frontend/modules/baserow_enterprise/pages/auditLog.vue
index 058f36fb8..486902dcd 100644
--- a/enterprise/web-frontend/modules/baserow_enterprise/pages/admin/auditLog.vue
+++ b/enterprise/web-frontend/modules/baserow_enterprise/pages/auditLog.vue
@@ -3,6 +3,7 @@
     <AuditLogExportModal
       ref="exportModal"
       :filters="filters"
+      :workspace-id="workspaceId"
     ></AuditLogExportModal>
     <CrudTable
       :columns="columns"
@@ -13,7 +14,11 @@
       row-id-key="id"
     >
       <template #title>
-        {{ $t('auditLog.title') }}
+        {{
+          workspaceId
+            ? $t('auditLog.workspaceTitle', { workspaceName })
+            : $t('auditLog.adminTitle')
+        }}
       </template>
       <template #header-right-side>
         <button
@@ -24,7 +29,10 @@
         </button>
       </template>
       <template #header-filters>
-        <div class="audit-log__filters">
+        <div
+          class="audit-log__filters"
+          :class="{ 'audit-log__filters--workspace': workspaceId }"
+        >
           <FilterWrapper :name="$t('auditLog.filterUserTitle')">
             <PaginatedDropdown
               ref="userFilter"
@@ -35,7 +43,10 @@
               @input="filterUser"
             ></PaginatedDropdown>
           </FilterWrapper>
-          <FilterWrapper :name="$t('auditLog.filterWorkspaceTitle')">
+          <FilterWrapper
+            v-if="!workspaceId"
+            :name="$t('auditLog.filterWorkspaceTitle')"
+          >
             <PaginatedDropdown
               ref="workspaceFilter"
               :value="filters.workspace_id"
@@ -88,7 +99,7 @@ import _ from 'lodash'
 import moment from '@baserow/modules/core/moment'
 import CrudTable from '@baserow/modules/core/components/crudTable/CrudTable'
 import PaginatedDropdown from '@baserow/modules/core/components/PaginatedDropdown'
-import AuditLogAdminService from '@baserow_enterprise/services/auditLogAdmin'
+import AuditLogService from '@baserow_enterprise/services/auditLog'
 import DateFilter from '@baserow_enterprise/components/crudTable/filters/DateFilter'
 import FilterWrapper from '@baserow_enterprise/components/crudTable/filters/FilterWrapper'
 import SimpleField from '@baserow/modules/core/components/crudTable/fields/SimpleField'
@@ -96,9 +107,10 @@ import LocalDateField from '@baserow/modules/core/components/crudTable/fields/Lo
 import CrudTableColumn from '@baserow/modules/core/crudTable/crudTableColumn'
 import LongTextField from '@baserow_enterprise/components/crudTable/fields/LongTextField'
 import AuditLogExportModal from '@baserow_enterprise/components/admin/modals/AuditLogExportModal'
+import EnterpriseFeatures from '@baserow_enterprise/features'
 
 export default {
-  name: 'AuditLogAdminTable',
+  name: 'AuditLog',
   components: {
     AuditLogExportModal,
     CrudTable,
@@ -107,9 +119,40 @@ export default {
     FilterWrapper,
   },
   layout: 'app',
-  middleware: 'staff',
+  middleware: 'authenticated',
+  asyncData({ app, error, route, store }) {
+    if (!app.$hasFeature(EnterpriseFeatures.AUDIT_LOG)) {
+      return error({
+        statusCode: 401,
+        message: 'Available in the advanced/enterprise version',
+      })
+    }
+
+    const workspaceId = route.params.workspaceId
+      ? parseInt(route.params.workspaceId)
+      : null
+    if (workspaceId) {
+      if (
+        !app.$hasPermission(
+          'workspace.list_audit_log_entries',
+          store.getters['workspace/get'](workspaceId),
+          workspaceId
+        )
+      ) {
+        return error({ statusCode: 404, message: 'Page not found' })
+      }
+    } else if (!store.getters['auth/isStaff']) {
+      return error({ statusCode: 403, message: 'Forbidden.' })
+    }
+
+    return { workspaceId }
+  },
   data() {
-    this.columns = [
+    const filters = {}
+    const params = this.$route.params
+    const workspaceId = params.workspaceId ? parseInt(params.workspaceId) : null
+
+    const columns = [
       new CrudTableColumn(
         'user',
         () => this.$t('auditLog.user'),
@@ -120,64 +163,85 @@ export default {
         {},
         '15'
       ),
-      new CrudTableColumn(
-        'workspace',
-        () => this.$t('auditLog.workspace'),
-        SimpleField,
-        true,
-        false,
-        false,
-        {},
-        '15'
-      ),
-      new CrudTableColumn(
-        'type',
-        () => this.$t('auditLog.actionType'),
-        SimpleField,
-        true,
-        false,
-        false,
-        {},
-        '10'
-      ),
-      new CrudTableColumn(
-        'description',
-        () => this.$t('auditLog.description'),
-        LongTextField,
-        false,
-        false,
-        false,
-        {},
-        '40'
-      ),
-      new CrudTableColumn(
-        'timestamp',
-        () => this.$t('auditLog.timestamp'),
-        LocalDateField,
-        true,
-        false,
-        false,
-        { dateTimeFormat: 'L LTS' },
-        '10'
-      ),
-      new CrudTableColumn(
-        'ip_address',
-        () => this.$t('auditLog.ip_address'),
-        SimpleField,
-        true,
-        false,
-        false,
-        {},
-        '10'
-      ),
     ]
-    this.service = AuditLogAdminService(this.$client)
+
+    if (!workspaceId) {
+      columns.push(
+        new CrudTableColumn(
+          'workspace',
+          () => this.$t('auditLog.workspace'),
+          SimpleField,
+          true,
+          false,
+          false,
+          {},
+          '15'
+        )
+      )
+    } else {
+      filters.workspace_id = workspaceId
+    }
+
+    columns.push(
+      ...[
+        new CrudTableColumn(
+          'type',
+          () => this.$t('auditLog.actionType'),
+          SimpleField,
+          true,
+          false,
+          false,
+          {},
+          '10'
+        ),
+        new CrudTableColumn(
+          'description',
+          () => this.$t('auditLog.description'),
+          LongTextField,
+          false,
+          false,
+          false,
+          {},
+          '40'
+        ),
+        new CrudTableColumn(
+          'timestamp',
+          () => this.$t('auditLog.timestamp'),
+          LocalDateField,
+          true,
+          false,
+          false,
+          { dateTimeFormat: 'L LTS' },
+          '10'
+        ),
+        new CrudTableColumn(
+          'ip_address',
+          () => this.$t('auditLog.ip_address'),
+          SimpleField,
+          true,
+          false,
+          false,
+          {},
+          '10'
+        ),
+      ]
+    )
+
+    this.columns = columns
+    this.service = AuditLogService(this.$client)
+
     return {
-      filters: {},
+      filters,
       dateTimeFormat: 'YYYY-MM-DDTHH:mm:ss.SSSZ',
     }
   },
   computed: {
+    workspaceName() {
+      const selectedWorkspace = this.$store.getters['workspace/get'](
+        this.workspaceId
+      )
+      return selectedWorkspace ? selectedWorkspace.name : ''
+    },
     disableDates() {
       const minimumDate = moment('2023-01-01', 'YYYY-MM-DD')
       const maximumDate = moment().add(1, 'day').endOf('day')
@@ -186,6 +250,23 @@ export default {
         from: maximumDate.toDate(),
       }
     },
+    selectedWorkspaceId() {
+      try {
+        return this.$store.getters['workspace/selectedId']
+      } catch (e) {
+        return null
+      }
+    },
+  },
+  watch: {
+    selectedWorkspaceId(newValue, oldValue) {
+      if (newValue !== oldValue && this.workspaceId) {
+        this.$router.push({
+          name: newValue ? 'workspace-audit-log' : 'dashboard',
+          params: { workspaceId: newValue },
+        })
+      }
+    },
   },
   methods: {
     clearFilters() {
@@ -196,7 +277,7 @@ export default {
         'fromTimestampFilter',
         'toTimestampFilter',
       ]) {
-        this.$refs[filterRef].clear()
+        this.$refs[filterRef]?.clear()
       }
       this.filters = {}
     },
@@ -215,7 +296,7 @@ export default {
       this.setFilter('user_id', userId)
     },
     fetchUsers(page, search) {
-      return this.service.fetchUsers(page, search)
+      return this.service.fetchUsers(page, search, this.workspaceId)
     },
     filterWorkspace(workspaceId) {
       this.setFilter('workspace_id', workspaceId)
@@ -224,7 +305,7 @@ export default {
       return this.service.fetchWorkspaces(page, search)
     },
     fetchActionTypes(page, search) {
-      return this.service.fetchActionTypes(page, search)
+      return this.service.fetchActionTypes(page, search, this.workspaceId)
     },
     filterActionType(actionTypeId) {
       this.setFilter('action_type', actionTypeId)
diff --git a/enterprise/web-frontend/modules/baserow_enterprise/plugins.js b/enterprise/web-frontend/modules/baserow_enterprise/plugins.js
index a9373360b..0b687d7c9 100644
--- a/enterprise/web-frontend/modules/baserow_enterprise/plugins.js
+++ b/enterprise/web-frontend/modules/baserow_enterprise/plugins.js
@@ -1,5 +1,6 @@
 import { BaserowPlugin } from '@baserow/modules/core/plugins'
 import ChatwootSupportSidebarWorkspace from '@baserow_enterprise/components/ChatwootSupportSidebarWorkspace'
+import AuditLogSidebarWorkspace from '@baserow_enterprise/components/AuditLogSidebarWorkspace'
 import MemberRolesDatabaseContextItem from '@baserow_enterprise/components/member-roles/MemberRolesDatabaseContextItem'
 import MemberRolesTableContextItem from '@baserow_enterprise/components/member-roles/MemberRolesTableContextItem'
 import EnterpriseFeatures from '@baserow_enterprise/features'
@@ -10,12 +11,17 @@ export class EnterprisePlugin extends BaserowPlugin {
     return 'enterprise'
   }
 
-  getSidebarWorkspaceComponent(workspace) {
+  getSidebarWorkspaceComponents(workspace) {
     const supportEnabled = this.app.$hasFeature(
       EnterpriseFeatures.SUPPORT,
       workspace.id
     )
-    return supportEnabled ? ChatwootSupportSidebarWorkspace : null
+    const sidebarItems = []
+    if (supportEnabled) {
+      sidebarItems.push(ChatwootSupportSidebarWorkspace)
+    }
+    sidebarItems.push(AuditLogSidebarWorkspace)
+    return sidebarItems
   }
 
   getAdditionalDatabaseContextComponents(workspace, database) {
diff --git a/enterprise/web-frontend/modules/baserow_enterprise/routes.js b/enterprise/web-frontend/modules/baserow_enterprise/routes.js
index 53b0ab93a..b89976f11 100644
--- a/enterprise/web-frontend/modules/baserow_enterprise/routes.js
+++ b/enterprise/web-frontend/modules/baserow_enterprise/routes.js
@@ -19,6 +19,11 @@ export const routes = [
   {
     name: 'admin-audit-log',
     path: '/admin/audit-log',
-    component: path.resolve(__dirname, 'pages/admin/auditLog.vue'),
+    component: path.resolve(__dirname, 'pages/auditLog.vue'),
+  },
+  {
+    name: 'workspace-audit-log',
+    path: '/workspace/:workspaceId/audit-log',
+    component: path.resolve(__dirname, 'pages/auditLog.vue'),
   },
 ]
diff --git a/enterprise/web-frontend/modules/baserow_enterprise/services/auditLogAdmin.js b/enterprise/web-frontend/modules/baserow_enterprise/services/auditLog.js
similarity index 65%
rename from enterprise/web-frontend/modules/baserow_enterprise/services/auditLogAdmin.js
rename to enterprise/web-frontend/modules/baserow_enterprise/services/auditLog.js
index 4e31c8d7c..bca3c6b00 100644
--- a/enterprise/web-frontend/modules/baserow_enterprise/services/auditLogAdmin.js
+++ b/enterprise/web-frontend/modules/baserow_enterprise/services/auditLog.js
@@ -2,36 +2,44 @@ import baseService from '@baserow/modules/core/crudTable/baseService'
 import jobService from '@baserow/modules/core/services/job'
 
 export default (client) => {
-  return Object.assign(baseService(client, '/admin/audit-log/'), {
-    fetchUsers(page, search) {
-      const usersUrl = '/admin/audit-log/users/'
+  return Object.assign(baseService(client, `/audit-log/`), {
+    fetchUsers(page, search, workspaceId = null) {
+      const usersUrl = `/audit-log/users/`
       const userPaginatedService = baseService(client, usersUrl)
-      return userPaginatedService.fetch(usersUrl, page, search, [], [])
+      const filters = {}
+      if (workspaceId) {
+        filters.workspace_id = workspaceId
+      }
+      return userPaginatedService.fetch(usersUrl, page, search, [], filters)
     },
     fetchWorkspaces(page, search) {
-      const workspacesUrl = '/admin/audit-log/workspaces/'
+      const workspacesUrl = `/audit-log/workspaces/`
       const workspacePaginatedService = baseService(client, workspacesUrl)
       return workspacePaginatedService.fetch(
         workspacesUrl,
         page,
         search,
         [],
-        []
+        {}
       )
     },
-    fetchActionTypes(page, search) {
-      const actionTypesUrl = '/admin/audit-log/action-types/'
+    fetchActionTypes(page, search, workspaceId = null) {
+      const actionTypesUrl = `/audit-log/action-types/`
       const actionTypePaginatedService = baseService(client, actionTypesUrl)
+      const filters = {}
+      if (workspaceId) {
+        filters.workspace_id = workspaceId
+      }
       return actionTypePaginatedService.fetch(
         actionTypesUrl,
         page,
         search,
         [],
-        []
+        filters
       )
     },
     startExportCsvJob(data) {
-      return client.post('/admin/audit-log/export/', data)
+      return client.post(`/audit-log/export/`, data)
     },
     getExportJobInfo(jobId) {
       return jobService(client).get(jobId)
diff --git a/premium/backend/src/baserow_premium/api/admin/views.py b/premium/backend/src/baserow_premium/api/admin/views.py
index 8220e751d..8577773d0 100755
--- a/premium/backend/src/baserow_premium/api/admin/views.py
+++ b/premium/backend/src/baserow_premium/api/admin/views.py
@@ -23,10 +23,9 @@ from baserow.api.pagination import PageNumberPagination
 from baserow.api.schemas import get_error_schema
 
 
-class AdminListingView(
+class APIListingView(
     APIView, SearchableViewMixin, SortableViewMixin, FilterableViewMixin
 ):
-    permission_classes = (IsAdminUser,)
     serializer_class = None
     search_fields: List[str] = ["id"]
     filters_field_mapping: Dict[str, str] = {}
@@ -72,32 +71,37 @@ class AdminListingView(
 
     @staticmethod
     def get_extend_schema_parameters(
-        name, serializer_class, search_fields, sort_field_mapping
+        name, serializer_class, search_fields, sort_field_mapping, extra_parameters=None
     ):
         """
         Returns the schema properties that can be used in in the @extend_schema
         decorator.
         """
 
-        fields = sort_field_mapping.keys()
-        all_fields = ", ".join(fields)
-        field_name_1 = "field_1"
-        field_name_2 = "field_2"
-        for i, field in enumerate(fields):
-            if i == 0:
-                field_name_1 = field
-            if i == 1:
-                field_name_2 = field
-
-        return {
-            "parameters": [
+        parameters = []
+        if search_fields:
+            parameters.append(
                 OpenApiParameter(
                     name="search",
                     location=OpenApiParameter.QUERY,
                     type=OpenApiTypes.STR,
-                    description=f"If provided only {name} that match the query will "
-                    f"be returned.",
-                ),
+                    description=f"If provided only {name} with {' or '.join(search_fields)} "
+                    "that match the query will be returned.",
+                )
+            )
+
+        if sort_field_mapping:
+            fields = sort_field_mapping.keys()
+            all_fields = ", ".join(fields)
+            field_name_1 = "field_1"
+            field_name_2 = "field_2"
+            for i, field in enumerate(fields):
+                if i == 0:
+                    field_name_1 = field
+                if i == 1:
+                    field_name_2 = field
+
+            parameters.append(
                 OpenApiParameter(
                     name="sorts",
                     location=OpenApiParameter.QUERY,
@@ -105,13 +109,18 @@ class AdminListingView(
                     description=f"A comma separated string of attributes to sort by, "
                     f"each attribute must be prefixed with `+` for a descending "
                     f"sort or a `-` for an ascending sort. The accepted attribute "
-                    f"names are: {all_fields}. For example `sorts=-{field_name_1},"
+                    f"names are: `{all_fields}`. For example `sorts=-{field_name_1},"
                     f"-{field_name_2}` will sort the {name} first by descending "
                     f"{field_name_1} and then ascending {field_name_2}. A sort"
                     f"parameter with multiple instances of the same sort attribute "
                     f"will respond with the ERROR_INVALID_SORT_ATTRIBUTE "
                     f"error.",
                 ),
+            )
+
+        return {
+            "parameters": [
+                *parameters,
                 OpenApiParameter(
                     name="page",
                     location=OpenApiParameter.QUERY,
@@ -125,6 +134,7 @@ class AdminListingView(
                     description=f"Defines how many {name} should be returned per "
                     f"page.",
                 ),
+                *(extra_parameters or []),
             ],
             "responses": {
                 200: serializer_class(many=True),
@@ -139,3 +149,7 @@ class AdminListingView(
                 401: None,
             },
         }
+
+
+class AdminListingView(APIListingView):
+    permission_classes = (IsAdminUser,)
diff --git a/web-frontend/modules/core/assets/scss/components/data_table.scss b/web-frontend/modules/core/assets/scss/components/data_table.scss
index b2cff184d..5f8a55e76 100644
--- a/web-frontend/modules/core/assets/scss/components/data_table.scss
+++ b/web-frontend/modules/core/assets/scss/components/data_table.scss
@@ -35,6 +35,8 @@
 }
 
 .data-table__title {
+  @extend %ellipsis;
+
   font-size: 24px;
   line-height: 32px;
   margin: 0;
diff --git a/web-frontend/modules/core/components/sidebar/Sidebar.vue b/web-frontend/modules/core/components/sidebar/Sidebar.vue
index 1c18f21b7..eddfa3dda 100644
--- a/web-frontend/modules/core/components/sidebar/Sidebar.vue
+++ b/web-frontend/modules/core/components/sidebar/Sidebar.vue
@@ -448,8 +448,8 @@ export default {
     },
     sidebarWorkspaceComponents() {
       return Object.values(this.$registry.getAll('plugin'))
-        .map((plugin) =>
-          plugin.getSidebarWorkspaceComponent(this.selectedWorkspace)
+        .flatMap((plugin) =>
+          plugin.getSidebarWorkspaceComponents(this.selectedWorkspace)
         )
         .filter((component) => component !== null)
     },
diff --git a/web-frontend/modules/core/locales/en.json b/web-frontend/modules/core/locales/en.json
index ea1ed1388..f8c164264 100644
--- a/web-frontend/modules/core/locales/en.json
+++ b/web-frontend/modules/core/locales/en.json
@@ -144,6 +144,7 @@
     "workspaceContext": {
         "renameWorkspace": "Rename workspace",
         "members": "Members",
+        "auditLog": "Audit log",
         "viewTrash": "View trash",
         "leaveWorkspace": "Leave workspace",
         "deleteWorkspace": "Delete workspace"
diff --git a/web-frontend/modules/core/plugins.js b/web-frontend/modules/core/plugins.js
index 5af0dcff9..7a8d7e4fa 100644
--- a/web-frontend/modules/core/plugins.js
+++ b/web-frontend/modules/core/plugins.js
@@ -36,7 +36,7 @@ export class BaserowPlugin extends Registerable {
    * Every registered plugin can display an additional item in the sidebar within
    * the workspace context.
    */
-  getSidebarWorkspaceComponent(workspace) {
+  getSidebarWorkspaceComponents(workspace) {
     return null
   }
 
diff --git a/web-frontend/modules/database/pages/table.vue b/web-frontend/modules/database/pages/table.vue
index 5ea8a946d..44eeee875 100644
--- a/web-frontend/modules/database/pages/table.vue
+++ b/web-frontend/modules/database/pages/table.vue
@@ -39,6 +39,7 @@ export default {
   beforeRouteLeave(to, from, next) {
     this.$store.dispatch('view/unselect')
     this.$store.dispatch('table/unselect')
+    this.$store.dispatch('application/unselect')
     next()
   },
   async beforeRouteUpdate(to, from, next) {