Dartvel, home
Docs
Features
Studio
Cloud
Compared

Search the site

GitHub
pub.dev

GETTING STARTED

Getting started
Existing Flutter apps
Existing Native apps
Run on your phone

APP

UI and styling
Routing
State
Accessibility
Keyboard shortcuts
Localization
Devices and desktop
Native device access
Media, 3D and XR

DATA

Data models
Forms
Search
Sync and offline
Import and export
Change capture
Database
Cache
File storage
Images
Privacy and erasure

BACKEND

Backend functions
Auth and sessions
Authorization
Queues and jobs
Workers and memory
Notifications and mail
Outbound HTTP
AI
Webhooks
GraphQL and OpenAPI
API keys and OAuth
Multi-tenancy
Billing and commerce
Modules

OPERATIONS

Edge security
Secrets and environments
Monitoring
Releases

SHIPPING

Build targets
Telegram Mini Apps
Static web hosting
Servers and deploying

REFERENCE

Testing
CLI reference
Coding agents

DATA

Privacy and erasure

Answer an export or deletion request with one call, across every model that holds the person's data.

Declare on each model whose data it is and how long to keep it.

ON THIS PAGE

Declare the subject and retention

Export and erase a person's data

Honour a browser that says no to tracking

Do it from the CLI

Status

Declare the subject and retention

@DVModel(
  subject: DVSubject.self,
  retain: DVRetention.days(730, from: 'lastOrderAt', then: DVRetention.anonymize),
)
class const _Customer({
  required final String id,
  required final String name,
  required final String lastOrderAt,
  @DVModel.sensitiveField(onErase: DVErase.anonymize)
  required final String email,
});

Copy code to clipboard

DVSubject.self

Means: The row is the person

DVSubject.field('authorId')

Means: A column holds the person's id

DVSubject.through('orderId', parent: 'Order')

Means: The row belongs to a row that belongs to the person

retain is DVRetention.days(n, from:, then:) or DVRetention.indefinite.

A model with personal data and no retention gets a DV-PRIVACY-002 warning.

Export and erase a person's data

final DVExportArchive archive = await DV.Privacy.export(subject: 'user-1042');

final DVErasureResult result = await DV.Privacy.erase(
  subject: 'user-1042',
  reason: 'Deletion request from the account page',
);

Copy code to clipboard

erase deletes rows, or keeps and anonymizes the ones a retention holds.

The result lists what was deleted, anonymized and kept, with a receipt.

It needs a database and DARTVEL_PRIVACY_KEY, 32 bytes or more as hex or base64.

Honour a browser that says no to tracking

Every generated route reads Sec-GPC before the handler runs, so a consent category declared tracking: true is already denied while the header is in force.

dvPrivacyOptOut is for what the application decides on top: what it personalises, and what it hands to somebody else.

It is a signal from the reader, so a page that ignores it is making a choice, and the log line says which was served.

@DVBackendFunction()
Future<Map<String, Object?>> _feed() async {
  // The reader's browser sent `Sec-GPC: 1`, and every generated route reads
  // it before the handler runs. Consent categories declared `tracking: true`
  // are already denied while it is in force, so nothing here has to remember
  // to check that. This is for what the application decides on top: what it
  // personalises, and what it passes on to somebody else.
  if (dvPrivacyOptOut) {
    DV.log('Serving the unpersonalised feed', code: 'GPC');
    return <String, Object?>{'items': await popular(), 'personalised': false};
  }
  return <String, Object?>{'items': await recommended(), 'personalised': true};
}

Copy code to clipboard

Do it from the CLI

dartvel privacy check
dartvel privacy export --subject user:1042 --out user-1042.json
dartvel privacy erase --subject user:1042 --reason "account closed"
dartvel privacy retention --plan

Copy code to clipboard

The commands read DATABASE_URL, or the SQLite file dartvel.database names. Erasing also needs DARTVEL_PRIVACY_KEY.

erase asks you to type yes at a terminal. In CI, or anywhere nobody can answer, pass --yes. It refuses before deleting anything when a table it reaches is missing.

export will not replace a file already at --out unless you pass --force.

retention only runs with --plan, which changes nothing. The sweep itself runs as a job.

Status

Partial

Spec section: Data Compliance and Lifecycle

Planned work and implementation limits

Retention sweeps run on a fixed schedule.

Export is a single JSON file and does not decrypt encrypted fields.

File storage, cache and crash data are not reached.

PREVIOUS Images Resized image variants for web builds
NEXT Backend functions A function in lib/backend is an endpoint
GitHub
pub.dev
npm
Acknowledgements
Privacy
Terms

FSL-1.1-MIT licensed. Built with Dartvel.

Dartvel is made by

SigmaDev Digital

To the bottom