The .n8np format in n8n 2.x: packaging and migrating multiple workflows in a single file
Published 27 August 2026 · 5 min read
Until now, migrating several related n8n workflows from one instance to another meant exporting them one by one (see our JSON import/export guide), manually recreating every credential, and hoping nothing got missed. n8n 2.x introduces a format built for exactly this: the .n8np package, which bundles multiple workflows, their credential dependencies, and a manifest into a single portable file. This guide covers its structure, the n8n-cli commands to create and import a package, and the pitfalls to know — including secret handling, a topic that Michael Meli, Matthew McNiece, and Bradley Reaves's study presented at NDSS in 2019 ("How Bad Can It Git? Characterizing Secret Leakage in Public GitHub Repositories", see on Google Scholar) quantified at over 100,000 affected GitHub repositories and thousands of newly leaked secrets every day — exactly the kind of leak the .n8np package design tries to make structurally impossible.
The problem .n8np solves
Classic JSON export — one file, one workflow — works fine for sharing a standalone template. It quickly hits its limits once a use case relies on several interdependent workflows: a main flow and its sub-workflows called via Execute Workflow (see our guide on breaking complex workflows into sub-workflows), a trigger and the processing workflow it fires, or a coherent bundle like the ones FlowKit sells (email triage, prioritization, digest, and drafts for the Inbox AI Pack, for instance). Without a package structure, you either export each file separately and reimport them in the right order, or bundle everything by hand into a homemade archive and hope nothing gets forgotten — credential dependencies included. .n8np formalizes that operation.
Anatomy of a .n8np file
Technically, a .n8np package is an uncompressed tar archive: the extension is just a naming convention — n8n doesn't check the file name on import and accepts any tar following the same layout. Extracting one reveals:
- a
manifest.jsonat the root, indexing the package's contents, which must be the very first entry in the archive — that's what lets n8n validate a package before reading the rest of it; - a
workflows/directory where each workflow is its own small JSON file, in its own subdirectory; - the credential stubs referenced by those workflows (more on this below).
That granularity — one entity, one file — makes a package's contents readable and diffable in Git, unlike a monolithic export.
Creating a package with n8n-cli
On a self-hosted instance, the export command takes either a list of specific workflows or an entire project:
# Package one or more specific workflows
n8n-cli package export --workflow-id=<id-1> --output=export.n8np
# Package every workflow in a project
n8n-cli package export --project-id=<project-id> --output=project.n8np
--output can be omitted (it defaults to export.n8np). This ID-based approach makes it automatable: a scheduled job that regenerates a package on every change, a build step that produces a deployment artifact, or — a direct use case for anyone white-labeling workflows to clients — a script that packages the current version of a bundle before each delivery.
Importing a package into another instance
Importing forces a decision that the old JSON export/import left implicit: what happens if a workflow with the same source ID already exists on the target?
n8n-cli package import --file=export.n8np --workflow-conflict-policy=fail
n8n-cli package import --file=export.n8np \
--project-id=<project-id> --workflow-conflict-policy=skip
--workflow-conflict-policy is required and accepts three values: fail (the import stops — the safest choice for a first migration), skip (workflows that already exist are ignored), and new-version (a new version of the existing workflow is created — useful for a controlled redeploy; see our guide on workflow history and version restoration). This explicit flag replaces the silent overwrite-by-ID behavior that could catch people off guard with the classic export/import CLI.
Credential stubs: never a secret inside the file
Just like standard JSON export, a .n8np package never embeds a credential's value. It exports a stub — ID, name, and type — that the target instance has to attach to a real credential. Two modes handle this resolution on import:
create-stub(the default): n8n creates an empty, placeholder credential in the target project. The workflow imports, but stays unpublishable until the credential is filled in manually — a safeguard against a workflow silently running without the right API keys.must-preexist: the import fails if a referenced credential doesn't already exist on the target instance — the strictest choice for deploying into an already-provisioned production environment (see our guide on dev/prod environments).
To avoid manually recreating every credential one by one, an explicit remap can be passed as a JSON object mapping package credential IDs to target IDs:
{"credentials": {"<package-credential-id>": "<target-credential-id>"}}
This stub-instead-of-value approach is exactly the best practice Meli and his co-authors call for: across the GitHub repositories they analyzed, secret leakage almost always came from a configuration or export file committed as-is, with keys in plain text. A .n8np package versioned in Git can't structurally reproduce that risk, as long as nobody swaps a stub for a real secret at the last minute. To further harden access management, see our guide on securing n8n credentials and the API.
The publish/unpublish trap (n8n 2.0)
n8n 2.0 replaced the simple active/inactive toggle for workflows with a publish/unpublish model, driven from the CLI via publish:workflow and unpublish:workflow. A workflow imported from a package isn't automatically live in production, then: depending on credential state (an unresolved stub or not), it can stay stuck in an unpublishable state. Always check the publish state after an import — an apparently "successful" import that isn't actually running yet is the classic trap of any migration, as our guide on evaluating and testing workflows points out.
.n8np, plain JSON export, or Git: when to use what
- Plain JSON export: sharing a standalone workflow, a template, one-off troubleshooting — see the complete import/export guide.
- Git + classic CLI (
export:workflow --separate): a versioned source of truth, full history, code review on every change — the recommended strategy for versioning your workflows. .n8nppackage: moving a coherent set of workflows between instances in a single operation — spinning up a new environment, delivering a bundle to a client, migrating between self-hosted instances, after a major upgrade.
The three combine well: a .n8np package generated from a build pipeline is still perfectly versionable in Git alongside it, as a release artifact rather than a day-to-day source of truth.
Distributing workflows across a team: the right instinct
If your organization maintains several related workflows — a main flow and its sub-workflows, a trigger and its processing, a coherent bundle much like a pack — the .n8np format avoids file-by-file export/import and manually resyncing credentials on every migration. That's exactly the problem our FlowKit packs solve at the product level: several linked workflows, one installation guide, credentials clearly identified per node. .n8np brings the same logic to n8n's native tooling, for your own internal workflows.
FAQ
Frequently asked questions
Does the .n8np format work on n8n Cloud?
The n8n-cli package export/import commands run from the command line against an instance: this is a self-hosted feature. On n8n Cloud, stick to the classic JSON export/import from the UI or the REST API to move workflows between projects or instances.
Does a .n8np file contain my API keys?
No. Just like standard JSON export, only credential stubs (ID, name, type) are included — never the secret value. On import, you either let n8n create an empty credential to fill in (create-stub), require the credential to already exist on the target (must-preexist), or explicitly remap to an existing credential.
What happens if the workflow already exists on the target instance?
The --workflow-conflict-policy flag decides: fail stops the import (the safest default), skip ignores workflows that already exist, and new-version creates a new version of the existing workflow instead of silently overwriting by ID.
Why doesn't my imported workflow run even though the import succeeded?
Two classic causes: a credential imported in create-stub mode that was left empty, or n8n 2.0's new publish/unpublish model, where an imported workflow isn't automatically published. Always check credentials and publish state after an import before considering the migration done.
Bundle FlowKit Complet
€269