diff --git a/ap_isolation.c b/ap_isolation.c
index 71dcd00..36fd4d6 100644
--- a/ap_isolation.c
+++ b/ap_isolation.c
@@ -28,7 +28,7 @@ static int get_attrs_ap_isolation(struct nl_msg *msg, void *arg)
 {
 	struct state *state = arg;
 
-	if (state->vid >= 0)
+	if (state->selector == SP_VLAN)
 		nla_put_u16(msg, BATADV_ATTR_VLANID, state->vid);
 
 	return 0;
@@ -38,7 +38,7 @@ static int get_ap_isolation(struct state *state)
 {
 	enum batadv_nl_commands nl_cmd = BATADV_CMD_SET_MESH;
 
-	if (state->vid >= 0)
+	if (state->selector == SP_VLAN)
 		nl_cmd = BATADV_CMD_GET_VLAN;
 
 	return sys_simple_nlquery(state, nl_cmd, get_attrs_ap_isolation,
@@ -53,7 +53,7 @@ static int set_attrs_ap_isolation(struct nl_msg *msg, void *arg)
 
 	nla_put_u8(msg, BATADV_ATTR_AP_ISOLATION_ENABLED, data->val);
 
-	if (state->vid >= 0)
+	if (state->selector == SP_VLAN)
 		nla_put_u16(msg, BATADV_ATTR_VLANID, state->vid);
 
 	return 0;
@@ -63,7 +63,7 @@ static int set_ap_isolation(struct state *state)
 {
 	enum batadv_nl_commands nl_cmd = BATADV_CMD_SET_MESH;
 
-	if (state->vid >= 0)
+	if (state->selector == SP_VLAN)
 		nl_cmd = BATADV_CMD_SET_VLAN;
 
 	return sys_simple_nlquery(state, nl_cmd, set_attrs_ap_isolation, NULL);
@@ -81,3 +81,8 @@ COMMAND_NAMED(SUBCOMMAND, ap_isolation, "ap", handle_sys_setting,
 	      COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK,
 	      &batctl_settings_ap_isolation,
 	      "[0|1]             \tdisplay or modify ap_isolation setting");
+
+COMMAND_NAMED(SUBCOMMAND_VID, ap_isolation, "ap", handle_sys_setting,
+	      COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK,
+	      &batctl_settings_ap_isolation,
+	      "[0|1]             \tdisplay or modify ap_isolation setting for vlan device or id");
diff --git a/functions.c b/functions.c
index aad6327..61ea487 100644
--- a/functions.c
+++ b/functions.c
@@ -919,32 +919,44 @@ static int query_rtnl_link_single(int mesh_ifindex,
 	return 0;
 }
 
-int translate_mesh_iface(struct state *state)
+int translate_vlan_iface(struct state *state, const char *vlandev)
 {
 	struct rtnl_link_iface_data link_data;
 	unsigned int arg_ifindex;
 
-	arg_ifindex = if_nametoindex(state->arg_iface);
+	arg_ifindex = if_nametoindex(vlandev);
 	if (arg_ifindex == 0)
-		goto fallback_meshif;
+		return -ENODEV;
 
 	query_rtnl_link_single(arg_ifindex, &link_data);
 	if (!link_data.vid_found)
-		goto fallback_meshif;
+		return -ENODEV;
 
 	if (!link_data.link_found)
-		goto fallback_meshif;
+		return -EINVAL;
 
 	if (!link_data.kind_found)
-		goto fallback_meshif;
+		return -EINVAL;
 
 	if (strcmp(link_data.kind, "vlan") != 0)
-		goto fallback_meshif;
+		return -EINVAL;
 
 	if (!if_indextoname(link_data.link, state->mesh_iface))
-		goto fallback_meshif;
+		return -ENODEV;
 
 	state->vid = link_data.vid;
+	state->selector = SP_VLAN;
+
+	return 0;
+}
+
+int translate_mesh_iface_vlan(struct state *state, const char *vlandev)
+{
+	int ret;
+
+	ret = translate_vlan_iface(state, vlandev);
+	if (ret < 0)
+		goto fallback_meshif;
 
 	return 0;
 
@@ -952,9 +964,36 @@ int translate_mesh_iface(struct state *state)
 	/* if there is no vid then the argument must be the
 	 * mesh interface
 	 */
-	snprintf(state->mesh_iface, sizeof(state->mesh_iface), "%s",
-		 state->arg_iface);
-	state->vid = -1;
+	snprintf(state->mesh_iface, sizeof(state->mesh_iface), "%s", vlandev);
+	state->selector = SP_NONE_OR_MESHIF;
+
+	return 0;
+}
+
+int translate_vid(struct state *state, const char *vidstr)
+{
+	unsigned long vid;
+	char *endptr;
+
+	if (vidstr[0] == '\0') {
+		fprintf(stderr, "Error - unparsable vid\n");
+		return -EINVAL;
+	}
+
+	vid = strtoul(vidstr, &endptr, 0);
+	if (!endptr || *endptr != '\0') {
+		fprintf(stderr, "Error - unparsable vid\n");
+		return -EINVAL;
+	}
+
+	if (vid > 4095) {
+		fprintf(stderr, "Error - too large vid (max 4095)\n");
+		return -ERANGE;
+	}
+
+	/* get mesh interface and overwrite vid afterwards */
+	state->vid = vid;
+	state->selector = SP_VLAN;
 
 	return 0;
 }
diff --git a/functions.h b/functions.h
index d4a5568..7474c40 100644
--- a/functions.h
+++ b/functions.h
@@ -50,7 +50,9 @@ struct ether_addr *translate_mac(const char *mesh_iface,
 struct ether_addr *resolve_mac(const char *asc);
 int query_rtnl_link(int ifindex, nl_recvmsg_msg_cb_t func, void *arg);
 int netlink_simple_request(struct nl_msg *msg);
-int translate_mesh_iface(struct state *state);
+int translate_mesh_iface_vlan(struct state *state, const char *vlandev);
+int translate_vlan_iface(struct state *state, const char *vlandev);
+int translate_vid(struct state *state, const char *vidstr);
 int get_algoname(const char *mesh_iface, char *algoname, size_t algoname_len);
 int check_mesh_iface(struct state *state);
 int check_mesh_iface_ownership(struct state *state, char *hard_iface);
diff --git a/main.c b/main.c
index 278683c..3090877 100644
--- a/main.c
+++ b/main.c
@@ -28,48 +28,75 @@ extern const struct command *__stop___command[];
 
 static void print_usage(void)
 {
-	enum command_type type[] = {
-		SUBCOMMAND,
-		DEBUGTABLE,
+	struct {
+		const char *label;
+		uint32_t types;
+	} type[] = {
+		{
+			.label = "commands:\n",
+			.types = BIT(SUBCOMMAND) |
+				 BIT(SUBCOMMAND_VID),
+		},
+		{
+			.label = "debug tables:                                   \tdisplay the corresponding debug table\n",
+			.types = BIT(DEBUGTABLE),
+		},
+	};
+	const char *default_prefixes[] = {
+		"",
+		NULL,
+	};
+	const char *vlan_prefixes[] = {
+		"vlan <vdev> ",
+		"vid <vid> ",
+		NULL,
 	};
 	const struct command **p;
-	char buf[32];
+	const char **prefixes;
+	const char **prefix;
+	char buf[64];
 	size_t i;
 
 	fprintf(stderr, "Usage: batctl [options] command|debug table [parameters]\n");
 	fprintf(stderr, "options:\n");
-	fprintf(stderr, " \t-m mesh interface or VLAN created on top of a mesh interface (default 'bat0')\n");
+	fprintf(stderr, " \t-m mesh interface (default 'bat0')\n");
 	fprintf(stderr, " \t-h print this help (or 'batctl <command|debug table> -h' for the parameter help)\n");
 	fprintf(stderr, " \t-v print version\n");
 
 	for (i = 0; i < sizeof(type) / sizeof(*type); i++) {
 		fprintf(stderr, "\n");
 
-		switch (type[i]) {
-		case SUBCOMMAND:
-			fprintf(stderr, "commands:\n");
-			break;
-		case DEBUGTABLE:
-			fprintf(stderr, "debug tables:                                   \tdisplay the corresponding debug table\n");
-			break;
-		}
+		fprintf(stderr, "%s", type[i].label);
 
 		for (p = __start___command; p < __stop___command; p++) {
 			const struct command *cmd = *p;
 
-			if (cmd->type != type[i])
+			if (!(BIT(cmd->type) & type[i].types))
 				continue;
 
 			if (!cmd->usage)
 				continue;
 
-			if (strcmp(cmd->name, cmd->abbr) == 0)
-				snprintf(buf, sizeof(buf), "%s", cmd->name);
-			else
-				snprintf(buf, sizeof(buf), "%s|%s", cmd->name,
-					 cmd->abbr);
+			switch (cmd->type) {
+			case SUBCOMMAND_VID:
+				prefixes = vlan_prefixes;
+				break;
+			default:
+				prefixes = default_prefixes;
+				break;
+			}
+
+			for (prefix = &prefixes[0]; *prefix; prefix++) {
+				if (strcmp(cmd->name, cmd->abbr) == 0)
+					snprintf(buf, sizeof(buf), "%s%s",
+						 *prefix, cmd->name);
+				else
+					snprintf(buf, sizeof(buf), "%s%s|%s",
+						 *prefix, cmd->name, cmd->abbr);
 
-			fprintf(stderr, " \t%-27s%s\n", buf, cmd->usage);
+				fprintf(stderr, " \t%-35s%s\n", buf,
+					cmd->usage);
+			}
 		}
 	}
 }
@@ -93,13 +120,17 @@ static void version(void)
 	exit(EXIT_SUCCESS);
 }
 
-static const struct command *find_command(const char *name)
+static const struct command *find_command_by_types(uint32_t types,
+						   const char *name)
 {
 	const struct command **p;
 
 	for (p = __start___command; p < __stop___command; p++) {
 		const struct command *cmd = *p;
 
+		if (!(BIT(cmd->type) & types))
+			continue;
+
 		if (strcmp(cmd->name, name) == 0)
 			return cmd;
 
@@ -110,13 +141,123 @@ static const struct command *find_command(const char *name)
 	return NULL;
 }
 
+static const struct command *find_command(struct state *state, const char *name)
+{
+	uint32_t types;
+
+	switch (state->selector) {
+	case SP_NONE_OR_MESHIF:
+		types = BIT(SUBCOMMAND) |
+			BIT(DEBUGTABLE);
+		break;
+	case SP_VLAN:
+		types = BIT(SUBCOMMAND_VID);
+		break;
+	default:
+		return NULL;
+	}
+
+	return find_command_by_types(types, name);
+}
+
+static int detect_selector_prefix(int argc, char *argv[],
+				  enum selector_prefix *selector)
+{
+	/* not enough remaining arguments to detect anything */
+	if (argc < 2)
+		return -EINVAL;
+
+	/* only detect selector prefix which identifies meshif */
+	if (strcmp(argv[0], "vlan") == 0) {
+		*selector = SP_VLAN;
+		return 2;
+	}
+
+	return 0;
+}
+
+static int parse_meshif_args(struct state *state, int argc, char *argv[])
+{
+	enum selector_prefix selector;
+	int parsed_args;
+	char *dev_arg;
+	int ret;
+
+	parsed_args = detect_selector_prefix(argc, argv, &selector);
+	if (parsed_args < 1)
+		goto fallback_meshif_vlan;
+
+	dev_arg = argv[parsed_args - 1];
+
+	switch (selector) {
+	case SP_VLAN:
+		ret = translate_vlan_iface(state, dev_arg);
+		if (ret < 0) {
+			fprintf(stderr, "Error - invalid vlan device %s: %s\n",
+				dev_arg, strerror(-ret));
+			return ret;
+		}
+
+		return parsed_args;
+	case SP_NONE_OR_MESHIF:
+		/* not allowed - see detect_selector_prefix */
+		break;
+	}
+
+fallback_meshif_vlan:
+	/* parse vlan as part of -m parameter or mesh_dfl_iface */
+	translate_mesh_iface_vlan(state, state->arg_iface);
+	return 0;
+}
+
+static int parse_dev_args(struct state *state, int argc, char *argv[])
+{
+	int dev_arguments;
+	int ret;
+
+	/* try to parse selector prefix which can be used to identify meshif */
+	dev_arguments = parse_meshif_args(state, argc, argv);
+	if (dev_arguments < 0)
+		return dev_arguments;
+
+	/* try to parse secondary prefix selectors which cannot be used to
+	 * identify the meshif
+	 */
+	argv += dev_arguments;
+	argc -= dev_arguments;
+
+	switch (state->selector) {
+	case SP_NONE_OR_MESHIF:
+		/* continue below */
+		break;
+	default:
+		return dev_arguments;
+	}
+
+	/* enough room for additional selectors? */
+	if (argc < 2)
+		return dev_arguments;
+
+	if (strcmp(argv[0], "vid") == 0) {
+		ret = translate_vid(state, argv[1]);
+		if (ret < 0)
+			return ret;
+
+		return dev_arguments + 2;
+	}
+
+	return dev_arguments;
+}
+
 int main(int argc, char **argv)
 {
 	const struct command *cmd;
 	struct state state = {
 		.arg_iface = mesh_dfl_iface,
+		.selector = SP_NONE_OR_MESHIF,
 		.cmd = NULL,
 	};
+	int dev_arguments;
 	int opt;
 	int ret;
 
@@ -152,7 +293,20 @@ int main(int argc, char **argv)
 	argc -= optind;
 	optind = 0;
 
-	cmd = find_command(argv[0]);
+	/* parse arguments to identify vlan, ... */
+	dev_arguments = parse_dev_args(&state, argc, argv);
+	if (dev_arguments < 0)
+		goto err;
+
+	argv += dev_arguments;
+	argc -= dev_arguments;
+
+	if (argc == 0) {
+		fprintf(stderr, "Error - no command specified\n");
+		goto err;
+	}
+
+	cmd = find_command(&state, argv[0]);
 	if (!cmd) {
 		fprintf(stderr,
 			"Error - no valid command or debug table specified: %s\n",
@@ -162,8 +316,6 @@ int main(int argc, char **argv)
 
 	state.cmd = cmd;
 
-	translate_mesh_iface(&state);
-
 	if (cmd->flags & COMMAND_FLAG_MESH_IFACE &&
 	    check_mesh_iface(&state) < 0) {
 		fprintf(stderr,
diff --git a/main.h b/main.h
index 3978797..846efed 100644
--- a/main.h
+++ b/main.h
@@ -56,13 +56,20 @@ enum command_flags {
 	COMMAND_FLAG_INVERSE = BIT(2),
 };
 
+enum selector_prefix {
+	SP_NONE_OR_MESHIF,
+	SP_VLAN,
+};
+
 enum command_type {
 	SUBCOMMAND,
+	SUBCOMMAND_VID,
 	DEBUGTABLE,
 };
 
 struct state {
 	char *arg_iface;
+	enum selector_prefix selector;
 	char mesh_iface[IF_NAMESIZE];
 	unsigned int mesh_ifindex;
 	int vid;
@@ -84,7 +91,7 @@ struct command {
 };
 
 #define COMMAND_NAMED(_type, _name, _abbr, _handler, _flags, _arg, _usage) \
-	static const struct command command_ ## _name = { \
+	static const struct command command_ ## _name ## _ ## _type = { \
 		.type = (_type), \
 		.name = (#_name), \
 		.abbr = _abbr, \
@@ -93,8 +100,8 @@ struct command {
 		.arg = (_arg), \
 		.usage = (_usage), \
 	}; \
-	static const struct command *__command_ ## _name \
-	__attribute__((__used__)) __attribute__ ((__section__ ("__command"))) = &command_ ## _name
+	static const struct command *__command_ ## _name ## _ ## _type \
+	__attribute__((__used__)) __attribute__ ((__section__ ("__command"))) = &command_ ## _name ## _ ## _type
 
 #define COMMAND(_type, _handler, _abbr, _flags, _arg, _usage) \
 	COMMAND_NAMED(_type, _handler, _abbr, _handler, _flags, _arg, _usage)
diff --git a/man/batctl.8 b/man/batctl.8
index 0b43031..a5656cf 100644
--- a/man/batctl.8
+++ b/man/batctl.8
@@ -46,7 +46,7 @@ performances, is also included.
 .SH OPTIONS
 .TP
 .I \fBoptions:
-\-m     specify mesh interface or VLAN created on top of a mesh interface (default 'bat0')
+\-m     specify mesh interface (default 'bat0')
 .br
 \-h     print general batctl help
 .br
@@ -70,7 +70,11 @@ originator interval. The interval is in units of milliseconds.
 .br
 .IP "\fBap_isolation\fP|\fBap\fP [\fB0\fP|\fB1\fP]"
 If no parameter is given the current ap isolation setting is displayed. Otherwise the parameter is used to enable or
-disable ap isolation. This command can be used in conjunction with "\-m" option to target per VLAN configurations.
+disable ap isolation.
+.br
+.IP "<\fBvlan <vdev>\fP|\fBvid <vid>\fP> \fBap_isolation\fP|\fBap\fP [\fB0\fP|\fB1\fP]"
+If no parameter is given the current ap isolation setting for the specified VLAN is displayed. Otherwise the parameter is used to enable or
+disable ap isolation for the specified VLAN.
 .br
 .IP "\fBbridge_loop_avoidance\fP|\fBbl\fP [\fB0\fP|\fB1\fP]"
 If no parameter is given the current bridge loop avoidance setting is displayed. Otherwise the parameter is used to enable
diff --git a/sys.c b/sys.c
index 39123db..61a314d 100644
--- a/sys.c
+++ b/sys.c
@@ -141,9 +141,35 @@ int sys_simple_print_boolean(struct nl_msg *msg, void *arg,
 
 static void settings_usage(struct state *state)
 {
-	fprintf(stderr, "Usage: batctl [options] %s|%s [parameters] %s\n",
-		state->cmd->name, state->cmd->abbr,
-		state->cmd->usage ? state->cmd->usage : "");
+	const char *default_prefixes[] = {
+		"",
+		NULL,
+	};
+	const char *vlan_prefixes[] = {
+		"vlan <vdev> ",
+		"vid <vid> ",
+		NULL,
+	};
+	const char *linestart = "Usage:";
+	const char **prefixes;
+	const char **prefix;
+
+	switch (state->cmd->type) {
+	case SUBCOMMAND_VID:
+		prefixes = vlan_prefixes;
+		break;
+	default:
+		prefixes = default_prefixes;
+		break;
+	}
+
+	for (prefix = &prefixes[0]; *prefix; prefix++) {
+		fprintf(stderr, "%s batctl [options] %s%s|%s [parameters] %s\n",
+			linestart, *prefix, state->cmd->name, state->cmd->abbr,
+			state->cmd->usage ? state->cmd->usage : "");
+
+		linestart = "      ";
+	}
 
 	fprintf(stderr, "parameters:\n");
 	fprintf(stderr, " \t -h print this help\n");
@@ -233,15 +259,19 @@ int handle_sys_setting(struct state *state, int argc, char **argv)
 		return EXIT_FAILURE;
 	}
 
-	/* if the specified interface is a VLAN then change the path to point
-	 * to the proper "vlan%{vid}" subfolder in the sysfs tree.
-	 */
-	if (state->vid >= 0)
-		snprintf(path_buff, PATH_BUFF_LEN, SYS_VLAN_PATH,
-			 state->mesh_iface, state->vid);
-	else
+	switch (state->selector) {
+	case SP_NONE_OR_MESHIF:
 		snprintf(path_buff, PATH_BUFF_LEN, SYS_BATIF_PATH_FMT,
 			 state->mesh_iface);
+		break;
+	case SP_VLAN:
+		/* if the specified interface is a VLAN then change the path to
+		 * point to the proper "vlan%{vid}" subfolder in the sysfs tree.
+		 */
+		snprintf(path_buff, PATH_BUFF_LEN, SYS_VLAN_PATH,
+			 state->mesh_iface, state->vid);
+		break;
+	}
 
 	if (argc == 1) {
 		res = sys_read_setting(state, path_buff, settings->sysfs_name);
