You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add first-party image uploads to the Doenet website so instructors can upload and embed images directly into documents without relying on third-party image hosting.
This RFC keeps the existing image markup syntax unchanged. Documents will continue to embed images by URL. The new work is to add a platform-managed image asset system that creates, stores, serves, and governs those URLs.
Motivation
Today, users who want to include images in documents must:
Upload the image to an external image hosting service
Copy the hosted URL
Insert that URL into the document markup
This workflow is slow, confusing, and brittle. It creates several problems:
high friction for authors
broken content when external URLs disappear
no way to manage uploaded images alongside other user content
A first-party upload system would make authoring easier and allow us to manage images as part of the platform.
Goals
Allow users to upload image assets directly from the editor
Keep the existing URL-based image markup unchanged
Store uploaded images in platform-managed storage
Insert stable platform media URLs into documents automatically
Represent uploaded images as items in the user's existing folder structure
Ensure embedded images remain readable wherever the containing document is readable
Add operational controls for image size, quota usage, upload entitlement, and cost visibility
Non-Goals
Redesign the document markup language
Build a full media management system beyond basic file-tree integration
Support image editing, cropping, or annotation
Guarantee cross-user deduplication in the initial version
Migrate historical externally hosted images
Support arbitrary file uploads beyond images
Background
When a user uploads an image, the platform will:
Create an image asset record
Store the binary in managed storage
Generate a stable platform URL
Insert that URL into the document
Show the image as a file-tree item in the user's content space
Proposal
1. Asset Model
Each uploaded image creates an image asset record in the application database.
Each asset should include at least:
asset UUID
owner user ID
filename
mime type
width and height
stored byte size
content hash
storage key/location
created at / updated at timestamps
upload status / processing status
The canonical identifier for an image asset is a platform UUID, the same kind of UUID that we use for other forms of content.
We will also store a content hash for integrity checks and future deduplication work, but the content hash is not the primary asset identifier.
2. Storage and Serving
Image binaries will be stored in S3.
Images should not be embedded into documents using raw S3 URLs. Instead, documents should use stable platform media URLs controlled by the application. This gives us flexibility to change storage strategy later and allows permission enforcement, logging, and delivery controls.
Example shape: https://doenet.org/media/<asset-uuid>
The exact URL shape is implementation detail, but it should be stable and asset-ID-based rather than path-based.
3. Document Embedding
The existing image markup syntax remains unchanged.
When a user uploads an image through the editor:
The upload is sent to the platform
The platform creates and processes the image asset
The editor inserts the resulting platform media URL into the document markup
This preserves compatibility with the current document model while shifting asset hosting under platform control.
4. File Tree UX
Uploaded images should appear as first-class items in the user's existing folder structure.
Initial behavior:
each uploaded image appears as its own item line in the file tree
users may later move or rename the image item
Asset references in documents must remain stable even if the image is moved or renamed in the file tree. That means the inserted document URL must reference stable asset identity, not a mutable folder path.
We will create images under a user's currently selected folder (same as other content items).
5. Permissions and Access Changes
Permissions are the most important behavioral rule in this RFC.
The system should follow this principle: If a user can view a document, they must be able to view any image embedded in that document.
This means image readability must remain consistent with document readability, including when document access changes over time.
Implications:
private document -> embedded image readable only in the context of authorized document access
shared/public document -> embedded image readable to viewers of that document
when a document's visibility changes, image access must continue to behave correctly
We should treat uploaded images as user-owned assets that may be referenced by documents. However, once an image is embedded in a document, the platform must ensure that authorized viewers of that document can fetch the image.
This does not necessarily mean the image becomes globally public. The enforcement mechanism may be authenticated media requests, signed URLs, or another serving-layer approach. The important part is the user-visible rule: document viewers can view embedded images.
6. Upload Eligibility
Image upload should be controlled by a per-user flag: can_upload_images
Behavior:
defaults to false
users without the flag cannot upload images through the UI or API
UI should hide or disable upload affordances when the flag is false
server-side enforcement is mandatory regardless of UI behavior
This allows gradual rollout, support-controlled enablement, and abuse mitigation.
Note that users with can_upload_images = false can still use third-party hosted images in their documents.
7. Image Processing and Sizing
We should not store and serve arbitrarily large images without processing.
The platform should normalize uploaded images to platform-defined limits suitable for document rendering.
Initial policy:
allow common web image formats: JPEG, PNG, WebP, GIF
reject unsupported or unsafe formats
optionally defer SVG support until its safety model is clear
enforce a maximum upload file size
enforce a maximum image dimension
downscale oversized uploads
compress and/or transcode to optimized storage/delivery formats as needed
Recommended starting constraints:
max upload size: 10 MB
max dimensions: 2560 x 2560
Exact thresholds can be adjusted before implementation, but the RFC should establish the principle that uploads are normalized for platform use rather than preserved as archival originals.
The platform does not guarantee exact binary preservation of the uploaded file.
8. Quotas
Each user should have a storage quota for uploaded image assets.
Behavior:
uploads that would exceed the user's quota are rejected
quota is charged to the uploading user
quota usage is based on platform-defined stored asset size
deleting an image releases quota only when the asset is no longer retained by the system
Recommended starting approach:
use processed stored size, not original upload size, for quota accounting
do not charge users separately for internal derivative variants in V1 unless that becomes materially important
This keeps the quota model simple and understandable.
9. Deduplication
This RFC does not require deduplication in V1. We will store a content hash on upload so that future work can support:
duplicate detection
backend blob reuse
integrity verification
However, we should not block the initial feature on full deduplication. Dedupe introduces complexity around ownership, deletion, quota accounting, and auditability.
If backend dedupe is added later, user quota accounting should remain logical and product-facing, not tied to whether multiple users happen to share identical binary content.
10. Cost Controls and Observability
This feature introduces storage, bandwidth, and processing costs. We should not launch it broadly without cost controls and visibility.
The initial system should include:
per-user upload entitlement flag
per-user storage quota
upload size limits
dimension limits and downscaling
restricted file type allowlist
We should also capture metrics for:
total uploaded bytes
total stored bytes
per-user storage usage
upload count per user
media egress volume
image processing job count and failure rate
highest storage-consuming users
highest egress-consuming assets or users if feasible
These metrics are necessary to evaluate rollout safety and long-term cost.
Data Model Notes
A likely separation of concerns is:
database record for logical image asset
object storage blob for binary content
application/media layer for serving and permission enforcement
This allows us to evolve storage, serving, and processing independently without changing the document model.
Rollout Plan
Phase 1: Controlled Internal Rollout
add asset model and media storage
add upload entitlement flag
add quota tracking and enforcement
add image processing and size limits
add stable media URLs
add editor upload flow
add file-tree integration
enable for a limited set of users
Phase 2: Broader Rollout
monitor usage, egress, and storage cost
adjust limits and quotas as needed
refine permission behavior under real document-sharing workflows
expand enablement cohort
Phase 3: Follow-Up Enhancements
Possible future work:
optional deduplication
better media management UX
drag-and-drop or paste uploads
orphan cleanup policies
richer admin controls and reporting
Alternatives Considered
1. Keep external image hosting only
Pros:
no storage or bandwidth cost
no media system to build
Cons:
poor user experience
broken images remain common
no control over reliability or permissions
2. Use raw S3 URLs in documents
Pros:
simpler implementation
Cons:
weak control over permissions
harder to change storage strategy later
more coupling between document content and storage backend
poor observability and policy enforcement
3. Use IPFS hash as the primary asset identifier
Pros:
content-addressed identity
natural support for dedupe semantics
Cons:
adds operational complexity
poor fit for mutable permissions and application ownership rules
increases implementation scope without clear user benefit for V1
This RFC proposes platform UUID as the canonical identifier, with a content hash stored as metadata.
4. Require deduplication in V1
Pros:
potentially lower storage cost
Cons:
complicates ownership and deletion semantics
complicates quota accounting
unnecessary for the first usable version
Risks
permission bugs could expose assets too broadly or block legitimate viewers
storage and bandwidth costs could grow faster than expected
image processing rules may be too aggressive or too conservative
users may be confused if uploads are resized or compressed unexpectedly
folder/file UX may create expectations about sharing semantics that differ from actual asset access rules
Closed Questions
Should new uploads always go under My Activities, or under the user's current folder context? Under current folder
Should SVG be supported in the initial rollout? No
Do we keep original uploads after processing, or only normalized stored versions? Only normalized versions
Open Questions
What default user quota should we start with?
What is the exact serving model for private/shared documents: authenticated media requests, signed URLs, or another approach?
What is the deletion policy when an image is still referenced by one or more documents?
Should users be able to reuse the same uploaded image across multiple documents immediately, or should we constrain this initially?
Cool, this is a great outline of the major issues.
My take on the open questions
I'd expect that uploading and image should behave just like creating a new document, i.e., get added in the current folder.
SVG support eventually, though not necessary for initial rollout. When we do add it, check out the SVG sanitation used in the prefigure renderer on DoenetML. We had to address the same security issues there.
I don't think we should keep the originals.
If we started with a quota of 1GB/users, that means our cost per user would be less than 2.3 cents/month, less than 30 cents/year. When we get to 10,000 users all with full quotas, it'll be noticeable. By that point, I'd hope we have the resources to manage that cost. 1 GB is something like 100-200 images, and we could always up the quota for users who are sharing a lot of good content.
I'd say use whatever approach for private/shared document is easiest with our current authentication system.
It makes sense to disallow deletion while any documents of that user reference the image, though the error message should indicate the documents so the user would know what to do if they really wanted to delete it.
We don't have a way to prevent use of a single image across multiple documents, as it is just a URL that can be copied and pasted. And, we want to be able to remix documents freely.
On remixing: when person B copies an activity of person A, we do not want to change the URL for the image or coordinating changes between the copies would be a nightmare. This means, the image counts against the quota of person A not person B, which makes sense. Person A, however, should not be prevented from deleting the image to save space even if person B has a document using the image. Does the image then go away and person B's document stops working? Long term, it'd be better if we kept the image working for person B, but then we should track how many such "free" images we are storing to prevent abuse.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Summary
Add first-party image uploads to the Doenet website so instructors can upload and embed images directly into documents without relying on third-party image hosting.
This RFC keeps the existing image markup syntax unchanged. Documents will continue to embed images by URL. The new work is to add a platform-managed image asset system that creates, stores, serves, and governs those URLs.
Motivation
Today, users who want to include images in documents must:
This workflow is slow, confusing, and brittle. It creates several problems:
A first-party upload system would make authoring easier and allow us to manage images as part of the platform.
Goals
Non-Goals
Background
When a user uploads an image, the platform will:
Proposal
1. Asset Model
Each uploaded image creates an image asset record in the application database.
Each asset should include at least:
The canonical identifier for an image asset is a platform UUID, the same kind of UUID that we use for other forms of content.
We will also store a content hash for integrity checks and future deduplication work, but the content hash is not the primary asset identifier.
2. Storage and Serving
Image binaries will be stored in S3.
Images should not be embedded into documents using raw S3 URLs. Instead, documents should use stable platform media URLs controlled by the application. This gives us flexibility to change storage strategy later and allows permission enforcement, logging, and delivery controls.
Example shape:
https://doenet.org/media/<asset-uuid>The exact URL shape is implementation detail, but it should be stable and asset-ID-based rather than path-based.
3. Document Embedding
The existing image markup syntax remains unchanged.
When a user uploads an image through the editor:
This preserves compatibility with the current document model while shifting asset hosting under platform control.
4. File Tree UX
Uploaded images should appear as first-class items in the user's existing folder structure.
Initial behavior:
Asset references in documents must remain stable even if the image is moved or renamed in the file tree. That means the inserted document URL must reference stable asset identity, not a mutable folder path.
We will create images under a user's currently selected folder (same as other content items).
5. Permissions and Access Changes
Permissions are the most important behavioral rule in this RFC.
The system should follow this principle:
If a user can view a document, they must be able to view any image embedded in that document.This means image readability must remain consistent with document readability, including when document access changes over time.
Implications:
We should treat uploaded images as user-owned assets that may be referenced by documents. However, once an image is embedded in a document, the platform must ensure that authorized viewers of that document can fetch the image.
This does not necessarily mean the image becomes globally public. The enforcement mechanism may be authenticated media requests, signed URLs, or another serving-layer approach. The important part is the user-visible rule: document viewers can view embedded images.
6. Upload Eligibility
Image upload should be controlled by a per-user flag:
can_upload_imagesBehavior:
This allows gradual rollout, support-controlled enablement, and abuse mitigation.
Note that users with
can_upload_images = falsecan still use third-party hosted images in their documents.7. Image Processing and Sizing
We should not store and serve arbitrarily large images without processing.
The platform should normalize uploaded images to platform-defined limits suitable for document rendering.
Initial policy:
Recommended starting constraints:
Exact thresholds can be adjusted before implementation, but the RFC should establish the principle that uploads are normalized for platform use rather than preserved as archival originals.
The platform does not guarantee exact binary preservation of the uploaded file.
8. Quotas
Each user should have a storage quota for uploaded image assets.
Behavior:
Recommended starting approach:
This keeps the quota model simple and understandable.
9. Deduplication
This RFC does not require deduplication in V1. We will store a content hash on upload so that future work can support:
However, we should not block the initial feature on full deduplication. Dedupe introduces complexity around ownership, deletion, quota accounting, and auditability.
If backend dedupe is added later, user quota accounting should remain logical and product-facing, not tied to whether multiple users happen to share identical binary content.
10. Cost Controls and Observability
This feature introduces storage, bandwidth, and processing costs. We should not launch it broadly without cost controls and visibility.
The initial system should include:
We should also capture metrics for:
These metrics are necessary to evaluate rollout safety and long-term cost.
Data Model Notes
A likely separation of concerns is:
This allows us to evolve storage, serving, and processing independently without changing the document model.
Rollout Plan
Phase 1: Controlled Internal Rollout
Phase 2: Broader Rollout
Phase 3: Follow-Up Enhancements
Possible future work:
Alternatives Considered
1. Keep external image hosting only
Pros:
Cons:
2. Use raw S3 URLs in documents
Pros:
Cons:
3. Use IPFS hash as the primary asset identifier
Pros:
Cons:
This RFC proposes platform UUID as the canonical identifier, with a content hash stored as metadata.
4. Require deduplication in V1
Pros:
Cons:
Risks
Closed Questions
Open Questions
All reactions