Skip to content

NetBox Create VLAN Task¤

task API name: create_vlan

The create_vlan task creates or updates one VLAN in an existing VLAN group. Supply vid for an explicit VLAN ID, or omit it to allocate the next available ID from the group's configured ranges.

Inputs¤

Parameter Required Default Description
vlan_group Yes — Existing VLAN group name
name Yes — VLAN name
vid No None Explicit VLAN ID from 1 through 4094; omit to allocate
status No active VLAN status
description No None VLAN description
tenant No None Tenant name
role No None IPAM role name
tags No None Tag names
custom_fields No None NetBox custom-field values
instance No Worker default NetBox instance to target
dry_run No False Return the selected VID without writing
branch No None NetBox Branching plugin branch name

Output¤

{
    "vid": 1000,
    "name": "USERS",
    "vlan_group": "CAMPUS",
    "status": "created",
}

status is create or update during dry-run and created or updated after an applied operation.

Examples¤

A dedicated NFCLI command is not currently registered for create_vlan.

# Use the Python API, REST API, MCP task interface, or design filter.
from norfab.core.nfapi import NorFab

with NorFab(inventory="./inventory.yaml") as nf:
    client = nf.make_client()

    allocated = client.run_job(
        "netbox",
        "create_vlan",
        workers="any",
        kwargs={"vlan_group": "CAMPUS", "name": "USERS"},
    )

    explicit = client.run_job(
        "netbox",
        "create_vlan",
        workers="any",
        kwargs={
            "vlan_group": "CAMPUS",
            "name": "SERVERS",
            "vid": 1100,
        },
    )

In a NetBox design:

{% set users_vid = "CAMPUS" | netbox.create_vlan("USERS") %}

vlans:
  - group: CAMPUS
    vid: {{ users_vid }}
    name: USERS

Notes / Gotchas¤

  • The VLAN group must already exist and have available VID ranges for allocation.
  • With an explicit vid, identity is VLAN group plus VID.
  • Without vid, an existing VLAN with the same group and name is reused.
  • When updating a VLAN, new tags and new items in list-valued custom fields are added without removing existing ones. Supplying [] leaves an existing list unchanged. For custom fields, null clears the value, and a new scalar value replaces the old one.
  • Object and multiobject custom fields accept related object names or IDs. The task resolves names using each field's NetBox definition; missing or ambiguous names fail.
  • Branch writes require the NetBox Branching plugin.

Troubleshooting¤

  • VLAN group not found: verify the exact group name in NetBox.
  • No available VLAN IDs: add or expand the group's VID ranges.
  • Ambiguous identity: remove duplicate VLANs with the same group and identity fields.

Task Command Shell Reference¤

create_vlan does not currently have a dedicated NFCLI command model.

Python API Reference¤

Create, update, or allocate a VLAN in an existing group.

A supplied VID identifies the VLAN within its group. Without a VID, an existing VLAN is matched by group and name, or the first available VID is allocated. Repeated calls without a VID and with the same name reuse that VLAN. Tags and array custom fields add values on updates; scalar custom fields replace their values. Existing VLANs are patched without a local diff check. Dry-run reports the selected VID but does not write attributes.

Parameters:

Name Type Description Default
job Job

Current NorFab job for events and branch provisioning.

required
vlan_group str

Name of the existing VLAN group.

required
name str

VLAN name to create or assign to the matched VLAN.

required
vid Union[None, int]

Explicit VLAN ID; omit to allocate from the group.

None
status str

NetBox VLAN status, defaulting to active.

'active'
description Union[None, str]

Optional VLAN description.

None
tenant Union[None, str]

Optional tenant name.

None
role Union[None, str]

Optional IPAM role name.

None
tags Union[None, list]

Optional tag names to add without removing existing tags.

None
custom_fields Union[None, dict]

Optional NetBox custom-field values. Arrays add missing values; empty arrays preserve existing arrays. Null and scalar values replace existing values. Object and multiobject fields accept related object names or IDs.

None
instance Union[None, str]

NetBox instance name, or the worker default when omitted.

None
dry_run bool

Select a VLAN without creating or updating it.

False
branch Union[None, str]

Optional NetBox Branching plugin branch name.

None

Returns:

Name Type Description
Result Result

Selected VLAN ID, name, group, and action. Applied results

Result

also include the NetBox object ID.

Raises:

Type Description
ValueError

The group is missing, the match is ambiguous, or no VLAN IDs remain available for allocation.

Source code in norfab\workers\netbox_worker\vlan_tasks.py
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
@Task(
    fastapi={"methods": ["POST"], "schema": NetboxFastApiArgs.model_json_schema()},
    input=CreateVlanInput,
    output=CreateVlanResult,
    mcp={
        "annotations": {
            "title": "Create VLAN",
            "readOnlyHint": False,
            "destructiveHint": False,
            "idempotentHint": True,
            "openWorldHint": True,
        }
    },
)
def create_vlan(
    self,
    job: Job,
    vlan_group: str,
    name: str,
    vid: Union[None, int] = None,
    status: str = "active",
    description: Union[None, str] = None,
    tenant: Union[None, str] = None,
    role: Union[None, str] = None,
    tags: Union[None, list] = None,
    custom_fields: Union[None, dict] = None,
    instance: Union[None, str] = None,
    dry_run: bool = False,
    branch: Union[None, str] = None,
) -> Result:
    """Create, update, or allocate a VLAN in an existing group.

    A supplied VID identifies the VLAN within its group. Without a VID,
    an existing VLAN is matched by group and name, or the first available
    VID is allocated. Repeated calls without a VID and with the same name
    reuse that VLAN. Tags and array custom fields add values on updates;
    scalar custom fields replace their values.
    Existing VLANs are patched without a local diff check.
    Dry-run reports the selected VID but does not write attributes.

    Args:
        job: Current NorFab job for events and branch provisioning.
        vlan_group: Name of the existing VLAN group.
        name: VLAN name to create or assign to the matched VLAN.
        vid: Explicit VLAN ID; omit to allocate from the group.
        status: NetBox VLAN status, defaulting to active.
        description: Optional VLAN description.
        tenant: Optional tenant name.
        role: Optional IPAM role name.
        tags: Optional tag names to add without removing existing tags.
        custom_fields: Optional NetBox custom-field values. Arrays add
            missing values; empty arrays preserve existing arrays. Null and
            scalar values replace existing values. Object and multiobject
            fields accept related object names or IDs.
        instance: NetBox instance name, or the worker default when omitted.
        dry_run: Select a VLAN without creating or updating it.
        branch: Optional NetBox Branching plugin branch name.

    Returns:
        Result: Selected VLAN ID, name, group, and action. Applied results
        also include the NetBox object ID.

    Raises:
        ValueError: The group is missing, the match is ambiguous, or no
            VLAN IDs remain available for allocation.
    """
    instance = instance or self.default_instance
    ret = Result(
        task=f"{self.name}:create_vlan",
        result={},
        resources=[instance],
        dry_run=dry_run,
    )
    nb = self._get_pynetbox(instance, branch=branch, job=job)
    group = nb.ipam.vlan_groups.get(name=vlan_group)
    if not group:
        raise ValueError(f"VLAN group '{vlan_group}' not found in NetBox")

    filters = (
        {"group_id": group.id, "vid": vid}
        if vid
        else {
            "group_id": group.id,
            "name": name,
        }
    )
    matches = self.bulk_filter(nb.ipam.vlans, **filters)
    if len(matches) > 1:
        raise ValueError(f"VLAN identity {filters} matched more than one VLAN")
    nb_vlan = matches[0] if matches else None

    if not nb_vlan and vid is None:
        available = group.available_vlans.list()
        if not available:
            raise ValueError(f"VLAN group '{vlan_group}' has no available VLAN IDs")
        vid = int(getattr(available[0], "vid", available[0]))
    if dry_run:
        ret.result = {
            "vid": int(nb_vlan.vid) if nb_vlan else vid,
            "name": name,
            "vlan_group": vlan_group,
            "status": "update" if nb_vlan else "create",
        }
        return ret

    payload = {"name": name, "status": status}
    if description is not None:
        payload["description"] = description
    if tenant is not None:
        payload["tenant"] = {"name": tenant}
    if role is not None:
        payload["role"] = {"name": role}
    if tags:
        payload["tags"] = [{"name": tag} for tag in tags]
    if custom_fields is not None:
        reference_fields = self.bulk_filter(
            nb.extras.custom_fields,
            object_type="ipam.vlan",
            type=["object", "multiobject"],
        )
        # Resolve names on creation and merge with current values on updates.
        _, payload["custom_fields"] = merge_resolved_custom_fields(
            self,
            nb,
            nb_vlan,
            custom_fields,
            [field for field in reference_fields if field.type.value == "object"],
            [
                field
                for field in reference_fields
                if field.type.value == "multiobject"
            ],
        )

    if nb_vlan:
        if tags:
            payload["tags"] = merge_array_values(
                nb_vlan.tags, tags, attribute="name"
            )
        nb_vlan.update(payload)
        ret.status = "updated"
    elif filters.get("vid") is None:
        nb_vlan = group.available_vlans.create(payload)
        ret.status = "created"
    else:
        nb_vlan = nb.ipam.vlans.create({"vid": vid, "group": group.id, **payload})
        ret.status = "created"

    ret.result = {
        "id": nb_vlan.id,
        "vid": int(nb_vlan.vid),
        "name": nb_vlan.name,
        "vlan_group": vlan_group,
        "status": ret.status,
    }
    job.event(f"{ret.status} VLAN {nb_vlan.vid} '{nb_vlan.name}'")
    return ret