diff --git a/app/__init__.py b/app/__init__.py
index dcb58e47b9943b4efcc77f9bc8f5ba060f9b0d78..59d6d5c99e88ded2b4644b44708bed1f4f59b74f 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -4,6 +4,7 @@ from docker import DockerClient
 from flask import Flask
 from flask_apscheduler import APScheduler
 from flask_assets import Environment
+from flask_breadcrumbs import Breadcrumbs, default_breadcrumb_root
 from flask_login import LoginManager
 from flask_mail import Mail
 from flask_marshmallow import Marshmallow
@@ -16,6 +17,7 @@ from flask_hashids import Hashids
 
 apifairy = APIFairy()
 assets = Environment()
+breadcrumbs = Breadcrumbs()
 db = SQLAlchemy()
 docker_client = DockerClient()
 hashids = Hashids()
@@ -44,6 +46,7 @@ def create_app(config: Config = Config) -> Flask:
 
     apifairy.init_app(app)
     assets.init_app(app)
+    breadcrumbs.init_app(app)
     db.init_app(app)
     hashids.init_app(app)
     login.init_app(app)
@@ -64,9 +67,11 @@ def create_app(config: Config = Config) -> Flask:
     app.register_blueprint(api_blueprint, url_prefix='/api')
 
     from .auth import bp as auth_blueprint
+    default_breadcrumb_root(auth_blueprint, '.')
     app.register_blueprint(auth_blueprint, url_prefix='/auth')
 
     from .contributions import bp as contributions_blueprint
+    default_breadcrumb_root(contributions_blueprint, '.contributions')
     app.register_blueprint(contributions_blueprint, url_prefix='/contributions')
 
     from .corpora import bp as corpora_blueprint
@@ -76,9 +81,11 @@ def create_app(config: Config = Config) -> Flask:
     app.register_blueprint(jobs_blueprint, url_prefix='/jobs')
 
     from .main import bp as main_blueprint
+    default_breadcrumb_root(main_blueprint, '.')
     app.register_blueprint(main_blueprint, url_prefix='/')
 
     from .services import bp as services_blueprint
+    default_breadcrumb_root(services_blueprint, '.services')
     app.register_blueprint(services_blueprint, url_prefix='/services')
 
     from .settings import bp as settings_blueprint
diff --git a/app/auth/routes.py b/app/auth/routes.py
index 5655d0dcf59fddf1482cd7d5bcd978758d1d2cb8..6e11a14090da3aa20ab20119750ba26f5e97bee8 100644
--- a/app/auth/routes.py
+++ b/app/auth/routes.py
@@ -1,11 +1,5 @@
-from flask import (
-    abort,
-    flash,
-    redirect,
-    render_template,
-    request,
-    url_for
-)
+from flask import abort, flash, redirect, render_template, request, url_for
+from flask_breadcrumbs import register_breadcrumb
 from flask_login import current_user, login_user, login_required, logout_user
 from app import db
 from app.email import create_message, send
@@ -36,6 +30,7 @@ def before_request():
 
 
 @bp.route('/register', methods=['GET', 'POST'])
+@register_breadcrumb(bp, '.register', 'Register')
 def register():
     if current_user.is_authenticated:
         return redirect(url_for('main.dashboard'))
@@ -71,6 +66,7 @@ def register():
 
 
 @bp.route('/login', methods=['GET', 'POST'])
+@register_breadcrumb(bp, '.login', 'Login')
 def login():
     if current_user.is_authenticated:
         return redirect(url_for('main.dashboard'))
@@ -97,6 +93,7 @@ def logout():
 
 
 @bp.route('/unconfirmed')
+@register_breadcrumb(bp, '.unconfirmed', 'Unconfirmed')
 @login_required
 def unconfirmed():
     if current_user.confirmed:
@@ -136,6 +133,7 @@ def confirm(token):
 
 
 @bp.route('/reset_password', methods=['GET', 'POST'])
+@register_breadcrumb(bp, '.reset_password_request', 'Password Reset')
 def reset_password_request():
     if current_user.is_authenticated:
         return redirect(url_for('main.dashboard'))
@@ -165,6 +163,7 @@ def reset_password_request():
 
 
 @bp.route('/reset_password/<token>', methods=['GET', 'POST'])
+@register_breadcrumb(bp, '.reset_password', 'Password Reset')
 def reset_password(token):
     if current_user.is_authenticated:
         return redirect(url_for('main.dashboard'))
diff --git a/app/contributions/__init__.py b/app/contributions/__init__.py
index 5175c0ce7d5c6b907a1cd0fe1a6f3df140f580b4..7749a27804487e49afd8017b21ebdae07601c85d 100644
--- a/app/contributions/__init__.py
+++ b/app/contributions/__init__.py
@@ -2,16 +2,4 @@ from flask import Blueprint
 
 
 bp = Blueprint('contributions', __name__)
-from . import routes
-
-from .spacy_nlp_pipeline_models import bp as spacy_nlp_pipeline_models_bp
-bp.register_blueprint(
-    spacy_nlp_pipeline_models_bp,
-    url_prefix='/spacy-nlp-pipeline-models'
-)
-
-from .tesseract_ocr_pipeline_models import bp as tesseract_ocr_pipeline_models_bp
-bp.register_blueprint(
-    tesseract_ocr_pipeline_models_bp,
-    url_prefix='/tesseract-ocr-pipeline-models'
-)
+from . import json_routes, routes
diff --git a/app/contributions/forms.py b/app/contributions/forms.py
index acec307f00b4e409ff962cc7e0021973a442f2fe..1ef4fdc79611ef7944c970621e6791a36813b43d 100644
--- a/app/contributions/forms.py
+++ b/app/contributions/forms.py
@@ -1,11 +1,14 @@
 from flask_wtf import FlaskForm
+from flask_wtf.file import FileField, FileRequired
 from wtforms import (
     StringField,
     SubmitField,
     SelectMultipleField,
-    IntegerField
+    IntegerField,
+    ValidationError
 )
 from wtforms.validators import InputRequired, Length
+from app.services import SERVICES
 
 
 class ContributionBaseForm(FlaskForm):
@@ -45,3 +48,79 @@ class ContributionBaseForm(FlaskForm):
 
 class EditContributionBaseForm(ContributionBaseForm):
     pass
+
+
+##############################################################################
+# /spacy-nlp-pipeline-models                                                 #
+##############################################################################
+class CreateSpaCyNLPPipelineModelForm(ContributionBaseForm):
+    spacy_model_file = FileField(
+        'File',
+        validators=[FileRequired()]
+    )
+    pipeline_name = StringField(
+        'Pipeline name',
+        validators=[InputRequired(), Length(max=64)]
+    )
+
+    def validate_spacy_model_file(self, field):
+        if not field.data.filename.lower().endswith('.tar.gz'):
+            raise ValidationError('.tar.gz files only!')
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        service_manifest = SERVICES['spacy-nlp-pipeline']
+        self.compatible_service_versions.choices = [('', 'Choose your option')]
+        self.compatible_service_versions.choices += [
+            (x, x) for x in service_manifest['versions'].keys()
+        ]
+        self.compatible_service_versions.default = ''
+
+
+class EditSpaCyNLPPipelineModelForm(EditContributionBaseForm):
+    pipeline_name = StringField(
+        'Pipeline name',
+        validators=[InputRequired(), Length(max=64)]
+    )
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        service_manifest = SERVICES['spacy-nlp-pipeline']
+        self.compatible_service_versions.choices = [('', 'Choose your option')]
+        self.compatible_service_versions.choices += [
+            (x, x) for x in service_manifest['versions'].keys()
+        ]
+        self.compatible_service_versions.default = ''
+
+
+##############################################################################
+# /tesseract-ocr-pipeline-models                                             #
+##############################################################################
+class CreateTesseractOCRPipelineModelForm(ContributionBaseForm):
+    tesseract_model_file = FileField(
+        'File',
+        validators=[FileRequired()]
+    )
+    
+    def validate_tesseract_model_file(self, field):
+        if not field.data.filename.lower().endswith('.traineddata'):
+            raise ValidationError('traineddata files only!')
+
+    def __init__(self, *args, **kwargs):
+        service_manifest = SERVICES['tesseract-ocr-pipeline']
+        super().__init__(*args, **kwargs)
+        self.compatible_service_versions.choices = [('', 'Choose your option')]
+        self.compatible_service_versions.choices += [
+            (x, x) for x in service_manifest['versions'].keys()
+        ]
+        self.compatible_service_versions.default = ''
+
+
+class EditTesseractOCRPipelineModelForm(EditContributionBaseForm):
+    def __init__(self, *args, **kwargs):
+        service_manifest = SERVICES['tesseract-ocr-pipeline']
+        super().__init__(*args, **kwargs)
+        self.compatible_service_versions.choices = [('', 'Choose your option')]
+        self.compatible_service_versions.choices += [
+            (x, x) for x in service_manifest['versions'].keys()
+        ]
+        self.compatible_service_versions.default = ''
diff --git a/app/contributions/json_routes.py b/app/contributions/json_routes.py
new file mode 100644
index 0000000000000000000000000000000000000000..c44a4c9ccb38f7ccbef335526efe80948d741a53
--- /dev/null
+++ b/app/contributions/json_routes.py
@@ -0,0 +1,107 @@
+from flask import abort, current_app, request
+from flask_login import login_required, current_user
+from threading import Thread
+from app import db
+from app.decorators import content_negotiation, permission_required
+from app.models import SpaCyNLPPipelineModel, TesseractOCRPipelineModel
+from . import bp
+
+
+##############################################################################
+# /spacy-nlp-pipeline-models                                                 #
+##############################################################################
+@bp.route('/spacy-nlp-pipeline-models<hashid:spacy_nlp_pipeline_model_id>', methods=['DELETE'])
+@login_required
+@content_negotiation(produces='application/json')
+def delete_spacy_model(spacy_nlp_pipeline_model_id):
+    def _delete_spacy_model(app, spacy_nlp_pipeline_model_id):
+        with app.app_context():
+            snpm = SpaCyNLPPipelineModel.query.get(spacy_nlp_pipeline_model_id)
+            snpm.delete()
+            db.session.commit()
+    
+    snpm = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id)
+    if not (snpm.user == current_user or current_user.is_administrator()):
+        abort(403)
+    thread = Thread(
+        target=_delete_spacy_model,
+        args=(current_app._get_current_object(), snpm.id)
+    )
+    thread.start()
+    resonse_data = {
+        'message': \
+            f'SpaCy NLP Pipeline Model "{snpm.title}" marked for deletion'
+    }
+    return resonse_data, 202
+
+
+@bp.route('/spacy-nlp-pipeline-models<hashid:spacy_nlp_pipeline_model_id>/is_public', methods=['PUT'])
+@login_required
+@permission_required('CONTRIBUTE')
+@content_negotiation(consumes='application/json', produces='application/json')
+def update_spacy_nlp_pipeline_model_is_public(spacy_nlp_pipeline_model_id):
+    is_public = request.json
+    if not isinstance(is_public, bool):
+        abort(400)
+    snpm = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id)
+    if not (snpm.user == current_user or current_user.is_administrator()):
+        abort(403)
+    snpm.is_public = is_public
+    db.session.commit()
+    response_data = {
+        'message': (
+            f'SpaCy NLP Pipeline Model "{snpm.title}"'
+            f' is now {"public" if is_public else "private"}'
+        )
+    }
+    return response_data, 200
+
+
+##############################################################################
+# /tesseract-ocr-pipeline-models                                             #
+##############################################################################
+@bp.route('/tesseract-ocr-pipeline-models/<hashid:tesseract_ocr_pipeline_model_id>', methods=['DELETE'])
+@login_required
+@content_negotiation(produces='application/json')
+def delete_tesseract_model(tesseract_ocr_pipeline_model_id):
+    def _delete_tesseract_ocr_pipeline_model(app, tesseract_ocr_pipeline_model_id):
+        with app.app_context():
+            topm = TesseractOCRPipelineModel.query.get(tesseract_ocr_pipeline_model_id)
+            topm.delete()
+            db.session.commit()
+
+    topm = TesseractOCRPipelineModel.query.get_or_404(tesseract_ocr_pipeline_model_id)
+    if not (topm.user == current_user or current_user.is_administrator()):
+        abort(403)
+    thread = Thread(
+        target=_delete_tesseract_ocr_pipeline_model,
+        args=(current_app._get_current_object(), topm.id)
+    )
+    thread.start()
+    response_data = {
+        'message': \
+            f'Tesseract OCR Pipeline Model "{topm.title}" marked for deletion'
+    }
+    return response_data, 202
+
+
+@bp.route('/tesseract-ocr-pipeline-models/<hashid:tesseract_ocr_pipeline_model_id>/is_public', methods=['PUT'])
+@login_required
+@permission_required('CONTRIBUTE')
+@content_negotiation(consumes='application/json', produces='application/json')
+def update_tesseract_ocr_pipeline_model_is_public(tesseract_ocr_pipeline_model_id):
+    is_public = request.json
+    if not isinstance(is_public, bool):
+        abort(400)
+    topm = TesseractOCRPipelineModel.query.get_or_404(tesseract_ocr_pipeline_model_id)
+    if not (topm.user == current_user or current_user.is_administrator()):
+        abort(403)
+    topm.is_public = is_public
+    db.session.commit()
+    response_data = {
+        'message': (
+            f'Tesseract OCR Pipeline Model "{topm.title}"'
+            f' is now {"public" if is_public else "private"}'
+        )
+    }
+    return response_data, 200
diff --git a/app/contributions/routes.py b/app/contributions/routes.py
index 6d8b9cc3ab62b318b9c9a61fe4442e2698c9ec87..6da7dc120bbb1923e64e9ad092fec4abd4b442c8 100644
--- a/app/contributions/routes.py
+++ b/app/contributions/routes.py
@@ -1,12 +1,189 @@
-from flask import render_template
-from flask_login import login_required
+from flask import abort, flash, redirect, render_template, request, url_for
+from flask_breadcrumbs import register_breadcrumb
+from flask_login import current_user, login_required
+from app import db
+from app.models import SpaCyNLPPipelineModel, TesseractOCRPipelineModel
 from . import bp
+from .forms import (
+    CreateSpaCyNLPPipelineModelForm,
+    EditSpaCyNLPPipelineModelForm,
+    CreateTesseractOCRPipelineModelForm,
+    EditTesseractOCRPipelineModelForm
+)
 
 
-@bp.route('/')
+@bp.route('')
+@register_breadcrumb(bp, '.', 'Contributions')
 @login_required
 def contributions():
     return render_template(
         'contributions/contributions.html.j2',
         title='Contributions'
     )
+
+
+##############################################################################
+# /spacy-nlp-pipeline-models                                                 #
+##############################################################################
+@bp.route('/spacy-nlp-pipeline-models')
+@register_breadcrumb(bp, '.spacy_nlp_pipeline_models', 'SpaCy NLP Pipeline Models')
+@login_required
+def spacy_nlp_pipeline_models():
+    return render_template(
+        'contributions/spacy_nlp_pipeline_models.html.j2',
+        title='SpaCy NLP Pipeline Models'
+    )
+
+
+@bp.route('/spacy-nlp-pipeline-models/create', methods=['GET', 'POST'])
+@register_breadcrumb(bp, '.spacy_nlp_pipeline_models.create', 'Create')
+@login_required
+def create_spacy_nlp_pipeline_model():
+    form_prefix = 'create-spacy-nlp-pipeline-model-form'
+    form = CreateSpaCyNLPPipelineModelForm(prefix=form_prefix)
+    if form.is_submitted():
+        if not form.validate():
+            return {'errors': form.errors}, 400
+        try:
+            snpm = SpaCyNLPPipelineModel.create(
+                form.spacy_model_file.data,
+                compatible_service_versions=form.compatible_service_versions.data,
+                description=form.description.data,
+                pipeline_name=form.pipeline_name.data,
+                publisher=form.publisher.data,
+                publisher_url=form.publisher_url.data,
+                publishing_url=form.publishing_url.data,
+                publishing_year=form.publishing_year.data,
+                is_public=False,
+                title=form.title.data,
+                version=form.version.data,
+                user=current_user
+            )
+        except OSError:
+            abort(500)
+        db.session.commit()
+        flash(f'SpaCy NLP Pipeline model "{snpm.title}" created')
+        return {}, 201, {'Location': url_for('.spacy_nlp_pipeline_models')}
+    return render_template(
+        'contributions/create_spacy_nlp_pipeline_model.html.j2',
+        form=form,
+        title='Create SpaCy NLP Pipeline Model'
+    )
+
+
+def spacy_nlp_pipeline_model_dlc(*args, **kwargs):
+    snpm_id = request.view_args['spacy_nlp_pipeline_model_id']
+    snpm = SpaCyNLPPipelineModel.query.get(snpm_id)
+    return [
+        {
+            'text': f'{snpm.title} {snpm.version}',
+            'url': url_for('.spacy_nlp_pipeline_model', spacy_nlp_pipeline_model_id=snpm_id)
+        }
+    ]
+
+
+@bp.route('/spacy-nlp-pipeline-models/<hashid:spacy_nlp_pipeline_model_id>', methods=['GET', 'POST'])
+@register_breadcrumb(bp, '.spacy_nlp_pipeline_models.entity', '', dynamic_list_constructor=spacy_nlp_pipeline_model_dlc)
+@login_required
+def spacy_nlp_pipeline_model(spacy_nlp_pipeline_model_id):
+    snpm = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id)
+    form_prefix = 'edit-spacy-nlp-pipeline-model-form'
+    form = EditSpaCyNLPPipelineModelForm(
+        data=snpm.to_json_serializeable(),
+        prefix=form_prefix
+    )
+    if form.validate_on_submit():
+        form.populate_obj(snpm)
+        if db.session.is_modified(snpm):
+            flash(f'SpaCy NLP Pipeline model "{snpm.title}" updated')
+            db.session.commit()
+        return redirect(url_for('.spacy_nlp_pipeline_models'))
+    return render_template(
+        'contributions/spacy_nlp_pipeline_model.html.j2',
+        form=form,
+        spacy_nlp_pipeline_model=snpm,
+        title=f'{snpm.title} {snpm.version}'
+    )
+
+
+##############################################################################
+# /tesseract-ocr-pipeline-models                                             #
+##############################################################################
+@bp.route('/tesseract-ocr-pipeline-models')
+@register_breadcrumb(bp, '.tesseract_ocr_pipeline_models', 'Tesseract OCR Pipeline Models')
+@login_required
+def tesseract_ocr_pipeline_models():
+    return render_template(
+        'contributions/tesseract_ocr_pipeline_models.html.j2',
+        title='Tesseract OCR Pipeline Models'
+    )
+
+
+@bp.route('/tesseract-ocr-pipeline-models/create', methods=['GET', 'POST'])
+@register_breadcrumb(bp, '.tesseract_ocr_pipeline_models.create', 'Create')
+@login_required
+def create_tesseract_ocr_pipeline_model():
+    form_prefix = 'create-tesseract-ocr-pipeline-model-form'
+    form = CreateTesseractOCRPipelineModelForm(prefix=form_prefix)
+    if form.is_submitted():
+        if not form.validate():
+            return {'errors': form.errors}, 400
+        try:
+            topm = TesseractOCRPipelineModel.create(
+                form.tesseract_model_file.data,
+                compatible_service_versions=form.compatible_service_versions.data,
+                description=form.description.data,
+                publisher=form.publisher.data,
+                publisher_url=form.publisher_url.data,
+                publishing_url=form.publishing_url.data,
+                publishing_year=form.publishing_year.data,
+                is_public=False,
+                title=form.title.data,
+                version=form.version.data,
+                user=current_user
+            )
+        except OSError:
+            abort(500)
+        db.session.commit()
+        flash(f'Tesseract OCR Pipeline model "{topm.title}" created')
+        return {}, 201, {'Location': url_for('.tesseract_ocr_pipeline_models')}
+    return render_template(
+        'contributions/create_tesseract_ocr_pipeline_model.html.j2',
+        form=form,
+        title='Create Tesseract OCR Pipeline Model'
+    )
+
+
+def tesseract_ocr_pipeline_model_dlc(*args, **kwargs):
+    topm_id = request.view_args['tesseract_ocr_pipeline_model_id']
+    topm = TesseractOCRPipelineModel.query.get(topm_id)
+    return [
+        {
+            'text': f'{topm.title} {topm.version}',
+            'url': url_for('.tesseract_ocr_pipeline_model', tesseract_ocr_pipeline_model_id=topm_id)
+        }
+    ]
+
+
+@bp.route('/tesseract-ocr-pipeline-models/<hashid:tesseract_ocr_pipeline_model_id>', methods=['GET', 'POST'])
+@register_breadcrumb(bp, '.tesseract_ocr_pipeline_models.entity', '', dynamic_list_constructor=tesseract_ocr_pipeline_model_dlc)
+@login_required
+def tesseract_ocr_pipeline_model(tesseract_ocr_pipeline_model_id):
+    topm = TesseractOCRPipelineModel.query.get_or_404(tesseract_ocr_pipeline_model_id)
+    form_prefix = 'edit-tesseract-ocr-pipeline-model-form'
+    form = EditTesseractOCRPipelineModelForm(
+        data=topm.to_json_serializeable(),
+        prefix=form_prefix
+    )
+    if form.validate_on_submit():
+        form.populate_obj(topm)
+        if db.session.is_modified(topm):
+            flash(f'Tesseract OCR Pipeline model "{topm.title}" updated')
+            db.session.commit()
+        return redirect(url_for('.tesseract_ocr_pipeline_models'))
+    return render_template(
+        'contributions/tesseract_ocr_pipeline_model.html.j2',
+        form=form,
+        tesseract_ocr_pipeline_model=topm,
+        title=f'{topm.title} {topm.version}'
+    )
diff --git a/app/contributions/spacy_nlp_pipeline_models/__init__.py b/app/contributions/spacy_nlp_pipeline_models/__init__.py
deleted file mode 100644
index 8ff119d01549a5b68ff40b0372669d6b07cd24fb..0000000000000000000000000000000000000000
--- a/app/contributions/spacy_nlp_pipeline_models/__init__.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from flask import Blueprint
-
-
-template_base_dir = 'contributions/spacy_nlp_pipeline_models'
-
-
-bp = Blueprint('spacy_nlp_pipeline_models', __name__)
-from . import routes, json_routes
diff --git a/app/contributions/spacy_nlp_pipeline_models/forms.py b/app/contributions/spacy_nlp_pipeline_models/forms.py
deleted file mode 100644
index 2670c1d1c0ba126c1eabf911b09475eda53c3799..0000000000000000000000000000000000000000
--- a/app/contributions/spacy_nlp_pipeline_models/forms.py
+++ /dev/null
@@ -1,44 +0,0 @@
-from flask_wtf.file import FileField, FileRequired
-from wtforms import StringField, ValidationError
-from wtforms.validators import InputRequired, Length
-from app.services import SERVICES
-from ..forms import ContributionBaseForm, EditContributionBaseForm
-
-
-class CreateSpaCyNLPPipelineModelForm(ContributionBaseForm):
-    spacy_model_file = FileField(
-        'File',
-        validators=[FileRequired()]
-    )
-    pipeline_name = StringField(
-        'Pipeline name',
-        validators=[InputRequired(), Length(max=64)]
-    )
-
-    def validate_spacy_model_file(self, field):
-        if not field.data.filename.lower().endswith('.tar.gz'):
-            raise ValidationError('.tar.gz files only!')
-
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        service_manifest = SERVICES['spacy-nlp-pipeline']
-        self.compatible_service_versions.choices = [('', 'Choose your option')]
-        self.compatible_service_versions.choices += [
-            (x, x) for x in service_manifest['versions'].keys()
-        ]
-        self.compatible_service_versions.default = ''
-
-
-class EditSpaCyNLPPipelineModelForm(EditContributionBaseForm):
-    pipeline_name = StringField(
-        'Pipeline name',
-        validators=[InputRequired(), Length(max=64)]
-    )
-    def __init__(self, *args, **kwargs):
-        super().__init__(*args, **kwargs)
-        service_manifest = SERVICES['spacy-nlp-pipeline']
-        self.compatible_service_versions.choices = [('', 'Choose your option')]
-        self.compatible_service_versions.choices += [
-            (x, x) for x in service_manifest['versions'].keys()
-        ]
-        self.compatible_service_versions.default = ''
diff --git a/app/contributions/spacy_nlp_pipeline_models/json_routes.py b/app/contributions/spacy_nlp_pipeline_models/json_routes.py
deleted file mode 100644
index f7a9a254586abd478d7401fcd19669670cfd1b57..0000000000000000000000000000000000000000
--- a/app/contributions/spacy_nlp_pipeline_models/json_routes.py
+++ /dev/null
@@ -1,54 +0,0 @@
-from flask import abort, current_app, request
-from flask_login import login_required, current_user
-from threading import Thread
-from app import db
-from app.decorators import content_negotiation, permission_required
-from app.models import SpaCyNLPPipelineModel
-from . import bp
-
-
-@bp.route('/<hashid:spacy_nlp_pipeline_model_id>', methods=['DELETE'])
-@login_required
-@content_negotiation(produces='application/json')
-def delete_spacy_model(spacy_nlp_pipeline_model_id):
-    def _delete_spacy_model(app, spacy_nlp_pipeline_model_id):
-        with app.app_context():
-            snpm = SpaCyNLPPipelineModel.query.get(spacy_nlp_pipeline_model_id)
-            snpm.delete()
-            db.session.commit()
-    
-    snpm = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id)
-    if not (snpm.user == current_user or current_user.is_administrator()):
-        abort(403)
-    thread = Thread(
-        target=_delete_spacy_model,
-        args=(current_app._get_current_object(), snpm.id)
-    )
-    thread.start()
-    resonse_data = {
-        'message': \
-            f'SpaCy NLP Pipeline Model "{snpm.title}" marked for deletion'
-    }
-    return resonse_data, 202
-
-
-@bp.route('/<hashid:spacy_nlp_pipeline_model_id>/is_public', methods=['PUT'])
-@login_required
-@permission_required('CONTRIBUTE')
-@content_negotiation(consumes='application/json', produces='application/json')
-def update_spacy_nlp_pipeline_model_is_public(spacy_nlp_pipeline_model_id):
-    is_public = request.json
-    if not isinstance(is_public, bool):
-        abort(400)
-    snpm = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id)
-    if not (snpm.user == current_user or current_user.is_administrator()):
-        abort(403)
-    snpm.is_public = is_public
-    db.session.commit()
-    response_data = {
-        'message': (
-            f'SpaCy NLP Pipeline Model "{snpm.title}"'
-            f' is now {"public" if is_public else "private"}'
-        )
-    }
-    return response_data, 200
diff --git a/app/contributions/spacy_nlp_pipeline_models/routes.py b/app/contributions/spacy_nlp_pipeline_models/routes.py
deleted file mode 100644
index fd972902087e00a156b912323005bf7f9827abc3..0000000000000000000000000000000000000000
--- a/app/contributions/spacy_nlp_pipeline_models/routes.py
+++ /dev/null
@@ -1,76 +0,0 @@
-from flask import abort, flash, redirect, render_template, url_for
-from flask_login import current_user, login_required
-from app import db
-from app.models import SpaCyNLPPipelineModel
-from . import bp, template_base_dir
-from .forms import (
-    CreateSpaCyNLPPipelineModelForm,
-    EditSpaCyNLPPipelineModelForm
-)
-
-
-@bp.route('')
-@login_required
-def spacy_nlp_pipeline_models():
-    return render_template(
-        f'{template_base_dir}/spacy_nlp_pipeline_models.html.j2',
-        title='SpaCy NLP Pipeline Models'
-    )
-
-
-@bp.route('/create', methods=['GET', 'POST'])
-@login_required
-def create_spacy_nlp_pipeline_model():
-    form_prefix = 'create-spacy-nlp-pipeline-model-form'
-    form = CreateSpaCyNLPPipelineModelForm(prefix=form_prefix)
-    if form.is_submitted():
-        if not form.validate():
-            return {'errors': form.errors}, 400
-        try:
-            snpm = SpaCyNLPPipelineModel.create(
-                form.spacy_model_file.data,
-                compatible_service_versions=form.compatible_service_versions.data,
-                description=form.description.data,
-                pipeline_name=form.pipeline_name.data,
-                publisher=form.publisher.data,
-                publisher_url=form.publisher_url.data,
-                publishing_url=form.publishing_url.data,
-                publishing_year=form.publishing_year.data,
-                is_public=False,
-                title=form.title.data,
-                version=form.version.data,
-                user=current_user
-            )
-        except OSError:
-            abort(500)
-        db.session.commit()
-        flash(f'SpaCy NLP Pipeline model "{snpm.title}" created')
-        return {}, 201, {'Location': url_for('.spacy_nlp_pipeline_models')}
-    return render_template(
-        f'{template_base_dir}/create_spacy_nlp_pipeline_model.html.j2',
-        form=form,
-        title='Create SpaCy NLP Pipeline Model'
-    )
-
-
-@bp.route('/<hashid:spacy_nlp_pipeline_model_id>', methods=['GET', 'POST'])
-@login_required
-def spacy_nlp_pipeline_model(spacy_nlp_pipeline_model_id):
-    snpm = SpaCyNLPPipelineModel.query.get_or_404(spacy_nlp_pipeline_model_id)
-    form_prefix = 'edit-spacy-nlp-pipeline-model-form'
-    form = EditSpaCyNLPPipelineModelForm(
-        data=snpm.to_json_serializeable(),
-        prefix=form_prefix
-    )
-    if form.validate_on_submit():
-        form.populate_obj(snpm)
-        if db.session.is_modified(snpm):
-            flash(f'SpaCy NLP Pipeline model "{snpm.title}" updated')
-            db.session.commit()
-        return redirect(url_for('.spacy_nlp_pipeline_models'))
-    return render_template(
-        f'{template_base_dir}/spacy_nlp_pipeline_model.html.j2',
-        form=form,
-        spacy_nlp_pipeline_model=snpm,
-        title=f'{snpm.title} {snpm.version}'
-    )
diff --git a/app/contributions/tesseract_ocr_pipeline_models/__init__.py b/app/contributions/tesseract_ocr_pipeline_models/__init__.py
deleted file mode 100644
index cf44126dd5a34aaefa85099995489ce61db43043..0000000000000000000000000000000000000000
--- a/app/contributions/tesseract_ocr_pipeline_models/__init__.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from flask import Blueprint
-
-
-template_base_dir = 'contributions/tesseract_ocr_pipeline_models'
-
-
-bp = Blueprint('tesseract_ocr_pipeline_models', __name__)
-from . import routes, json_routes
diff --git a/app/contributions/tesseract_ocr_pipeline_models/forms.py b/app/contributions/tesseract_ocr_pipeline_models/forms.py
deleted file mode 100644
index 51f0d76c2017029c46691719d3131ee779d5c39c..0000000000000000000000000000000000000000
--- a/app/contributions/tesseract_ocr_pipeline_models/forms.py
+++ /dev/null
@@ -1,35 +0,0 @@
-from flask_wtf.file import FileField, FileRequired
-from wtforms import ValidationError
-from app.services import SERVICES
-from ..forms import ContributionBaseForm, EditContributionBaseForm
-
-
-class CreateTesseractOCRPipelineModelForm(ContributionBaseForm):
-    tesseract_model_file = FileField(
-        'File',
-        validators=[FileRequired()]
-    )
-    
-    def validate_tesseract_model_file(self, field):
-        if not field.data.filename.lower().endswith('.traineddata'):
-            raise ValidationError('traineddata files only!')
-
-    def __init__(self, *args, **kwargs):
-        service_manifest = SERVICES['tesseract-ocr-pipeline']
-        super().__init__(*args, **kwargs)
-        self.compatible_service_versions.choices = [('', 'Choose your option')]
-        self.compatible_service_versions.choices += [
-            (x, x) for x in service_manifest['versions'].keys()
-        ]
-        self.compatible_service_versions.default = ''
-
-
-class EditTesseractOCRPipelineModelForm(EditContributionBaseForm):
-    def __init__(self, *args, **kwargs):
-        service_manifest = SERVICES['tesseract-ocr-pipeline']
-        super().__init__(*args, **kwargs)
-        self.compatible_service_versions.choices = [('', 'Choose your option')]
-        self.compatible_service_versions.choices += [
-            (x, x) for x in service_manifest['versions'].keys()
-        ]
-        self.compatible_service_versions.default = ''
diff --git a/app/contributions/tesseract_ocr_pipeline_models/json_routes.py b/app/contributions/tesseract_ocr_pipeline_models/json_routes.py
deleted file mode 100644
index 81aa65984ac95a1d9d01772fcdde8593df81f904..0000000000000000000000000000000000000000
--- a/app/contributions/tesseract_ocr_pipeline_models/json_routes.py
+++ /dev/null
@@ -1,54 +0,0 @@
-from flask import abort, current_app, request
-from flask_login import login_required, current_user
-from threading import Thread
-from app import db
-from app.decorators import content_negotiation, permission_required
-from app.models import TesseractOCRPipelineModel
-from . import bp
-
-
-@bp.route('/<hashid:tesseract_ocr_pipeline_model_id>', methods=['DELETE'])
-@login_required
-@content_negotiation(produces='application/json')
-def delete_tesseract_model(tesseract_ocr_pipeline_model_id):
-    def _delete_tesseract_ocr_pipeline_model(app, tesseract_ocr_pipeline_model_id):
-        with app.app_context():
-            topm = TesseractOCRPipelineModel.query.get(tesseract_ocr_pipeline_model_id)
-            topm.delete()
-            db.session.commit()
-
-    topm = TesseractOCRPipelineModel.query.get_or_404(tesseract_ocr_pipeline_model_id)
-    if not (topm.user == current_user or current_user.is_administrator()):
-        abort(403)
-    thread = Thread(
-        target=_delete_tesseract_ocr_pipeline_model,
-        args=(current_app._get_current_object(), topm.id)
-    )
-    thread.start()
-    response_data = {
-        'message': \
-            f'Tesseract OCR Pipeline Model "{topm.title}" marked for deletion'
-    }
-    return response_data, 202
-
-
-@bp.route('/<hashid:tesseract_ocr_pipeline_model_id>/is_public', methods=['PUT'])
-@login_required
-@permission_required('CONTRIBUTE')
-@content_negotiation(consumes='application/json', produces='application/json')
-def update_tesseract_ocr_pipeline_model_is_public(tesseract_ocr_pipeline_model_id):
-    is_public = request.json
-    if not isinstance(is_public, bool):
-        abort(400)
-    topm = TesseractOCRPipelineModel.query.get_or_404(tesseract_ocr_pipeline_model_id)
-    if not (topm.user == current_user or current_user.is_administrator()):
-        abort(403)
-    topm.is_public = is_public
-    db.session.commit()
-    response_data = {
-        'message': (
-            f'Tesseract OCR Pipeline Model "{topm.title}"'
-            f' is now {"public" if is_public else "private"}'
-        )
-    }
-    return response_data, 200
diff --git a/app/contributions/tesseract_ocr_pipeline_models/routes.py b/app/contributions/tesseract_ocr_pipeline_models/routes.py
deleted file mode 100644
index b888cef75865598bf0a850b158e5119872a40851..0000000000000000000000000000000000000000
--- a/app/contributions/tesseract_ocr_pipeline_models/routes.py
+++ /dev/null
@@ -1,75 +0,0 @@
-from flask import abort, flash, redirect, render_template, url_for
-from flask_login import login_required, current_user
-from app import db
-from app.models import TesseractOCRPipelineModel
-from . import bp, template_base_dir
-from .forms import (
-    CreateTesseractOCRPipelineModelForm,
-    EditTesseractOCRPipelineModelForm
-)
-
-
-@bp.route('')
-@login_required
-def tesseract_ocr_pipeline_models():
-    return render_template(
-        f'{template_base_dir}/tesseract_ocr_pipeline_models.html.j2',
-        title='Tesseract OCR Pipeline Models'
-    )
-
-
-@bp.route('/create', methods=['GET', 'POST'])
-@login_required
-def create_tesseract_ocr_pipeline_model():
-    form_prefix = 'create-tesseract-ocr-pipeline-model-form'
-    form = CreateTesseractOCRPipelineModelForm(prefix=form_prefix)
-    if form.is_submitted():
-        if not form.validate():
-            return {'errors': form.errors}, 400
-        try:
-            topm = TesseractOCRPipelineModel.create(
-                form.tesseract_model_file.data,
-                compatible_service_versions=form.compatible_service_versions.data,
-                description=form.description.data,
-                publisher=form.publisher.data,
-                publisher_url=form.publisher_url.data,
-                publishing_url=form.publishing_url.data,
-                publishing_year=form.publishing_year.data,
-                is_public=False,
-                title=form.title.data,
-                version=form.version.data,
-                user=current_user
-            )
-        except OSError:
-            abort(500)
-        db.session.commit()
-        flash(f'Tesseract OCR Pipeline model "{topm.title}" created')
-        return {}, 201, {'Location': url_for('.tesseract_ocr_pipeline_models')}
-    return render_template(
-        f'{template_base_dir}/create_tesseract_ocr_pipeline_model.html.j2',
-        form=form,
-        title='Create Tesseract OCR Pipeline Model'
-    )
-
-
-@bp.route('/<hashid:tesseract_ocr_pipeline_model_id>', methods=['GET', 'POST'])
-@login_required
-def tesseract_ocr_pipeline_model(tesseract_ocr_pipeline_model_id):
-    topm = TesseractOCRPipelineModel.query.get_or_404(tesseract_ocr_pipeline_model_id)
-    form_prefix = 'edit-tesseract-ocr-pipeline-model-form'
-    form = EditTesseractOCRPipelineModelForm(
-        data=topm.to_json_serializeable(),
-        prefix=form_prefix
-    )
-    if form.validate_on_submit():
-        form.populate_obj(topm)
-        if db.session.is_modified(topm):
-            flash(f'Tesseract OCR Pipeline model "{topm.title}" updated')
-            db.session.commit()
-        return redirect(url_for('.tesseract_ocr_pipeline_models'))
-    return render_template(
-        f'{template_base_dir}/tesseract_ocr_pipeline_model.html.j2',
-        form=form,
-        tesseract_ocr_pipeline_model=topm,
-        title=f'{topm.title} {topm.version}'
-    )
diff --git a/app/corpora/routes.py b/app/corpora/routes.py
index 5484fb50af31a6a80984eeffa475aa1e02cc4953..82701cea4e8a418f370375d9b87aa2d956282faa 100644
--- a/app/corpora/routes.py
+++ b/app/corpora/routes.py
@@ -1,23 +1,18 @@
-from flask import (
-    abort,
-    flash,
-    Markup,
-    redirect,
-    render_template,
-    url_for
-)
+from flask import abort, flash, redirect, render_template, url_for
 from flask_login import current_user, login_required
 from .decorators import corpus_follower_permission_required
 from app import db
-from app.models import (
-    Corpus,
-    CorpusFollowerAssociation,
-    CorpusFollowerRole,
-)
+from app.models import Corpus, CorpusFollowerAssociation, CorpusFollowerRole
 from . import bp
 from .forms import CreateCorpusForm
 
 
+@bp.route('')
+@login_required
+def corpora():
+    return redirect(url_for('main.dashboard', _anchor='corpora'))
+
+
 @bp.route('/create', methods=['GET', 'POST'])
 @login_required
 def create_corpus():
diff --git a/app/main/routes.py b/app/main/routes.py
index 918f7414d2415f510dbf28ca0a92aa63878b6fc7..5399b5e96aeff112a75a3b2f85295917cf417c22 100644
--- a/app/main/routes.py
+++ b/app/main/routes.py
@@ -1,4 +1,5 @@
 from flask import flash, redirect, render_template, url_for
+from flask_breadcrumbs import register_breadcrumb
 from flask_login import current_user, login_required, login_user
 from app.auth.forms import LoginForm
 from app.models import Corpus, User
@@ -6,6 +7,7 @@ from . import bp
 
 
 @bp.route('', methods=['GET', 'POST'])
+@register_breadcrumb(bp, '.', '<i class="material-icons">home</i>')
 def index():
     form = LoginForm(prefix='login-form')
     if form.validate_on_submit():
@@ -20,37 +22,44 @@ def index():
 
 
 @bp.route('/faq')
+@register_breadcrumb(bp, '.faq', 'Frequently Asked Questions')
 def faq():
     return render_template('main/faq.html.j2', title='Frequently Asked Questions')
 
 
 @bp.route('/dashboard')
+@register_breadcrumb(bp, '.dashboard', 'Dashboard')
 @login_required
 def dashboard():
     return render_template('main/dashboard.html.j2', title='Dashboard')
 
 
 @bp.route('/user_manual')
+@register_breadcrumb(bp, '.user_manual', 'User manual')
 def user_manual():
     return render_template('main/user_manual.html.j2', title='User manual')
 
 
 @bp.route('/news')
+@register_breadcrumb(bp, '.news', 'News')
 def news():
     return render_template('main/news.html.j2', title='News')
 
 
 @bp.route('/privacy_policy')
+@register_breadcrumb(bp, '.privacy_policy', 'Private statement (GDPR)')
 def privacy_policy():
     return render_template('main/privacy_policy.html.j2', title='Privacy statement (GDPR)')
 
 
 @bp.route('/terms_of_use')
+@register_breadcrumb(bp, '.terms_of_use', 'Terms of Use')
 def terms_of_use():
     return render_template('main/terms_of_use.html.j2', title='Terms of Use')
 
 
 @bp.route('/social-area')
+@register_breadcrumb(bp, '.social_area', 'Social Area')
 def social_area():
     users = [
         u.to_json_serializeable(relationships=True, filter_by_privacy_settings=True,) for u
diff --git a/app/services/routes.py b/app/services/routes.py
index 0fb4b1684deb0cf9ddbbe85262262766e6056e0c..5d5a69a025d5a79913a5d280456af189168e53a6 100644
--- a/app/services/routes.py
+++ b/app/services/routes.py
@@ -1,4 +1,5 @@
-from flask import abort, current_app, flash, make_response, Markup, render_template, request
+from flask import abort, current_app, flash, Markup, redirect, render_template, request, url_for
+from flask_breadcrumbs import register_breadcrumb
 from flask_login import current_user, login_required
 import requests
 from app import db, hashids
@@ -18,7 +19,15 @@ from .forms import (
 )
 
 
+@bp.route('/services')
+@register_breadcrumb(bp, '.', 'Services')
+@login_required
+def services():
+    return redirect(url_for('main.dashboard'))
+
+
 @bp.route('/file-setup-pipeline', methods=['GET', 'POST'])
+@register_breadcrumb(bp, '.file_setup_pipeline', 'File Setup')
 @login_required
 def file_setup_pipeline():
     service = 'file-setup-pipeline'
@@ -60,6 +69,7 @@ def file_setup_pipeline():
 
 
 @bp.route('/tesseract-ocr-pipeline', methods=['GET', 'POST'])
+@register_breadcrumb(bp, '.tesseract_ocr_pipeline', 'Tesseract OCR Pipeline')
 @login_required
 def tesseract_ocr_pipeline():
     service_name = 'tesseract-ocr-pipeline'
@@ -109,6 +119,7 @@ def tesseract_ocr_pipeline():
 
 
 @bp.route('/transkribus-htr-pipeline', methods=['GET', 'POST'])
+@register_breadcrumb(bp, '.transkribus_htr_pipeline', 'Transkribus HTR Pipeline')
 @login_required
 def transkribus_htr_pipeline():
     if not current_app.config.get('NOPAQUE_TRANSKRIBUS_ENABLED'):
@@ -168,6 +179,7 @@ def transkribus_htr_pipeline():
 
 
 @bp.route('/spacy-nlp-pipeline', methods=['GET', 'POST'])
+@register_breadcrumb(bp, '.spacy_nlp_pipeline', 'SpaCy NLP Pipeline')
 @login_required
 def spacy_nlp_pipeline():
     service = 'spacy-nlp-pipeline'
@@ -213,9 +225,10 @@ def spacy_nlp_pipeline():
 
 
 @bp.route('/corpus-analysis')
+@register_breadcrumb(bp, '.corpus_analysis', 'Corpus Analysis')
 @login_required
 def corpus_analysis():
     return render_template(
         'services/corpus_analysis.html.j2',
-        title='Corpus analysis'
+        title='Corpus Analysis'
     )
diff --git a/app/templates/_navbar.html.j2 b/app/templates/_navbar.html.j2
index d943c0e4b597a893b628fbcc6a9eeeacb9e0db1f..2790ef1a41d38fa2f710a85fe993cc0b5b907d80 100644
--- a/app/templates/_navbar.html.j2
+++ b/app/templates/_navbar.html.j2
@@ -14,10 +14,12 @@
     </div>
     <div class="nav-content primary-variant-color">
       <ul class="tabs tabs-transparent">
-        <li class="tab"><a href="{{ url_for('main.index') }}" target="_self"><i class="material-icons">home</i></a></li>
-        {% if breadcrumbs is defined %}
-        {{ breadcrumbs }}
+        {%- for breadcrumb in breadcrumbs -%}
+        <li class="tab"><a {{ 'class="active"' if loop.last }} href="{{ breadcrumb.url }}" target="_self">{{ breadcrumb.text }}</a></li>
+        {% if not loop.last %}
+        <li class="tab disabled"><i class="material-icons">navigate_next</i></li>
         {% endif %}
+        {%- endfor -%}
       </ul>
       {% if current_user.is_authenticated %}
       <a class="btn-floating btn-large halfway-fab modal-trigger pink tooltipped waves-effect waves-light" data-tooltip="Roadmap" href="#roadmap-modal"><i class="material-icons">explore</i></a>
diff --git a/app/templates/_sidenav.html.j2 b/app/templates/_sidenav.html.j2
index 0027772e260e7743468c9699d92cf071197ff641..fbaeddbe0524b2bd07556ed2698bbd7a2f9af4ff 100644
--- a/app/templates/_sidenav.html.j2
+++ b/app/templates/_sidenav.html.j2
@@ -35,7 +35,7 @@
   <li class="service-color service-color-border border-darken" data-service="transkribus-htr-pipeline" style="border-left: 10px solid; margin-top: 5px;"><a href="{{ url_for('services.transkribus_htr_pipeline') }}"><i class="nopaque-icons service-icons" data-service="transkribus-htr-pipeline"></i>HTR</a></li>
   {% endif %}
   <li class="service-color service-color-border border-darken" data-service="spacy-nlp-pipeline" style="border-left: 10px solid; margin-top: 5px;"><a href="{{ url_for('services.spacy_nlp_pipeline') }}"><i class="nopaque-icons service-icons" data-service="spacy-nlp-pipeline"></i>NLP</a></li>
-  <li class="service-color service-color-border border-darken" data-service="corpus-analysis" style="border-left: 10px solid; margin-top: 5px;"><a href="{{ url_for('services.corpus_analysis') }}"><i class="nopaque-icons service-icons" data-service="corpus-analysis"></i>Corpus analysis</a></li>
+  <li class="service-color service-color-border border-darken" data-service="corpus-analysis" style="border-left: 10px solid; margin-top: 5px;"><a href="{{ url_for('services.corpus_analysis') }}"><i class="nopaque-icons service-icons" data-service="corpus-analysis"></i>Corpus Analysis</a></li>
   <li><div class="divider"></div></li>
   <li><a class="subheader">Account</a></li>
   <li><a href="{{ url_for('settings.settings') }}"><i class="material-icons">settings</i>General Settings</a></li>
diff --git a/app/templates/admin/_breadcrumbs.html.j2 b/app/templates/admin/_breadcrumbs.html.j2
deleted file mode 100644
index c4a640466e8478a8741776fadd206f0d13fb655e..0000000000000000000000000000000000000000
--- a/app/templates/admin/_breadcrumbs.html.j2
+++ /dev/null
@@ -1,18 +0,0 @@
-{% set breadcrumbs %}
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-<li class="tab"><a href="{{ url_for('.index') }}" target="_self">Administration</a></li>
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-{% if request.path == url_for('.users') %}
-<li class="tab"><a class="active" href="{{ url_for('.users') }}" target="_self">Users</a></li>
-{% elif request.path == url_for('.user', user_id=user.id) %}
-<li class="tab"><a href="{{ url_for('.users') }}" target="_self">Users</a></li>
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-<li class="tab"><a class="active" href="{{ url_for('.user', user_id=user.id) }}" target="_self">{{ user.username }}</a></li>
-{% elif request.path == url_for('.edit_user', user_id=user.id) %}
-<li class="tab"><a href="{{ url_for('.users') }}" target="_self">Users</a></li>
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-<li class="tab"><a href="{{ url_for('.user', user_id=user.id) }}" target="_self">{{ user.username }}</a></li>
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-<li class="tab"><a class="active" href="{{ url_for('.edit_user', user_id=user.id) }}" target="_self">Edit</a></li>
-{% endif %}
-{% endset %}
diff --git a/app/templates/admin/edit_user.html.j2 b/app/templates/admin/edit_user.html.j2
index c963b7f73d4de0edebc6984deb3b04f10f18fe24..0ac4f27dd4d260c9c427573cd691efb8430d5937 100644
--- a/app/templates/admin/edit_user.html.j2
+++ b/app/templates/admin/edit_user.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "admin/_breadcrumbs.html.j2" import breadcrumbs with context %}
 {% import "materialize/wtf.html.j2" as wtf %}
 
 {% block page_content %}
diff --git a/app/templates/admin/user.html.j2 b/app/templates/admin/user.html.j2
index b4b0e30322c9dcdaecd98be307085f4b74d56130..aa4899cef7150f3a3c3f474537ec91564dcdd61a 100644
--- a/app/templates/admin/user.html.j2
+++ b/app/templates/admin/user.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "admin/_breadcrumbs.html.j2" import breadcrumbs with context %}
 
 {% block page_content %}
 <div class="container">
diff --git a/app/templates/admin/users.html.j2 b/app/templates/admin/users.html.j2
index c96024cffdf1d1a9ae8f1ecd1ade599351d40c54..25b27c9512b8435ee361f821f616325eb7293a53 100644
--- a/app/templates/admin/users.html.j2
+++ b/app/templates/admin/users.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "admin/_breadcrumbs.html.j2" import breadcrumbs with context %}
 
 {% block page_content %}
 <div class="container">
diff --git a/app/templates/auth/_breadcrumbs.html.j2 b/app/templates/auth/_breadcrumbs.html.j2
deleted file mode 100644
index 2f46d9dc206032274281c6c671aa288549101fc5..0000000000000000000000000000000000000000
--- a/app/templates/auth/_breadcrumbs.html.j2
+++ /dev/null
@@ -1,14 +0,0 @@
-{% set breadcrumbs %}
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-{% if request.path == url_for('.login') %}
-<li class="tab"><a class="active" href="{{ url_for('.login') }}" target="_self">{{ title }}</a></li>
-{% elif request.path == url_for('.register') %}
-<li class="tab"><a class="active" href="{{ url_for('.register') }}" target="_self">{{ title }}</a></li>
-{% elif request.path == url_for('.reset_password', token=token) %}
-<li class="tab"><a class="active" href="{{ url_for('.reset_password', token=token) }}" target="_self">{{ title }}</a></li>
-{% elif request.path == url_for('.reset_password_request') %}
-<li class="tab"><a class="active" href="{{ url_for('.reset_password_request') }}" target="_self">{{ title }}</a></li>
-{% elif request.path == url_for('.unconfirmed') %}
-<li class="tab"><a class="active" href="{{ url_for('.unconfirmed') }}" target="_self">{{ title }}</a></li>
-{% endif %}
-{% endset %}
diff --git a/app/templates/auth/login.html.j2 b/app/templates/auth/login.html.j2
index 213c0a5f19bc24183951b8c050f3fe2a862b7184..7aa402f6bc30660eb97447489158a7dfe7dc1cea 100644
--- a/app/templates/auth/login.html.j2
+++ b/app/templates/auth/login.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "auth/_breadcrumbs.html.j2" import breadcrumbs with context %}
 {% import "materialize/wtf.html.j2" as wtf %}
 
 
diff --git a/app/templates/auth/register.html.j2 b/app/templates/auth/register.html.j2
index 69a019126dd1eb314bfd628da3741c5cc3667e93..d8e023a779eb4a73cbb2d1c09aed8a1aeb3ba039 100644
--- a/app/templates/auth/register.html.j2
+++ b/app/templates/auth/register.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "auth/_breadcrumbs.html.j2" import breadcrumbs with context %}
 {% import "materialize/wtf.html.j2" as wtf %}
 
 
diff --git a/app/templates/auth/reset_password.html.j2 b/app/templates/auth/reset_password.html.j2
index 06f11059b759db639572621195fefac4b2dc6f63..7df460bbdfb8785c68fb949e997c62511180c100 100644
--- a/app/templates/auth/reset_password.html.j2
+++ b/app/templates/auth/reset_password.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "auth/_breadcrumbs.html.j2" import breadcrumbs with context %}
 {% import "materialize/wtf.html.j2" as wtf %}
 
 {% block page_content %}
diff --git a/app/templates/auth/reset_password_request.html.j2 b/app/templates/auth/reset_password_request.html.j2
index a94d18da91521dbd6b38ec67e04be35b1366dc3c..5bf28111670963d07b223e222abeae7cee0b6b7c 100644
--- a/app/templates/auth/reset_password_request.html.j2
+++ b/app/templates/auth/reset_password_request.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "auth/_breadcrumbs.html.j2" import breadcrumbs with context %}
 {% import "materialize/wtf.html.j2" as wtf %}
 
 {% block page_content %}
diff --git a/app/templates/auth/unconfirmed.html.j2 b/app/templates/auth/unconfirmed.html.j2
index 26384eccbd71a207ee690c6486c87f5b5dca11d5..f927fcfff82527f37ab4d5862c3324f708bea915 100644
--- a/app/templates/auth/unconfirmed.html.j2
+++ b/app/templates/auth/unconfirmed.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "auth/_breadcrumbs.html.j2" import breadcrumbs with context %}
 
 {% block page_content %}
 <div class="container">
diff --git a/app/templates/contributions/_breadcrumbs.html.j2 b/app/templates/contributions/_breadcrumbs.html.j2
deleted file mode 100644
index ab64dec4794d6df46d5f50e6fbbeb6cc324733ea..0000000000000000000000000000000000000000
--- a/app/templates/contributions/_breadcrumbs.html.j2
+++ /dev/null
@@ -1,46 +0,0 @@
-{% set breadcrumbs %}
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-{% if request.path == url_for('.contributions') %}
-<li class="tab"><a class="active" href="{{ url_for('.contributions') }}" target="_self">Contributions Overview</a></li>
-{% elif request.path == url_for('.tesseract_ocr_pipeline_models')%}
-<li class="tab"><a href="{{ url_for('.contributions') }}" target="_self">Contributions Overview</a></li>
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-<li class="tab"><a class="active" href="{{ url_for('.tesseract_ocr_pipeline_models') }}" target="_self">Tesseract OCR Pipeline Models</a></li>
-{% elif request.path == url_for('.spacy_nlp_pipeline_models')%}
-<li class="tab"><a href="{{ url_for('.contributions') }}" target="_self">Contributions Overview</a></li>
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-<li class="tab"><a class="active" href="{{ url_for('.spacy_nlp_pipeline_models') }}" target="_self">SpaCy NLP Pipeline Models</a></li>
-{% elif request.path == url_for('.create_tesseract_ocr_pipeline_model') %}
-<li class="tab"><a href="{{ url_for('.contributions') }}" target="_self">Contributions Overview</a></li>
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-<li class="tab"><a href="{{ url_for('.tesseract_ocr_pipeline_models') }}" target="_self">Tesseract OCR Pipeline Models</a></li>
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-<li class="tab"><a class="active" href="{{ url_for('.create_tesseract_ocr_pipeline_model') }}" target="_self">{{ title }}</a></li>
-{% elif request.path == url_for('.create_spacy_nlp_pipeline_model') %}
-<li class="tab"><a href="{{ url_for('.contributions') }}" target="_self">Contributions Overview</a></li>
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-<li class="tab"><a href="{{ url_for('.spacy_nlp_pipeline_models') }}" target="_self">SpaCy NLP Pipeline Models</a></li>
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-<li class="tab"><a class="active" href="{{ url_for('.create_spacy_nlp_pipeline_model') }}" target="_self">{{ title }}</a></li>
-{% elif tesseract_ocr_pipeline_model and request.path == url_for('.tesseract_ocr_pipeline_model', tesseract_ocr_pipeline_model_id=tesseract_ocr_pipeline_model.id) %}
-<li class="tab"><a href="{{ url_for('.contributions') }}" target="_self">Contributions Overview</a></li>
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-<li class="tab"><a href="{{ url_for('.tesseract_ocr_pipeline_models') }}" target="_self">Tesseract OCR Pipeline Models</a></li>
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-<li class="tab">
-  <a class="active" href="{{ url_for('.tesseract_ocr_pipeline_model', tesseract_ocr_pipeline_model_id=tesseract_ocr_pipeline_model.id) }}" target="_self">
-    {{ tesseract_ocr_pipeline_model.title }} {{ tesseract_ocr_pipeline_model.version }}
-  </a>
-</li>
-{% elif spacy_nlp_pipeline_model and request.path == url_for('.spacy_nlp_pipeline_model', spacy_nlp_pipeline_model_id=spacy_nlp_pipeline_model.id) %}
-<li class="tab"><a href="{{ url_for('.contributions') }}" target="_self">Contributions Overview</a></li>
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-<li class="tab"><a href="{{ url_for('.spacy_nlp_pipeline_models') }}" target="_self">SpaCy NLP Pipeline Models</a></li>
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-<li class="tab">
-  <a class="active" href="{{ url_for('.spacy_nlp_pipeline_model', spacy_nlp_pipeline_model_id=spacy_nlp_pipeline_model.id) }}" target="_self">
-    {{ spacy_nlp_pipeline_model.title }} {{ spacy_nlp_pipeline_model.version }}
-  </a>
-</li>
-{% endif %}
-{% endset %}
diff --git a/app/templates/contributions/contributions.html.j2 b/app/templates/contributions/contributions.html.j2
index efefcbbb2385915d9d3e8f4de39f3d1debe46027..a09447230ef7549b80d9be2444a8eb12df2a17b6 100644
--- a/app/templates/contributions/contributions.html.j2
+++ b/app/templates/contributions/contributions.html.j2
@@ -10,7 +10,7 @@
 
     <div class="col s4">
       <div class="card extension-selector hoverable service-color" data-service="tesseract-ocr-pipeline">
-        <a href="{{ url_for('.tesseract_ocr_pipeline_models.tesseract_ocr_pipeline_models') }}" style="position: absolute; width: 100%; height: 100%;"></a>
+        <a href="{{ url_for('.tesseract_ocr_pipeline_models') }}" style="position: absolute; width: 100%; height: 100%;"></a>
         <div class="card-content">
           <span class="card-title" data-service="tesseract-ocr-pipeline"><i class="nopaque-icons service-icons" data-service="tesseract-ocr-pipeline"></i>Tesseract OCR Pipeline Models</span>
           <p>Here you can see and edit the models that you have created. You can also create new models.</p>
@@ -20,7 +20,7 @@
 
     <div class="col s4">
       <div class="card extension-selector hoverable service-color" data-service="spacy-nlp-pipeline">
-        <a href="{{ url_for('.spacy_nlp_pipeline_models.spacy_nlp_pipeline_models') }}" style="position: absolute; width: 100%; height: 100%;"></a>
+        <a href="{{ url_for('.spacy_nlp_pipeline_models') }}" style="position: absolute; width: 100%; height: 100%;"></a>
         <div class="card-content">
           <span class="card-title"><i class="nopaque-icons service-icons" data-service="spacy-nlp-pipeline"></i>SpaCy NLP Pipeline Models</span>
           <p>Here you can see and edit the models that you have created. You can also create new models.</p>
diff --git a/app/templates/contributions/spacy_nlp_pipeline_models/create_spacy_nlp_pipeline_model.html.j2 b/app/templates/contributions/create_spacy_nlp_pipeline_model.html.j2
similarity index 100%
rename from app/templates/contributions/spacy_nlp_pipeline_models/create_spacy_nlp_pipeline_model.html.j2
rename to app/templates/contributions/create_spacy_nlp_pipeline_model.html.j2
diff --git a/app/templates/contributions/tesseract_ocr_pipeline_models/create_tesseract_ocr_pipeline_model.html.j2 b/app/templates/contributions/create_tesseract_ocr_pipeline_model.html.j2
similarity index 100%
rename from app/templates/contributions/tesseract_ocr_pipeline_models/create_tesseract_ocr_pipeline_model.html.j2
rename to app/templates/contributions/create_tesseract_ocr_pipeline_model.html.j2
diff --git a/app/templates/contributions/spacy_nlp_pipeline_models/spacy_nlp_pipeline_model.html.j2 b/app/templates/contributions/spacy_nlp_pipeline_model.html.j2
similarity index 100%
rename from app/templates/contributions/spacy_nlp_pipeline_models/spacy_nlp_pipeline_model.html.j2
rename to app/templates/contributions/spacy_nlp_pipeline_model.html.j2
diff --git a/app/templates/contributions/spacy_nlp_pipeline_models/spacy_nlp_pipeline_models.html.j2 b/app/templates/contributions/spacy_nlp_pipeline_models.html.j2
similarity index 100%
rename from app/templates/contributions/spacy_nlp_pipeline_models/spacy_nlp_pipeline_models.html.j2
rename to app/templates/contributions/spacy_nlp_pipeline_models.html.j2
diff --git a/app/templates/contributions/tesseract_ocr_pipeline_models/tesseract_ocr_pipeline_model.html.j2 b/app/templates/contributions/tesseract_ocr_pipeline_model.html.j2
similarity index 100%
rename from app/templates/contributions/tesseract_ocr_pipeline_models/tesseract_ocr_pipeline_model.html.j2
rename to app/templates/contributions/tesseract_ocr_pipeline_model.html.j2
diff --git a/app/templates/contributions/tesseract_ocr_pipeline_models/tesseract_ocr_pipeline_models.html.j2 b/app/templates/contributions/tesseract_ocr_pipeline_models.html.j2
similarity index 100%
rename from app/templates/contributions/tesseract_ocr_pipeline_models/tesseract_ocr_pipeline_models.html.j2
rename to app/templates/contributions/tesseract_ocr_pipeline_models.html.j2
diff --git a/app/templates/corpora/_breadcrumbs.html.j2 b/app/templates/corpora/_breadcrumbs.html.j2
deleted file mode 100644
index bdbe0f3449c9a514c920e01055f728aba56d1419..0000000000000000000000000000000000000000
--- a/app/templates/corpora/_breadcrumbs.html.j2
+++ /dev/null
@@ -1,28 +0,0 @@
-{# {% set breadcrumbs %}
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-<li class="tab"><a href="{{ url_for('main.dashboard', _anchor='jobs') }}" target="_self">My corpora</a></li>
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-{% if request.path == url_for('.create_corpus') %}
-<li class="tab"><a class="active" href="{{ url_for('.create_corpus') }}" target="_self">{{ title }}</a></li>
-{% elif request.path == url_for('.import_corpus') %}
-<li class="tab"><a class="active" href="{{ url_for('.import_corpus') }}" target="_self">{{ title }}</a></li>
-{% elif request.path == url_for('.corpus', corpus_id=corpus.id) %}
-<li class="tab"><a class="active" href="{{ url_for('.corpus', corpus_id=corpus.id) }}" target="_self">{{ corpus.title }}</a></li>
-{% elif request.path == url_for('.analyse_corpus', corpus_id=corpus.id) %}
-<li class="tab"><a href="{{ url_for('.corpus', corpus_id=corpus.id) }}" target="_self">{{ corpus.title }}</a></li>
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-<li class="tab"><a class="active" href="{{ url_for('.analyse_corpus', corpus_id=corpus.id) }}" target="_self">{{ title }}</a></li>
-{% elif request.path == url_for('.create_corpus_file', corpus_id=corpus.id) %}
-<li class="tab"><a href="{{ url_for('.corpus', corpus_id=corpus.id) }}" target="_self">{{ corpus.title }}</a></li>
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-<li class="tab"><a href="{{ url_for('.corpus', corpus_id=corpus.id, _anchor='files') }}" target="_self">Corpus files</a></li>
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-<li class="tab"><a class="active" href="{{ url_for('.create_corpus_file', corpus_id=corpus.id) }}" target="_self">{{ title }}</a></li>
-{% elif request.path == url_for('.corpus_file', corpus_file_id=corpus_file.id, corpus_id=corpus.id) %}
-<li class="tab"><a href="{{ url_for('.corpus', corpus_id=corpus.id) }}" target="_self">{{ corpus.title }}</a></li>
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-<li class="tab"><a href="{{ url_for('.corpus', corpus_id=corpus.id, _anchor='files') }}" target="_self">Corpus files</a></li>
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-<li class="tab"><a class="active" href="{{ url_for('.corpus_file', corpus_file_id=corpus_file.id, corpus_id=corpus.id) }}" target="_self">{{ corpus_file.author }}: {{ corpus_file.title }} ({{ corpus_file.publishing_year }})</a></li>
-{% endif %}
-{% endset %} #}
diff --git a/app/templates/corpora/corpus.html.j2 b/app/templates/corpora/corpus.html.j2
index 053dbdc0a775997a7c0b89158d729796dcf28b7f..f17e1e884923b03dabadef535ab255897c68f689 100644
--- a/app/templates/corpora/corpus.html.j2
+++ b/app/templates/corpora/corpus.html.j2
@@ -1,6 +1,5 @@
 {% extends "base.html.j2" %}
 {% import "materialize/wtf.html.j2" as wtf %}
-{% from "corpora/_breadcrumbs.html.j2" import breadcrumbs with context %}
 
 {% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}
 
diff --git a/app/templates/corpora/create_corpus.html.j2 b/app/templates/corpora/create_corpus.html.j2
index ccb4987724ab3d7d8bc7a8c8aa4bf79d3537df0d..5feb72254d7f4d5f2240146b639dda07e2325f75 100644
--- a/app/templates/corpora/create_corpus.html.j2
+++ b/app/templates/corpora/create_corpus.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "corpora/_breadcrumbs.html.j2" import breadcrumbs with context %}
 {% import "materialize/wtf.html.j2" as wtf %}
 
 {% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}
diff --git a/app/templates/corpora/files/corpus_file.html.j2 b/app/templates/corpora/files/corpus_file.html.j2
index 781ea05915613f30b9855f4e2d5755c3ae045e1e..28a762d41a7870c09da5692c1aec935e228450f8 100644
--- a/app/templates/corpora/files/corpus_file.html.j2
+++ b/app/templates/corpora/files/corpus_file.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "corpora/_breadcrumbs.html.j2" import breadcrumbs with context %}
 {% import "materialize/wtf.html.j2" as wtf %}
 
 {% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}
diff --git a/app/templates/corpora/files/create_corpus_file.html.j2 b/app/templates/corpora/files/create_corpus_file.html.j2
index 8cff13a61803d50a68efa89ffa954f10cb44af0e..ff634294274c579f4f517ce2c557f0ed86988902 100644
--- a/app/templates/corpora/files/create_corpus_file.html.j2
+++ b/app/templates/corpora/files/create_corpus_file.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "corpora/_breadcrumbs.html.j2" import breadcrumbs with context %}
 {% import "materialize/wtf.html.j2" as wtf %}
 
 {% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}
diff --git a/app/templates/corpora/import_corpus.html.j2 b/app/templates/corpora/import_corpus.html.j2
index 957668e6817d8b306c02f2760d336e27ac1d7d99..dd0806a75c7e7a04f2bf5d1a53c79e0c24e20212 100644
--- a/app/templates/corpora/import_corpus.html.j2
+++ b/app/templates/corpora/import_corpus.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "corpora/_breadcrumbs.html.j2" import breadcrumbs with context %}
 {% import "materialize/wtf.html.j2" as wtf %}
 
 {% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}
diff --git a/app/templates/jobs/_breadcrumbs.html.j2 b/app/templates/jobs/_breadcrumbs.html.j2
deleted file mode 100644
index e3de43f313d7d460f3196d2889f10136803b0a82..0000000000000000000000000000000000000000
--- a/app/templates/jobs/_breadcrumbs.html.j2
+++ /dev/null
@@ -1,8 +0,0 @@
-{% set breadcrumbs %}
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-<li class="tab"><a href="{{ url_for('main.dashboard', _anchor='jobs') }}" target="_self">My jobs</a></li>
-{% if request.path == url_for('.job', job_id=job.id) %}
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-<li class="tab"><a class="active" href="{{ url_for('.job', job_id=job.id) }}" target="_self">{{ job.title }}</a></li>
-{% endif %}
-{% endset %}
diff --git a/app/templates/jobs/job.html.j2 b/app/templates/jobs/job.html.j2
index b5055ce61fd84a1ae3dacbd2eedb20773b910503..51f2fc6daddd5c232997512433f8e9f00b6b2beb 100644
--- a/app/templates/jobs/job.html.j2
+++ b/app/templates/jobs/job.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "jobs/_breadcrumbs.html.j2" import breadcrumbs with context %}
 
 {% block main_attribs %} class="service-scheme" data-service="{{ job.service }}"{% endblock main_attribs %}
 
diff --git a/app/templates/main/_breadcrumbs.html.j2 b/app/templates/main/_breadcrumbs.html.j2
deleted file mode 100644
index d65978d65b2c92b072b8c445452d38719f4ca0c2..0000000000000000000000000000000000000000
--- a/app/templates/main/_breadcrumbs.html.j2
+++ /dev/null
@@ -1,14 +0,0 @@
-{% set breadcrumbs %}
-{% if not (request.path == url_for('.index')) %}
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-{% endif %}
-{% if request.path == url_for('.faq') %}
-<li class="tab"><a class="active" href="{{ url_for('.faq') }}" target="_self">Frequently Asked Questions</a></li>
-{% elif request.path == url_for('.dashboard') %}
-<li class="tab"><a class="active" href="{{ url_for('.dashboard') }}" target="_self">Dashboard</a></li>
-{% elif request.path == url_for('.news') %}
-<li class="tab"><a class="active" href="{{ url_for('.news') }}" target="_self">News</a></li>
-{% elif request.path == url_for('.terms_of_use') %}
-<li class="tab"><a class="active" href="{{ url_for('.terms_of_use') }}" target="_self">Terms of use</a></li>
-{% endif %}
-{% endset %}
diff --git a/app/templates/main/dashboard.html.j2 b/app/templates/main/dashboard.html.j2
index 5474db970b33fc11aedb8245a4a9b1149a23dbc4..1598e1396eab87ca038cd96a0bb0cc2101379c44 100644
--- a/app/templates/main/dashboard.html.j2
+++ b/app/templates/main/dashboard.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "main/_breadcrumbs.html.j2" import breadcrumbs with context %}
 
 {% block page_content %}
 <div class="container">
diff --git a/app/templates/main/faq.html.j2 b/app/templates/main/faq.html.j2
index fb10e3b49c1bf1f80af95d08313b9c6856b8a450..5c1680072e0f5b0b9174f0db2b8d2802ea1b3b90 100644
--- a/app/templates/main/faq.html.j2
+++ b/app/templates/main/faq.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "main/_breadcrumbs.html.j2" import breadcrumbs with context %}
 
 {% block page_content %}
 <div class="container">
diff --git a/app/templates/main/index.html.j2 b/app/templates/main/index.html.j2
index 74bfa30646af16ad69f1c340c24bd8cce846e0a0..7b80a794b9202a304e80000b1c2d807e99bf8fd9 100644
--- a/app/templates/main/index.html.j2
+++ b/app/templates/main/index.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "main/_breadcrumbs.html.j2" import breadcrumbs with context %}
 {% import "materialize/wtf.html.j2" as wtf %}
 
 {% block page_content %}
diff --git a/app/templates/main/news.html.j2 b/app/templates/main/news.html.j2
index b8961b023382bfb3b3373961378e9dffa10bb887..d1111d8b05363691474cd41e2cf2e213a15b5421 100644
--- a/app/templates/main/news.html.j2
+++ b/app/templates/main/news.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "main/_breadcrumbs.html.j2" import breadcrumbs with context %}
 
 {% block page_content %}
 <div class="container">
diff --git a/app/templates/main/news_new.html.j2 b/app/templates/main/news_new.html.j2
index d3e2f9b68c673f2a24b46346f382cff830e5ef61..d2b339e3ddc87ad497527ed99e5041a22cbd319c 100644
--- a/app/templates/main/news_new.html.j2
+++ b/app/templates/main/news_new.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "main/_breadcrumbs.html.j2" import breadcrumbs with context %}
 
 {% block page_content %}
 <div class="container">
diff --git a/app/templates/main/privacy_policy.html.j2 b/app/templates/main/privacy_policy.html.j2
index ff22b10ad8b9b0befa524dc3e5b0bfd14d9e63e4..c55ae722916b0ee5cb77b0140ab8915f5ce06a28 100644
--- a/app/templates/main/privacy_policy.html.j2
+++ b/app/templates/main/privacy_policy.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "main/_breadcrumbs.html.j2" import breadcrumbs with context %}
 
 {% block page_content %}
 <div class="container">
diff --git a/app/templates/main/terms_of_use.html.j2 b/app/templates/main/terms_of_use.html.j2
index baedd0a28530a56d4660c9aae335fbff15eddaf2..d61084d64177fa7e1e9113508766a91c8dd43600 100644
--- a/app/templates/main/terms_of_use.html.j2
+++ b/app/templates/main/terms_of_use.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "main/_breadcrumbs.html.j2" import breadcrumbs with context %}
 
 {% block page_content %}
 <div class="container">
diff --git a/app/templates/main/user_manual.html.j2 b/app/templates/main/user_manual.html.j2
index 0da85809df2ca2487b55756d61b7a69138c8bc23..2ef1da01a3cd79f22f1be64634bc6f643adb7acc 100644
--- a/app/templates/main/user_manual.html.j2
+++ b/app/templates/main/user_manual.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "main/_breadcrumbs.html.j2" import breadcrumbs with context %}
 
 {% block page_content %}
 <div class="container">
diff --git a/app/templates/services/_breadcrumbs.html.j2 b/app/templates/services/_breadcrumbs.html.j2
deleted file mode 100644
index a08beafab6595a87910f44350e8a569205a7a79c..0000000000000000000000000000000000000000
--- a/app/templates/services/_breadcrumbs.html.j2
+++ /dev/null
@@ -1,16 +0,0 @@
-{% set breadcrumbs %}
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-<li class="tab"><a href="{{ url_for('main.index', _anchor='services') }}" target="_self">Processes & Services</a></li>
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-{% if request.path == url_for('.corpus_analysis') %}
-<li class="tab"><a class="active" href="{{ url_for('.corpus_analysis') }}" target="_self">{{ title }}</a></li>
-{% elif request.path == url_for('.file_setup_pipeline') %}
-<li class="tab"><a class="active" href="{{ url_for('.file_setup_pipeline') }}" target="_self">{{ title }}</a></li>
-{% elif request.path == url_for('.spacy_nlp_pipeline') %}
-<li class="tab"><a class="active" href="{{ url_for('.spacy_nlp_pipeline') }}" target="_self">{{ title }}</a></li>
-{% elif request.path == url_for('.tesseract_ocr_pipeline') %}
-<li class="tab"><a class="active" href="{{ url_for('.tesseract_ocr_pipeline') }}" target="_self">{{ title }}</a></li>
-{% elif request.path == url_for('.transkribus_htr_pipeline') %}
-<li class="tab"><a class="active" href="{{ url_for('.transkribus_htr_pipeline') }}" target="_self">{{ title }}</a></li>
-{% endif %}
-{% endset %}
diff --git a/app/templates/services/corpus_analysis.html.j2 b/app/templates/services/corpus_analysis.html.j2
index a7e3da8e7a04634df25bb02047168a847e06897f..9ddc9ec3d1fa3e2c3bf2d5152fe8e70ad07a0f28 100644
--- a/app/templates/services/corpus_analysis.html.j2
+++ b/app/templates/services/corpus_analysis.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "services/_breadcrumbs.html.j2" import breadcrumbs with context %}
 
 {% block main_attribs %} class="service-scheme" data-service="corpus-analysis"{% endblock main_attribs %}
 
diff --git a/app/templates/services/file_setup_pipeline.html.j2 b/app/templates/services/file_setup_pipeline.html.j2
index ebc4cfc4cba500de29ff5ced062525244603c9ce..0f046e98b0860e2369d689562acb0f6e6537e21e 100644
--- a/app/templates/services/file_setup_pipeline.html.j2
+++ b/app/templates/services/file_setup_pipeline.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "services/_breadcrumbs.html.j2" import breadcrumbs with context %}
 {% import "materialize/wtf.html.j2" as wtf %}
 
 {% block main_attribs %} class="service-scheme" data-service="file-setup-pipeline"{% endblock main_attribs %}
diff --git a/app/templates/services/spacy_nlp_pipeline.html.j2 b/app/templates/services/spacy_nlp_pipeline.html.j2
index 8f466e3dc9431583ecef8e641dd9740d572daa2b..2ebba838724ba9dae11f63ea8e199efdefa94832 100644
--- a/app/templates/services/spacy_nlp_pipeline.html.j2
+++ b/app/templates/services/spacy_nlp_pipeline.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "services/_breadcrumbs.html.j2" import breadcrumbs with context %}
 {% import "materialize/wtf.html.j2" as wtf %}
 
 {% block main_attribs %} class="service-scheme" data-service="spacy-nlp-pipeline"{% endblock main_attribs %}
@@ -77,7 +76,7 @@
                   {{ form.model.label }}
                   <span class="helper-text">
                     <a class="modal-trigger tooltipped" href="#models-modal" data-position="bottom" data-tooltip="See more information about models"><i class="material-icons" style="color:#0064A3;">help_outline</i></a>
-                    <a class="tooltipped" href="{{ url_for('contributions.spacy_nlp_pipeline_models.create_spacy_nlp_pipeline_model') }}" data-position="bottom" data-tooltip="Add your own spaCy NLP models"><i class="material-icons" style="color:#0064A3">new_label</i></a>
+                    <a class="tooltipped" href="{{ url_for('contributions.create_spacy_nlp_pipeline_model') }}" data-position="bottom" data-tooltip="Add your own spaCy NLP models"><i class="material-icons" style="color:#0064A3">new_label</i></a>
                   </span>
                 </div>
               </div>
diff --git a/app/templates/services/tesseract_ocr_pipeline.html.j2 b/app/templates/services/tesseract_ocr_pipeline.html.j2
index 11d65c6439850bfb2f18787de2f911bb56efde3a..9b1d61877be4ce59eba286fb669a31b31d1f16c2 100644
--- a/app/templates/services/tesseract_ocr_pipeline.html.j2
+++ b/app/templates/services/tesseract_ocr_pipeline.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "services/_breadcrumbs.html.j2" import breadcrumbs with context %}
 {% import "materialize/wtf.html.j2" as wtf %}
 
 {% block main_attribs %} class="service-scheme" data-service="tesseract-ocr-pipeline"{% endblock main_attribs %}
@@ -59,7 +58,7 @@
                   {{ form.model.label }}
                   <span class="helper-text">
                     <a class="modal-trigger tooltipped" href="#models-modal" data-position="bottom" data-tooltip="See more information about models"><i class="material-icons" style="color:#00A58B;">help_outline</i></a>
-                    <a class="tooltipped" href="{{ url_for('contributions.tesseract_ocr_pipeline_models.create_tesseract_ocr_pipeline_model') }}" data-position="bottom" data-tooltip="Add your own Tesseract OCR models"><i class="material-icons" style="color:#00A58B">new_label</i></a>
+                    <a class="tooltipped" href="{{ url_for('contributions.create_tesseract_ocr_pipeline_model') }}" data-position="bottom" data-tooltip="Add your own Tesseract OCR models"><i class="material-icons" style="color:#00A58B">new_label</i></a>
                   </span>
                   {% for error in form.model.errors %}
                   <span class="helper-text error-color-text">{{ error }}</span>
diff --git a/app/templates/services/transkribus_htr_pipeline.html.j2 b/app/templates/services/transkribus_htr_pipeline.html.j2
index f5468ce97ebc065a95f16981d56542101121da53..50eb3b6e1d69960c1fe1f6b38e1252d8308f4d87 100644
--- a/app/templates/services/transkribus_htr_pipeline.html.j2
+++ b/app/templates/services/transkribus_htr_pipeline.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "services/_breadcrumbs.html.j2" import breadcrumbs with context %}
 {% import "materialize/wtf.html.j2" as wtf %}
 
 {% block main_attribs %} class="service-scheme" data-service="transkribus-htr-pipeline"{% endblock main_attribs %}
diff --git a/app/templates/settings/_breadcrumbs.html.j2 b/app/templates/settings/_breadcrumbs.html.j2
deleted file mode 100644
index 3b5077bfd8e7dd07315128eef5e87fcf34b4721b..0000000000000000000000000000000000000000
--- a/app/templates/settings/_breadcrumbs.html.j2
+++ /dev/null
@@ -1,6 +0,0 @@
-{% set breadcrumbs %}
-<li class="tab disabled"><i class="material-icons">navigate_next</i></li>
-{% if request.path == url_for('settings.settings') %}
-<li class="tab"><a{%if request.path == url_for('settings.settings') %} class="active"{% endif %} href="{{ url_for('settings.settings') }}" target="_self">Settings</a></li>
-{% endif %}
-{% endset %}
diff --git a/app/templates/settings/settings.html.j2 b/app/templates/settings/settings.html.j2
index e9cfb97bda684a8a654a6d0bbde8bc1a19564ae1..fd830aee644b4c24513af2cb00377f4ebeddf04d 100644
--- a/app/templates/settings/settings.html.j2
+++ b/app/templates/settings/settings.html.j2
@@ -1,5 +1,4 @@
 {% extends "base.html.j2" %}
-{% from "settings/_breadcrumbs.html.j2" import breadcrumbs with context %}
 {% import "materialize/wtf.html.j2" as wtf %}
 
 {% block page_content %}
diff --git a/requirements.txt b/requirements.txt
index 838cee8fe7e067fa51462010d6b50f507849c1dc..e621d2ad4947a3df3b27513bd8bde5d72f2bf567 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,6 +6,7 @@ eventlet
 Flask==2.1.3
 Flask-APScheduler
 Flask-Assets
+Flask-Breadcrumbs
 Flask-Hashids==1.0.1
 Flask-HTTPAuth
 Flask-Login