diff --git a/warehouse/__init__.py b/warehouse/__init__.py
index 64379153013478f4f7cad240e8fbe06e79dd4b89..bbe5aec1978affa54cd639a5169a1aa566910f73 100644
--- a/warehouse/__init__.py
+++ b/warehouse/__init__.py
@@ -178,7 +178,37 @@ def item_delete(item_id):
 @app.route('/item/<item_id>/photo', methods=['POST'])
 def item_upload_photo(item_id):
 	item = Item.query.get_or_404(item_id)
-	item.photos.append(Photo.from_form(request.files['file']))
+	photo = Photo.from_form(request.files['file'])
+	item.photos.append(photo)
+	if not item.photo:
+		item.photo = photo
+	db.session.commit()
+	return redirect(url_for('item_view', item_id=item_id))
+
+@app.route('/item/<item_id>/photo/<int:photo_id>/set', methods=['POST'])
+def item_set_photo(item_id, photo_id):
+	item = Item.query.get_or_404(item_id)
+	photo = Photo.query.get_or_404(photo_id)
+	item.photo = photo
+	db.session.commit()
+	return redirect(url_for('item_view', item_id=item_id))
+
+@app.route('/item/<item_id>/photo/clear', methods=['POST'])
+def item_clear_photo(item_id):
+	item = Item.query.get_or_404(item_id)
+	item.photo = None
+	db.session.commit()
+	return redirect(url_for('item_view', item_id=item_id))
+
+@app.route('/item/<item_id>/photo/<int:photo_id>/delete', methods=['GET', 'POST'])
+def item_delete_photo(item_id, photo_id):
+	item = Item.query.get_or_404(item_id)
+	photo = Photo.query.get_or_404(photo_id)
+	if request.method == 'GET':
+		return render_template('item/delete_photo.html', item=item, photo=photo)
+	if item.photo == photo:
+		item.photo = None
+	item.photos.remove(photo)
 	db.session.commit()
 	return redirect(url_for('item_view', item_id=item_id))
 
diff --git a/warehouse/migrations/versions/bf26c4ca926e_primary_item_photo.py b/warehouse/migrations/versions/bf26c4ca926e_primary_item_photo.py
new file mode 100644
index 0000000000000000000000000000000000000000..95c6e509e1943f0d7a0348b47f7d596102598637
--- /dev/null
+++ b/warehouse/migrations/versions/bf26c4ca926e_primary_item_photo.py
@@ -0,0 +1,46 @@
+"""Primary item photo
+
+Revision ID: bf26c4ca926e
+Revises: aabfcc081e98
+Create Date: 2023-04-23 19:33:11.072575
+
+"""
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = 'bf26c4ca926e'
+down_revision = 'aabfcc081e98'
+branch_labels = None
+depends_on = None
+
+def upgrade():
+	with op.batch_alter_table('item', schema=None) as batch_op:
+		batch_op.add_column(sa.Column('photo_id', sa.Integer(), nullable=True))
+		batch_op.create_foreign_key('fk_item_photo_id_photo', 'photo', ['photo_id'], ['id'])
+	item_photo = sa.table(
+		'item_photo',
+		sa.column('item_id', sa.String(5)),
+		sa.column('photo_id', sa.Integer())
+	)
+	item = sa.table(
+		'item',
+		sa.column('id', sa.String(5)),
+		sa.column('photo_id', sa.Integer())
+	)
+	conn = op.get_bind()
+	conn.execute(
+		sa.update(item).values(
+			photo_id=sa.select([item_photo.c.photo_id])
+				.where(item_photo.c.item_id == item.c.id)
+				.order_by(item_photo.c.photo_id)
+				.limit(1)
+				.scalar_subquery()
+		)
+	)
+
+def downgrade():
+	with op.batch_alter_table('item', schema=None) as batch_op:
+		batch_op.drop_constraint('fk_item_photo_id_photo', type_='foreignkey')
+		batch_op.drop_column('photo_id')
diff --git a/warehouse/models.py b/warehouse/models.py
index e30bf8d3e32639dd06d1e2d59ef92259bbc52340..b81eed22213076b93d1ce68fcbc12375073f7213 100644
--- a/warehouse/models.py
+++ b/warehouse/models.py
@@ -82,6 +82,8 @@ class Item(db.Model):
 	qr_code = db.Column(db.String(128), nullable=False, unique=True, default=token_qrfriendly)
 	name = db.Column(db.String(128), nullable=False)
 	description = db.Column(db.Text(), nullable=False, server_default='')
+	photo_id = db.Column(db.Integer(), db.ForeignKey('photo.id'))
+	photo = db.relationship('Photo')
 	photos = db.relationship('Photo', secondary='item_photo')
 	location_id = db.Column(db.String(5), db.ForeignKey('location.id'))
 	location = db.relationship('Location')
diff --git a/warehouse/templates/item/delete_photo.html b/warehouse/templates/item/delete_photo.html
new file mode 100644
index 0000000000000000000000000000000000000000..87aa5d8f3988cd3dae0cdd7aa56ce03d0079e35b
--- /dev/null
+++ b/warehouse/templates/item/delete_photo.html
@@ -0,0 +1,15 @@
+{% extends 'layout.html' %}
+
+{% block body %}
+<form method="POST" class="form">
+	<input type="hidden" name="csrf_token" value="{{ request.csrf_token }}">
+	<p>Really delete this photo from item {{ item.name }} ({{ item.id }}):</p>
+	<div style="width: 390px; height: 300px;" class="d-flex align-items-center justify-content-center border text-bg-light">
+		<img src="{{ url_for('photo_medium', photo_id=photo.id) }}" style="max-width: 100%; max-height: 100%;">
+	</div>
+	<div class="d-flex justify-content-end gap-1">
+		<a href="{{ url_for('item_view', item_id=item.id) }}" class="btn btn-light">Cancel</a>
+		<button type="submit" class="btn btn-danger">Delete photo</button>
+	</div>
+</form>
+{% endblock %}
diff --git a/warehouse/templates/item/list.html b/warehouse/templates/item/list.html
index 6c14261306bab17c85f7578e3c9ad8c93cf4bc90..7f83dda75a0d4d359e2a5751e767b90f4f15296a 100644
--- a/warehouse/templates/item/list.html
+++ b/warehouse/templates/item/list.html
@@ -22,9 +22,9 @@
 		{% for item in page.items %}
 		<tr>
 			<td style="width: 140px;">
-				{% if item.photos %}
+				{% if item.photo %}
 					<a href="{{ url_for('item_view', item_id=item.id) }}" style="width: 130px; height: 100px;" class="d-flex align-items-center justify-content-center border text-bg-light">
-						<img src="{{ url_for('photo_small', photo_id=(item.photos|first).id) }}" style="max-width: 100%; max-height: 100%;">
+						<img src="{{ url_for('photo_small', photo_id=item.photo.id) }}" style="max-width: 100%; max-height: 100%;">
 					</a>
 				{% endif %}
 			</td>
diff --git a/warehouse/templates/item/view.html b/warehouse/templates/item/view.html
index 88af4d6a1709a6ea83064298128e842109ba1875..5a81be6316fb018e94f65e41ce731323d709804c 100644
--- a/warehouse/templates/item/view.html
+++ b/warehouse/templates/item/view.html
@@ -37,34 +37,13 @@
 
 <div class="row">
 	<div class="col-12 col-md-4">
-		<div id="photo-carousel" class="carousel carousel-dark slide border text-bg-light p-1 my-2" style="width: 100%; height: 300px;">
-			{% if item.photos|length > 1 %}
-			<div class="carousel-indicators">
-				{% for photo in item.photos %}
-				<button type="button" data-bs-target="#photo-carousel" data-bs-slide-to="{{ loop.index0 }}" {% if loop.first %} class="active" aria-current="true" {% endif %}aria-label="Photo {{ loop.index }}"></button>
-				{% endfor %}
-			</div>
-			{% endif %}
-			<div class="carousel-inner" style="width: 100%; height: 100%;">
-				{% for photo in item.photos %}
-				<div class="carousel-item {{ 'active' if loop.first }}" style="width: 100%; height: 100%;">
-					<a href="{{ url_for('photo_show', photo_id=photo.id) }}" style="width: 100%; height: 100%;" class="d-flex align-items-center justify-content-center">
-						<img src="{{ url_for('photo_medium', photo_id=photo.id) }}" style="max-width: 100%; max-height: 100%;">
-					</a>
-				</div>
-				{% endfor %}
-			</div>
-			{% if item.photos|length > 1 %}
-			<button class="carousel-control-prev" type="button" data-bs-target="#photo-carousel" data-bs-slide="prev">
-				<span class="carousel-control-prev-icon" aria-hidden="true"></span>
-				<span class="visually-hidden">Previous</span>
-			</button>
-			<button class="carousel-control-next" type="button" data-bs-target="#photo-carousel" data-bs-slide="next">
-				<span class="carousel-control-next-icon" aria-hidden="true"></span>
-				<span class="visually-hidden">Next</span>
-			</button>
-			{% endif %}
-		</div>
+		{% if item.photo %}
+		<a href="{{ url_for('photo_show', photo_id=item.photo.id) }}" style="width: 100%; height: 100%;" class="d-flex align-items-center justify-content-center border text-bg-light p-1">
+			<img src="{{ url_for('photo_medium', photo_id=item.photo.id) }}" style="max-width: 100%; max-height: 100%;">
+		</a>
+		{% else %}
+		<div style="width: 100%; height: 100%; min-width: 390px; min-height: 300px;" class="d-flex align-items-center justify-content-center border text-bg-light"></div>
+		{% endif %}
 	</div>
 	<div class="col-12 col-md-8">
 		<h1>{{ item.name }}</h1>
@@ -74,4 +53,37 @@
 	</div>
 </div>
 
+<h2>Photos</h2>
+<table class="table table-hover">
+	<tbody>
+		{% for photo in item.photos %}
+		<tr>
+			<td style="width: 140px;">
+				<a href="{{ url_for('photo_show', photo_id=photo.id) }}" style="width: 100%; height: 100%;" class="d-flex align-items-center justify-content-center border text-bg-light">
+					<img src="{{ url_for('photo_small', photo_id=photo.id) }}" style="max-width: 100%; max-height: 100%;">
+				</a>
+			</td>
+			<td>
+				<p>Uploaded {{ photo.created.date() }}</p>
+			</td>
+			<td>
+				<div class="d-flex justify-content-end gap-1">
+					<a href="{{ url_for('item_delete_photo', item_id=item.id, photo_id=photo.id) }}" class="btn btn-danger">Delete</a>
+					{% if item.photo != photo %}
+					<form method="POST" action="{{ url_for('item_set_photo', item_id=item.id, photo_id=photo.id) }}">
+						<input type="hidden" name="csrf_token" value="{{ request.csrf_token }}">
+						<button type="submit" class="btn btn-primary">Make primary</button>
+					</form>
+					{% else %}
+					<form method="POST" action="{{ url_for('item_clear_photo', item_id=item.id) }}">
+						<input type="hidden" name="csrf_token" value="{{ request.csrf_token }}">
+						<button type="submit" class="btn btn-light">Clear primary</button>
+					</form>
+					{% endif %}
+				</div>
+			</td>
+		</tr>
+		{% endfor %}
+	</tbody>
+</table>
 {% endblock %}