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

BACKEND

Modules

Run dartvel add <source> to add a module. It can be a complete Dartvel app with its own pages, data models and backend, or a package wrapped automatically behind the same generated surface.

Its Dart can call a Rust crate or a C library over FFI, or an Android library over JNI. The parent mounts it, grants it and calls it the same way whatever is underneath. See /docs/native-access for the native binding graph and module-level capabilities.

ON THIS PAGE

Add a module in one command

Publish a signed module, and pin what you mount

Where a module can come from

Wrap a package from any ecosystem

Under the hood: native bindings and capabilities

Under the hood: mount configuration

Status

Add a module in one command

dartvel add ../store
dartvel add pub:slugify@^2.0.0
dartvel add npm:@acme/text-kit@^3.0.0
dartvel add cargo:vendor/fastmath
dartvel add swift:vendor/TextKit
dartvel modules list
dartvel build web-server

Copy code to clipboard

add resolves the source, generates a wrapper when needed, writes the mount and pins it in dartvel.module.lock. A Dartvel application is mounted directly. Build generates the client automatically; the parent calls DV.Modules.<id>.

Use --as to choose the module id, --mount to choose its mount path, and --dry-run to inspect the changes first. A bare directory holding C sources, a Cargo.toml, a Package.swift, a .wasm or a .jar file is detected and wrapped automatically. For an npm package, a Maven library or a plain Dart package directory, name the source as npm:, maven: or path: and it is wrapped the same way.

Planned

Not yet implemented: PyPI and Go packages, and .proto service definitions, as module sources. dartvel add refuses them with an explanation and writes nothing.

Publish a signed module, and pin what you mount

dartvel modules publish --key signing.key --key-id acme
dartvel modules pin
dartvel doctor --modules

Copy code to clipboard

publish reads the capabilities your code uses, such as the domains it calls, the secrets it reads and the native code it binds, and refuses when they differ from what you declared. Then it signs and publishes to pub.dev.

pin writes dartvel.module.lock with each module's version, digest, signing key and publisher. A changed key, a stripped signature or an older version stops the build.

The parent grants each module's capabilities under dartvel.modules. A module that uses more than it was granted is refused.

Where a module can come from

A module is also how capability from outside Dart reaches an application. Every capability a product needs already exists behind somebody's SDK, so one command resolves supported sources and what comes back is a module.

dartvel add ../store
dartvel add ./vendor_api --as vendorErp
dartvel add ./vendor_api --as vendorErp \
  --url https://api.vendor.com/graphql
dartvel add ./vendor_api --as vendorErp --dry-run

Copy code to clipboard

add reads the directory and says what it found. A pubspec with a dartvel: key is a Dartvel project and is mounted. C sources, a Cargo.toml, a Package.swift and a .wasm are wrapped, and an openapi.yaml or a GraphQL schema is generated into a module. A build.gradle or a package.json is named, with the maven:, jar: or npm: source to use instead. A directory matching none of them is refused with a list of what it holds (DV-MODULE-009).

A source that is already a Dartvel project is mounted directly. Nothing is wrapped, because wrapping a module that is already a module adds a layer whose only job is to be walked through.

An OpenAPI document or a GraphQL schema is generated into modules/<package>: a pubspec declaring the module and the host its calls go through, and a Dart library of typed methods. There is no binding and no foreign runtime, so a described API is the cheapest source there is.

A GraphQL schema names no server, so --url gives the endpoint to post to. It overrides an OpenAPI document's servers too, which is how a published document is used against staging without editing it.

Each GraphQL call gets its own result types. A class carries the fields that query selected and no others, so a field that is null is one the service answered null for. Where the graph turns back on itself the selection stops and the class says which type it returned to.

The host goes under dartvel.http, so the base URL, the credential, the retries and the timeout are configuration. The generated calls carry none of them.

--dry-run prints every file it would write and changes nothing. Everything is generated before anything is written, so a document add cannot read stops the command with an empty modules directory.

Packages from other ecosystems are wrapped: see the next section.

Wrap a package from any ecosystem

dartvel add turns a Dart package, an npm package, a C library, a Rust crate, a WebAssembly binary, a Maven artifact or jar, a Swift package or a CocoaPod into a module you call as DV.Modules.<id>. Each operation says what it does on a device, in a browser and on the backend, and the build checks your calls against it.

dartvel add pub:slugify@^2.0.0
dartvel add git:https://github.com/acme/textkit.git#v1.4.0
dartvel add npm:@acme/text-kit@^3.0.0
dartvel add vendor/mathkit          # C headers and sources
dartvel add cargo:vendor/fastmath   # or cargo:<crate>@<version>
dartvel add wasm:vendor/engine.wasm
dartvel add maven:com.acme:scanner@4.2.0 --class com.acme.Scanner
dartvel add swift:vendor/TextKit
dartvel add pod:CalcKit@^1.0.0
dartvel inspect modules

Copy code to clipboard

// dartvel add pub:slugify wrote modules/dv_slugify_module, and the
// parent calls it through the one surface it already has.
Future<void> publish(Article article) async {
  final String slug = DV.Modules.slugify.slugify(article.title);
  await article.copyWith(slug: slug).save();
}

Copy code to clipboard

Dart package

Device: the package

Browser: the package; one that needs dart:io runs on the backend over generated RPC

Backend: the package, unless it needs Flutter

npm package

Device: Node bundled beside a desktop app (Planned: phones)

Browser: dynamic import() of the bundled package

Backend: Node, one process per call

C library, Rust crate

Device: @Native, built by the module's hook

Browser: the same sources compiled to WebAssembly, for functions of numbers

Backend: @Native, built by the hook

WebAssembly binary

Device: Node bundled beside a desktop app (Planned: phones)

Browser: WebAssembly.instantiate

Backend: Node's WebAssembly

Maven artifact or jar

Device: JNI through package:jni on Android (Planned: other targets)

Browser: --elsewhere

Backend: --elsewhere

Swift package or pod

Device: C-ABI shim built by the hook: iOS and macOS, and Linux and Windows with the Swift toolchain unless it imports an Apple framework

Browser: --elsewhere

Backend: the Swift toolchain, for a package that imports no Apple framework

Every download is checked against what its registry publishes: pub.dev's sha256, npm's sha512 integrity, crates.io's sha256, Maven Central's sha1. git and pod sources are pinned to a commit.

The module exposes what can cross every environment it runs in: numbers, booleans, strings, lists and maps. What cannot is listed in the module's README with the reason: a callback, a generic, an instance method, or a pointer whose owner nobody named (DV-BIND-003).

Where a source cannot run but the backend can, an asynchronous call crosses to the backend, and the build asks who may make it: dartvel.modules.<id>.backendPolicy names a policy, or public (DV-MODULE-021 when it is missing), because the call runs with the server's authority.

--elsewhere unavailable makes a call from an environment the source cannot reach throw DVModuleUnavailable naming the module, the call and the environment, instead of crossing to the backend. --elsewhere noop makes it do nothing.

dartvel build refuses a call it can see reaching an unavailable operation (DV-MODULE-013), and a call from a target the module does not run on (DV-MODULE-014).

dartvel.module.lock pins the source, its digest, the hash of the generated module and the generator version. dartvel inspect modules reports a module edited by hand (DV-MODULE-016).

npm and WebAssembly calls on the backend need Node on the host (DV-MODULE-020 when it is missing); dartvel build copies Node into a desktop bundle that calls one. Compiling C for the browser needs clang and a wasm-ld (a Rust toolchain carries one), and a crate needs the wasm32-unknown-unknown target. Building a C library needs a C compiler, a crate needs cargo, and a Swift package or pod needs the appropriate Swift toolchain; Apple frameworks need Xcode.

Under the hood: native bindings and capabilities

The parent sees a module. Whether its Dart calls a REST API, a Rust crate over FFI or an Android library over JNI, it is mounted, granted, pinned and called the same way, and nothing in the parent names the language underneath.

modules/payments/
  pubspec.yaml       the module, and what it may reach
  hook/build.dart    runs cargo for the target being built
  rust/Cargo.toml    the crate, built as a cdylib
  lib/payments.dart  the Dart the parent calls

Copy code to clipboard

// modules/payments/lib/payments.dart
import 'dart:ffi';

// The crate's payments_fee, bound by its symbol. The module's build hook
// compiles the crate for the target being built and registers the library
// under this file's asset id, so no path or platform is written here.
@Native<Int64 Function(Int64)>(symbol: 'payments_fee')
external int _paymentsFee(int amountMinor);

/// The fee on [amountMinor], in the same minor units.
///
/// This is all the parent calls. It cannot tell a crate is underneath.
int paymentFee(int amountMinor) => _paymentsFee(amountMinor);

Copy code to clipboard

# modules/payments/pubspec.yaml
name: payments
dependencies:
  hooks: ^2.0.0
  code_assets: ^0.19.7
dartvel:
  module:
    id: payments
    capabilities:
      nativeBindings: true

Copy code to clipboard

# pubspec.yaml
dartvel:
  modules:
    payments:
      source:
        path: modules/payments
      mount: /payments
      deployment: embedded
      grant:
        nativeBindings: true

Copy code to clipboard

dartvel build reads the module's lib, bin and hook directories first. An import of dart:ffi or package:jni, a DynamicLibrary.open or an @Native function is a native binding, and one the parent did not grant stops the build with DV-MODULE-001.

The module says so too. Binding native code without declaring nativeBindings is a DV-MODULE-007 warning, and a grant that differs from what the module asks for stops the build with DV-MODULE-003. dartvel doctor --modules runs the same checks.

A module holding its own Cargo.toml is still a Dartvel project, so dartvel add ../payments mounts it directly and wraps nothing.

The crate is compiled by the module's build hook, which Dart runs for each target it builds and bundles with the app. Dartvel's own server is a Rust crate shipped this way: it lands in bundle/lib on Linux and in Contents/Frameworks on macOS.

An Android library is reached the same way over JNI: jnigen generates Dart classes for it and the module's surface calls them. Dartvel's own Android features under DV.Platform are bound like this, and nothing uses platform channels.

The web has no FFI. A C library or a Rust crate added with dartvel add is also compiled to WebAssembly for the browser, for functions of numbers. A module whose FFI surface you wrote yourself keeps those calls behind a conditional import.

What dartvel add does with a bare directory

Pointed at a directory with a Cargo.toml, C sources, a Package.swift or a .wasm file, it generates the module as cargo:, c:, swift: or wasm: would. A build.gradle or a package.json is named and refused: add the library as maven: or jar:, or the package as npm:.

Partial

Spec section: Native Binding Graph

Planned work and implementation limits

The binding graph itself: DVBindingGraph and its five binding kinds. A module you write yourself around a crate or an Android library still has a hand-written FFI or JNI surface.

Typed errors naming the module, the operation and the binding kind, and foreign callbacks turned into a Future or a Stream at the boundary.

An ownership annotation on every pointer that crosses, with an ambiguous one refused as DV-BIND-003.

dartvel inspect bindings --json, and DV-BIND-001 through DV-BIND-008.

Under the hood: mount configuration

# pubspec.yaml
dartvel:
  modules:
    notes:
      source:
        path: modules/notes
      mount: /notes
      deployment: embedded

Copy code to clipboard

The module's own /view/:id is served at /notes/view/:id. Its backend functions, schedules and AI tools join the parent's, and dartvel db migrate creates its tables.

The parent reaches it as DV.Modules.notes, with paths resolved against wherever it is mounted, so the module never names its own mount point.

Two functions on one path, or two models on one table, stop the build instead of one quietly winning.

dartvel modules list

Copy code to clipboard

Status

Partial

Spec section: Modules

Planned work and implementation limits

Tree-shaking per environment, so a module with a heavy native library costs a web build nothing until a web page calls it.

Ambient requirements: a module declaring the app lifecycle hooks, background work and push registration an SDK such as Firebase needs, wired into the target's entry points.

Partial

Spec section: Module Sources

Planned work and implementation limits

Node on phones, a WebAssembly runtime without Node, and strings across WebAssembly.

A .proto source, a Swift package with dependencies, the JVM outside Android, and a backend JVM carrier.

A component library exported with exports: components.

Planned

Spec section: Module Health

Partial

Spec section: Module Distribution and Trust

Planned work and implementation limits

Capabilities are checked at build time only. A running module's network and secret access are not checked yet.

Nothing calls pub.dev to verify the publisher, so without a registry lookup it is recorded as unverified.

PREVIOUS Billing and commerce Subscriptions, store purchases, tax and usage limits
NEXT Edge security Sign-in limits, WAF rules and query budgets
GitHub
pub.dev
npm
Acknowledgements
Privacy
Terms

FSL-1.1-MIT licensed. Built with Dartvel.

Dartvel is made by

SigmaDev Digital

To the bottom