Open app →
Documentation

Files, attachments & embeds

How to upload files and images, attach them to comments, notes, tasks, projects, and AI chats, and turn pasted links into rich iframe players or link cards.

Files, attachments and embeds

Work is not just text. You attach the design file, the spreadsheet, the screenshot that explains the bug. Elliptic gives you two ways to bring outside content into your workspace: file attachments, which upload a real file into your organization's private storage, and link embeds, which turn a pasted URL into a playable video, an interactive frame, or a rich preview card. Both are org-scoped, both keep their context, and both are available to your agents on the same surfaces, since agents operate Elliptic the same way your team does.

This page covers uploading files, attaching them to the things you work on, and embedding external links. Where it matters, it explains exactly what happens behind the scenes so you can reason about size limits, link lifetimes, and how self-hosted air-gapped mode changes embed behavior.

Uploading files

When you attach a file in Elliptic, the file does not pass through the application server. It goes straight from your browser to object storage over a temporary, signed URL. This keeps uploads fast and keeps large files off the API. The flow has three parts: reserve, upload, and confirm.

  1. Reserve
    Your browser calls presign-upload with the filename, content type, and the file size in bytes. The server validates that the content type is allowed and that the claimed size is within the limit, reserves a StoredObject record for the file, and hands back a presigned PUT URL valid for 15 minutes. The object is marked not-yet-uploaded at this point, with is_uploaded set to false.
  2. Upload
    Your browser sends the file directly to storage with a single PUT to that presigned URL, using the same content type. The bytes never touch the Elliptic API. If the PUT fails, nothing is attached and the reserved record stays not-yet-uploaded.
  3. Confirm
    Your browser calls confirm on the object. The server checks that the file actually landed in storage, reads back its real size, and re-validates the size limit server-side. If the landed file is over the limit it is deleted from storage and the upload is rejected. On success the object's true size and ETag are recorded and is_uploaded flips to true, which is what makes it downloadable and listable.
Why confirm re-checks the size
The size you send at reserve time is a claim from the browser. The presigned PUT is signed for a specific key, but a client could still push a larger file than it claimed. So confirm reads the object's real size back from storage and enforces the limit again. A file is never considered attached until it passes this server-side re-check.

Size and type limits

Uploads are size- and type-limited. The default maximum file size is 25 MB. The size limit is enforced in three places: the web client checks it before starting an upload and shows a clear error, the server checks the claimed size at reserve time, and the server re-checks the real size at confirm time.

Only an allowlist of content types may be uploaded. Out of the box, the allowed types are:

  • Images: PNG, JPEG, GIF, WebP, and SVG.
  • Documents: PDF, plain text, CSV, Markdown, and JSON.
  • Office files: Word (.doc and .docx), Excel (.xlsx), and PowerPoint (.pptx).
  • Archives: ZIP.

Anything uploaded with an image/* content type is recorded as an image, and everything else is recorded as a file. That distinction is what lets the interface show images as thumbnails and other files as document rows. A content type that is not on the allowlist is rejected at reserve time, before any upload begins.

Self-hosting note
The 25 MB ceiling and the allowed content type list are server settings (file_size_limit_bytes and allowed_upload_content_types). On the hosted instance at elliptic.sh they are the defaults above. If you self-host, you can adjust both in your API configuration. File storage must also be configured: if your instance has no object storage set up, uploads fail with "Object storage is not configured" and attachment features stay off.

Attaching anywhere

Every uploaded file belongs to your organization and is bound to one thing it is attached to. Elliptic uses a single polymorphic attachment model, so the same upload mechanism works across the product. A file can be attached to a:

  • Comment, so feedback can carry a screenshot or a document.
  • Note, alongside the embeds in the note's attachment panel.
  • Task, to keep a spec or asset next to the work.
  • Project, for files that belong to the whole workstream.
  • AI chat, so a conversation with your company brain can reference a file.
  • General, when a file is not tied to anything in particular yet.

Attachments are always scoped to your organization. A file uploaded in one org is never visible from another, and every list, download, and delete is checked against the org you are in.

Attaching on a note

Notes have a dedicated side panel for attachments and embeds. Use Add in the Attachments section to pick a file from your machine. It uploads with the three-step flow above, bound directly to the note, and appears in the attachment list when it finishes. The note panel enforces the same 25 MB limit on the client and shows a toast if you pick something larger.

Attaching on a comment

Comments attach files by binding already-uploaded objects. You upload first, which gives you object IDs, then you create the comment with those IDs in its attachment_ids. When the comment is created, Elliptic binds those objects to it. Binding only succeeds for objects you uploaded yourself, in the same org, that have finished uploading. This two-step pattern, upload then bind, is what lets you stage attachments before the comment exists.

Downloading

Files are private. You never get a permanent public link to them. Instead, when you open or download an attachment, Elliptic mints a fresh short-lived presigned download link that points straight at storage and expires in 5 minutes. Opening the same file later mints a new link. A download is only possible once the file has finished uploading, so a half-finished upload cannot be fetched.

Links expire on purpose
Because download links are signed and short-lived, do not paste one into a chat or a doc expecting it to keep working. It is good for a few minutes, not forever. To give a teammate access, point them at the attachment inside Elliptic, where their own org membership generates a fresh link for them.

Deleting

Deleting an attachment removes both the stored file and its record. The object is deleted from storage and the database row is removed, so it stops appearing in lists and can no longer be downloaded. Deletion is a member-level action, the same level required to upload.

Not everything needs to be uploaded. A lot of your context already lives at a URL: a Loom walkthrough, a Figma frame, a Google Doc, a GitHub pull request. Elliptic turns a pasted link into one of two things, depending on the provider: an iframe you can watch or interact with inline, or a link card with a title, description, and thumbnail.

Players and cards

Some providers are recognized by their URL pattern and rendered in an iframe. These are:

ProviderRenders asFrom a URL like
YouTubeInline video playeryoutube.com/watch?v=… or youtu.be/…
LoomInline video playerloom.com/share/… or loom.com/embed/…
VimeoInline video playervimeo.com/…
FigmaInline interactive framefigma.com/file/…, /design/…, or /proto/…

Other providers are rendered as a link card rather than an iframe. Elliptic recognizes Google Docs (docs.google.com), Notion (notion.so), Airtable (airtable.com), and GitHub (github.com) by their host, and any other http or https URL is treated as a generic link. For these, Elliptic fetches the page and reads its Open Graph tags to build the card: the og:title, og:description, and og:image become the card's title, description, and thumbnail. If a page has no Open Graph title, Elliptic falls back to its plain HTML <title>. When no preview metadata can be found, the card simply shows the provider name and a link out.

Iframes do not need a fetch
Iframe providers like YouTube, Loom, Vimeo, and Figma are resolved purely from the URL into an embed URL. Elliptic does not fetch the page to render them, so they work even when outbound page fetching is turned off. Open Graph cards are the only embeds that require fetching the target page.

Preview without saving

Before you commit an embed, you can preview what it will look like. The unfurl step resolves a URL's embed metadata, the provider, whether it is an iframe or a card, the iframe URL or the title, description, and thumbnail, without persisting anything. This is what powers the live preview as you paste. Only http and https URLs can be embedded, anything else is rejected. The page fetch during unfurl uses a short timeout, and if the fetch fails for any reason, the embed still resolves to a plain link card rather than erroring out.

Persisted note embeds

Notes can hold saved embeds. Paste a link into the Embeds section of a note's side panel and press Enter or Add, and Elliptic unfurls the URL and stores the result as a note embed with its metadata. Iframes render inline in a video-shaped frame, cards render with their thumbnail, title, and description and a link out to the original. The metadata is cached on the embed when it is created, so the note keeps showing the preview without re-fetching every time you open it. Remove an embed with the trash control on its card.

  1. Open the note panel
    In a note, find the Embeds section in the right-hand attachment panel.
  2. Paste a link
    Paste a YouTube, Figma, Loom, or any other link into the input. A YouTube, Loom, Vimeo, or Figma link becomes an iframe, a Google Doc or GitHub link becomes a card.
  3. Add it
    Press Enter or select Add. Elliptic unfurls the link, saves it to the note, and renders it inline.

Air-gapped mode disables outbound fetch

Self-hosted instances can run in a zero-egress air-gapped mode. When air-gapped mode is enabled, Elliptic does not make outbound requests to fetch external pages, so Open Graph unfurling is turned off. In that mode, link cards resolve from the URL alone: you still get the provider and a link out, but there is no fetched title, description, or thumbnail. Iframes are unaffected, because they are built from the URL and never required a fetch in the first place. The hosted instance at elliptic.sh is not air-gapped, so it fetches Open Graph metadata normally.

Embeds load third-party content
An iframe or a card thumbnail is content served by the provider, YouTube, Loom, Figma, and so on, not by Elliptic. If your organization needs to guarantee no requests leave your network, that is exactly what air-gapped mode is for. With it off, viewing an embed will reach the provider to play the video or load the thumbnail.

How it fits together

Attachments and embeds are deliberately different tools. An attachment is a real file you own, stored privately in your org, reached only through short-lived signed links. An embed is a reference to something that lives elsewhere, rendered as an iframe or a card. Reach for an attachment when the artifact should live in Elliptic, and an embed when it already has a home and you just want it visible in context.

  • Attachments are size- and type-limited, uploaded directly to storage, confirmed server-side, and downloadable only via 5-minute signed links.
  • Embeds are iframes (YouTube, Loom, Vimeo, Figma) or Open Graph cards (Google Docs, Notion, Airtable, GitHub, and any link), previewable before saving and persistable on notes.
  • Both are scoped to your organization, and both are surfaces your agents can use the same way your team does.