Skip to content

Attachments

Uploading and downloading files attached to an entity — a front-end concern layered on top of the basic CRUD slice.

The shape

Attachments are themselves a small entity (entity-attachments), referenced as a child collection on the host model. The host carries an optional array; soft-deleted rows are dropped before save:

ts
import type { Entity as EntityAttachment } from "../../entity-attachments"

class Vehicle extends EntityBase {
    attachments?: Array<EntityAttachment>
    // …
}

A file is a Blob; an attachment wraps one. The entity-attachments module exports the helpers that turn files into savable attachments and round-trip them with the host (createEntity, useEntityAttachments, insertWithAttachments, updateWithAttachments, download) plus the reusable Overview component (imported as EntityAttachments) and the model Entity (imported as EntityAttachment).

The attachment service

A host that owns files uses the "with attachments" service variant: it overrides insert / update to round-trip files alongside the record. setup.ts needs nothing — the helpers upload through useAxios(), so the service keeps its plain AxiosInstance constructor and the default registration stands. The shape:

ts
export class EntityService extends EntityServiceBase<Entity> {
    constructor(axios: AxiosInstance, config: IConfig) {
        super(axios, config)
    }

    override insert(item) {
        // the follow-up update callback sends the attachments in display order — the server assigns SortOrder from array position
        return insertWithAttachments(
            this.config.api,
            item,
            () => super.insert(item),
            (saved) => super.update(saved)
        )
    }
    override update(item) {
        return updateWithAttachments(this.config.api, item, () => super.update(item))
    }
}

It is only when you add file endpoints of your own — e.g. the advanced example's optional getAttachments(so?) (GET {api}/attachments) and addAttachment(itemId, file) (POST {api}/{id}/files), which call this.axios.upload / getFile — that the constructor takes an AxiosWithFilesInstance (the file-aware axios from vue/http) and setup.ts has to resolve one.

The base prepareItem only strips top-level _-prefixed keys — it does not filter soft-deleted children, so override it to drop _deleted attachments (and other owned rows) before save — see services.md for prepareItem / processItem.

Upload / download UI — file fields

In the form, a dedicated files tab binds the host's attachments array to the shared EntityAttachments overview (imported from entity-attachments):

html
<EntityAttachments v-model="item.attachments" :readonly="readonly" />

That component owns the add/remove/preview interactions; you do not hand-roll the file <input>. Wire it into a tab alongside the main form.

Download URLs

Downloads go through the file-aware axios from vue/http, not a bare <a href>: AxiosWithFilesInstance exposes getFile(url, method?, filename?, type?) (returns a Blob) and upload(url, files, options?). Resolve the instance with useAxios() and build the URL from config.api (e.g. {api}/{id}/files).

When you need it

Only complex entities that own files. The simple/standard slices have none of the above — no file fields and the default insert/update. Reach for the attachments variant only when the entity actually carries files, and keep everything else identical to those slices.

Overview

  1. Abstractions
  2. Services
  3. Config
  4. Views
  5. Built-in features
  6. Attachments
  7. Checklist