diff --git a/app/apps/api/serializers.py b/app/apps/api/serializers.py
index f5cc969b79c8387116f03f6516d0e001949dffb8..3feb762dd85c532be2073fce3520346be257194a 100644
--- a/app/apps/api/serializers.py
+++ b/app/apps/api/serializers.py
@@ -12,7 +12,8 @@ from easy_thumbnails.files import get_thumbnailer
 
 from api.fields import DisplayChoiceField
 from users.models import User
-from core.models import (Document,
+from core.models import (Project,
+                         Document,
                          DocumentPart,
                          Block,
                          Line,
@@ -60,6 +61,12 @@ class ScriptSerializer(serializers.ModelSerializer):
         fields = '__all__'
 
 
+class ProjectSerializer(serializers.ModelSerializer):
+    class Meta:
+        model = Project
+        fields = '__all__'
+
+
 class PartMoveSerializer(serializers.ModelSerializer):
     index = serializers.IntegerField()
 
@@ -114,11 +121,18 @@ class DocumentSerializer(serializers.ModelSerializer):
     valid_block_types = BlockTypeSerializer(many=True, read_only=True)
     valid_line_types = LineTypeSerializer(many=True, read_only=True)
     parts_count = serializers.SerializerMethodField()
+    project = serializers.SlugRelatedField(slug_field='slug',
+                                           queryset=Project.objects.all())
 
     class Meta:
         model = Document
-        fields = ('pk', 'name', 'transcriptions', 'main_script', 'read_direction',
-                  'valid_block_types', 'valid_line_types', 'parts_count')
+        fields = ('pk', 'name', 'project', 'transcriptions', 'main_script', 'read_direction',
+                  'valid_block_types', 'valid_line_types', 'parts_count',
+                  'created_at', 'updated_at')
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.fields['project'].queryset = Project.objects.for_user(self.context['user'])
 
     def get_parts_count(self, document):
         return document.parts.count()
diff --git a/app/apps/api/urls.py b/app/apps/api/urls.py
index 928ec985a8265e478e0bdf05e380b396641746cc..e2a8c20f3f14c9c52ac077566a338181cc81c839 100644
--- a/app/apps/api/urls.py
+++ b/app/apps/api/urls.py
@@ -2,7 +2,8 @@ from django.urls import include, path
 from rest_framework_nested import routers
 from rest_framework.authtoken import views
 
-from api.views import (DocumentViewSet,
+from api.views import (ProjectViewSet,
+                       DocumentViewSet,
                        UserViewSet,
                        PartViewSet,
                        DocumentTranscriptionViewSet,
@@ -16,6 +17,7 @@ from api.views import (DocumentViewSet,
 
 router = routers.DefaultRouter()
 router.register(r'scripts', ScriptViewSet)
+router.register(r'projects', ProjectViewSet)
 router.register(r'documents', DocumentViewSet)
 router.register(r'user', UserViewSet)
 router.register(r'types/block', BlockTypeViewSet)
diff --git a/app/apps/api/views.py b/app/apps/api/views.py
index 66cc6afc8601985e861519aaa3778d2b8929415d..d4429bc3cae4829400d83a71166ee7e8e042047d 100644
--- a/app/apps/api/views.py
+++ b/app/apps/api/views.py
@@ -16,6 +16,7 @@ from rest_framework.pagination import PageNumberPagination
 from rest_framework.serializers import PrimaryKeyRelatedField
 
 from api.serializers import (UserOnboardingSerializer,
+                             ProjectSerializer,
                              DocumentSerializer,
                              PartDetailSerializer,
                              PartSerializer,
@@ -35,7 +36,8 @@ from api.serializers import (UserOnboardingSerializer,
                              TranscribeSerializer,
                              OcrModelSerializer)
 
-from core.models import (Document,
+from core.models import (Project,
+                         Document,
                          DocumentPart,
                          Block,
                          Line,
@@ -75,6 +77,12 @@ class ScriptViewSet(ReadOnlyModelViewSet):
     serializer_class = ScriptSerializer
 
 
+class ProjectViewSet(ModelViewSet):
+    queryset = Project.objects.all()
+    serializer_class = ProjectSerializer
+    paginate_by = 10
+
+
 class DocumentViewSet(ModelViewSet):
     queryset = Document.objects.all()
     serializer_class = DocumentSerializer
@@ -86,6 +94,11 @@ class DocumentViewSet(ModelViewSet):
             Prefetch('valid_line_types', queryset=LineType.objects.order_by('name')),
         )
 
+    def get_serializer_context(self):
+        context = super().get_serializer_context()
+        context['user'] = self.request.user
+        return context
+
     def form_error(self, msg):
         return Response({'status': 'error', 'error': msg}, status=400)
 
diff --git a/app/apps/core/admin.py b/app/apps/core/admin.py
index fc4881acc459bfda675ba233c15881cad5a6c3e6..97c0787e630228b7fedc96e27e5ffa24b6044775 100644
--- a/app/apps/core/admin.py
+++ b/app/apps/core/admin.py
@@ -1,6 +1,7 @@
 from django.contrib import admin
 
-from core.models import (Document,
+from core.models import (Project,
+                         Document,
                          DocumentPart,
                          Metadata,
                          DocumentMetadata,
@@ -23,7 +24,7 @@ class OcrModelDocumentInline(admin.TabularInline):
 
 
 class DocumentAdmin(admin.ModelAdmin):
-    list_display = ['pk', 'name', 'owner']
+    list_display = ['pk', 'name', 'owner', 'project']
     inlines = (MetadataInline, OcrModelDocumentInline)
 
 
@@ -54,6 +55,7 @@ class OcrModelDocumentAdmin(admin.ModelAdmin):
     list_display = ['document', 'ocr_model', 'trained_on', 'executed_on', 'created_at']
 
 
+admin.site.register(Project)
 admin.site.register(Document, DocumentAdmin)
 admin.site.register(DocumentPart, DocumentPartAdmin)
 admin.site.register(LineTranscription, LineTranscriptionAdmin)
diff --git a/app/apps/core/forms.py b/app/apps/core/forms.py
index dbddbe187612c210da646ed1a973631f1bb9aca5..26a65f8c6cfb9ff3773c77ead6ceb5aec02c6952 100644
--- a/app/apps/core/forms.py
+++ b/app/apps/core/forms.py
@@ -12,14 +12,21 @@ from django.utils.functional import cached_property
 from django.utils.translation import gettext_lazy as _
 
 from bootstrap.forms import BootstrapFormMixin
-from core.models import (Document, Metadata, DocumentMetadata,
+from core.models import (Project, Document, Metadata, DocumentMetadata,
                          DocumentPart, OcrModel, OcrModelDocument, Transcription,
+
                          BlockType, LineType, AlreadyProcessingException)
 from users.models import User
 
 logger = logging.getLogger(__name__)
 
 
+class ProjectForm(BootstrapFormMixin, forms.ModelForm):
+    class Meta:
+        model = Project
+        fields = ['name']
+
+
 class DocumentForm(BootstrapFormMixin, forms.ModelForm):
     def __init__(self, *args, **kwargs):
         self.request = kwargs.pop('request')
@@ -39,12 +46,16 @@ class DocumentForm(BootstrapFormMixin, forms.ModelForm):
             self.initial['valid_block_types'] = BlockType.objects.filter(default=True)
             self.initial['valid_line_types'] = LineType.objects.filter(default=True)
 
+        self.fields['project'].queryset = Project.objects.for_user(self.request.user)
+        if self.instance.pk and self.instance.owner != self.request.user:
+            self.fields['project'].disabled = True
+
         self.fields['valid_block_types'].queryset = block_qs.order_by('name')
         self.fields['valid_line_types'].queryset = line_qs.order_by('name')
 
     class Meta:
         model = Document
-        fields = ['name', 'read_direction', 'main_script',
+        fields = ['project', 'name', 'read_direction', 'main_script',
                   'valid_block_types', 'valid_line_types']
         widgets = {
             'valid_block_types': forms.CheckboxSelectMultiple,
@@ -52,11 +63,11 @@ class DocumentForm(BootstrapFormMixin, forms.ModelForm):
         }
 
 
-class DocumentShareForm(BootstrapFormMixin, forms.ModelForm):
+class ProjectShareForm(BootstrapFormMixin, forms.ModelForm):
     username = forms.CharField(required=False)
 
     class Meta:
-        model = Document
+        model = Project
         fields = ['shared_with_groups', 'shared_with_users', 'username']
 
     def __init__(self, *args, **kwargs):
@@ -79,10 +90,10 @@ class DocumentShareForm(BootstrapFormMixin, forms.ModelForm):
         return user
 
     def save(self, commit=True):
-        doc = super().save(commit=commit)
+        proj = super().save(commit=commit)
         if self.cleaned_data['username']:
-            doc.shared_with_users.add(self.cleaned_data['username'])
-        return doc
+            proj.shared_with_users.add(self.cleaned_data['username'])
+        return proj
 
 
 class MetadataForm(BootstrapFormMixin, forms.ModelForm):
@@ -141,6 +152,7 @@ class DocumentProcessForm1(BootstrapFormMixin, forms.Form):
     def process(self):
         model = self.cleaned_data.get('model')
 
+
 class DocumentSegmentForm(DocumentProcessForm1):
     SEG_STEPS_CHOICES = (
         ('both', _('Lines and regions')),
diff --git a/app/apps/core/migrations/0044_auto_20210427_0946.py b/app/apps/core/migrations/0044_auto_20210427_0946.py
new file mode 100644
index 0000000000000000000000000000000000000000..d68d7b4f7142fb5649ccc26392e1d1eef6506bc5
--- /dev/null
+++ b/app/apps/core/migrations/0044_auto_20210427_0946.py
@@ -0,0 +1,39 @@
+# Generated by Django 2.2.20 on 2021-04-27 09:46
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('auth', '0011_update_proxy_permissions'),
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('core', '0043_auto_20210324_1016'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='document',
+            name='read_direction',
+            field=models.CharField(choices=[('ltr', 'Left to right'), ('rtl', 'Right to left')], default='ltr', help_text='The read direction describes the order of the elements in the document, in opposition with the text direction which describes the order of the words in a line and is set by the script.', max_length=3),
+        ),
+        migrations.CreateModel(
+            name='Project',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=512)),
+                ('created_at', models.DateTimeField(auto_now_add=True)),
+                ('updated_at', models.DateTimeField(auto_now=True)),
+                ('owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
+                ('shared_with_groups', models.ManyToManyField(blank=True, related_name='shared_projects', to='auth.Group', verbose_name='Share with teams')),
+                ('shared_with_users', models.ManyToManyField(blank=True, related_name='shared_projects', to=settings.AUTH_USER_MODEL, verbose_name='Share with users')),
+            ],
+        ),
+        migrations.AddField(
+            model_name='document',
+            name='project',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.Project'),
+        ),
+    ]
diff --git a/app/apps/core/migrations/0044_tag_on_documents_and_parts.py.backup b/app/apps/core/migrations/0044_tag_on_documents_and_parts.py.backup
new file mode 100644
index 0000000000000000000000000000000000000000..a6f56dc787c442052d2288a6564494a1a0ce586d
--- /dev/null
+++ b/app/apps/core/migrations/0044_tag_on_documents_and_parts.py.backup
@@ -0,0 +1,46 @@
+# Generated by Django 2.2.19 on 2021-03-23 12:50
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0043_auto_20210324_1016'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='DocumentTag',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=100)),
+                ('priority', models.PositiveSmallIntegerField(choices=[(5, 'Very high'), (4, 'High'), (3, 'Medium'), (2, 'Low'), (1, 'Very low')], default=1)),
+            ],
+            options={
+                'abstract': False,
+            },
+        ),
+        migrations.CreateModel(
+            name='PartTag',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=100)),
+                ('priority', models.PositiveSmallIntegerField(choices=[(5, 'Very high'), (4, 'High'), (3, 'Medium'), (2, 'Low'), (1, 'Very low')], default=1)),
+            ],
+            options={
+                'abstract': False,
+            },
+        ),
+        migrations.AddField(
+            model_name='document',
+            name='tags',
+            field=models.ManyToManyField(blank=True, to='core.DocumentTag'),
+        ),
+        migrations.AddField(
+            model_name='documentpart',
+            name='tags',
+            field=models.ManyToManyField(blank=True, to='core.PartTag'),
+        ),
+    ]
diff --git a/app/apps/core/migrations/0045_project_slug.py b/app/apps/core/migrations/0045_project_slug.py
new file mode 100644
index 0000000000000000000000000000000000000000..817618e77d1b00a5dfcead4491f762d789faadf3
--- /dev/null
+++ b/app/apps/core/migrations/0045_project_slug.py
@@ -0,0 +1,20 @@
+# Generated by Django 2.2.20 on 2021-04-27 10:22
+
+import datetime
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0044_auto_20210427_0946'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='project',
+            name='slug',
+            field=models.SlugField(default=datetime.datetime(2021, 4, 27, 10, 22, 51, 880783)),
+            preserve_default=False,
+        ),
+    ]
diff --git a/app/apps/core/migrations/0046_auto_20210521_1444.py b/app/apps/core/migrations/0046_auto_20210521_1444.py
new file mode 100644
index 0000000000000000000000000000000000000000..f2b7053905979a64a65fcf8ed6d94c8cf1f4894e
--- /dev/null
+++ b/app/apps/core/migrations/0046_auto_20210521_1444.py
@@ -0,0 +1,43 @@
+# Generated by Django 2.2.20 on 2021-05-21 14:44
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('auth', '0011_update_proxy_permissions'),
+        ('core', '0045_auto_20210521_1034'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='document',
+            name='workflow_state',
+            field=models.PositiveSmallIntegerField(choices=[(0, 'Draft'), (2, 'Published'), (3, 'Archived')], default=0),
+        ),
+        migrations.CreateModel(
+            name='Project',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=512)),
+                ('slug', models.SlugField(unique=True)),
+                ('created_at', models.DateTimeField(auto_now_add=True)),
+                ('updated_at', models.DateTimeField(auto_now=True)),
+                ('owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
+                ('shared_with_groups', models.ManyToManyField(blank=True, related_name='shared_projects', to='auth.Group', verbose_name='Share with teams')),
+                ('shared_with_users', models.ManyToManyField(blank=True, related_name='shared_projects', to=settings.AUTH_USER_MODEL, verbose_name='Share with users')),
+            ],
+            options={
+                'ordering': ('-updated_at',),
+            },
+        ),
+        migrations.AddField(
+            model_name='document',
+            name='project',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='documents', to='core.Project'),
+        ),
+    ]
diff --git a/app/apps/core/migrations/0046_data_share_doc_to_proj.py b/app/apps/core/migrations/0046_data_share_doc_to_proj.py
new file mode 100644
index 0000000000000000000000000000000000000000..5007decefb9efcc6eb7b0ebcb04f618855399483
--- /dev/null
+++ b/app/apps/core/migrations/0046_data_share_doc_to_proj.py
@@ -0,0 +1,81 @@
+# Generated by Django 2.2.20 on 2021-05-10 13:06
+import time
+
+from django.db import migrations
+from django.template.defaultfilters import slugify
+
+
+def make_slug(proj, Project):
+    # models in migrations don't have access to models methods ;(
+    slug = slugify(proj.name)
+    # check unicity
+    exists = Project.objects.filter(slug=slug).count()
+    if not exists:
+        proj.slug = slug
+    else:
+        proj.slug = slug[:40] + hex(int(time.time()))[2:]
+
+    proj.save()
+
+
+def forwards(apps, schema_editor):
+    User = apps.get_model('users', 'User')
+    Project = apps.get_model('core', 'Project')
+    Document = apps.get_model('core', 'Document')
+    # create user projects
+    for user in User.objects.all():
+        proj, created = Project.objects.get_or_create(name=user.username+"'s Project",
+                                                      owner=user)
+        if not proj.slug:
+            make_slug(proj, Project)
+        # move documents into projects
+        user.document_set.update(project=proj)
+        # move share from docs to created projects
+        for doc in user.document_set.all():
+            for share in doc.shared_with_users.all():
+                proj.shared_with_users.add(share)
+            for share in doc.shared_with_groups.all():
+                proj.shared_with_groups.add(share)
+
+        # shared to draft
+        user.document_set.filter(workflow_state=1).update(workflow_state=0)
+
+    # deal with documents without owner (shouldn't be any but let's be safe)
+    # move them to admin's
+    user = User.objects.filter(is_superuser=True).first()
+    proj, dummy = Project.objects.get_or_create(name=user.username+"'s Project",
+                                                owner=user)
+    if not proj.slug:
+        make_slug(proj, Project)
+    for doc in Document.objects.filter(owner=None):
+        doc.project = proj
+        doc.save()
+        # move share from docs to created projects
+        for doc in user.document_set.all():
+            for share in doc.shared_with_users.all():
+                proj.shared_with_users.add(share)
+            for share in doc.shared_with_groups.all():
+                proj.shared_with_groups.add(share)
+
+
+def backwards(apps, schema_editor):
+    Document = apps.get_model('core', 'Document')
+    for doc in Document.objects.all():
+        if doc.project:
+            for share in doc.project.shared_with_users.all():
+                doc.shared_with_users.add(share)
+            for share in doc.project.shared_with_groups.all():
+                doc.shared_with_groups.add(share)
+
+    Document.objects.update(project=None)
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0045_project_slug'),
+    ]
+
+    operations = [
+        migrations.RunPython(forwards, backwards),
+    ]
diff --git a/app/apps/core/migrations/0047_auto_20210510_1512.py b/app/apps/core/migrations/0047_auto_20210510_1512.py
new file mode 100644
index 0000000000000000000000000000000000000000..e0b7560b07481f384ba4f2565cbb48015ab1e706
--- /dev/null
+++ b/app/apps/core/migrations/0047_auto_20210510_1512.py
@@ -0,0 +1,36 @@
+# Generated by Django 2.2.20 on 2021-05-10 15:12
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0046_data_share_doc_to_proj'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='project',
+            options={'ordering': ('-updated_at',)},
+        ),
+        migrations.RemoveField(
+            model_name='document',
+            name='shared_with_groups',
+        ),
+        migrations.RemoveField(
+            model_name='document',
+            name='shared_with_users',
+        ),
+        migrations.AlterField(
+            model_name='document',
+            name='project',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='documents', to='core.Project'),
+        ),
+        migrations.AlterField(
+            model_name='document',
+            name='workflow_state',
+            field=models.PositiveSmallIntegerField(choices=[(0, 'Draft'), (2, 'Published'), (3, 'Archived')], default=0),
+        ),
+    ]
diff --git a/app/apps/core/migrations/0047_datamigration_share_doc_to_proj.py b/app/apps/core/migrations/0047_datamigration_share_doc_to_proj.py
new file mode 100644
index 0000000000000000000000000000000000000000..d42de99d2ee4210dd6473d8c21a8cf0812fe65fe
--- /dev/null
+++ b/app/apps/core/migrations/0047_datamigration_share_doc_to_proj.py
@@ -0,0 +1,81 @@
+# Generated by Django 2.2.20 on 2021-05-10 13:06
+import time
+
+from django.db import migrations
+from django.template.defaultfilters import slugify
+
+
+def make_slug(proj, Project):
+    # models in migrations don't have access to models methods ;(
+    slug = slugify(proj.name)
+    # check unicity
+    exists = Project.objects.filter(slug=slug).count()
+    if not exists:
+        proj.slug = slug
+    else:
+        proj.slug = slug[:40] + hex(int(time.time()))[2:]
+
+    proj.save()
+
+
+def forwards(apps, schema_editor):
+    User = apps.get_model('users', 'User')
+    Project = apps.get_model('core', 'Project')
+    Document = apps.get_model('core', 'Document')
+    # create user projects
+    for user in User.objects.all():
+        proj, created = Project.objects.get_or_create(name=user.username+"'s Project",
+                                                      owner=user)
+        if not proj.slug:
+            make_slug(proj, Project)
+        # move documents into projects
+        user.document_set.update(project=proj)
+        # move share from docs to created projects
+        for doc in user.document_set.all():
+            for share in doc.shared_with_users.all():
+                proj.shared_with_users.add(share)
+            for share in doc.shared_with_groups.all():
+                proj.shared_with_groups.add(share)
+
+        # shared to draft
+        user.document_set.filter(workflow_state=1).update(workflow_state=0)
+
+    # deal with documents without owner (shouldn't be any but let's be safe)
+    # move them to admin's
+    user = User.objects.filter(is_superuser=True).first()
+    proj, dummy = Project.objects.get_or_create(name=user.username+"'s Project",
+                                                owner=user)
+    if not proj.slug:
+        make_slug(proj, Project)
+    for doc in Document.objects.filter(owner=None):
+        doc.project = proj
+        doc.save()
+        # move share from docs to created projects
+        for doc in user.document_set.all():
+            for share in doc.shared_with_users.all():
+                proj.shared_with_users.add(share)
+            for share in doc.shared_with_groups.all():
+                proj.shared_with_groups.add(share)
+
+
+def backwards(apps, schema_editor):
+    Document = apps.get_model('core', 'Document')
+    for doc in Document.objects.all():
+        if doc.project:
+            for share in doc.project.shared_with_users.all():
+                doc.shared_with_users.add(share)
+            for share in doc.project.shared_with_groups.all():
+                doc.shared_with_groups.add(share)
+
+    Document.objects.update(project=None)
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0046_auto_20210521_1444'),
+    ]
+
+    operations = [
+        migrations.RunPython(forwards, backwards),
+    ]
diff --git a/app/apps/core/migrations/0048_auto_20210520_1308.py b/app/apps/core/migrations/0048_auto_20210520_1308.py
new file mode 100644
index 0000000000000000000000000000000000000000..31bfc70455cc2db516958f3134cf2ceb29e8f11c
--- /dev/null
+++ b/app/apps/core/migrations/0048_auto_20210520_1308.py
@@ -0,0 +1,19 @@
+# Generated by Django 2.2.20 on 2021-05-20 13:08
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0047_auto_20210510_1512'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='document',
+            name='project',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documents', to='core.Project'),
+        ),
+    ]
diff --git a/app/apps/core/migrations/0048_auto_20210521_1445.py b/app/apps/core/migrations/0048_auto_20210521_1445.py
new file mode 100644
index 0000000000000000000000000000000000000000..4e410d45fd57cb3ba813f08f318998a6554068c5
--- /dev/null
+++ b/app/apps/core/migrations/0048_auto_20210521_1445.py
@@ -0,0 +1,27 @@
+# Generated by Django 2.2.20 on 2021-05-21 14:45
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0047_datamigration_share_doc_to_proj'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='document',
+            name='shared_with_groups',
+        ),
+        migrations.RemoveField(
+            model_name='document',
+            name='shared_with_users',
+        ),
+        migrations.AlterField(
+            model_name='document',
+            name='project',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='documents', to='core.Project'),
+        ),
+    ]
diff --git a/app/apps/core/models.py b/app/apps/core/models.py
index f199741fb8fa07c77c474e7da5adf6b19a520af3..a9fb102b2b42ee563a1b0402b2654604d38f8348 100644
--- a/app/apps/core/models.py
+++ b/app/apps/core/models.py
@@ -5,6 +5,7 @@ import os
 import json
 import functools
 import subprocess
+import time
 import uuid
 from PIL import Image
 from datetime import datetime
@@ -21,8 +22,8 @@ from django.contrib.postgres.fields import JSONField
 from django.core.validators import FileExtensionValidator
 from django.dispatch import receiver
 from django.forms import ValidationError
+from django.template.defaultfilters import slugify
 from django.utils.functional import cached_property
-from django.utils.text import slugify
 from django.utils.translation import gettext_lazy as _
 
 from celery.task.control import inspect, revoke
@@ -134,32 +135,73 @@ class DocumentMetadata(models.Model):
         return '%s:%s' % (self.document.name, self.key.name)
 
 
+class ProjectManager(models.Manager):
+    def for_user(self, user):
+        # return the list of editable projects
+        # Note: Monitor this query
+        return (Project.objects
+                .filter(Q(owner=user)
+                        | (Q(shared_with_users=user)
+                           | Q(shared_with_groups__in=user.groups.all())))
+                .prefetch_related('shared_with_groups')
+                .distinct())
+
+
+class Project(models.Model):
+    name = models.CharField(max_length=512)
+    slug = models.SlugField()
+
+    created_at = models.DateTimeField(auto_now_add=True)
+    updated_at = models.DateTimeField(auto_now=True)
+
+    owner = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
+
+    shared_with_users = models.ManyToManyField(User, blank=True,
+                                               verbose_name=_("Share with users"),
+                                               related_name='shared_projects')
+    shared_with_groups = models.ManyToManyField(Group, blank=True,
+                                                verbose_name=_("Share with teams"),
+                                                related_name='shared_projects')
+
+    # strict_ontology =
+
+    objects = ProjectManager()
+
+    class Meta:
+        ordering = ('-updated_at',)
+
+    def __str__(self):
+        return self.name
+
+    def make_slug(self):
+        slug = slugify(self.name)
+        # check unicity
+        exists = Project.objects.filter(slug=slug).count()
+        if not exists:
+            self.slug = slug
+        else:
+            self.slug = slug[:40] + hex(int(time.time()))[2:]
+
+    def save(self, *args, **kwargs):
+        if not self.pk:
+            self.make_slug()
+        super().save(*args, **kwargs)
+
+
 class DocumentManager(models.Manager):
     def get_queryset(self):
         return super().get_queryset().select_related('typology')
 
     def for_user(self, user):
-        # return the list of editable documents
-        # Note: Monitor this query
-        return (Document.objects
-                .filter(Q(owner=user)
-                        | (Q(workflow_state__gt=Document.WORKFLOW_STATE_DRAFT)
-                           & (Q(shared_with_users=user)
-                              | Q(shared_with_groups__in=user.groups.all()))))
-                .exclude(workflow_state=Document.WORKFLOW_STATE_ARCHIVED)
-                .prefetch_related('shared_with_groups', 'transcriptions')
-                .select_related('typology')
-                .distinct())
+        return Document.objects.filter(project__in=Project.objects.for_user(user))
 
 
 class Document(models.Model):
     WORKFLOW_STATE_DRAFT = 0
-    WORKFLOW_STATE_SHARED = 1  # editable a viewable by shared_with people
     WORKFLOW_STATE_PUBLISHED = 2  # viewable by the world
     WORKFLOW_STATE_ARCHIVED = 3  #
     WORKFLOW_STATE_CHOICES = (
         (WORKFLOW_STATE_DRAFT, _("Draft")),
-        (WORKFLOW_STATE_SHARED, _("Shared")),
         (WORKFLOW_STATE_PUBLISHED, _("Published")),
         (WORKFLOW_STATE_ARCHIVED, _("Archived")),
     )
@@ -176,13 +218,6 @@ class Document(models.Model):
     workflow_state = models.PositiveSmallIntegerField(
         default=WORKFLOW_STATE_DRAFT,
         choices=WORKFLOW_STATE_CHOICES)
-    shared_with_users = models.ManyToManyField(User, blank=True,
-                                               verbose_name=_("Share with users"),
-                                               related_name='shared_documents')
-    shared_with_groups = models.ManyToManyField(Group, blank=True,
-                                                verbose_name=_("Share with teams"),
-                                                related_name='shared_documents')
-
     main_script = models.ForeignKey(Script, null=True, blank=True, on_delete=models.SET_NULL)
     read_direction = models.CharField(
         max_length=3,
@@ -201,6 +236,10 @@ class Document(models.Model):
 
     metadatas = models.ManyToManyField(Metadata, through=DocumentMetadata, blank=True)
 
+    project = models.ForeignKey(Project,
+                                on_delete=models.CASCADE,
+                                related_name='documents')
+
     objects = DocumentManager()
 
     class Meta:
@@ -214,11 +253,6 @@ class Document(models.Model):
         Transcription.objects.get_or_create(document=self, name=_(Transcription.DEFAULT_NAME))
         return res
 
-    @property
-    def is_shared(self):
-        return self.workflow_state in [self.WORKFLOW_STATE_PUBLISHED,
-                                       self.WORKFLOW_STATE_SHARED]
-
     @property
     def is_published(self):
         return self.workflow_state == self.WORKFLOW_STATE_PUBLISHED
diff --git a/app/apps/core/urls.py b/app/apps/core/urls.py
index 313b2e1e9d5e67116feb03c0f543214e618eb8df..2420d62545807b9cf2eef5a9efe52d053ed0a1eb 100644
--- a/app/apps/core/urls.py
+++ b/app/apps/core/urls.py
@@ -2,8 +2,10 @@ from django.urls import path
 from django.views.generic import TemplateView
 
 from core.views import (Home,
+                        CreateProject,
+                        ProjectList,
                         DocumentsList,
-                        DocumentDetail,
+                        # DocumentDetail,
                         CreateDocument,
                         UpdateDocument,
                         EditPart,
@@ -12,14 +14,20 @@ from core.views import (Home,
                         ModelDelete,
                         ModelCancelTraining,
                         PublishDocument,
-                        ShareDocument,
+                        ShareProject,
                         DocumentPartsProcessAjax)
 
 urlpatterns = [
     path('', Home.as_view(), name='home'),
-    path('documents/', DocumentsList.as_view(), name='documents-list'),
-    path('document/<int:pk>/', DocumentDetail.as_view(), name='document-detail'),
-    path('document/create/', CreateDocument.as_view(), name='document-create'),
+
+    path('projects/create/', CreateProject.as_view(), name='project-create'),
+    path('projects/', ProjectList.as_view(), name='projects-list'),
+    # path('project/<str:slug>/', ProjectDetail.as_view(), name='project-detail'),
+    path('project/<str:slug>/documents/', DocumentsList.as_view(), name='documents-list'),
+    path('project/<str:slug>/document/create/', CreateDocument.as_view(), name='document-create'),
+    path('project/<int:pk>/share/', ShareProject.as_view(), name='project-share'),
+
+    # path('document/<int:pk>/', DocumentDetail.as_view(), name='document-detail'),
     path('document/<int:pk>/edit/', UpdateDocument.as_view(), name='document-update'),
     path('document/<int:pk>/parts/edit/', EditPart.as_view(), name='document-part-edit'),
     path('document/<int:pk>/part/<int:part_pk>/edit/', EditPart.as_view(),
@@ -31,7 +39,6 @@ urlpatterns = [
          name='model-cancel-training'),
     path('document/<int:document_pk>/models/', ModelsList.as_view(), name='document-models'),
     path('document/<int:pk>/publish/', PublishDocument.as_view(), name='document-publish'),
-    path('document/<int:pk>/share/', ShareDocument.as_view(), name='document-share'),
     path('document/<int:pk>/process/', DocumentPartsProcessAjax.as_view(),
          name='document-parts-process'),
 
diff --git a/app/apps/core/views.py b/app/apps/core/views.py
index a140d63d3ecd99ef868ee91d9ede80a119aa5424..d8cfd458e7ce1edf8f7b27949464e1f0a9cf1fd6 100644
--- a/app/apps/core/views.py
+++ b/app/apps/core/views.py
@@ -13,9 +13,9 @@ from django.urls import reverse
 from django.views.generic import View, TemplateView, ListView, DetailView
 from django.views.generic import CreateView, UpdateView, DeleteView
 
-from core.models import (Document, DocumentPart, Metadata,
+from core.models import (Project, Document, DocumentPart, Metadata,
                          OcrModel, AlreadyProcessingException)
-from core.forms import (DocumentForm, MetadataFormSet, DocumentShareForm,
+from core.forms import (ProjectForm, DocumentForm, MetadataFormSet, ProjectShareForm,
                         UploadImageForm, DocumentProcessForm)
 from imports.forms import ImportForm, ExportForm
 
@@ -33,16 +33,47 @@ class Home(TemplateView):
         return context
 
 
+class ProjectList(LoginRequiredMixin, ListView):
+    model = Project
+    paginate_by = 10
+
+    def get_queryset(self):
+        return (Project.objects
+                .for_user(self.request.user)
+                .select_related('owner'))
+
+
+class CreateProject(LoginRequiredMixin, SuccessMessageMixin, CreateView):
+    model = Project
+    form_class = ProjectForm
+    success_message = _("Project created successfully!")
+
+    def get_success_url(self):
+        return reverse('projects-list')
+
+    def form_valid(self, form):
+        form.instance.owner = self.request.user
+        response = super().form_valid(form)
+        return response
+
+
 class DocumentsList(LoginRequiredMixin, ListView):
     model = Document
     paginate_by = 10
 
     def get_queryset(self):
-        return (Document.objects
-                .for_user(self.request.user)
+        self.project = Project.objects.for_user(self.request.user).get(slug=self.kwargs['slug'])
+        return (Document.objects.filter(project=self.project)
                 .select_related('owner', 'main_script')
                 .annotate(parts_updated_at=Max('parts__updated_at')))
 
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+        context['project'] = self.project
+        if self.project.owner == self.request.user:
+            context['share_form'] = ProjectShareForm(instance=self.project, request=self.request)
+        return context
+
 
 class DocumentMixin():
     def get_success_url(self):
@@ -66,7 +97,9 @@ class DocumentMixin():
         obj = super().get_object()
         try:
             # we fetched the object already, now we check that the user has perms to edit it
-            Document.objects.for_user(self.request.user).get(pk=obj.pk)
+            (Document.objects
+             .filter(project__in=Project.objects.for_user(self.request.user))
+             .get(pk=obj.pk))
         except Document.DoesNotExist:
             raise PermissionDenied
         return obj
@@ -78,6 +111,13 @@ class CreateDocument(LoginRequiredMixin, SuccessMessageMixin, DocumentMixin, Cre
 
     success_message = _("Document created successfully!")
 
+    def get_form(self, *args, **kwargs):
+
+        form = super().get_form(*args, **kwargs)
+        form.initial = {'project': Project.objects.get(
+            slug=self.request.resolver_match.kwargs['slug'])}
+        return form
+
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
         context['metadata_form'] = self.get_metadata_formset()
@@ -90,8 +130,6 @@ class CreateDocument(LoginRequiredMixin, SuccessMessageMixin, DocumentMixin, Cre
         if form.is_valid() and metadata_form.is_valid():
             return self.form_valid(form, metadata_form)
         else:
-            print(form.errors)
-            print(metadata_form.errors)
             return self.form_invalid(form)
 
     def form_valid(self, form, metadata_form):
@@ -113,7 +151,6 @@ class UpdateDocument(LoginRequiredMixin, SuccessMessageMixin, DocumentMixin, Upd
         context['can_publish'] = self.object.owner == self.request.user
         if 'metadata_form' not in kwargs:
             context['metadata_form'] = self.get_metadata_formset(instance=self.object)
-        context['share_form'] = DocumentShareForm(instance=self.object, request=self.request)
         return context
 
     def post(self, request, *args, **kwargs):
@@ -150,33 +187,29 @@ class DocumentImages(LoginRequiredMixin, DocumentMixin, DetailView):
         return context
 
 
-class ShareDocument(LoginRequiredMixin, SuccessMessageMixin, DocumentMixin, UpdateView):
-    model = Document
-    form_class = DocumentShareForm
-    success_message = _("Document shared successfully!")
+class ShareProject(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
+    model = Project
+    form_class = ProjectShareForm
+    success_message = _("Project shared successfully!")
     http_method_names = ('post',)
 
-    def get_redirect_url(self):
-        return reverse('document-update', kwargs={'pk': self.object.pk})
+    def get_form_kwargs(self):
+        kwargs = super().get_form_kwargs()
+        kwargs['request'] = self.request
+        return kwargs
 
-    def get_queryset(self):
-        return Document.objects.for_user(self.request.user).select_related('owner')
+    def get_success_url(self):
+        return reverse('documents-list', kwargs={'slug': self.object.slug})
 
-    def form_valid(self, form):
-        if form.instance.workflow_state == Document.WORKFLOW_STATE_DRAFT:
-            form.instance.workflow_state = Document.WORKFLOW_STATE_SHARED
-        return super().form_valid(form)
+    def get_queryset(self):
+        return Project.objects.for_user(self.request.user).select_related('owner')
 
 
-class PublishDocument(LoginRequiredMixin, SuccessMessageMixin, UpdateView):
+class PublishDocument(LoginRequiredMixin, SuccessMessageMixin, DocumentMixin, UpdateView):
     model = Document
     fields = ['workflow_state']
     http_method_names = ('post',)
 
-    def get_queryset(self):
-        # will raise a 404 instead of a 403 if user can't edit, but avoids a query
-        return Document.objects.for_user(self.request.user)
-
     def get_success_message(self, form_data):
         if self.object.is_archived:
             return _("Document archived successfully!")
diff --git a/app/escriptorium/settings.py b/app/escriptorium/settings.py
index 4c1ebec8415d05b8725acb96e4f05eb0fbbfd42a..a1ae037cc561dafd6365abbf1136022a9bbf35b0 100644
--- a/app/escriptorium/settings.py
+++ b/app/escriptorium/settings.py
@@ -139,7 +139,7 @@ AUTH_PASSWORD_VALIDATORS = [
 
 AUTH_USER_MODEL = 'users.User'
 LOGIN_URL = 'login'
-LOGIN_REDIRECT_URL = 'documents-list'
+LOGIN_REDIRECT_URL = 'projects-list'
 LOGOUT_REDIRECT_URL = '/'
 
 # Internationalization
diff --git a/app/escriptorium/templates/base.html b/app/escriptorium/templates/base.html
index ac673a3fa27359b2a874bef5bd2dc37c0398a55a..e88a8886e2fab5c74e9993b6bf7732b6b9c89313 100644
--- a/app/escriptorium/templates/base.html
+++ b/app/escriptorium/templates/base.html
@@ -63,7 +63,7 @@
         <ul class="navbar-nav mr-right">
           {% if user.is_authenticated %}
           <li class="nav-item">
-            <a class="nav-link {% block nav-doc-list-class %}{% endblock %}" href="{% url 'documents-list' %}">{% trans "My Documents" %}</a>
+            <a class="nav-link {% block nav-proj-list-class %}{% endblock %}" href="{% url 'projects-list' %}">{% trans "My Projects" %}</a>
           </li>
           <li class="nav-item dropdown">
               <a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
diff --git a/app/escriptorium/templates/core/document_form.html b/app/escriptorium/templates/core/document_form.html
index baaf9abf1a7800e52d5d28bdbb5248ad876f6e8b..9c61c0dfca1528aeab4d50893a02e1eddfd8a664 100644
--- a/app/escriptorium/templates/core/document_form.html
+++ b/app/escriptorium/templates/core/document_form.html
@@ -7,7 +7,6 @@
 {% block extra_nav %}
 {% if object and can_publish %}
 <div class="ml-auto">
-    <button type="button" class="nav-item btn btn-primary mr-1" data-toggle="modal" data-target="#shareModal" title="{% trans "Share" %}"><i class="fas fa-share"></i></button>
     {% comment %}
     {% if not object.is_published %}
     <form method="post" class="inline-form" action="{% url 'document-publish' pk=object.pk %}">{% csrf_token %}
@@ -42,6 +41,7 @@
             {% else %}
             {% render_field form.name autofocus=1 %}
             {% endif %}
+            {% render_field form.project %}
 
             {% render_field form.main_script %}
             {% render_field form.read_direction %}
@@ -132,39 +132,6 @@
 </div>
 {% endblock %}
 
-{% block modals %}
-{% if share_form %}
-<!-- shareModal -->
-<div class="modal fade" id="shareModal" tabindex="-1" role="dialog" aria-labelledby="shareModalLabel" aria-hidden="true">
-    <div class="modal-dialog" role="document">
-        <div class="modal-content">
-            <form method="post" action="{% url 'document-share' pk=object.pk %}">
-                {% csrf_token %}
-
-                <div class="modal-header">
-                    <h3 class="modal-title" id="exampleModalLabel">{% trans "Share with" %}</h3>
-                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
-                        <span aria-hidden="true">&times;</span>
-                    </button>
-                </div>
-                <div class="modal-body">
-                    <h5>{% trans "Teams" %}</h5>
-                    {% render_field share_form.shared_with_groups %}
-                    <h5>{% trans "Users" %}</h5>
-                    {% render_field share_form.username %}
-                    {% render_field share_form.shared_with_users %}
-                </div>
-                <div class="modal-footer">
-                    <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
-                    <input type="submit" value="{% trans 'Share' %}" class="btn btn-primary">
-                </div>
-            </form>
-        </div>
-    </div>
-</div>
-{% endif %}
-{% endblock %}
-
 {% block scripts %}
 <script type="text/javascript">
  {% if user.onboarding %}
diff --git a/app/escriptorium/templates/core/document_list.html b/app/escriptorium/templates/core/document_list.html
index 3cd72eefb101f353d85d2c9e6a3e39abd3abb312..5bf5cf3513ba44088cfe25781fdf356b3196ce4f 100644
--- a/app/escriptorium/templates/core/document_list.html
+++ b/app/escriptorium/templates/core/document_list.html
@@ -1,19 +1,20 @@
-{% extends 'base.html' %}
-{% load i18n staticfiles thumbnail %}
+{% extends 'core/project_nav.html' %}
+{% load i18n staticfiles thumbnail bootstrap %}
 {% block head_title %}{% trans "My Documents" %}{% endblock %}
 
-{% block styles %}
-{{ block.super }}
-{% endblock %}
+{% block nav-docs-active %}active{% endblock %}
 
-{% block nav-doc-list-class %}active{% endblock %}
+{% block extra_nav %}
+<a href="{% url 'document-create' slug=project.slug %}" class="btn btn-success ml-auto mb-2">{% trans 'Create new Document' %}</a>
 
-{% block body %}
-<div class="row">
-  <div class="col-md-12 col-md-offset-4">
-    <a href="{% url 'document-create' %}" class="btn btn-success float-sm-right">{% trans 'Create new' %}</a>
-    <h2>{% trans "My Documents" %}</h2>
+{% if share_form %}
+<button type="button" class="btn btn-primary mb-2 ml-1" data-toggle="modal" data-target="#shareModal" title="{% trans "Share this Project" %}"><i class="fas fa-share"></i></button>
+{% endif %}
+{% endblock %}
 
+{% block tab_content %}
+<div class="row">
+    <div class="col-md-12 col-md-offset-4">
     <table id="document-list" class="table table-hover">
       <tbody>
         {% for document in object_list %}
@@ -65,7 +66,40 @@
         {% endfor %}
       </tbody>
     </table>
-  </div>
+    </div>
 </div>
 {% include 'includes/pagination.html' %}
 {% endblock %}
+
+{% block modals %}
+{% if share_form %}
+<!-- shareModal -->
+<div class="modal fade" id="shareModal" tabindex="-1" role="dialog" aria-labelledby="shareModalLabel" aria-hidden="true">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <form method="post" action="{% url 'project-share' pk=project.pk %}">
+                {% csrf_token %}
+
+                <div class="modal-header">
+                    <h3 class="modal-title" id="exampleModalLabel">{% trans "Share with" %}</h3>
+                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                        <span aria-hidden="true">&times;</span>
+                    </button>
+                </div>
+                <div class="modal-body">
+                    <h5>{% trans "Teams" %}</h5>
+                    {% render_field share_form.shared_with_groups %}
+                    <h5>{% trans "Users" %}</h5>
+                    {% render_field share_form.username %}
+                    {% render_field share_form.shared_with_users %}
+                </div>
+                <div class="modal-footer">
+                    <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
+                    <input type="submit" value="{% trans 'Share' %}" class="btn btn-primary">
+                </div>
+            </form>
+        </div>
+    </div>
+</div>
+{% endif %}
+{% endblock %}
diff --git a/app/escriptorium/templates/core/project_form.html b/app/escriptorium/templates/core/project_form.html
new file mode 100644
index 0000000000000000000000000000000000000000..36e10b24307896d0cb46652060e9387bb39bd00d
--- /dev/null
+++ b/app/escriptorium/templates/core/project_form.html
@@ -0,0 +1,21 @@
+{% extends 'base.html' %}
+{% load i18n staticfiles bootstrap json %}
+
+{% block head_title %}{% if object %}{% trans "Update a Project" %}{% else %}{% trans "Create a new Project" %}{% endif %}{% endblock %}
+
+{% block body %}
+    <form id="project-form" method="post">
+        {% csrf_token %}
+        <fieldset>
+            <div class="form-group">
+                {% for err in form.non_field_errors %}
+                <p class="error">{{ err }}</p>
+                {% endfor %}
+            </div>
+
+            {% render_field form.name %}
+
+            <input type="submit" value="{% if object %}{% trans 'Update' %}{% else %}{% trans 'Create' %}{% endif %}" class="btn btn-success btn-block my-3" {% if object %}autofocus{% endif %}>
+        </fieldset>
+    </form>
+{% endblock %}
diff --git a/app/escriptorium/templates/core/project_list.html b/app/escriptorium/templates/core/project_list.html
new file mode 100644
index 0000000000000000000000000000000000000000..a9751f985d458a19d5bc6e469a170cff80b26017
--- /dev/null
+++ b/app/escriptorium/templates/core/project_list.html
@@ -0,0 +1,25 @@
+{% extends 'base.html' %}
+{% load i18n staticfiles %}
+
+{% block head_title %}{% trans "My Projects" %}{% endblock %}
+
+{% block nav-proj-list-class %}active{% endblock %}
+
+{% block body %}
+<div class="row">
+    <div class="col-md-12 col-md-offset-4">
+        <a href="{% url 'project-create' %}" class="btn btn-success float-sm-right">{% trans 'Create new Project' %}</a>
+    <h2>{% trans "My Projects" %}</h2>
+
+    <table id="project-list" class="table table-hover">
+        <tbody>
+            {% for project in project_list %}
+            <tr onclick="document.location='{% url 'documents-list' slug=project.slug %}'" role="button">
+                <td><a href="{% url 'documents-list' slug=project.slug %}">{{project.name}}</a></td>
+                <td title="{% trans "Owner" %}">{{project.owner}}</td>
+                <td>{% blocktrans with count=project.documents.count %}{{ count }} document(s).{% endblocktrans %}</td>
+            </tr>
+            {% endfor %}
+        </tbody>
+    </table>
+{% endblock %}
diff --git a/app/escriptorium/templates/core/project_nav.html b/app/escriptorium/templates/core/project_nav.html
new file mode 100644
index 0000000000000000000000000000000000000000..3bbd91730c3d4b68f4c48ab6cc0234bdbb53cef4
--- /dev/null
+++ b/app/escriptorium/templates/core/project_nav.html
@@ -0,0 +1,23 @@
+{% extends 'base.html' %}
+{% load i18n staticfiles bootstrap %}
+
+{% block body %}
+<nav>
+    <div class="nav nav-tabs mb-3" id="nav-tab" role="tablist">
+        <h5 class="nav-item nav-link disabled">{{project.name|capfirst}}</h5>
+        <a href="{% url 'documents-list' slug=project.slug %}"
+           class="nav-item nav-link {% block nav-docs-active %}{% endblock %}"
+           id="nav-docs-tab"
+           role="tab" aria-controls="nav-docs" aria-selected="true">{% trans "Documents" %}</a>
+
+        {% comment %}
+        <a class="nav-item nav-link {% block nav-onto-active %}{% endblock %}">{% trans "Ontology" %}</a>
+        {% endcomment %}
+
+        {% block extra_nav %}{% endblock %}
+    </div>
+</nav>
+
+{% block tab_content %}{% endblock %}
+{% block modals %}{% endblock %}
+{% endblock %}
diff --git a/docker-compose.yml b/docker-compose.yml
index f889b78849fa5a8cdb7121f759d3c39c052f7323..e596ec99fcef606d30031fc732f7a3837c050596 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -119,4 +119,3 @@ volumes:
    media:
    postgres:
    esdata:
-   logs: