Getting started
Link2Label lets you design labels, drop in text and codes from your inventory app, and print. You can start from scratch, from a ready-made template, or from data someone sends you.
Home screen tabs
- New — pick a size and draw a blank label.
- Saved — labels you already made. Open, duplicate, export, or delete.
- Templates — reusable layouts. Fill them with data or copy and customize them.
Quick paths
- Blank label: New tab → pick a size → design → print or export.
- From a link or file: tap a link, paste JSON, pick a file, or import a photo with embedded data.
- From a template: see the two template workflows below.
Working with templates
Copy an included template and edit it
Included templates ship with the app. They are a good starting point if you want your own layout without building from zero.
- Open the Templates tab.
- Scroll to Included templates at the bottom.
- Tap the copy icon on the template you want.
- A copy appears under Your templates (named like "Strip QR Title copy").
- Tap the copy to open it in the template editor.
- Change text placeholders, QR layout, fonts, or add images and shapes as you like.
- Your copy is yours to keep. The original included template stays unchanged.
Fill a template, save as a label, then edit freely
Use this when you want one finished label, not a reusable template. After you save, the label lives under Saved and you can change anything without touching the template.
- Open the Templates tab.
- Tap the tags (fill) icon on a template, or use Import item data to load JSON first.
- Fill in the fields in the form, or let imported data fill them for you.
- Review the preview (fill-review screen).
- Tap Save (disk icon in the header). The label is stored under Saved.
- Open it from the Saved tab. It is a normal label now: edit text, move objects, add stamps, then print.
You can also fill from item data (paste, JSON file, link, or photo) and save the result the same way from fill-review.
Header buttons
- Menu (top left) — settings, printer setup, help, ingress link tool.
- Import item data — paste JSON or pick a
.jsonfile. - Import image — pick a photo. If it has embedded item data, labels are filled automatically. Otherwise you get a simple photo label.
- Plus — new label or new template, depending on the tab.
Label editor
The canvas shows your label at real print size. Objects snap to a grid. Undo and redo are in the header.
Tools (bottom strip)
- Select — move, resize, rotate.
- Draw — freehand lines.
- Insert — text, text box, lists, images, QR, barcode, shapes, frames, SVG, dates, stamps, map markers.
- Shapes — rectangles, circles, lines, arrows.
- Stamps — decorative icons.
When something is selected
A toolbar appears for alignment, delete, and type-specific options (font, QR content, list layout, map marker link, and so on).
Header
- Undo / redo.
- Print.
- Overflow (⋯) — settings, export PNG/PDF, save, duplicate, delete.
Two modes
- Label — a one-off design under Saved.
- Template — a reusable layout with placeholders and data mappings under Templates.
Grid spacing and units (metric/imperial) are in Menu → Settings.
Printing
On iPhone and Android you can print to Bluetooth label printers. If that fails or you are on web, the normal system print dialog is used.
Supported printers
| Family | Examples | Paper |
|---|---|---|
| Marklife | P12 and similar (name starts with P12_) |
Continuous roll |
| Cat-Printer | GB01, GB02, GB03, GT01, MX05–MX10, YT01 | Portrait sticker |
Unknown printer? Add it as generic Marklife or Cat-Printer in Printer settings and tune options.
Setup
- Menu → Settings → Printer settings.
- Scan and connect your printer.
- Set it as default if you like (also from the print sheet).
What happens when you print
- Bluetooth printer ready → prints directly.
- Bluetooth fails → falls back to system print dialog.
- No Bluetooth printer → system print dialog.
- Web app → system print dialog only (no Bluetooth in browser).
Auto-print from item data
If JSON includes "action": "fill_and_print", the app fills and prints without an extra tap. Large batches ask for confirmation first (threshold in Settings → Template ingress).
Platforms
| Platform | Editor | BLE print | Share extension | Deep links | EXIF import |
|---|---|---|---|---|---|
| iOS | Yes | Yes | Yes | link2label:// |
Yes |
| Android | Yes | Yes | Yes (intent) | link2label:// |
Yes |
| Web | Yes | No | No | HTTPS /ingress/… |
Limited |
Native share-to-Link2Label (text, URLs, JSON) requires a store build with the share extension. It is disabled in Expo Go.
Templates
Templates are layouts you use again and again. Placeholders (like {title} or a QR slot) get filled from item data or from a manual form.
Included vs yours
- Included templates — built into the app, listed at the bottom of the Templates tab. Good defaults; also used by Containd.
- Your templates — ones you created or copied. You can rename, duplicate, export, and delete them.
Tip: copy an included template first if you want your own version without changing the original. See Getting started for step-by-step.
Editing an included template directly changes that included copy (Containd still finds it by catalog ID). Use Reset to default (refresh icon) to undo your edits to an included template.
Included catalog IDs
| Catalog ID | Use case |
|---|---|
containd-box-label | Box/container labels with content tree (Containd preset: box-label) |
containd-strip-label | Compact strip labels (Containd preset: strip-label) |
strip-qr-title-01-template | Strip with title and QR |
strip-qr-only-01-template | Strip with QR only |
strip-barcode-01-template | Strip with barcode |
sticker-qr-title-01-template | Sticker with title and QR |
sticker-qr-only-01-template | Sticker with QR only |
full-box-sticker-01-template | Full box sticker layout |
image-full-box-01-template | Full-page image label |
image-qr-sticker-01-template | Image sticker with QR |
map-full-page-01-template | Full-page map label |
map-full-page-square-01-template | Square full-page map |
map-sticker-01-template | Map sticker |
Manual fill
Tap the tags icon on a template row to open a form. Type values by hand without any JSON file or link. Good for one-off labels or trying a layout.
Data mappings
Mappings tell the app which item field goes to which placeholder (for example title → title text box). They live inside each template. Open the mapping editor from a template to set them up. See Mapping reference.
After filling (fill-review)
You see a preview before printing. From here you can:
- Save to Saved labels (then edit freely under Saved).
- Switch to a different template (same data).
- View the contents tree on box labels.
- Shrink the page to fit content.
- Go back to Containd when a return link was sent.
Export / import
Export a template as a .labeltemplate.json file from the share icon on its row. Import from the Templates tab menu.
Map labels
Map labels show a floor plan or photo with numbered markers and a legend list. You can build them by hand or send coordinates in JSON.
From item data (ingress)
Use format label-containd-map-ingress. Send a list of items:
- First item (index 0) = map title and overall frame.
- Other items = regions with
x,y,width,heighton the shared image. - Optional colors:
backgroundColor,textColor,borderColor. - Optional
canvasW/canvasHfor the coordinate system.
After import, pick the map photo when asked, then choose a map template (for example map-full-page-01-template).
Map ingress JSON example
Index 0 is the map itself. Other entries are regions with x/y/width/height on the shared image.
{
"format": "label-containd-map-ingress",
"version": 1,
"senderId": "containd",
"returnDeepLink": "containd://location/warehouse-a",
"data": [
{
"title": "Warehouse A — Floor plan",
"fullDeepLink": "containd://location/warehouse-a",
"canvasW": 1200,
"canvasH": 800,
"x": 0, "y": 0, "width": 1200, "height": 800
},
{
"title": "Shelf A1",
"description": "Electronics",
"x": 120, "y": 80, "width": 140, "height": 90,
"backgroundColor": "#FFFFFF",
"textColor": "#000000",
"borderColor": "#333333"
},
{
"title": "Shelf B2",
"x": 420, "y": 220, "width": 120, "height": 80
}
]
}
Build a map by hand
On any label that already has an image and a list:
- Insert → Map marker to place numbers on the image.
- Select a marker → Link to legend row.
- Select the list → List style → Legend markers.
Sending item data
Other apps (like Containd) can send item details into Link2Label as JSON. The app reads that JSON and fills your template placeholders.
Basic payload shape
Every payload needs format, version, and data. Optional extras control which template to use and what happens next.
Minimal payload with command
{
"format": "label-containd-ingress",
"version": 1,
"senderId": "containd",
"returnDeepLink": "containd://item/abc123",
"data": {
"title": "Storage Box A-12",
"productCode": "4007817326004"
},
"command": {
"action": "fill_and_show",
"templateId": "containd-strip-label",
"mappingId": "default"
}
}
data— one item object, or an array for a batch of labels.command— optional. Says which template to use and whether to show preview, print, etc. Defaults to preview with the default mapping.senderId+returnDeepLink— optional. Lets the user jump back to the sending app after printing.
Two different "return" links
| Field | Where | What it does |
|---|---|---|
returnDeepLink |
Top level of payload | Opens the sender app again (Back to Containd button) |
fullDeepLink |
Inside data |
URL printed in the QR code on the label |
How data can arrive
- Tap a
link2label://link (native app). - Open an
https://…/ingress/…link (web app). - Paste JSON or pick a
.jsonfile. - Share text or a URL to Link2Label (native).
- Import a JPEG with JSON embedded in the file (see Image, EXIF, and file import).
JSON structure
One item or many
- One item:
"data": { "title": "Widget" } - Many items:
"data": [ { … }, { … } ]— one label per item in the batch. Use a JSON file, paste, or base64 link (not the short flat URL format).
Formats
label-containd-ingress— normal labels (strips, stickers, box labels).label-containd-map-ingress— map labels with regions on an image.
Item fields (all optional)
Only include fields your template actually uses.
| Field | Type | Typical use |
|---|---|---|
title, description | string | Main text on the label |
productCode, internalProductCode | string | Barcodes, small print |
fullDeepLink | string | URL for the QR code |
location, owner, condition | string | Extra lines of text |
amount, amountUnit, contentNumber | number / string | Counts and quantities |
createdAt, etc. | number | Dates as Unix milliseconds |
valueNew, valueNow, currency, weight | number / string | Value lines |
tagList, categories | string[] | Tag lists |
arbitraryFieldsList | object[] | Custom fields: { categoryId, propertyId, value } |
content | tree[] | What's inside a box (see tree example below) |
x, y, width, height | number | Map regions only |
Content tree (content field)
Each node has t (title). Child nodes go in c.
"content": [
{
"t": "Electronics",
"c": [
{ "t": "HDMI cables" },
{ "t": "USB adapters", "c": [{ "t": "USB-C hub" }] }
]
},
{ "t": "Tools", "c": [{ "t": "Screwdriver set" }] },
{ "t": "Labels & tags" }
]
Command actions
action | What happens |
|---|---|
fill_and_show | Fill and show preview |
open_mapping | Open mapping editor |
fill_and_export_image | Fill and export PNG |
fill_and_print | Fill and print (if a printer is set up) |
Print options (command.options)
| Field | Effect |
|---|---|
printerId | Use a specific saved Bluetooth printer |
autoPrint: false | Show print sheet instead of printing immediately |
fitToContent: true | Shrink label to fit content |
returnToSender: false | Do not open Containd after print |
Single item with content tree (box label)
{
"format": "label-containd-ingress",
"version": 1,
"data": {
"title": "Storage Box A-12",
"location": "Garage · Shelf 3",
"description": "Winter equipment and cables",
"productCode": "BOX-A12",
"fullDeepLink": "https://example.com/item/box-a12",
"contentNumber": 9,
"content": [
{
"t": "Electronics",
"c": [{ "t": "HDMI cables" }, { "t": "USB adapters" }]
},
{ "t": "Tools", "c": [{ "t": "Screwdriver set" }] }
]
},
"command": {
"action": "fill_and_show",
"templateId": "containd-box-label",
"mappingId": "default"
}
}
Multi-item batch (two cameras)
Each object in data becomes one label in fill-review.
{
"format": "label-containd-ingress",
"version": 1,
"data": [
{
"title": "Vintage Leica M3",
"productCode": "4007817326004",
"location": "Archive · Shelf B",
"tagList": ["camera", "vintage", "leica"],
"fullDeepLink": "https://example.com/items/leica-m3"
},
{
"title": "Hasselblad 500C/M",
"productCode": "4007817326011",
"location": "Archive · Shelf C",
"tagList": ["camera", "medium-format"],
"fullDeepLink": "https://example.com/items/hasselblad-500"
}
],
"command": {
"action": "fill_and_show",
"templateId": "sticker-qr-title-01-template",
"mappingId": "default"
}
}
Containd handoff with auto-print
{
"format": "label-containd-ingress",
"version": 1,
"senderId": "containd",
"returnDeepLink": "containd://item/YOUR_ITEM_ID",
"data": {
"title": "Storage Box A-12",
"productCode": "4007817326004",
"fullDeepLink": "https://example.com/item/box-a12"
},
"command": {
"action": "fill_and_print",
"templateId": "containd-strip-label",
"mappingId": "containd-default",
"options": { "autoPrint": true }
}
}
Link formats
You can wrap the same JSON payload in different URL styles. Pick the one that fits your case (short SMS link vs full box contents).
Which encoding to use
| Style | Looks like | Best for |
|---|---|---|
| Base64 path | link2label://ingress/eyJ… |
Default. Multiple items, content trees, long data. |
| Flat query | link2label://ingress?title=Widget&… |
One item, few text fields. Shortest links. |
| JSON query | link2label://ingress?data=%7B… |
Testing and debugging. |
Web links work the same with https://your-site/ingress/… instead of link2label://ingress/….
Base64 path link
The whole JSON file is compressed to base64url and placed after /ingress/.
link2label://ingress/eyJmb3JtYXQiOiJsYWJlbC1jb250YWluZC1pbmdyZXNzIiwidmVyc2lvbiI6MSwiZGF0YSI6eyJ0aXRsZSI6IldpZGdldCIsInByb2R1Y3RDb2RlIjoiNDAwNzgxNzMyNjAwNCJ9LCJjb21tYW5kIjp7ImFjdGlvbiI6ImZpbGxfYW5kX3Nob3ciLCJ0ZW1wbGF0ZUlkIjoiY29udGFpbmQtc3RyaXAtbGFiZWwiLCJtYXBwaW5nSWQiOiJkZWZhdWx0In19
This decodes to a payload with title "Widget", productCode, and a fill_and_show command. Use the Ingress link tool in Settings to convert your own JSON.
Flat query link (readable)
Each simple field becomes a query parameter. No nested content trees or arrays.
link2label://ingress?format=label-containd-ingress&version=1&title=Widget&productCode=4007817326004&location=Shelf%203&action=fill_and_show&templateId=containd-strip-label&mappingId=default
Supported parameters include title, description, productCode, fullDeepLink, location, dates, values, tagList (comma-separated), action, templateId, mappingId, senderId, and returnDeepLink.
JSON query link
The entire payload is URL-encoded in the data parameter.
link2label://ingress?data=%7B%22format%22%3A%22label-containd-ingress%22%2C%22version%22%3A1%2C%22data%22%3A%7B%22title%22%3A%22Widget%22%7D%7D
Decoded data value:
{"format":"label-containd-ingress","version":1,"data":{"title":"Widget"}}
Web link (HTTPS)
https://labels.example.com/ingress/eyJmb3JtYXQiOiJsYWJlbC1jb250YWluZC1pbmdyZXNzI…
Any host works if the path contains /ingress. Set your web base URL when using Share link in the app.
How long can a link be?
| Channel | Safe size | Tip |
|---|---|---|
| Web URL | About 2,000 characters | QR codes and email often break sooner |
| Deep link | About 2–8 KB | Use a file or paste for big payloads |
| Photo embed | 64 KB JSON max | Separate limit from URLs |
Creating links in the app
Share link (easiest)
- Templates tab → Import item data → paste or load your JSON.
- Tap Share link.
- Pick deeplink (opens app) or weblink (opens browser).
- Pick encoding: base64 (usual choice), flat (short), or json (debug).
- For weblinks, enter your hosted app URL.
- Copy or share the result.
Ingress link tool
Menu → Settings → Template ingress → Ingress link tool.
Paste JSON on one side, see base64 and full URLs on the other. Handy when building integrations.
TypeScript: build a link in code
import { buildIngressDeepLink, buildIngressLink } from './ingress-url'
import { createIngressPayload } from './ingress-payload'
const payload = createIngressPayload(
{ title: 'Widget', productCode: '4007817326004' },
{ action: 'fill_and_show', templateId: 'containd-strip-label', mappingId: 'default' },
{ senderId: 'containd', returnDeepLink: 'containd://item/YOUR_ITEM_ID' }
)
const deepLink = buildIngressDeepLink(payload)
// link2label://ingress/eyJ…
const webLink = buildIngressLink(payload, {
kind: 'weblink',
encoding: 'base64',
webBaseUrl: 'https://labels.example.com',
})
Image, EXIF, and file import
Besides links, you can send item data as a JSON file or hidden inside a JPEG photo.
Quick comparison
| You want to… | Use |
|---|---|
| Test once by hand | Paste JSON in the app |
| Send a file from your computer or cloud | .json file import |
| Share a photo that also carries item data | JPEG with embedded JSON |
| Short link in SMS or QR | Flat URL (single item only) |
| Box contents or many items | JSON file, paste, or base64 link |
Paste JSON (in the app)
- Home → Import item data.
- Paste your JSON.
- Confirm. The app opens fill-review or asks you to pick a template.
DIY: JSON file step by step
Create a plain text file you can email, sync, or open from the Files app.
- Create a new file named something like
my-item.json. - Start with the required wrapper (see example below).
- Put your item fields inside
data. Use one object for one label, or an array for several. - Add
commandif you want a specific template or auto-print. If you omit it, the app shows preview with the default mapping. - Validate: must be valid JSON (double quotes, no trailing commas). Online JSON validators help.
- Save the file with UTF-8 encoding.
- In Link2Label: Home → Import item data → choose file picker → select your
.json. - If
templateIdin the command is missing or unknown, pick a template when prompted.
Starter JSON file (single item)
Save this as widget.json and import it to test.
{
"format": "label-containd-ingress",
"version": 1,
"data": {
"title": "Blue storage bin",
"description": "Winter clothes",
"productCode": "BIN-042",
"location": "Attic shelf 2",
"fullDeepLink": "https://example.com/items/bin-042"
},
"command": {
"action": "fill_and_show",
"templateId": "containd-strip-label",
"mappingId": "default"
}
}
Starter JSON file (batch of two items)
{
"format": "label-containd-ingress",
"version": 1,
"data": [
{ "title": "Item A", "productCode": "A001" },
{ "title": "Item B", "productCode": "B002" }
],
"command": {
"action": "fill_and_show",
"mappingId": "default"
}
}
Without templateId, the app asks you to pick a template once for the whole batch.
DIY: embed JSON in a JPEG (EXIF-style)
Link2Label scans the photo file for a JSON blob containing "format":"label-containd-ingress" (or the map format). The JSON can sit in EXIF metadata or anywhere in the JPEG bytes. This is how Containd shares a photo plus item data in one file.
Rules
- File must be a JPEG (
.jpg/.jpeg). - JSON must be valid and include
format,version, anddata. - The marker string must appear exactly as
"format":"label-containd-ingress"(no spaces after the colon). - Keep the JSON under 64 KB. Larger payloads are ignored.
- If no JSON is found, the app treats the image as a normal photo label instead.
Step by step (using ExifTool)
- Install ExifTool on your computer.
- Write your payload to a file, e.g.
payload.json(use the starter examples above). - Pick a JPEG photo (the map image for map labels, or any photo for standard labels).
- Embed the JSON into the photo's UserComment field:
exiftool -overwrite_original -UserComment<=payload.json myphoto.jpg
- Transfer
myphoto.jpgto your phone (AirDrop, email, cloud, etc.). - In Link2Label: Home → Import image → select the photo.
- If JSON was found, labels are filled. For map ingress, confirm the image when prompted.
- Review in fill-review, then print or save.
Payload file for EXIF embed (copy into payload.json)
{
"format": "label-containd-ingress",
"version": 1,
"data": {
"title": "Tool drawer",
"productCode": "DRW-07",
"fullDeepLink": "https://example.com/items/drw-07"
},
"command": {
"action": "fill_and_show",
"templateId": "sticker-qr-title-01-template",
"mappingId": "default"
}
}
Without ExifTool
Any method that places the raw JSON string inside the JPEG file can work, as long as the format marker is present and the JSON is complete. ExifTool is the most reliable option for manual testing. App developers typically embed the string in an EXIF or APP segment when exporting from their own app.
Plain photo (no JSON)
If you import an image with no embedded data, Link2Label opens the photo print flow: a simple label with your picture, not auto-filled fields.
Share sheet (native app only)
From another app, share text (JSON or a link) to Link2Label. The share extension parses it the same way as paste or tap. Requires a store build, not Expo Go.
Mapping reference
Mappings tell each placeholder where to get its value when item data arrives. They are stored inside the template file.
Mapping structure in a template file
"mappings": [
{
"mappingId": "default",
"entries": [
{ "placeholderId": "text-title", "sourceKey": "title" },
{ "placeholderId": "text-code", "sourceKey": "productCode" },
{ "placeholderId": "qr", "sourceKey": "fullDeepLink" }
]
}
]
Map templates may also set mapTemplate.legendPlaceholderId for the legend list.
Common source keys
| Source key | Goes to |
|---|---|
title, description | Text placeholders |
content | Tree / list placeholder only |
contentNumber | Text (item count) |
fullDeepLink | QR or barcode |
productCode, location, … | Any text field |
cat:categoryId:propertyId | Custom fields from arbitraryFieldsList |
@now | Date placeholders (today's date at fill time) |
Custom category fields
If item data includes:
"arbitraryFieldsList": [
{ "categoryId": "equipment", "propertyId": "serial_number", "value": "826492" }
]
Map it with source key cat:equipment:serial_number.
Multiple mappings
One template can have several mapping profiles (different mappingId values). The incoming JSON picks one via command.mappingId.
Editing in the app
Open a template → mapping editor → add rows pairing each placeholder with a source key. Placeholders you leave unmapped keep their default sample text.